sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from functools import reduce, wraps 8 9from sqlglot import exp 10from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 11from sqlglot.helper import apply_index_offset, csv, name_sequence, seq_get 12from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 13from sqlglot.time import format_time 14from sqlglot.tokens import TokenType 15 16if t.TYPE_CHECKING: 17 from sqlglot._typing import E 18 from sqlglot.dialects.dialect import DialectType 19 20 G = t.TypeVar("G", bound="Generator") 21 GeneratorMethod = t.Callable[[G, E], str] 22 23logger = logging.getLogger("sqlglot") 24 25ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 26UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 27 28 29def unsupported_args( 30 *args: t.Union[str, t.Tuple[str, str]], 31) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 32 """ 33 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 34 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 35 """ 36 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 37 for arg in args: 38 if isinstance(arg, str): 39 diagnostic_by_arg[arg] = None 40 else: 41 diagnostic_by_arg[arg[0]] = arg[1] 42 43 def decorator(func: GeneratorMethod) -> GeneratorMethod: 44 @wraps(func) 45 def _func(generator: G, expression: E) -> str: 46 expression_name = expression.__class__.__name__ 47 dialect_name = generator.dialect.__class__.__name__ 48 49 for arg_name, diagnostic in diagnostic_by_arg.items(): 50 if expression.args.get(arg_name): 51 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 52 arg_name, expression_name, dialect_name 53 ) 54 generator.unsupported(diagnostic) 55 56 return func(generator, expression) 57 58 return _func 59 60 return decorator 61 62 63class _Generator(type): 64 def __new__(cls, clsname, bases, attrs): 65 klass = super().__new__(cls, clsname, bases, attrs) 66 67 # Remove transforms that correspond to unsupported JSONPathPart expressions 68 for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS: 69 klass.TRANSFORMS.pop(part, None) 70 71 return klass 72 73 74class Generator(metaclass=_Generator): 75 """ 76 Generator converts a given syntax tree to the corresponding SQL string. 77 78 Args: 79 pretty: Whether to format the produced SQL string. 80 Default: False. 81 identify: Determines when an identifier should be quoted. Possible values are: 82 False (default): Never quote, except in cases where it's mandatory by the dialect. 83 True or 'always': Always quote. 84 'safe': Only quote identifiers that are case insensitive. 85 normalize: Whether to normalize identifiers to lowercase. 86 Default: False. 87 pad: The pad size in a formatted string. For example, this affects the indentation of 88 a projection in a query, relative to its nesting level. 89 Default: 2. 90 indent: The indentation size in a formatted string. For example, this affects the 91 indentation of subqueries and filters under a `WHERE` clause. 92 Default: 2. 93 normalize_functions: How to normalize function names. Possible values are: 94 "upper" or True (default): Convert names to uppercase. 95 "lower": Convert names to lowercase. 96 False: Disables function name normalization. 97 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 98 Default ErrorLevel.WARN. 99 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 100 This is only relevant if unsupported_level is ErrorLevel.RAISE. 101 Default: 3 102 leading_comma: Whether the comma is leading or trailing in select expressions. 103 This is only relevant when generating in pretty mode. 104 Default: False 105 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 106 The default is on the smaller end because the length only represents a segment and not the true 107 line length. 108 Default: 80 109 comments: Whether to preserve comments in the output SQL code. 110 Default: True 111 """ 112 113 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 114 **JSON_PATH_PART_TRANSFORMS, 115 exp.AllowedValuesProperty: lambda self, 116 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 117 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 118 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 119 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 120 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 121 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 122 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 123 exp.CaseSpecificColumnConstraint: lambda _, 124 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 125 exp.Ceil: lambda self, e: self.ceil_floor(e), 126 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 127 exp.CharacterSetProperty: lambda self, 128 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 129 exp.ClusteredColumnConstraint: lambda self, 130 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 131 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 132 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 133 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 134 exp.ConvertToCharset: lambda self, e: self.func( 135 "CONVERT", e.this, e.args["dest"], e.args.get("source") 136 ), 137 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 138 exp.CredentialsProperty: lambda self, 139 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 140 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 141 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 142 exp.DynamicProperty: lambda *_: "DYNAMIC", 143 exp.EmptyProperty: lambda *_: "EMPTY", 144 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 145 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 146 exp.EphemeralColumnConstraint: lambda self, 147 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 148 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 149 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 150 exp.Except: lambda self, e: self.set_operations(e), 151 exp.ExternalProperty: lambda *_: "EXTERNAL", 152 exp.Floor: lambda self, e: self.ceil_floor(e), 153 exp.Get: lambda self, e: self.get_put_sql(e), 154 exp.GlobalProperty: lambda *_: "GLOBAL", 155 exp.HeapProperty: lambda *_: "HEAP", 156 exp.IcebergProperty: lambda *_: "ICEBERG", 157 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 158 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 159 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 160 exp.Intersect: lambda self, e: self.set_operations(e), 161 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 162 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 163 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 164 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 165 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 166 exp.LanguageProperty: lambda self, e: self.naked_property(e), 167 exp.LocationProperty: lambda self, e: self.naked_property(e), 168 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 169 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 170 exp.NonClusteredColumnConstraint: lambda self, 171 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 172 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 173 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 174 exp.OnCommitProperty: lambda _, 175 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 176 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 177 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 178 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 179 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 180 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 181 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 182 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 183 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 184 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 185 exp.ProjectionPolicyColumnConstraint: lambda self, 186 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 187 exp.Put: lambda self, e: self.get_put_sql(e), 188 exp.RemoteWithConnectionModelProperty: lambda self, 189 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 190 exp.ReturnsProperty: lambda self, e: ( 191 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 192 ), 193 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 194 exp.SecureProperty: lambda *_: "SECURE", 195 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 196 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 197 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 198 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 199 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 200 exp.SqlReadWriteProperty: lambda _, e: e.name, 201 exp.SqlSecurityProperty: lambda _, 202 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 203 exp.StabilityProperty: lambda _, e: e.name, 204 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 205 exp.StreamingTableProperty: lambda *_: "STREAMING", 206 exp.StrictProperty: lambda *_: "STRICT", 207 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 208 exp.TableColumn: lambda self, e: self.sql(e.this), 209 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 210 exp.TemporaryProperty: lambda *_: "TEMPORARY", 211 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 212 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 213 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 214 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 215 exp.TransientProperty: lambda *_: "TRANSIENT", 216 exp.Union: lambda self, e: self.set_operations(e), 217 exp.UnloggedProperty: lambda *_: "UNLOGGED", 218 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 219 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 220 exp.Uuid: lambda *_: "UUID()", 221 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 222 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 223 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 224 exp.UtcTimestamp: lambda self, e: self.sql( 225 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 226 ), 227 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 228 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 229 exp.VolatileProperty: lambda *_: "VOLATILE", 230 exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})", 231 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 232 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 233 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 234 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 235 exp.ForceProperty: lambda *_: "FORCE", 236 } 237 238 # Whether null ordering is supported in order by 239 # True: Full Support, None: No support, False: No support for certain cases 240 # such as window specifications, aggregate functions etc 241 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 242 243 # Whether ignore nulls is inside the agg or outside. 244 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 245 IGNORE_NULLS_IN_FUNC = False 246 247 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 248 LOCKING_READS_SUPPORTED = False 249 250 # Whether the EXCEPT and INTERSECT operations can return duplicates 251 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 252 253 # Wrap derived values in parens, usually standard but spark doesn't support it 254 WRAP_DERIVED_VALUES = True 255 256 # Whether create function uses an AS before the RETURN 257 CREATE_FUNCTION_RETURN_AS = True 258 259 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 260 MATCHED_BY_SOURCE = True 261 262 # Whether the INTERVAL expression works only with values like '1 day' 263 SINGLE_STRING_INTERVAL = False 264 265 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 266 INTERVAL_ALLOWS_PLURAL_FORM = True 267 268 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 269 LIMIT_FETCH = "ALL" 270 271 # Whether limit and fetch allows expresions or just limits 272 LIMIT_ONLY_LITERALS = False 273 274 # Whether a table is allowed to be renamed with a db 275 RENAME_TABLE_WITH_DB = True 276 277 # The separator for grouping sets and rollups 278 GROUPINGS_SEP = "," 279 280 # The string used for creating an index on a table 281 INDEX_ON = "ON" 282 283 # Whether join hints should be generated 284 JOIN_HINTS = True 285 286 # Whether table hints should be generated 287 TABLE_HINTS = True 288 289 # Whether query hints should be generated 290 QUERY_HINTS = True 291 292 # What kind of separator to use for query hints 293 QUERY_HINT_SEP = ", " 294 295 # Whether comparing against booleans (e.g. x IS TRUE) is supported 296 IS_BOOL_ALLOWED = True 297 298 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 299 DUPLICATE_KEY_UPDATE_WITH_SET = True 300 301 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 302 LIMIT_IS_TOP = False 303 304 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 305 RETURNING_END = True 306 307 # Whether to generate an unquoted value for EXTRACT's date part argument 308 EXTRACT_ALLOWS_QUOTES = True 309 310 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 311 TZ_TO_WITH_TIME_ZONE = False 312 313 # Whether the NVL2 function is supported 314 NVL2_SUPPORTED = True 315 316 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 317 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 318 319 # Whether VALUES statements can be used as derived tables. 320 # MySQL 5 and Redshift do not allow this, so when False, it will convert 321 # SELECT * VALUES into SELECT UNION 322 VALUES_AS_TABLE = True 323 324 # Whether the word COLUMN is included when adding a column with ALTER TABLE 325 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 326 327 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 328 UNNEST_WITH_ORDINALITY = True 329 330 # Whether FILTER (WHERE cond) can be used for conditional aggregation 331 AGGREGATE_FILTER_SUPPORTED = True 332 333 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 334 SEMI_ANTI_JOIN_WITH_SIDE = True 335 336 # Whether to include the type of a computed column in the CREATE DDL 337 COMPUTED_COLUMN_WITH_TYPE = True 338 339 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 340 SUPPORTS_TABLE_COPY = True 341 342 # Whether parentheses are required around the table sample's expression 343 TABLESAMPLE_REQUIRES_PARENS = True 344 345 # Whether a table sample clause's size needs to be followed by the ROWS keyword 346 TABLESAMPLE_SIZE_IS_ROWS = True 347 348 # The keyword(s) to use when generating a sample clause 349 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 350 351 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 352 TABLESAMPLE_WITH_METHOD = True 353 354 # The keyword to use when specifying the seed of a sample clause 355 TABLESAMPLE_SEED_KEYWORD = "SEED" 356 357 # Whether COLLATE is a function instead of a binary operator 358 COLLATE_IS_FUNC = False 359 360 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 361 DATA_TYPE_SPECIFIERS_ALLOWED = False 362 363 # Whether conditions require booleans WHERE x = 0 vs WHERE x 364 ENSURE_BOOLS = False 365 366 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 367 CTE_RECURSIVE_KEYWORD_REQUIRED = True 368 369 # Whether CONCAT requires >1 arguments 370 SUPPORTS_SINGLE_ARG_CONCAT = True 371 372 # Whether LAST_DAY function supports a date part argument 373 LAST_DAY_SUPPORTS_DATE_PART = True 374 375 # Whether named columns are allowed in table aliases 376 SUPPORTS_TABLE_ALIAS_COLUMNS = True 377 378 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 379 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 380 381 # What delimiter to use for separating JSON key/value pairs 382 JSON_KEY_VALUE_PAIR_SEP = ":" 383 384 # INSERT OVERWRITE TABLE x override 385 INSERT_OVERWRITE = " OVERWRITE TABLE" 386 387 # Whether the SELECT .. INTO syntax is used instead of CTAS 388 SUPPORTS_SELECT_INTO = False 389 390 # Whether UNLOGGED tables can be created 391 SUPPORTS_UNLOGGED_TABLES = False 392 393 # Whether the CREATE TABLE LIKE statement is supported 394 SUPPORTS_CREATE_TABLE_LIKE = True 395 396 # Whether the LikeProperty needs to be specified inside of the schema clause 397 LIKE_PROPERTY_INSIDE_SCHEMA = False 398 399 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 400 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 401 MULTI_ARG_DISTINCT = True 402 403 # Whether the JSON extraction operators expect a value of type JSON 404 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 405 406 # Whether bracketed keys like ["foo"] are supported in JSON paths 407 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 408 409 # Whether to escape keys using single quotes in JSON paths 410 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 411 412 # The JSONPathPart expressions supported by this dialect 413 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 414 415 # Whether any(f(x) for x in array) can be implemented by this dialect 416 CAN_IMPLEMENT_ARRAY_ANY = False 417 418 # Whether the function TO_NUMBER is supported 419 SUPPORTS_TO_NUMBER = True 420 421 # Whether EXCLUDE in window specification is supported 422 SUPPORTS_WINDOW_EXCLUDE = False 423 424 # Whether or not set op modifiers apply to the outer set op or select. 425 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 426 # True means limit 1 happens after the set op, False means it it happens on y. 427 SET_OP_MODIFIERS = True 428 429 # Whether parameters from COPY statement are wrapped in parentheses 430 COPY_PARAMS_ARE_WRAPPED = True 431 432 # Whether values of params are set with "=" token or empty space 433 COPY_PARAMS_EQ_REQUIRED = False 434 435 # Whether COPY statement has INTO keyword 436 COPY_HAS_INTO_KEYWORD = True 437 438 # Whether the conditional TRY(expression) function is supported 439 TRY_SUPPORTED = True 440 441 # Whether the UESCAPE syntax in unicode strings is supported 442 SUPPORTS_UESCAPE = True 443 444 # Function used to replace escaped unicode codes in unicode strings 445 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 446 447 # The keyword to use when generating a star projection with excluded columns 448 STAR_EXCEPT = "EXCEPT" 449 450 # The HEX function name 451 HEX_FUNC = "HEX" 452 453 # The keywords to use when prefixing & separating WITH based properties 454 WITH_PROPERTIES_PREFIX = "WITH" 455 456 # Whether to quote the generated expression of exp.JsonPath 457 QUOTE_JSON_PATH = True 458 459 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 460 PAD_FILL_PATTERN_IS_REQUIRED = False 461 462 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 463 SUPPORTS_EXPLODING_PROJECTIONS = True 464 465 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 466 ARRAY_CONCAT_IS_VAR_LEN = True 467 468 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 469 SUPPORTS_CONVERT_TIMEZONE = False 470 471 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 472 SUPPORTS_MEDIAN = True 473 474 # Whether UNIX_SECONDS(timestamp) is supported 475 SUPPORTS_UNIX_SECONDS = False 476 477 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 478 ALTER_SET_WRAPPED = False 479 480 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 481 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 482 # TODO: The normalization should be done by default once we've tested it across all dialects. 483 NORMALIZE_EXTRACT_DATE_PARTS = False 484 485 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 486 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 487 488 # The function name of the exp.ArraySize expression 489 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 490 491 # The syntax to use when altering the type of a column 492 ALTER_SET_TYPE = "SET DATA TYPE" 493 494 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 495 # None -> Doesn't support it at all 496 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 497 # True (Postgres) -> Explicitly requires it 498 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 499 500 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 501 SUPPORTS_DECODE_CASE = True 502 503 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 504 SUPPORTS_BETWEEN_FLAGS = False 505 506 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 507 SUPPORTS_LIKE_QUANTIFIERS = True 508 509 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 510 MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None 511 512 TYPE_MAPPING = { 513 exp.DataType.Type.DATETIME2: "TIMESTAMP", 514 exp.DataType.Type.NCHAR: "CHAR", 515 exp.DataType.Type.NVARCHAR: "VARCHAR", 516 exp.DataType.Type.MEDIUMTEXT: "TEXT", 517 exp.DataType.Type.LONGTEXT: "TEXT", 518 exp.DataType.Type.TINYTEXT: "TEXT", 519 exp.DataType.Type.BLOB: "VARBINARY", 520 exp.DataType.Type.MEDIUMBLOB: "BLOB", 521 exp.DataType.Type.LONGBLOB: "BLOB", 522 exp.DataType.Type.TINYBLOB: "BLOB", 523 exp.DataType.Type.INET: "INET", 524 exp.DataType.Type.ROWVERSION: "VARBINARY", 525 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 526 } 527 528 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 529 530 TIME_PART_SINGULARS = { 531 "MICROSECONDS": "MICROSECOND", 532 "SECONDS": "SECOND", 533 "MINUTES": "MINUTE", 534 "HOURS": "HOUR", 535 "DAYS": "DAY", 536 "WEEKS": "WEEK", 537 "MONTHS": "MONTH", 538 "QUARTERS": "QUARTER", 539 "YEARS": "YEAR", 540 } 541 542 AFTER_HAVING_MODIFIER_TRANSFORMS = { 543 "cluster": lambda self, e: self.sql(e, "cluster"), 544 "distribute": lambda self, e: self.sql(e, "distribute"), 545 "sort": lambda self, e: self.sql(e, "sort"), 546 "windows": lambda self, e: ( 547 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 548 if e.args.get("windows") 549 else "" 550 ), 551 "qualify": lambda self, e: self.sql(e, "qualify"), 552 } 553 554 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 555 556 STRUCT_DELIMITER = ("<", ">") 557 558 PARAMETER_TOKEN = "@" 559 NAMED_PLACEHOLDER_TOKEN = ":" 560 561 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 562 563 PROPERTIES_LOCATION = { 564 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 566 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 570 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 572 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 573 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 575 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 576 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 579 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 580 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 581 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 582 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 583 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 584 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 588 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 592 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 593 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 594 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 595 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 596 exp.HeapProperty: exp.Properties.Location.POST_WITH, 597 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 599 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 600 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 602 exp.JournalProperty: exp.Properties.Location.POST_NAME, 603 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 608 exp.LogProperty: exp.Properties.Location.POST_NAME, 609 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 610 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 611 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 612 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 614 exp.Order: exp.Properties.Location.POST_SCHEMA, 615 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 617 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 619 exp.Property: exp.Properties.Location.POST_WITH, 620 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 626 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 628 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 629 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 630 exp.Set: exp.Properties.Location.POST_SCHEMA, 631 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 632 exp.SetProperty: exp.Properties.Location.POST_CREATE, 633 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 634 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 635 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 636 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 637 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 639 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 640 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 641 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 642 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 643 exp.Tags: exp.Properties.Location.POST_WITH, 644 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 645 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 646 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 647 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 648 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 649 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 650 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 651 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 652 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 653 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 654 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 655 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 656 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 657 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 658 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 659 } 660 661 # Keywords that can't be used as unquoted identifier names 662 RESERVED_KEYWORDS: t.Set[str] = set() 663 664 # Expressions whose comments are separated from them for better formatting 665 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 666 exp.Command, 667 exp.Create, 668 exp.Describe, 669 exp.Delete, 670 exp.Drop, 671 exp.From, 672 exp.Insert, 673 exp.Join, 674 exp.MultitableInserts, 675 exp.Order, 676 exp.Group, 677 exp.Having, 678 exp.Select, 679 exp.SetOperation, 680 exp.Update, 681 exp.Where, 682 exp.With, 683 ) 684 685 # Expressions that should not have their comments generated in maybe_comment 686 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 687 exp.Binary, 688 exp.SetOperation, 689 ) 690 691 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 692 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 693 exp.Column, 694 exp.Literal, 695 exp.Neg, 696 exp.Paren, 697 ) 698 699 PARAMETERIZABLE_TEXT_TYPES = { 700 exp.DataType.Type.NVARCHAR, 701 exp.DataType.Type.VARCHAR, 702 exp.DataType.Type.CHAR, 703 exp.DataType.Type.NCHAR, 704 } 705 706 # Expressions that need to have all CTEs under them bubbled up to them 707 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 708 709 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 710 711 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 712 713 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 714 715 __slots__ = ( 716 "pretty", 717 "identify", 718 "normalize", 719 "pad", 720 "_indent", 721 "normalize_functions", 722 "unsupported_level", 723 "max_unsupported", 724 "leading_comma", 725 "max_text_width", 726 "comments", 727 "dialect", 728 "unsupported_messages", 729 "_escaped_quote_end", 730 "_escaped_byte_quote_end", 731 "_escaped_identifier_end", 732 "_next_name", 733 "_identifier_start", 734 "_identifier_end", 735 "_quote_json_path_key_using_brackets", 736 ) 737 738 def __init__( 739 self, 740 pretty: t.Optional[bool] = None, 741 identify: str | bool = False, 742 normalize: bool = False, 743 pad: int = 2, 744 indent: int = 2, 745 normalize_functions: t.Optional[str | bool] = None, 746 unsupported_level: ErrorLevel = ErrorLevel.WARN, 747 max_unsupported: int = 3, 748 leading_comma: bool = False, 749 max_text_width: int = 80, 750 comments: bool = True, 751 dialect: DialectType = None, 752 ): 753 import sqlglot 754 from sqlglot.dialects import Dialect 755 756 self.pretty = pretty if pretty is not None else sqlglot.pretty 757 self.identify = identify 758 self.normalize = normalize 759 self.pad = pad 760 self._indent = indent 761 self.unsupported_level = unsupported_level 762 self.max_unsupported = max_unsupported 763 self.leading_comma = leading_comma 764 self.max_text_width = max_text_width 765 self.comments = comments 766 self.dialect = Dialect.get_or_raise(dialect) 767 768 # This is both a Dialect property and a Generator argument, so we prioritize the latter 769 self.normalize_functions = ( 770 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 771 ) 772 773 self.unsupported_messages: t.List[str] = [] 774 self._escaped_quote_end: str = ( 775 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 776 ) 777 self._escaped_byte_quote_end: str = ( 778 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 779 if self.dialect.BYTE_END 780 else "" 781 ) 782 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 783 784 self._next_name = name_sequence("_t") 785 786 self._identifier_start = self.dialect.IDENTIFIER_START 787 self._identifier_end = self.dialect.IDENTIFIER_END 788 789 self._quote_json_path_key_using_brackets = True 790 791 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 792 """ 793 Generates the SQL string corresponding to the given syntax tree. 794 795 Args: 796 expression: The syntax tree. 797 copy: Whether to copy the expression. The generator performs mutations so 798 it is safer to copy. 799 800 Returns: 801 The SQL string corresponding to `expression`. 802 """ 803 if copy: 804 expression = expression.copy() 805 806 expression = self.preprocess(expression) 807 808 self.unsupported_messages = [] 809 sql = self.sql(expression).strip() 810 811 if self.pretty: 812 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 813 814 if self.unsupported_level == ErrorLevel.IGNORE: 815 return sql 816 817 if self.unsupported_level == ErrorLevel.WARN: 818 for msg in self.unsupported_messages: 819 logger.warning(msg) 820 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 821 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 822 823 return sql 824 825 def preprocess(self, expression: exp.Expression) -> exp.Expression: 826 """Apply generic preprocessing transformations to a given expression.""" 827 expression = self._move_ctes_to_top_level(expression) 828 829 if self.ENSURE_BOOLS: 830 from sqlglot.transforms import ensure_bools 831 832 expression = ensure_bools(expression) 833 834 return expression 835 836 def _move_ctes_to_top_level(self, expression: E) -> E: 837 if ( 838 not expression.parent 839 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 840 and any(node.parent is not expression for node in expression.find_all(exp.With)) 841 ): 842 from sqlglot.transforms import move_ctes_to_top_level 843 844 expression = move_ctes_to_top_level(expression) 845 return expression 846 847 def unsupported(self, message: str) -> None: 848 if self.unsupported_level == ErrorLevel.IMMEDIATE: 849 raise UnsupportedError(message) 850 self.unsupported_messages.append(message) 851 852 def sep(self, sep: str = " ") -> str: 853 return f"{sep.strip()}\n" if self.pretty else sep 854 855 def seg(self, sql: str, sep: str = " ") -> str: 856 return f"{self.sep(sep)}{sql}" 857 858 def sanitize_comment(self, comment: str) -> str: 859 comment = " " + comment if comment[0].strip() else comment 860 comment = comment + " " if comment[-1].strip() else comment 861 862 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 863 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 864 comment = comment.replace("*/", "* /") 865 866 return comment 867 868 def maybe_comment( 869 self, 870 sql: str, 871 expression: t.Optional[exp.Expression] = None, 872 comments: t.Optional[t.List[str]] = None, 873 separated: bool = False, 874 ) -> str: 875 comments = ( 876 ((expression and expression.comments) if comments is None else comments) # type: ignore 877 if self.comments 878 else None 879 ) 880 881 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 882 return sql 883 884 comments_sql = " ".join( 885 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 886 ) 887 888 if not comments_sql: 889 return sql 890 891 comments_sql = self._replace_line_breaks(comments_sql) 892 893 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 894 return ( 895 f"{self.sep()}{comments_sql}{sql}" 896 if not sql or sql[0].isspace() 897 else f"{comments_sql}{self.sep()}{sql}" 898 ) 899 900 return f"{sql} {comments_sql}" 901 902 def wrap(self, expression: exp.Expression | str) -> str: 903 this_sql = ( 904 self.sql(expression) 905 if isinstance(expression, exp.UNWRAPPED_QUERIES) 906 else self.sql(expression, "this") 907 ) 908 if not this_sql: 909 return "()" 910 911 this_sql = self.indent(this_sql, level=1, pad=0) 912 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 913 914 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 915 original = self.identify 916 self.identify = False 917 result = func(*args, **kwargs) 918 self.identify = original 919 return result 920 921 def normalize_func(self, name: str) -> str: 922 if self.normalize_functions == "upper" or self.normalize_functions is True: 923 return name.upper() 924 if self.normalize_functions == "lower": 925 return name.lower() 926 return name 927 928 def indent( 929 self, 930 sql: str, 931 level: int = 0, 932 pad: t.Optional[int] = None, 933 skip_first: bool = False, 934 skip_last: bool = False, 935 ) -> str: 936 if not self.pretty or not sql: 937 return sql 938 939 pad = self.pad if pad is None else pad 940 lines = sql.split("\n") 941 942 return "\n".join( 943 ( 944 line 945 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 946 else f"{' ' * (level * self._indent + pad)}{line}" 947 ) 948 for i, line in enumerate(lines) 949 ) 950 951 def sql( 952 self, 953 expression: t.Optional[str | exp.Expression], 954 key: t.Optional[str] = None, 955 comment: bool = True, 956 ) -> str: 957 if not expression: 958 return "" 959 960 if isinstance(expression, str): 961 return expression 962 963 if key: 964 value = expression.args.get(key) 965 if value: 966 return self.sql(value) 967 return "" 968 969 transform = self.TRANSFORMS.get(expression.__class__) 970 971 if callable(transform): 972 sql = transform(self, expression) 973 elif isinstance(expression, exp.Expression): 974 exp_handler_name = f"{expression.key}_sql" 975 976 if hasattr(self, exp_handler_name): 977 sql = getattr(self, exp_handler_name)(expression) 978 elif isinstance(expression, exp.Func): 979 sql = self.function_fallback_sql(expression) 980 elif isinstance(expression, exp.Property): 981 sql = self.property_sql(expression) 982 else: 983 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 984 else: 985 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 986 987 return self.maybe_comment(sql, expression) if self.comments and comment else sql 988 989 def uncache_sql(self, expression: exp.Uncache) -> str: 990 table = self.sql(expression, "this") 991 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 992 return f"UNCACHE TABLE{exists_sql} {table}" 993 994 def cache_sql(self, expression: exp.Cache) -> str: 995 lazy = " LAZY" if expression.args.get("lazy") else "" 996 table = self.sql(expression, "this") 997 options = expression.args.get("options") 998 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 999 sql = self.sql(expression, "expression") 1000 sql = f" AS{self.sep()}{sql}" if sql else "" 1001 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1002 return self.prepend_ctes(expression, sql) 1003 1004 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1005 if isinstance(expression.parent, exp.Cast): 1006 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1007 default = "DEFAULT " if expression.args.get("default") else "" 1008 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1009 1010 def column_parts(self, expression: exp.Column) -> str: 1011 return ".".join( 1012 self.sql(part) 1013 for part in ( 1014 expression.args.get("catalog"), 1015 expression.args.get("db"), 1016 expression.args.get("table"), 1017 expression.args.get("this"), 1018 ) 1019 if part 1020 ) 1021 1022 def column_sql(self, expression: exp.Column) -> str: 1023 join_mark = " (+)" if expression.args.get("join_mark") else "" 1024 1025 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1026 join_mark = "" 1027 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1028 1029 return f"{self.column_parts(expression)}{join_mark}" 1030 1031 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1032 this = self.sql(expression, "this") 1033 this = f" {this}" if this else "" 1034 position = self.sql(expression, "position") 1035 return f"{position}{this}" 1036 1037 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1038 column = self.sql(expression, "this") 1039 kind = self.sql(expression, "kind") 1040 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1041 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1042 kind = f"{sep}{kind}" if kind else "" 1043 constraints = f" {constraints}" if constraints else "" 1044 position = self.sql(expression, "position") 1045 position = f" {position}" if position else "" 1046 1047 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1048 kind = "" 1049 1050 return f"{exists}{column}{kind}{constraints}{position}" 1051 1052 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1053 this = self.sql(expression, "this") 1054 kind_sql = self.sql(expression, "kind").strip() 1055 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1056 1057 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1058 this = self.sql(expression, "this") 1059 if expression.args.get("not_null"): 1060 persisted = " PERSISTED NOT NULL" 1061 elif expression.args.get("persisted"): 1062 persisted = " PERSISTED" 1063 else: 1064 persisted = "" 1065 1066 return f"AS {this}{persisted}" 1067 1068 def autoincrementcolumnconstraint_sql(self, _) -> str: 1069 return self.token_sql(TokenType.AUTO_INCREMENT) 1070 1071 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1072 if isinstance(expression.this, list): 1073 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1074 else: 1075 this = self.sql(expression, "this") 1076 1077 return f"COMPRESS {this}" 1078 1079 def generatedasidentitycolumnconstraint_sql( 1080 self, expression: exp.GeneratedAsIdentityColumnConstraint 1081 ) -> str: 1082 this = "" 1083 if expression.this is not None: 1084 on_null = " ON NULL" if expression.args.get("on_null") else "" 1085 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1086 1087 start = expression.args.get("start") 1088 start = f"START WITH {start}" if start else "" 1089 increment = expression.args.get("increment") 1090 increment = f" INCREMENT BY {increment}" if increment else "" 1091 minvalue = expression.args.get("minvalue") 1092 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1093 maxvalue = expression.args.get("maxvalue") 1094 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1095 cycle = expression.args.get("cycle") 1096 cycle_sql = "" 1097 1098 if cycle is not None: 1099 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1100 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1101 1102 sequence_opts = "" 1103 if start or increment or cycle_sql: 1104 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1105 sequence_opts = f" ({sequence_opts.strip()})" 1106 1107 expr = self.sql(expression, "expression") 1108 expr = f"({expr})" if expr else "IDENTITY" 1109 1110 return f"GENERATED{this} AS {expr}{sequence_opts}" 1111 1112 def generatedasrowcolumnconstraint_sql( 1113 self, expression: exp.GeneratedAsRowColumnConstraint 1114 ) -> str: 1115 start = "START" if expression.args.get("start") else "END" 1116 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1117 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1118 1119 def periodforsystemtimeconstraint_sql( 1120 self, expression: exp.PeriodForSystemTimeConstraint 1121 ) -> str: 1122 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1123 1124 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1125 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1126 1127 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1128 desc = expression.args.get("desc") 1129 if desc is not None: 1130 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1131 options = self.expressions(expression, key="options", flat=True, sep=" ") 1132 options = f" {options}" if options else "" 1133 return f"PRIMARY KEY{options}" 1134 1135 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1136 this = self.sql(expression, "this") 1137 this = f" {this}" if this else "" 1138 index_type = expression.args.get("index_type") 1139 index_type = f" USING {index_type}" if index_type else "" 1140 on_conflict = self.sql(expression, "on_conflict") 1141 on_conflict = f" {on_conflict}" if on_conflict else "" 1142 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1143 options = self.expressions(expression, key="options", flat=True, sep=" ") 1144 options = f" {options}" if options else "" 1145 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1146 1147 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1148 return self.sql(expression, "this") 1149 1150 def create_sql(self, expression: exp.Create) -> str: 1151 kind = self.sql(expression, "kind") 1152 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1153 properties = expression.args.get("properties") 1154 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1155 1156 this = self.createable_sql(expression, properties_locs) 1157 1158 properties_sql = "" 1159 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1160 exp.Properties.Location.POST_WITH 1161 ): 1162 props_ast = exp.Properties( 1163 expressions=[ 1164 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1165 *properties_locs[exp.Properties.Location.POST_WITH], 1166 ] 1167 ) 1168 props_ast.parent = expression 1169 properties_sql = self.sql(props_ast) 1170 1171 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1172 properties_sql = self.sep() + properties_sql 1173 elif not self.pretty: 1174 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1175 properties_sql = f" {properties_sql}" 1176 1177 begin = " BEGIN" if expression.args.get("begin") else "" 1178 end = " END" if expression.args.get("end") else "" 1179 1180 expression_sql = self.sql(expression, "expression") 1181 if expression_sql: 1182 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1183 1184 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1185 postalias_props_sql = "" 1186 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1187 postalias_props_sql = self.properties( 1188 exp.Properties( 1189 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1190 ), 1191 wrapped=False, 1192 ) 1193 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1194 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1195 1196 postindex_props_sql = "" 1197 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1198 postindex_props_sql = self.properties( 1199 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1200 wrapped=False, 1201 prefix=" ", 1202 ) 1203 1204 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1205 indexes = f" {indexes}" if indexes else "" 1206 index_sql = indexes + postindex_props_sql 1207 1208 replace = " OR REPLACE" if expression.args.get("replace") else "" 1209 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1210 unique = " UNIQUE" if expression.args.get("unique") else "" 1211 1212 clustered = expression.args.get("clustered") 1213 if clustered is None: 1214 clustered_sql = "" 1215 elif clustered: 1216 clustered_sql = " CLUSTERED COLUMNSTORE" 1217 else: 1218 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1219 1220 postcreate_props_sql = "" 1221 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1222 postcreate_props_sql = self.properties( 1223 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1224 sep=" ", 1225 prefix=" ", 1226 wrapped=False, 1227 ) 1228 1229 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1230 1231 postexpression_props_sql = "" 1232 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1233 postexpression_props_sql = self.properties( 1234 exp.Properties( 1235 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1236 ), 1237 sep=" ", 1238 prefix=" ", 1239 wrapped=False, 1240 ) 1241 1242 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1243 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1244 no_schema_binding = ( 1245 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1246 ) 1247 1248 clone = self.sql(expression, "clone") 1249 clone = f" {clone}" if clone else "" 1250 1251 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1252 properties_expression = f"{expression_sql}{properties_sql}" 1253 else: 1254 properties_expression = f"{properties_sql}{expression_sql}" 1255 1256 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1257 return self.prepend_ctes(expression, expression_sql) 1258 1259 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1260 start = self.sql(expression, "start") 1261 start = f"START WITH {start}" if start else "" 1262 increment = self.sql(expression, "increment") 1263 increment = f" INCREMENT BY {increment}" if increment else "" 1264 minvalue = self.sql(expression, "minvalue") 1265 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1266 maxvalue = self.sql(expression, "maxvalue") 1267 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1268 owned = self.sql(expression, "owned") 1269 owned = f" OWNED BY {owned}" if owned else "" 1270 1271 cache = expression.args.get("cache") 1272 if cache is None: 1273 cache_str = "" 1274 elif cache is True: 1275 cache_str = " CACHE" 1276 else: 1277 cache_str = f" CACHE {cache}" 1278 1279 options = self.expressions(expression, key="options", flat=True, sep=" ") 1280 options = f" {options}" if options else "" 1281 1282 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1283 1284 def clone_sql(self, expression: exp.Clone) -> str: 1285 this = self.sql(expression, "this") 1286 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1287 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1288 return f"{shallow}{keyword} {this}" 1289 1290 def describe_sql(self, expression: exp.Describe) -> str: 1291 style = expression.args.get("style") 1292 style = f" {style}" if style else "" 1293 partition = self.sql(expression, "partition") 1294 partition = f" {partition}" if partition else "" 1295 format = self.sql(expression, "format") 1296 format = f" {format}" if format else "" 1297 1298 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1299 1300 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1301 tag = self.sql(expression, "tag") 1302 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1303 1304 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1305 with_ = self.sql(expression, "with") 1306 if with_: 1307 sql = f"{with_}{self.sep()}{sql}" 1308 return sql 1309 1310 def with_sql(self, expression: exp.With) -> str: 1311 sql = self.expressions(expression, flat=True) 1312 recursive = ( 1313 "RECURSIVE " 1314 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1315 else "" 1316 ) 1317 search = self.sql(expression, "search") 1318 search = f" {search}" if search else "" 1319 1320 return f"WITH {recursive}{sql}{search}" 1321 1322 def cte_sql(self, expression: exp.CTE) -> str: 1323 alias = expression.args.get("alias") 1324 if alias: 1325 alias.add_comments(expression.pop_comments()) 1326 1327 alias_sql = self.sql(expression, "alias") 1328 1329 materialized = expression.args.get("materialized") 1330 if materialized is False: 1331 materialized = "NOT MATERIALIZED " 1332 elif materialized: 1333 materialized = "MATERIALIZED " 1334 1335 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1336 1337 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1338 alias = self.sql(expression, "this") 1339 columns = self.expressions(expression, key="columns", flat=True) 1340 columns = f"({columns})" if columns else "" 1341 1342 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1343 columns = "" 1344 self.unsupported("Named columns are not supported in table alias.") 1345 1346 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1347 alias = self._next_name() 1348 1349 return f"{alias}{columns}" 1350 1351 def bitstring_sql(self, expression: exp.BitString) -> str: 1352 this = self.sql(expression, "this") 1353 if self.dialect.BIT_START: 1354 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1355 return f"{int(this, 2)}" 1356 1357 def hexstring_sql( 1358 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1359 ) -> str: 1360 this = self.sql(expression, "this") 1361 is_integer_type = expression.args.get("is_integer") 1362 1363 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1364 not self.dialect.HEX_START and not binary_function_repr 1365 ): 1366 # Integer representation will be returned if: 1367 # - The read dialect treats the hex value as integer literal but not the write 1368 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1369 return f"{int(this, 16)}" 1370 1371 if not is_integer_type: 1372 # Read dialect treats the hex value as BINARY/BLOB 1373 if binary_function_repr: 1374 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1375 return self.func(binary_function_repr, exp.Literal.string(this)) 1376 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1377 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1378 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1379 1380 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1381 1382 def bytestring_sql(self, expression: exp.ByteString) -> str: 1383 this = self.sql(expression, "this") 1384 if self.dialect.BYTE_START: 1385 escaped_byte_string = self.escape_str( 1386 this, 1387 escape_backslash=False, 1388 delimiter=self.dialect.BYTE_END, 1389 escaped_delimiter=self._escaped_byte_quote_end, 1390 ) 1391 return f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1392 return this 1393 1394 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1395 this = self.sql(expression, "this") 1396 escape = expression.args.get("escape") 1397 1398 if self.dialect.UNICODE_START: 1399 escape_substitute = r"\\\1" 1400 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1401 else: 1402 escape_substitute = r"\\u\1" 1403 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1404 1405 if escape: 1406 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1407 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1408 else: 1409 escape_pattern = ESCAPED_UNICODE_RE 1410 escape_sql = "" 1411 1412 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1413 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1414 1415 return f"{left_quote}{this}{right_quote}{escape_sql}" 1416 1417 def rawstring_sql(self, expression: exp.RawString) -> str: 1418 string = expression.this 1419 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1420 string = string.replace("\\", "\\\\") 1421 1422 string = self.escape_str(string, escape_backslash=False) 1423 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1424 1425 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1426 this = self.sql(expression, "this") 1427 specifier = self.sql(expression, "expression") 1428 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1429 return f"{this}{specifier}" 1430 1431 def datatype_sql(self, expression: exp.DataType) -> str: 1432 nested = "" 1433 values = "" 1434 interior = self.expressions(expression, flat=True) 1435 1436 type_value = expression.this 1437 if type_value in self.UNSUPPORTED_TYPES: 1438 self.unsupported( 1439 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1440 ) 1441 1442 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1443 type_sql = self.sql(expression, "kind") 1444 else: 1445 type_sql = ( 1446 self.TYPE_MAPPING.get(type_value, type_value.value) 1447 if isinstance(type_value, exp.DataType.Type) 1448 else type_value 1449 ) 1450 1451 if interior: 1452 if expression.args.get("nested"): 1453 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1454 if expression.args.get("values") is not None: 1455 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1456 values = self.expressions(expression, key="values", flat=True) 1457 values = f"{delimiters[0]}{values}{delimiters[1]}" 1458 elif type_value == exp.DataType.Type.INTERVAL: 1459 nested = f" {interior}" 1460 else: 1461 nested = f"({interior})" 1462 1463 type_sql = f"{type_sql}{nested}{values}" 1464 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1465 exp.DataType.Type.TIMETZ, 1466 exp.DataType.Type.TIMESTAMPTZ, 1467 ): 1468 type_sql = f"{type_sql} WITH TIME ZONE" 1469 1470 return type_sql 1471 1472 def directory_sql(self, expression: exp.Directory) -> str: 1473 local = "LOCAL " if expression.args.get("local") else "" 1474 row_format = self.sql(expression, "row_format") 1475 row_format = f" {row_format}" if row_format else "" 1476 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1477 1478 def delete_sql(self, expression: exp.Delete) -> str: 1479 this = self.sql(expression, "this") 1480 this = f" FROM {this}" if this else "" 1481 using = self.sql(expression, "using") 1482 using = f" USING {using}" if using else "" 1483 cluster = self.sql(expression, "cluster") 1484 cluster = f" {cluster}" if cluster else "" 1485 where = self.sql(expression, "where") 1486 returning = self.sql(expression, "returning") 1487 limit = self.sql(expression, "limit") 1488 tables = self.expressions(expression, key="tables") 1489 tables = f" {tables}" if tables else "" 1490 if self.RETURNING_END: 1491 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1492 else: 1493 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1494 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1495 1496 def drop_sql(self, expression: exp.Drop) -> str: 1497 this = self.sql(expression, "this") 1498 expressions = self.expressions(expression, flat=True) 1499 expressions = f" ({expressions})" if expressions else "" 1500 kind = expression.args["kind"] 1501 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1502 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1503 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1504 on_cluster = self.sql(expression, "cluster") 1505 on_cluster = f" {on_cluster}" if on_cluster else "" 1506 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1507 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1508 cascade = " CASCADE" if expression.args.get("cascade") else "" 1509 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1510 purge = " PURGE" if expression.args.get("purge") else "" 1511 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1512 1513 def set_operation(self, expression: exp.SetOperation) -> str: 1514 op_type = type(expression) 1515 op_name = op_type.key.upper() 1516 1517 distinct = expression.args.get("distinct") 1518 if ( 1519 distinct is False 1520 and op_type in (exp.Except, exp.Intersect) 1521 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1522 ): 1523 self.unsupported(f"{op_name} ALL is not supported") 1524 1525 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1526 1527 if distinct is None: 1528 distinct = default_distinct 1529 if distinct is None: 1530 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1531 1532 if distinct is default_distinct: 1533 distinct_or_all = "" 1534 else: 1535 distinct_or_all = " DISTINCT" if distinct else " ALL" 1536 1537 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1538 side_kind = f"{side_kind} " if side_kind else "" 1539 1540 by_name = " BY NAME" if expression.args.get("by_name") else "" 1541 on = self.expressions(expression, key="on", flat=True) 1542 on = f" ON ({on})" if on else "" 1543 1544 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1545 1546 def set_operations(self, expression: exp.SetOperation) -> str: 1547 if not self.SET_OP_MODIFIERS: 1548 limit = expression.args.get("limit") 1549 order = expression.args.get("order") 1550 1551 if limit or order: 1552 select = self._move_ctes_to_top_level( 1553 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1554 ) 1555 1556 if limit: 1557 select = select.limit(limit.pop(), copy=False) 1558 if order: 1559 select = select.order_by(order.pop(), copy=False) 1560 return self.sql(select) 1561 1562 sqls: t.List[str] = [] 1563 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1564 1565 while stack: 1566 node = stack.pop() 1567 1568 if isinstance(node, exp.SetOperation): 1569 stack.append(node.expression) 1570 stack.append( 1571 self.maybe_comment( 1572 self.set_operation(node), comments=node.comments, separated=True 1573 ) 1574 ) 1575 stack.append(node.this) 1576 else: 1577 sqls.append(self.sql(node)) 1578 1579 this = self.sep().join(sqls) 1580 this = self.query_modifiers(expression, this) 1581 return self.prepend_ctes(expression, this) 1582 1583 def fetch_sql(self, expression: exp.Fetch) -> str: 1584 direction = expression.args.get("direction") 1585 direction = f" {direction}" if direction else "" 1586 count = self.sql(expression, "count") 1587 count = f" {count}" if count else "" 1588 limit_options = self.sql(expression, "limit_options") 1589 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1590 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1591 1592 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1593 percent = " PERCENT" if expression.args.get("percent") else "" 1594 rows = " ROWS" if expression.args.get("rows") else "" 1595 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1596 if not with_ties and rows: 1597 with_ties = " ONLY" 1598 return f"{percent}{rows}{with_ties}" 1599 1600 def filter_sql(self, expression: exp.Filter) -> str: 1601 if self.AGGREGATE_FILTER_SUPPORTED: 1602 this = self.sql(expression, "this") 1603 where = self.sql(expression, "expression").strip() 1604 return f"{this} FILTER({where})" 1605 1606 agg = expression.this 1607 agg_arg = agg.this 1608 cond = expression.expression.this 1609 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1610 return self.sql(agg) 1611 1612 def hint_sql(self, expression: exp.Hint) -> str: 1613 if not self.QUERY_HINTS: 1614 self.unsupported("Hints are not supported") 1615 return "" 1616 1617 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1618 1619 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1620 using = self.sql(expression, "using") 1621 using = f" USING {using}" if using else "" 1622 columns = self.expressions(expression, key="columns", flat=True) 1623 columns = f"({columns})" if columns else "" 1624 partition_by = self.expressions(expression, key="partition_by", flat=True) 1625 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1626 where = self.sql(expression, "where") 1627 include = self.expressions(expression, key="include", flat=True) 1628 if include: 1629 include = f" INCLUDE ({include})" 1630 with_storage = self.expressions(expression, key="with_storage", flat=True) 1631 with_storage = f" WITH ({with_storage})" if with_storage else "" 1632 tablespace = self.sql(expression, "tablespace") 1633 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1634 on = self.sql(expression, "on") 1635 on = f" ON {on}" if on else "" 1636 1637 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1638 1639 def index_sql(self, expression: exp.Index) -> str: 1640 unique = "UNIQUE " if expression.args.get("unique") else "" 1641 primary = "PRIMARY " if expression.args.get("primary") else "" 1642 amp = "AMP " if expression.args.get("amp") else "" 1643 name = self.sql(expression, "this") 1644 name = f"{name} " if name else "" 1645 table = self.sql(expression, "table") 1646 table = f"{self.INDEX_ON} {table}" if table else "" 1647 1648 index = "INDEX " if not table else "" 1649 1650 params = self.sql(expression, "params") 1651 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1652 1653 def identifier_sql(self, expression: exp.Identifier) -> str: 1654 text = expression.name 1655 lower = text.lower() 1656 text = lower if self.normalize and not expression.quoted else text 1657 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1658 if ( 1659 expression.quoted 1660 or self.dialect.can_identify(text, self.identify) 1661 or lower in self.RESERVED_KEYWORDS 1662 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1663 ): 1664 text = f"{self._identifier_start}{text}{self._identifier_end}" 1665 return text 1666 1667 def hex_sql(self, expression: exp.Hex) -> str: 1668 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1669 if self.dialect.HEX_LOWERCASE: 1670 text = self.func("LOWER", text) 1671 1672 return text 1673 1674 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1675 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1676 if not self.dialect.HEX_LOWERCASE: 1677 text = self.func("LOWER", text) 1678 return text 1679 1680 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1681 input_format = self.sql(expression, "input_format") 1682 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1683 output_format = self.sql(expression, "output_format") 1684 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1685 return self.sep().join((input_format, output_format)) 1686 1687 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1688 string = self.sql(exp.Literal.string(expression.name)) 1689 return f"{prefix}{string}" 1690 1691 def partition_sql(self, expression: exp.Partition) -> str: 1692 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1693 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1694 1695 def properties_sql(self, expression: exp.Properties) -> str: 1696 root_properties = [] 1697 with_properties = [] 1698 1699 for p in expression.expressions: 1700 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1701 if p_loc == exp.Properties.Location.POST_WITH: 1702 with_properties.append(p) 1703 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1704 root_properties.append(p) 1705 1706 root_props_ast = exp.Properties(expressions=root_properties) 1707 root_props_ast.parent = expression.parent 1708 1709 with_props_ast = exp.Properties(expressions=with_properties) 1710 with_props_ast.parent = expression.parent 1711 1712 root_props = self.root_properties(root_props_ast) 1713 with_props = self.with_properties(with_props_ast) 1714 1715 if root_props and with_props and not self.pretty: 1716 with_props = " " + with_props 1717 1718 return root_props + with_props 1719 1720 def root_properties(self, properties: exp.Properties) -> str: 1721 if properties.expressions: 1722 return self.expressions(properties, indent=False, sep=" ") 1723 return "" 1724 1725 def properties( 1726 self, 1727 properties: exp.Properties, 1728 prefix: str = "", 1729 sep: str = ", ", 1730 suffix: str = "", 1731 wrapped: bool = True, 1732 ) -> str: 1733 if properties.expressions: 1734 expressions = self.expressions(properties, sep=sep, indent=False) 1735 if expressions: 1736 expressions = self.wrap(expressions) if wrapped else expressions 1737 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1738 return "" 1739 1740 def with_properties(self, properties: exp.Properties) -> str: 1741 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1742 1743 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1744 properties_locs = defaultdict(list) 1745 for p in properties.expressions: 1746 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1747 if p_loc != exp.Properties.Location.UNSUPPORTED: 1748 properties_locs[p_loc].append(p) 1749 else: 1750 self.unsupported(f"Unsupported property {p.key}") 1751 1752 return properties_locs 1753 1754 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1755 if isinstance(expression.this, exp.Dot): 1756 return self.sql(expression, "this") 1757 return f"'{expression.name}'" if string_key else expression.name 1758 1759 def property_sql(self, expression: exp.Property) -> str: 1760 property_cls = expression.__class__ 1761 if property_cls == exp.Property: 1762 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1763 1764 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1765 if not property_name: 1766 self.unsupported(f"Unsupported property {expression.key}") 1767 1768 return f"{property_name}={self.sql(expression, 'this')}" 1769 1770 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1771 if self.SUPPORTS_CREATE_TABLE_LIKE: 1772 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1773 options = f" {options}" if options else "" 1774 1775 like = f"LIKE {self.sql(expression, 'this')}{options}" 1776 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1777 like = f"({like})" 1778 1779 return like 1780 1781 if expression.expressions: 1782 self.unsupported("Transpilation of LIKE property options is unsupported") 1783 1784 select = exp.select("*").from_(expression.this).limit(0) 1785 return f"AS {self.sql(select)}" 1786 1787 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1788 no = "NO " if expression.args.get("no") else "" 1789 protection = " PROTECTION" if expression.args.get("protection") else "" 1790 return f"{no}FALLBACK{protection}" 1791 1792 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1793 no = "NO " if expression.args.get("no") else "" 1794 local = expression.args.get("local") 1795 local = f"{local} " if local else "" 1796 dual = "DUAL " if expression.args.get("dual") else "" 1797 before = "BEFORE " if expression.args.get("before") else "" 1798 after = "AFTER " if expression.args.get("after") else "" 1799 return f"{no}{local}{dual}{before}{after}JOURNAL" 1800 1801 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1802 freespace = self.sql(expression, "this") 1803 percent = " PERCENT" if expression.args.get("percent") else "" 1804 return f"FREESPACE={freespace}{percent}" 1805 1806 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1807 if expression.args.get("default"): 1808 property = "DEFAULT" 1809 elif expression.args.get("on"): 1810 property = "ON" 1811 else: 1812 property = "OFF" 1813 return f"CHECKSUM={property}" 1814 1815 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1816 if expression.args.get("no"): 1817 return "NO MERGEBLOCKRATIO" 1818 if expression.args.get("default"): 1819 return "DEFAULT MERGEBLOCKRATIO" 1820 1821 percent = " PERCENT" if expression.args.get("percent") else "" 1822 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1823 1824 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1825 default = expression.args.get("default") 1826 minimum = expression.args.get("minimum") 1827 maximum = expression.args.get("maximum") 1828 if default or minimum or maximum: 1829 if default: 1830 prop = "DEFAULT" 1831 elif minimum: 1832 prop = "MINIMUM" 1833 else: 1834 prop = "MAXIMUM" 1835 return f"{prop} DATABLOCKSIZE" 1836 units = expression.args.get("units") 1837 units = f" {units}" if units else "" 1838 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1839 1840 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1841 autotemp = expression.args.get("autotemp") 1842 always = expression.args.get("always") 1843 default = expression.args.get("default") 1844 manual = expression.args.get("manual") 1845 never = expression.args.get("never") 1846 1847 if autotemp is not None: 1848 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1849 elif always: 1850 prop = "ALWAYS" 1851 elif default: 1852 prop = "DEFAULT" 1853 elif manual: 1854 prop = "MANUAL" 1855 elif never: 1856 prop = "NEVER" 1857 return f"BLOCKCOMPRESSION={prop}" 1858 1859 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1860 no = expression.args.get("no") 1861 no = " NO" if no else "" 1862 concurrent = expression.args.get("concurrent") 1863 concurrent = " CONCURRENT" if concurrent else "" 1864 target = self.sql(expression, "target") 1865 target = f" {target}" if target else "" 1866 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1867 1868 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1869 if isinstance(expression.this, list): 1870 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1871 if expression.this: 1872 modulus = self.sql(expression, "this") 1873 remainder = self.sql(expression, "expression") 1874 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1875 1876 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1877 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1878 return f"FROM ({from_expressions}) TO ({to_expressions})" 1879 1880 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1881 this = self.sql(expression, "this") 1882 1883 for_values_or_default = expression.expression 1884 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1885 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1886 else: 1887 for_values_or_default = " DEFAULT" 1888 1889 return f"PARTITION OF {this}{for_values_or_default}" 1890 1891 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1892 kind = expression.args.get("kind") 1893 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1894 for_or_in = expression.args.get("for_or_in") 1895 for_or_in = f" {for_or_in}" if for_or_in else "" 1896 lock_type = expression.args.get("lock_type") 1897 override = " OVERRIDE" if expression.args.get("override") else "" 1898 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1899 1900 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1901 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1902 statistics = expression.args.get("statistics") 1903 statistics_sql = "" 1904 if statistics is not None: 1905 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1906 return f"{data_sql}{statistics_sql}" 1907 1908 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1909 this = self.sql(expression, "this") 1910 this = f"HISTORY_TABLE={this}" if this else "" 1911 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1912 data_consistency = ( 1913 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1914 ) 1915 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1916 retention_period = ( 1917 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1918 ) 1919 1920 if this: 1921 on_sql = self.func("ON", this, data_consistency, retention_period) 1922 else: 1923 on_sql = "ON" if expression.args.get("on") else "OFF" 1924 1925 sql = f"SYSTEM_VERSIONING={on_sql}" 1926 1927 return f"WITH({sql})" if expression.args.get("with") else sql 1928 1929 def insert_sql(self, expression: exp.Insert) -> str: 1930 hint = self.sql(expression, "hint") 1931 overwrite = expression.args.get("overwrite") 1932 1933 if isinstance(expression.this, exp.Directory): 1934 this = " OVERWRITE" if overwrite else " INTO" 1935 else: 1936 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1937 1938 stored = self.sql(expression, "stored") 1939 stored = f" {stored}" if stored else "" 1940 alternative = expression.args.get("alternative") 1941 alternative = f" OR {alternative}" if alternative else "" 1942 ignore = " IGNORE" if expression.args.get("ignore") else "" 1943 is_function = expression.args.get("is_function") 1944 if is_function: 1945 this = f"{this} FUNCTION" 1946 this = f"{this} {self.sql(expression, 'this')}" 1947 1948 exists = " IF EXISTS" if expression.args.get("exists") else "" 1949 where = self.sql(expression, "where") 1950 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1951 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1952 on_conflict = self.sql(expression, "conflict") 1953 on_conflict = f" {on_conflict}" if on_conflict else "" 1954 by_name = " BY NAME" if expression.args.get("by_name") else "" 1955 returning = self.sql(expression, "returning") 1956 1957 if self.RETURNING_END: 1958 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1959 else: 1960 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1961 1962 partition_by = self.sql(expression, "partition") 1963 partition_by = f" {partition_by}" if partition_by else "" 1964 settings = self.sql(expression, "settings") 1965 settings = f" {settings}" if settings else "" 1966 1967 source = self.sql(expression, "source") 1968 source = f"TABLE {source}" if source else "" 1969 1970 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1971 return self.prepend_ctes(expression, sql) 1972 1973 def introducer_sql(self, expression: exp.Introducer) -> str: 1974 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1975 1976 def kill_sql(self, expression: exp.Kill) -> str: 1977 kind = self.sql(expression, "kind") 1978 kind = f" {kind}" if kind else "" 1979 this = self.sql(expression, "this") 1980 this = f" {this}" if this else "" 1981 return f"KILL{kind}{this}" 1982 1983 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1984 return expression.name 1985 1986 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1987 return expression.name 1988 1989 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1990 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1991 1992 constraint = self.sql(expression, "constraint") 1993 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1994 1995 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1996 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1997 action = self.sql(expression, "action") 1998 1999 expressions = self.expressions(expression, flat=True) 2000 if expressions: 2001 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2002 expressions = f" {set_keyword}{expressions}" 2003 2004 where = self.sql(expression, "where") 2005 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2006 2007 def returning_sql(self, expression: exp.Returning) -> str: 2008 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2009 2010 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2011 fields = self.sql(expression, "fields") 2012 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2013 escaped = self.sql(expression, "escaped") 2014 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2015 items = self.sql(expression, "collection_items") 2016 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2017 keys = self.sql(expression, "map_keys") 2018 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2019 lines = self.sql(expression, "lines") 2020 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2021 null = self.sql(expression, "null") 2022 null = f" NULL DEFINED AS {null}" if null else "" 2023 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2024 2025 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2026 return f"WITH ({self.expressions(expression, flat=True)})" 2027 2028 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2029 this = f"{self.sql(expression, 'this')} INDEX" 2030 target = self.sql(expression, "target") 2031 target = f" FOR {target}" if target else "" 2032 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2033 2034 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2035 this = self.sql(expression, "this") 2036 kind = self.sql(expression, "kind") 2037 expr = self.sql(expression, "expression") 2038 return f"{this} ({kind} => {expr})" 2039 2040 def table_parts(self, expression: exp.Table) -> str: 2041 return ".".join( 2042 self.sql(part) 2043 for part in ( 2044 expression.args.get("catalog"), 2045 expression.args.get("db"), 2046 expression.args.get("this"), 2047 ) 2048 if part is not None 2049 ) 2050 2051 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2052 table = self.table_parts(expression) 2053 only = "ONLY " if expression.args.get("only") else "" 2054 partition = self.sql(expression, "partition") 2055 partition = f" {partition}" if partition else "" 2056 version = self.sql(expression, "version") 2057 version = f" {version}" if version else "" 2058 alias = self.sql(expression, "alias") 2059 alias = f"{sep}{alias}" if alias else "" 2060 2061 sample = self.sql(expression, "sample") 2062 if self.dialect.ALIAS_POST_TABLESAMPLE: 2063 sample_pre_alias = sample 2064 sample_post_alias = "" 2065 else: 2066 sample_pre_alias = "" 2067 sample_post_alias = sample 2068 2069 hints = self.expressions(expression, key="hints", sep=" ") 2070 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2071 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2072 joins = self.indent( 2073 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2074 ) 2075 laterals = self.expressions(expression, key="laterals", sep="") 2076 2077 file_format = self.sql(expression, "format") 2078 if file_format: 2079 pattern = self.sql(expression, "pattern") 2080 pattern = f", PATTERN => {pattern}" if pattern else "" 2081 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2082 2083 ordinality = expression.args.get("ordinality") or "" 2084 if ordinality: 2085 ordinality = f" WITH ORDINALITY{alias}" 2086 alias = "" 2087 2088 when = self.sql(expression, "when") 2089 if when: 2090 table = f"{table} {when}" 2091 2092 changes = self.sql(expression, "changes") 2093 changes = f" {changes}" if changes else "" 2094 2095 rows_from = self.expressions(expression, key="rows_from") 2096 if rows_from: 2097 table = f"ROWS FROM {self.wrap(rows_from)}" 2098 2099 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2100 2101 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2102 table = self.func("TABLE", expression.this) 2103 alias = self.sql(expression, "alias") 2104 alias = f" AS {alias}" if alias else "" 2105 sample = self.sql(expression, "sample") 2106 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2107 joins = self.indent( 2108 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2109 ) 2110 return f"{table}{alias}{pivots}{sample}{joins}" 2111 2112 def tablesample_sql( 2113 self, 2114 expression: exp.TableSample, 2115 tablesample_keyword: t.Optional[str] = None, 2116 ) -> str: 2117 method = self.sql(expression, "method") 2118 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2119 numerator = self.sql(expression, "bucket_numerator") 2120 denominator = self.sql(expression, "bucket_denominator") 2121 field = self.sql(expression, "bucket_field") 2122 field = f" ON {field}" if field else "" 2123 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2124 seed = self.sql(expression, "seed") 2125 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2126 2127 size = self.sql(expression, "size") 2128 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2129 size = f"{size} ROWS" 2130 2131 percent = self.sql(expression, "percent") 2132 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2133 percent = f"{percent} PERCENT" 2134 2135 expr = f"{bucket}{percent}{size}" 2136 if self.TABLESAMPLE_REQUIRES_PARENS: 2137 expr = f"({expr})" 2138 2139 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2140 2141 def pivot_sql(self, expression: exp.Pivot) -> str: 2142 expressions = self.expressions(expression, flat=True) 2143 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2144 2145 group = self.sql(expression, "group") 2146 2147 if expression.this: 2148 this = self.sql(expression, "this") 2149 if not expressions: 2150 return f"UNPIVOT {this}" 2151 2152 on = f"{self.seg('ON')} {expressions}" 2153 into = self.sql(expression, "into") 2154 into = f"{self.seg('INTO')} {into}" if into else "" 2155 using = self.expressions(expression, key="using", flat=True) 2156 using = f"{self.seg('USING')} {using}" if using else "" 2157 return f"{direction} {this}{on}{into}{using}{group}" 2158 2159 alias = self.sql(expression, "alias") 2160 alias = f" AS {alias}" if alias else "" 2161 2162 fields = self.expressions( 2163 expression, 2164 "fields", 2165 sep=" ", 2166 dynamic=True, 2167 new_line=True, 2168 skip_first=True, 2169 skip_last=True, 2170 ) 2171 2172 include_nulls = expression.args.get("include_nulls") 2173 if include_nulls is not None: 2174 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2175 else: 2176 nulls = "" 2177 2178 default_on_null = self.sql(expression, "default_on_null") 2179 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2180 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2181 2182 def version_sql(self, expression: exp.Version) -> str: 2183 this = f"FOR {expression.name}" 2184 kind = expression.text("kind") 2185 expr = self.sql(expression, "expression") 2186 return f"{this} {kind} {expr}" 2187 2188 def tuple_sql(self, expression: exp.Tuple) -> str: 2189 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2190 2191 def update_sql(self, expression: exp.Update) -> str: 2192 this = self.sql(expression, "this") 2193 set_sql = self.expressions(expression, flat=True) 2194 from_sql = self.sql(expression, "from") 2195 where_sql = self.sql(expression, "where") 2196 returning = self.sql(expression, "returning") 2197 order = self.sql(expression, "order") 2198 limit = self.sql(expression, "limit") 2199 if self.RETURNING_END: 2200 expression_sql = f"{from_sql}{where_sql}{returning}" 2201 else: 2202 expression_sql = f"{returning}{from_sql}{where_sql}" 2203 options = self.expressions(expression, key="options") 2204 options = f" OPTION({options})" if options else "" 2205 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}{options}" 2206 return self.prepend_ctes(expression, sql) 2207 2208 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2209 values_as_table = values_as_table and self.VALUES_AS_TABLE 2210 2211 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2212 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2213 args = self.expressions(expression) 2214 alias = self.sql(expression, "alias") 2215 values = f"VALUES{self.seg('')}{args}" 2216 values = ( 2217 f"({values})" 2218 if self.WRAP_DERIVED_VALUES 2219 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2220 else values 2221 ) 2222 return f"{values} AS {alias}" if alias else values 2223 2224 # Converts `VALUES...` expression into a series of select unions. 2225 alias_node = expression.args.get("alias") 2226 column_names = alias_node and alias_node.columns 2227 2228 selects: t.List[exp.Query] = [] 2229 2230 for i, tup in enumerate(expression.expressions): 2231 row = tup.expressions 2232 2233 if i == 0 and column_names: 2234 row = [ 2235 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2236 ] 2237 2238 selects.append(exp.Select(expressions=row)) 2239 2240 if self.pretty: 2241 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2242 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2243 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2244 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2245 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2246 2247 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2248 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2249 return f"({unions}){alias}" 2250 2251 def var_sql(self, expression: exp.Var) -> str: 2252 return self.sql(expression, "this") 2253 2254 @unsupported_args("expressions") 2255 def into_sql(self, expression: exp.Into) -> str: 2256 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2257 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2258 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2259 2260 def from_sql(self, expression: exp.From) -> str: 2261 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2262 2263 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2264 grouping_sets = self.expressions(expression, indent=False) 2265 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2266 2267 def rollup_sql(self, expression: exp.Rollup) -> str: 2268 expressions = self.expressions(expression, indent=False) 2269 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2270 2271 def cube_sql(self, expression: exp.Cube) -> str: 2272 expressions = self.expressions(expression, indent=False) 2273 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2274 2275 def group_sql(self, expression: exp.Group) -> str: 2276 group_by_all = expression.args.get("all") 2277 if group_by_all is True: 2278 modifier = " ALL" 2279 elif group_by_all is False: 2280 modifier = " DISTINCT" 2281 else: 2282 modifier = "" 2283 2284 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2285 2286 grouping_sets = self.expressions(expression, key="grouping_sets") 2287 cube = self.expressions(expression, key="cube") 2288 rollup = self.expressions(expression, key="rollup") 2289 2290 groupings = csv( 2291 self.seg(grouping_sets) if grouping_sets else "", 2292 self.seg(cube) if cube else "", 2293 self.seg(rollup) if rollup else "", 2294 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2295 sep=self.GROUPINGS_SEP, 2296 ) 2297 2298 if ( 2299 expression.expressions 2300 and groupings 2301 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2302 ): 2303 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2304 2305 return f"{group_by}{groupings}" 2306 2307 def having_sql(self, expression: exp.Having) -> str: 2308 this = self.indent(self.sql(expression, "this")) 2309 return f"{self.seg('HAVING')}{self.sep()}{this}" 2310 2311 def connect_sql(self, expression: exp.Connect) -> str: 2312 start = self.sql(expression, "start") 2313 start = self.seg(f"START WITH {start}") if start else "" 2314 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2315 connect = self.sql(expression, "connect") 2316 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2317 return start + connect 2318 2319 def prior_sql(self, expression: exp.Prior) -> str: 2320 return f"PRIOR {self.sql(expression, 'this')}" 2321 2322 def join_sql(self, expression: exp.Join) -> str: 2323 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2324 side = None 2325 else: 2326 side = expression.side 2327 2328 op_sql = " ".join( 2329 op 2330 for op in ( 2331 expression.method, 2332 "GLOBAL" if expression.args.get("global") else None, 2333 side, 2334 expression.kind, 2335 expression.hint if self.JOIN_HINTS else None, 2336 ) 2337 if op 2338 ) 2339 match_cond = self.sql(expression, "match_condition") 2340 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2341 on_sql = self.sql(expression, "on") 2342 using = expression.args.get("using") 2343 2344 if not on_sql and using: 2345 on_sql = csv(*(self.sql(column) for column in using)) 2346 2347 this = expression.this 2348 this_sql = self.sql(this) 2349 2350 exprs = self.expressions(expression) 2351 if exprs: 2352 this_sql = f"{this_sql},{self.seg(exprs)}" 2353 2354 if on_sql: 2355 on_sql = self.indent(on_sql, skip_first=True) 2356 space = self.seg(" " * self.pad) if self.pretty else " " 2357 if using: 2358 on_sql = f"{space}USING ({on_sql})" 2359 else: 2360 on_sql = f"{space}ON {on_sql}" 2361 elif not op_sql: 2362 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2363 return f" {this_sql}" 2364 2365 return f", {this_sql}" 2366 2367 if op_sql != "STRAIGHT_JOIN": 2368 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2369 2370 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2371 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2372 2373 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2374 args = self.expressions(expression, flat=True) 2375 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2376 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2377 2378 def lateral_op(self, expression: exp.Lateral) -> str: 2379 cross_apply = expression.args.get("cross_apply") 2380 2381 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2382 if cross_apply is True: 2383 op = "INNER JOIN " 2384 elif cross_apply is False: 2385 op = "LEFT JOIN " 2386 else: 2387 op = "" 2388 2389 return f"{op}LATERAL" 2390 2391 def lateral_sql(self, expression: exp.Lateral) -> str: 2392 this = self.sql(expression, "this") 2393 2394 if expression.args.get("view"): 2395 alias = expression.args["alias"] 2396 columns = self.expressions(alias, key="columns", flat=True) 2397 table = f" {alias.name}" if alias.name else "" 2398 columns = f" AS {columns}" if columns else "" 2399 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2400 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2401 2402 alias = self.sql(expression, "alias") 2403 alias = f" AS {alias}" if alias else "" 2404 2405 ordinality = expression.args.get("ordinality") or "" 2406 if ordinality: 2407 ordinality = f" WITH ORDINALITY{alias}" 2408 alias = "" 2409 2410 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2411 2412 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2413 this = self.sql(expression, "this") 2414 2415 args = [ 2416 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2417 for e in (expression.args.get(k) for k in ("offset", "expression")) 2418 if e 2419 ] 2420 2421 args_sql = ", ".join(self.sql(e) for e in args) 2422 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2423 expressions = self.expressions(expression, flat=True) 2424 limit_options = self.sql(expression, "limit_options") 2425 expressions = f" BY {expressions}" if expressions else "" 2426 2427 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2428 2429 def offset_sql(self, expression: exp.Offset) -> str: 2430 this = self.sql(expression, "this") 2431 value = expression.expression 2432 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2433 expressions = self.expressions(expression, flat=True) 2434 expressions = f" BY {expressions}" if expressions else "" 2435 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2436 2437 def setitem_sql(self, expression: exp.SetItem) -> str: 2438 kind = self.sql(expression, "kind") 2439 kind = f"{kind} " if kind else "" 2440 this = self.sql(expression, "this") 2441 expressions = self.expressions(expression) 2442 collate = self.sql(expression, "collate") 2443 collate = f" COLLATE {collate}" if collate else "" 2444 global_ = "GLOBAL " if expression.args.get("global") else "" 2445 return f"{global_}{kind}{this}{expressions}{collate}" 2446 2447 def set_sql(self, expression: exp.Set) -> str: 2448 expressions = f" {self.expressions(expression, flat=True)}" 2449 tag = " TAG" if expression.args.get("tag") else "" 2450 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2451 2452 def queryband_sql(self, expression: exp.QueryBand) -> str: 2453 this = self.sql(expression, "this") 2454 update = " UPDATE" if expression.args.get("update") else "" 2455 scope = self.sql(expression, "scope") 2456 scope = f" FOR {scope}" if scope else "" 2457 2458 return f"QUERY_BAND = {this}{update}{scope}" 2459 2460 def pragma_sql(self, expression: exp.Pragma) -> str: 2461 return f"PRAGMA {self.sql(expression, 'this')}" 2462 2463 def lock_sql(self, expression: exp.Lock) -> str: 2464 if not self.LOCKING_READS_SUPPORTED: 2465 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2466 return "" 2467 2468 update = expression.args["update"] 2469 key = expression.args.get("key") 2470 if update: 2471 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2472 else: 2473 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2474 expressions = self.expressions(expression, flat=True) 2475 expressions = f" OF {expressions}" if expressions else "" 2476 wait = expression.args.get("wait") 2477 2478 if wait is not None: 2479 if isinstance(wait, exp.Literal): 2480 wait = f" WAIT {self.sql(wait)}" 2481 else: 2482 wait = " NOWAIT" if wait else " SKIP LOCKED" 2483 2484 return f"{lock_type}{expressions}{wait or ''}" 2485 2486 def literal_sql(self, expression: exp.Literal) -> str: 2487 text = expression.this or "" 2488 if expression.is_string: 2489 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2490 return text 2491 2492 def escape_str( 2493 self, 2494 text: str, 2495 escape_backslash: bool = True, 2496 delimiter: t.Optional[str] = None, 2497 escaped_delimiter: t.Optional[str] = None, 2498 ) -> str: 2499 if self.dialect.ESCAPED_SEQUENCES: 2500 to_escaped = self.dialect.ESCAPED_SEQUENCES 2501 text = "".join( 2502 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2503 ) 2504 2505 delimiter = delimiter or self.dialect.QUOTE_END 2506 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2507 2508 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2509 2510 def loaddata_sql(self, expression: exp.LoadData) -> str: 2511 local = " LOCAL" if expression.args.get("local") else "" 2512 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2513 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2514 this = f" INTO TABLE {self.sql(expression, 'this')}" 2515 partition = self.sql(expression, "partition") 2516 partition = f" {partition}" if partition else "" 2517 input_format = self.sql(expression, "input_format") 2518 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2519 serde = self.sql(expression, "serde") 2520 serde = f" SERDE {serde}" if serde else "" 2521 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2522 2523 def null_sql(self, *_) -> str: 2524 return "NULL" 2525 2526 def boolean_sql(self, expression: exp.Boolean) -> str: 2527 return "TRUE" if expression.this else "FALSE" 2528 2529 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2530 this = self.sql(expression, "this") 2531 this = f"{this} " if this else this 2532 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2533 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2534 2535 def withfill_sql(self, expression: exp.WithFill) -> str: 2536 from_sql = self.sql(expression, "from") 2537 from_sql = f" FROM {from_sql}" if from_sql else "" 2538 to_sql = self.sql(expression, "to") 2539 to_sql = f" TO {to_sql}" if to_sql else "" 2540 step_sql = self.sql(expression, "step") 2541 step_sql = f" STEP {step_sql}" if step_sql else "" 2542 interpolated_values = [ 2543 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2544 if isinstance(e, exp.Alias) 2545 else self.sql(e, "this") 2546 for e in expression.args.get("interpolate") or [] 2547 ] 2548 interpolate = ( 2549 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2550 ) 2551 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2552 2553 def cluster_sql(self, expression: exp.Cluster) -> str: 2554 return self.op_expressions("CLUSTER BY", expression) 2555 2556 def distribute_sql(self, expression: exp.Distribute) -> str: 2557 return self.op_expressions("DISTRIBUTE BY", expression) 2558 2559 def sort_sql(self, expression: exp.Sort) -> str: 2560 return self.op_expressions("SORT BY", expression) 2561 2562 def ordered_sql(self, expression: exp.Ordered) -> str: 2563 desc = expression.args.get("desc") 2564 asc = not desc 2565 2566 nulls_first = expression.args.get("nulls_first") 2567 nulls_last = not nulls_first 2568 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2569 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2570 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2571 2572 this = self.sql(expression, "this") 2573 2574 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2575 nulls_sort_change = "" 2576 if nulls_first and ( 2577 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2578 ): 2579 nulls_sort_change = " NULLS FIRST" 2580 elif ( 2581 nulls_last 2582 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2583 and not nulls_are_last 2584 ): 2585 nulls_sort_change = " NULLS LAST" 2586 2587 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2588 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2589 window = expression.find_ancestor(exp.Window, exp.Select) 2590 if isinstance(window, exp.Window) and window.args.get("spec"): 2591 self.unsupported( 2592 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2593 ) 2594 nulls_sort_change = "" 2595 elif self.NULL_ORDERING_SUPPORTED is False and ( 2596 (asc and nulls_sort_change == " NULLS LAST") 2597 or (desc and nulls_sort_change == " NULLS FIRST") 2598 ): 2599 # BigQuery does not allow these ordering/nulls combinations when used under 2600 # an aggregation func or under a window containing one 2601 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2602 2603 if isinstance(ancestor, exp.Window): 2604 ancestor = ancestor.this 2605 if isinstance(ancestor, exp.AggFunc): 2606 self.unsupported( 2607 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2608 ) 2609 nulls_sort_change = "" 2610 elif self.NULL_ORDERING_SUPPORTED is None: 2611 if expression.this.is_int: 2612 self.unsupported( 2613 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2614 ) 2615 elif not isinstance(expression.this, exp.Rand): 2616 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2617 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2618 nulls_sort_change = "" 2619 2620 with_fill = self.sql(expression, "with_fill") 2621 with_fill = f" {with_fill}" if with_fill else "" 2622 2623 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2624 2625 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2626 window_frame = self.sql(expression, "window_frame") 2627 window_frame = f"{window_frame} " if window_frame else "" 2628 2629 this = self.sql(expression, "this") 2630 2631 return f"{window_frame}{this}" 2632 2633 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2634 partition = self.partition_by_sql(expression) 2635 order = self.sql(expression, "order") 2636 measures = self.expressions(expression, key="measures") 2637 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2638 rows = self.sql(expression, "rows") 2639 rows = self.seg(rows) if rows else "" 2640 after = self.sql(expression, "after") 2641 after = self.seg(after) if after else "" 2642 pattern = self.sql(expression, "pattern") 2643 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2644 definition_sqls = [ 2645 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2646 for definition in expression.args.get("define", []) 2647 ] 2648 definitions = self.expressions(sqls=definition_sqls) 2649 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2650 body = "".join( 2651 ( 2652 partition, 2653 order, 2654 measures, 2655 rows, 2656 after, 2657 pattern, 2658 define, 2659 ) 2660 ) 2661 alias = self.sql(expression, "alias") 2662 alias = f" {alias}" if alias else "" 2663 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2664 2665 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2666 limit = expression.args.get("limit") 2667 2668 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2669 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2670 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2671 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2672 2673 return csv( 2674 *sqls, 2675 *[self.sql(join) for join in expression.args.get("joins") or []], 2676 self.sql(expression, "match"), 2677 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2678 self.sql(expression, "prewhere"), 2679 self.sql(expression, "where"), 2680 self.sql(expression, "connect"), 2681 self.sql(expression, "group"), 2682 self.sql(expression, "having"), 2683 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2684 self.sql(expression, "order"), 2685 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2686 *self.after_limit_modifiers(expression), 2687 self.options_modifier(expression), 2688 self.for_modifiers(expression), 2689 sep="", 2690 ) 2691 2692 def options_modifier(self, expression: exp.Expression) -> str: 2693 options = self.expressions(expression, key="options") 2694 return f" {options}" if options else "" 2695 2696 def for_modifiers(self, expression: exp.Expression) -> str: 2697 for_modifiers = self.expressions(expression, key="for") 2698 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2699 2700 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2701 self.unsupported("Unsupported query option.") 2702 return "" 2703 2704 def offset_limit_modifiers( 2705 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2706 ) -> t.List[str]: 2707 return [ 2708 self.sql(expression, "offset") if fetch else self.sql(limit), 2709 self.sql(limit) if fetch else self.sql(expression, "offset"), 2710 ] 2711 2712 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2713 locks = self.expressions(expression, key="locks", sep=" ") 2714 locks = f" {locks}" if locks else "" 2715 return [locks, self.sql(expression, "sample")] 2716 2717 def select_sql(self, expression: exp.Select) -> str: 2718 into = expression.args.get("into") 2719 if not self.SUPPORTS_SELECT_INTO and into: 2720 into.pop() 2721 2722 hint = self.sql(expression, "hint") 2723 distinct = self.sql(expression, "distinct") 2724 distinct = f" {distinct}" if distinct else "" 2725 kind = self.sql(expression, "kind") 2726 2727 limit = expression.args.get("limit") 2728 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2729 top = self.limit_sql(limit, top=True) 2730 limit.pop() 2731 else: 2732 top = "" 2733 2734 expressions = self.expressions(expression) 2735 2736 if kind: 2737 if kind in self.SELECT_KINDS: 2738 kind = f" AS {kind}" 2739 else: 2740 if kind == "STRUCT": 2741 expressions = self.expressions( 2742 sqls=[ 2743 self.sql( 2744 exp.Struct( 2745 expressions=[ 2746 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2747 if isinstance(e, exp.Alias) 2748 else e 2749 for e in expression.expressions 2750 ] 2751 ) 2752 ) 2753 ] 2754 ) 2755 kind = "" 2756 2757 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2758 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2759 2760 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2761 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2762 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2763 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2764 sql = self.query_modifiers( 2765 expression, 2766 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2767 self.sql(expression, "into", comment=False), 2768 self.sql(expression, "from", comment=False), 2769 ) 2770 2771 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2772 if expression.args.get("with"): 2773 sql = self.maybe_comment(sql, expression) 2774 expression.pop_comments() 2775 2776 sql = self.prepend_ctes(expression, sql) 2777 2778 if not self.SUPPORTS_SELECT_INTO and into: 2779 if into.args.get("temporary"): 2780 table_kind = " TEMPORARY" 2781 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2782 table_kind = " UNLOGGED" 2783 else: 2784 table_kind = "" 2785 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2786 2787 return sql 2788 2789 def schema_sql(self, expression: exp.Schema) -> str: 2790 this = self.sql(expression, "this") 2791 sql = self.schema_columns_sql(expression) 2792 return f"{this} {sql}" if this and sql else this or sql 2793 2794 def schema_columns_sql(self, expression: exp.Schema) -> str: 2795 if expression.expressions: 2796 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2797 return "" 2798 2799 def star_sql(self, expression: exp.Star) -> str: 2800 except_ = self.expressions(expression, key="except", flat=True) 2801 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2802 replace = self.expressions(expression, key="replace", flat=True) 2803 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2804 rename = self.expressions(expression, key="rename", flat=True) 2805 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2806 return f"*{except_}{replace}{rename}" 2807 2808 def parameter_sql(self, expression: exp.Parameter) -> str: 2809 this = self.sql(expression, "this") 2810 return f"{self.PARAMETER_TOKEN}{this}" 2811 2812 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2813 this = self.sql(expression, "this") 2814 kind = expression.text("kind") 2815 if kind: 2816 kind = f"{kind}." 2817 return f"@@{kind}{this}" 2818 2819 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2820 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2821 2822 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2823 alias = self.sql(expression, "alias") 2824 alias = f"{sep}{alias}" if alias else "" 2825 sample = self.sql(expression, "sample") 2826 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2827 alias = f"{sample}{alias}" 2828 2829 # Set to None so it's not generated again by self.query_modifiers() 2830 expression.set("sample", None) 2831 2832 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2833 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2834 return self.prepend_ctes(expression, sql) 2835 2836 def qualify_sql(self, expression: exp.Qualify) -> str: 2837 this = self.indent(self.sql(expression, "this")) 2838 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2839 2840 def unnest_sql(self, expression: exp.Unnest) -> str: 2841 args = self.expressions(expression, flat=True) 2842 2843 alias = expression.args.get("alias") 2844 offset = expression.args.get("offset") 2845 2846 if self.UNNEST_WITH_ORDINALITY: 2847 if alias and isinstance(offset, exp.Expression): 2848 alias.append("columns", offset) 2849 2850 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2851 columns = alias.columns 2852 alias = self.sql(columns[0]) if columns else "" 2853 else: 2854 alias = self.sql(alias) 2855 2856 alias = f" AS {alias}" if alias else alias 2857 if self.UNNEST_WITH_ORDINALITY: 2858 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2859 else: 2860 if isinstance(offset, exp.Expression): 2861 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2862 elif offset: 2863 suffix = f"{alias} WITH OFFSET" 2864 else: 2865 suffix = alias 2866 2867 return f"UNNEST({args}){suffix}" 2868 2869 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2870 return "" 2871 2872 def where_sql(self, expression: exp.Where) -> str: 2873 this = self.indent(self.sql(expression, "this")) 2874 return f"{self.seg('WHERE')}{self.sep()}{this}" 2875 2876 def window_sql(self, expression: exp.Window) -> str: 2877 this = self.sql(expression, "this") 2878 partition = self.partition_by_sql(expression) 2879 order = expression.args.get("order") 2880 order = self.order_sql(order, flat=True) if order else "" 2881 spec = self.sql(expression, "spec") 2882 alias = self.sql(expression, "alias") 2883 over = self.sql(expression, "over") or "OVER" 2884 2885 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2886 2887 first = expression.args.get("first") 2888 if first is None: 2889 first = "" 2890 else: 2891 first = "FIRST" if first else "LAST" 2892 2893 if not partition and not order and not spec and alias: 2894 return f"{this} {alias}" 2895 2896 args = self.format_args( 2897 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2898 ) 2899 return f"{this} ({args})" 2900 2901 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2902 partition = self.expressions(expression, key="partition_by", flat=True) 2903 return f"PARTITION BY {partition}" if partition else "" 2904 2905 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2906 kind = self.sql(expression, "kind") 2907 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2908 end = ( 2909 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2910 or "CURRENT ROW" 2911 ) 2912 2913 window_spec = f"{kind} BETWEEN {start} AND {end}" 2914 2915 exclude = self.sql(expression, "exclude") 2916 if exclude: 2917 if self.SUPPORTS_WINDOW_EXCLUDE: 2918 window_spec += f" EXCLUDE {exclude}" 2919 else: 2920 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2921 2922 return window_spec 2923 2924 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2925 this = self.sql(expression, "this") 2926 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2927 return f"{this} WITHIN GROUP ({expression_sql})" 2928 2929 def between_sql(self, expression: exp.Between) -> str: 2930 this = self.sql(expression, "this") 2931 low = self.sql(expression, "low") 2932 high = self.sql(expression, "high") 2933 symmetric = expression.args.get("symmetric") 2934 2935 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2936 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2937 2938 flag = ( 2939 " SYMMETRIC" 2940 if symmetric 2941 else " ASYMMETRIC" 2942 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2943 else "" # silently drop ASYMMETRIC – semantics identical 2944 ) 2945 return f"{this} BETWEEN{flag} {low} AND {high}" 2946 2947 def bracket_offset_expressions( 2948 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2949 ) -> t.List[exp.Expression]: 2950 return apply_index_offset( 2951 expression.this, 2952 expression.expressions, 2953 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2954 dialect=self.dialect, 2955 ) 2956 2957 def bracket_sql(self, expression: exp.Bracket) -> str: 2958 expressions = self.bracket_offset_expressions(expression) 2959 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2960 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2961 2962 def all_sql(self, expression: exp.All) -> str: 2963 this = self.sql(expression, "this") 2964 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2965 this = self.wrap(this) 2966 return f"ALL {this}" 2967 2968 def any_sql(self, expression: exp.Any) -> str: 2969 this = self.sql(expression, "this") 2970 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2971 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2972 this = self.wrap(this) 2973 return f"ANY{this}" 2974 return f"ANY {this}" 2975 2976 def exists_sql(self, expression: exp.Exists) -> str: 2977 return f"EXISTS{self.wrap(expression)}" 2978 2979 def case_sql(self, expression: exp.Case) -> str: 2980 this = self.sql(expression, "this") 2981 statements = [f"CASE {this}" if this else "CASE"] 2982 2983 for e in expression.args["ifs"]: 2984 statements.append(f"WHEN {self.sql(e, 'this')}") 2985 statements.append(f"THEN {self.sql(e, 'true')}") 2986 2987 default = self.sql(expression, "default") 2988 2989 if default: 2990 statements.append(f"ELSE {default}") 2991 2992 statements.append("END") 2993 2994 if self.pretty and self.too_wide(statements): 2995 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2996 2997 return " ".join(statements) 2998 2999 def constraint_sql(self, expression: exp.Constraint) -> str: 3000 this = self.sql(expression, "this") 3001 expressions = self.expressions(expression, flat=True) 3002 return f"CONSTRAINT {this} {expressions}" 3003 3004 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3005 order = expression.args.get("order") 3006 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3007 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3008 3009 def extract_sql(self, expression: exp.Extract) -> str: 3010 from sqlglot.dialects.dialect import map_date_part 3011 3012 this = ( 3013 map_date_part(expression.this, self.dialect) 3014 if self.NORMALIZE_EXTRACT_DATE_PARTS 3015 else expression.this 3016 ) 3017 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3018 expression_sql = self.sql(expression, "expression") 3019 3020 return f"EXTRACT({this_sql} FROM {expression_sql})" 3021 3022 def trim_sql(self, expression: exp.Trim) -> str: 3023 trim_type = self.sql(expression, "position") 3024 3025 if trim_type == "LEADING": 3026 func_name = "LTRIM" 3027 elif trim_type == "TRAILING": 3028 func_name = "RTRIM" 3029 else: 3030 func_name = "TRIM" 3031 3032 return self.func(func_name, expression.this, expression.expression) 3033 3034 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3035 args = expression.expressions 3036 if isinstance(expression, exp.ConcatWs): 3037 args = args[1:] # Skip the delimiter 3038 3039 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3040 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3041 3042 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3043 3044 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3045 if not e.type: 3046 from sqlglot.optimizer.annotate_types import annotate_types 3047 3048 e = annotate_types(e, dialect=self.dialect) 3049 3050 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3051 return e 3052 3053 return exp.func("coalesce", e, exp.Literal.string("")) 3054 3055 args = [_wrap_with_coalesce(e) for e in args] 3056 3057 return args 3058 3059 def concat_sql(self, expression: exp.Concat) -> str: 3060 expressions = self.convert_concat_args(expression) 3061 3062 # Some dialects don't allow a single-argument CONCAT call 3063 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3064 return self.sql(expressions[0]) 3065 3066 return self.func("CONCAT", *expressions) 3067 3068 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3069 return self.func( 3070 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3071 ) 3072 3073 def check_sql(self, expression: exp.Check) -> str: 3074 this = self.sql(expression, key="this") 3075 return f"CHECK ({this})" 3076 3077 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3078 expressions = self.expressions(expression, flat=True) 3079 expressions = f" ({expressions})" if expressions else "" 3080 reference = self.sql(expression, "reference") 3081 reference = f" {reference}" if reference else "" 3082 delete = self.sql(expression, "delete") 3083 delete = f" ON DELETE {delete}" if delete else "" 3084 update = self.sql(expression, "update") 3085 update = f" ON UPDATE {update}" if update else "" 3086 options = self.expressions(expression, key="options", flat=True, sep=" ") 3087 options = f" {options}" if options else "" 3088 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3089 3090 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3091 expressions = self.expressions(expression, flat=True) 3092 include = self.sql(expression, "include") 3093 options = self.expressions(expression, key="options", flat=True, sep=" ") 3094 options = f" {options}" if options else "" 3095 return f"PRIMARY KEY ({expressions}){include}{options}" 3096 3097 def if_sql(self, expression: exp.If) -> str: 3098 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3099 3100 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3101 if self.MATCH_AGAINST_TABLE_PREFIX: 3102 expressions = [] 3103 for expr in expression.expressions: 3104 if isinstance(expr, exp.Table): 3105 expressions.append(f"TABLE {self.sql(expr)}") 3106 else: 3107 expressions.append(expr) 3108 else: 3109 expressions = expression.expressions 3110 3111 modifier = expression.args.get("modifier") 3112 modifier = f" {modifier}" if modifier else "" 3113 return ( 3114 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3115 ) 3116 3117 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3118 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3119 3120 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3121 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3122 3123 if expression.args.get("escape"): 3124 path = self.escape_str(path) 3125 3126 if self.QUOTE_JSON_PATH: 3127 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3128 3129 return path 3130 3131 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3132 if isinstance(expression, exp.JSONPathPart): 3133 transform = self.TRANSFORMS.get(expression.__class__) 3134 if not callable(transform): 3135 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3136 return "" 3137 3138 return transform(self, expression) 3139 3140 if isinstance(expression, int): 3141 return str(expression) 3142 3143 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3144 escaped = expression.replace("'", "\\'") 3145 escaped = f"\\'{expression}\\'" 3146 else: 3147 escaped = expression.replace('"', '\\"') 3148 escaped = f'"{escaped}"' 3149 3150 return escaped 3151 3152 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3153 return f"{self.sql(expression, 'this')} FORMAT JSON" 3154 3155 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3156 # Output the Teradata column FORMAT override. 3157 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3158 this = self.sql(expression, "this") 3159 fmt = self.sql(expression, "format") 3160 return f"{this} (FORMAT {fmt})" 3161 3162 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3163 null_handling = expression.args.get("null_handling") 3164 null_handling = f" {null_handling}" if null_handling else "" 3165 3166 unique_keys = expression.args.get("unique_keys") 3167 if unique_keys is not None: 3168 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3169 else: 3170 unique_keys = "" 3171 3172 return_type = self.sql(expression, "return_type") 3173 return_type = f" RETURNING {return_type}" if return_type else "" 3174 encoding = self.sql(expression, "encoding") 3175 encoding = f" ENCODING {encoding}" if encoding else "" 3176 3177 return self.func( 3178 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3179 *expression.expressions, 3180 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3181 ) 3182 3183 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3184 return self.jsonobject_sql(expression) 3185 3186 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3187 null_handling = expression.args.get("null_handling") 3188 null_handling = f" {null_handling}" if null_handling else "" 3189 return_type = self.sql(expression, "return_type") 3190 return_type = f" RETURNING {return_type}" if return_type else "" 3191 strict = " STRICT" if expression.args.get("strict") else "" 3192 return self.func( 3193 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3194 ) 3195 3196 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3197 this = self.sql(expression, "this") 3198 order = self.sql(expression, "order") 3199 null_handling = expression.args.get("null_handling") 3200 null_handling = f" {null_handling}" if null_handling else "" 3201 return_type = self.sql(expression, "return_type") 3202 return_type = f" RETURNING {return_type}" if return_type else "" 3203 strict = " STRICT" if expression.args.get("strict") else "" 3204 return self.func( 3205 "JSON_ARRAYAGG", 3206 this, 3207 suffix=f"{order}{null_handling}{return_type}{strict})", 3208 ) 3209 3210 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3211 path = self.sql(expression, "path") 3212 path = f" PATH {path}" if path else "" 3213 nested_schema = self.sql(expression, "nested_schema") 3214 3215 if nested_schema: 3216 return f"NESTED{path} {nested_schema}" 3217 3218 this = self.sql(expression, "this") 3219 kind = self.sql(expression, "kind") 3220 kind = f" {kind}" if kind else "" 3221 3222 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3223 return f"{this}{kind}{path}{ordinality}" 3224 3225 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3226 return self.func("COLUMNS", *expression.expressions) 3227 3228 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3229 this = self.sql(expression, "this") 3230 path = self.sql(expression, "path") 3231 path = f", {path}" if path else "" 3232 error_handling = expression.args.get("error_handling") 3233 error_handling = f" {error_handling}" if error_handling else "" 3234 empty_handling = expression.args.get("empty_handling") 3235 empty_handling = f" {empty_handling}" if empty_handling else "" 3236 schema = self.sql(expression, "schema") 3237 return self.func( 3238 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3239 ) 3240 3241 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3242 this = self.sql(expression, "this") 3243 kind = self.sql(expression, "kind") 3244 path = self.sql(expression, "path") 3245 path = f" {path}" if path else "" 3246 as_json = " AS JSON" if expression.args.get("as_json") else "" 3247 return f"{this} {kind}{path}{as_json}" 3248 3249 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3250 this = self.sql(expression, "this") 3251 path = self.sql(expression, "path") 3252 path = f", {path}" if path else "" 3253 expressions = self.expressions(expression) 3254 with_ = ( 3255 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3256 if expressions 3257 else "" 3258 ) 3259 return f"OPENJSON({this}{path}){with_}" 3260 3261 def in_sql(self, expression: exp.In) -> str: 3262 query = expression.args.get("query") 3263 unnest = expression.args.get("unnest") 3264 field = expression.args.get("field") 3265 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3266 3267 if query: 3268 in_sql = self.sql(query) 3269 elif unnest: 3270 in_sql = self.in_unnest_op(unnest) 3271 elif field: 3272 in_sql = self.sql(field) 3273 else: 3274 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3275 3276 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3277 3278 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3279 return f"(SELECT {self.sql(unnest)})" 3280 3281 def interval_sql(self, expression: exp.Interval) -> str: 3282 unit_expression = expression.args.get("unit") 3283 unit = self.sql(unit_expression) if unit_expression else "" 3284 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3285 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3286 unit = f" {unit}" if unit else "" 3287 3288 if self.SINGLE_STRING_INTERVAL: 3289 this = expression.this.name if expression.this else "" 3290 if this: 3291 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3292 return f"INTERVAL '{this}'{unit}" 3293 return f"INTERVAL '{this}{unit}'" 3294 return f"INTERVAL{unit}" 3295 3296 this = self.sql(expression, "this") 3297 if this: 3298 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3299 this = f" {this}" if unwrapped else f" ({this})" 3300 3301 return f"INTERVAL{this}{unit}" 3302 3303 def return_sql(self, expression: exp.Return) -> str: 3304 return f"RETURN {self.sql(expression, 'this')}" 3305 3306 def reference_sql(self, expression: exp.Reference) -> str: 3307 this = self.sql(expression, "this") 3308 expressions = self.expressions(expression, flat=True) 3309 expressions = f"({expressions})" if expressions else "" 3310 options = self.expressions(expression, key="options", flat=True, sep=" ") 3311 options = f" {options}" if options else "" 3312 return f"REFERENCES {this}{expressions}{options}" 3313 3314 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3315 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3316 parent = expression.parent 3317 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3318 return self.func( 3319 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3320 ) 3321 3322 def paren_sql(self, expression: exp.Paren) -> str: 3323 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3324 return f"({sql}{self.seg(')', sep='')}" 3325 3326 def neg_sql(self, expression: exp.Neg) -> str: 3327 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3328 this_sql = self.sql(expression, "this") 3329 sep = " " if this_sql[0] == "-" else "" 3330 return f"-{sep}{this_sql}" 3331 3332 def not_sql(self, expression: exp.Not) -> str: 3333 return f"NOT {self.sql(expression, 'this')}" 3334 3335 def alias_sql(self, expression: exp.Alias) -> str: 3336 alias = self.sql(expression, "alias") 3337 alias = f" AS {alias}" if alias else "" 3338 return f"{self.sql(expression, 'this')}{alias}" 3339 3340 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3341 alias = expression.args["alias"] 3342 3343 parent = expression.parent 3344 pivot = parent and parent.parent 3345 3346 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3347 identifier_alias = isinstance(alias, exp.Identifier) 3348 literal_alias = isinstance(alias, exp.Literal) 3349 3350 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3351 alias.replace(exp.Literal.string(alias.output_name)) 3352 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3353 alias.replace(exp.to_identifier(alias.output_name)) 3354 3355 return self.alias_sql(expression) 3356 3357 def aliases_sql(self, expression: exp.Aliases) -> str: 3358 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3359 3360 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3361 this = self.sql(expression, "this") 3362 index = self.sql(expression, "expression") 3363 return f"{this} AT {index}" 3364 3365 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3366 this = self.sql(expression, "this") 3367 zone = self.sql(expression, "zone") 3368 return f"{this} AT TIME ZONE {zone}" 3369 3370 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3371 this = self.sql(expression, "this") 3372 zone = self.sql(expression, "zone") 3373 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3374 3375 def add_sql(self, expression: exp.Add) -> str: 3376 return self.binary(expression, "+") 3377 3378 def and_sql( 3379 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3380 ) -> str: 3381 return self.connector_sql(expression, "AND", stack) 3382 3383 def or_sql( 3384 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3385 ) -> str: 3386 return self.connector_sql(expression, "OR", stack) 3387 3388 def xor_sql( 3389 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3390 ) -> str: 3391 return self.connector_sql(expression, "XOR", stack) 3392 3393 def connector_sql( 3394 self, 3395 expression: exp.Connector, 3396 op: str, 3397 stack: t.Optional[t.List[str | exp.Expression]] = None, 3398 ) -> str: 3399 if stack is not None: 3400 if expression.expressions: 3401 stack.append(self.expressions(expression, sep=f" {op} ")) 3402 else: 3403 stack.append(expression.right) 3404 if expression.comments and self.comments: 3405 for comment in expression.comments: 3406 if comment: 3407 op += f" /*{self.sanitize_comment(comment)}*/" 3408 stack.extend((op, expression.left)) 3409 return op 3410 3411 stack = [expression] 3412 sqls: t.List[str] = [] 3413 ops = set() 3414 3415 while stack: 3416 node = stack.pop() 3417 if isinstance(node, exp.Connector): 3418 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3419 else: 3420 sql = self.sql(node) 3421 if sqls and sqls[-1] in ops: 3422 sqls[-1] += f" {sql}" 3423 else: 3424 sqls.append(sql) 3425 3426 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3427 return sep.join(sqls) 3428 3429 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3430 return self.binary(expression, "&") 3431 3432 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3433 return self.binary(expression, "<<") 3434 3435 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3436 return f"~{self.sql(expression, 'this')}" 3437 3438 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3439 return self.binary(expression, "|") 3440 3441 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3442 return self.binary(expression, ">>") 3443 3444 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3445 return self.binary(expression, "^") 3446 3447 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3448 format_sql = self.sql(expression, "format") 3449 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3450 to_sql = self.sql(expression, "to") 3451 to_sql = f" {to_sql}" if to_sql else "" 3452 action = self.sql(expression, "action") 3453 action = f" {action}" if action else "" 3454 default = self.sql(expression, "default") 3455 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3456 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3457 3458 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3459 zone = self.sql(expression, "this") 3460 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3461 3462 def collate_sql(self, expression: exp.Collate) -> str: 3463 if self.COLLATE_IS_FUNC: 3464 return self.function_fallback_sql(expression) 3465 return self.binary(expression, "COLLATE") 3466 3467 def command_sql(self, expression: exp.Command) -> str: 3468 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3469 3470 def comment_sql(self, expression: exp.Comment) -> str: 3471 this = self.sql(expression, "this") 3472 kind = expression.args["kind"] 3473 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3474 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3475 expression_sql = self.sql(expression, "expression") 3476 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3477 3478 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3479 this = self.sql(expression, "this") 3480 delete = " DELETE" if expression.args.get("delete") else "" 3481 recompress = self.sql(expression, "recompress") 3482 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3483 to_disk = self.sql(expression, "to_disk") 3484 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3485 to_volume = self.sql(expression, "to_volume") 3486 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3487 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3488 3489 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3490 where = self.sql(expression, "where") 3491 group = self.sql(expression, "group") 3492 aggregates = self.expressions(expression, key="aggregates") 3493 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3494 3495 if not (where or group or aggregates) and len(expression.expressions) == 1: 3496 return f"TTL {self.expressions(expression, flat=True)}" 3497 3498 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3499 3500 def transaction_sql(self, expression: exp.Transaction) -> str: 3501 modes = self.expressions(expression, key="modes") 3502 modes = f" {modes}" if modes else "" 3503 return f"BEGIN{modes}" 3504 3505 def commit_sql(self, expression: exp.Commit) -> str: 3506 chain = expression.args.get("chain") 3507 if chain is not None: 3508 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3509 3510 return f"COMMIT{chain or ''}" 3511 3512 def rollback_sql(self, expression: exp.Rollback) -> str: 3513 savepoint = expression.args.get("savepoint") 3514 savepoint = f" TO {savepoint}" if savepoint else "" 3515 return f"ROLLBACK{savepoint}" 3516 3517 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3518 this = self.sql(expression, "this") 3519 3520 dtype = self.sql(expression, "dtype") 3521 if dtype: 3522 collate = self.sql(expression, "collate") 3523 collate = f" COLLATE {collate}" if collate else "" 3524 using = self.sql(expression, "using") 3525 using = f" USING {using}" if using else "" 3526 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3527 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3528 3529 default = self.sql(expression, "default") 3530 if default: 3531 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3532 3533 comment = self.sql(expression, "comment") 3534 if comment: 3535 return f"ALTER COLUMN {this} COMMENT {comment}" 3536 3537 visible = expression.args.get("visible") 3538 if visible: 3539 return f"ALTER COLUMN {this} SET {visible}" 3540 3541 allow_null = expression.args.get("allow_null") 3542 drop = expression.args.get("drop") 3543 3544 if not drop and not allow_null: 3545 self.unsupported("Unsupported ALTER COLUMN syntax") 3546 3547 if allow_null is not None: 3548 keyword = "DROP" if drop else "SET" 3549 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3550 3551 return f"ALTER COLUMN {this} DROP DEFAULT" 3552 3553 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3554 this = self.sql(expression, "this") 3555 3556 visible = expression.args.get("visible") 3557 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3558 3559 return f"ALTER INDEX {this} {visible_sql}" 3560 3561 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3562 this = self.sql(expression, "this") 3563 if not isinstance(expression.this, exp.Var): 3564 this = f"KEY DISTKEY {this}" 3565 return f"ALTER DISTSTYLE {this}" 3566 3567 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3568 compound = " COMPOUND" if expression.args.get("compound") else "" 3569 this = self.sql(expression, "this") 3570 expressions = self.expressions(expression, flat=True) 3571 expressions = f"({expressions})" if expressions else "" 3572 return f"ALTER{compound} SORTKEY {this or expressions}" 3573 3574 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3575 if not self.RENAME_TABLE_WITH_DB: 3576 # Remove db from tables 3577 expression = expression.transform( 3578 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3579 ).assert_is(exp.AlterRename) 3580 this = self.sql(expression, "this") 3581 to_kw = " TO" if include_to else "" 3582 return f"RENAME{to_kw} {this}" 3583 3584 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3585 exists = " IF EXISTS" if expression.args.get("exists") else "" 3586 old_column = self.sql(expression, "this") 3587 new_column = self.sql(expression, "to") 3588 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3589 3590 def alterset_sql(self, expression: exp.AlterSet) -> str: 3591 exprs = self.expressions(expression, flat=True) 3592 if self.ALTER_SET_WRAPPED: 3593 exprs = f"({exprs})" 3594 3595 return f"SET {exprs}" 3596 3597 def alter_sql(self, expression: exp.Alter) -> str: 3598 actions = expression.args["actions"] 3599 3600 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3601 actions[0], exp.ColumnDef 3602 ): 3603 actions_sql = self.expressions(expression, key="actions", flat=True) 3604 actions_sql = f"ADD {actions_sql}" 3605 else: 3606 actions_list = [] 3607 for action in actions: 3608 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3609 action_sql = self.add_column_sql(action) 3610 else: 3611 action_sql = self.sql(action) 3612 if isinstance(action, exp.Query): 3613 action_sql = f"AS {action_sql}" 3614 3615 actions_list.append(action_sql) 3616 3617 actions_sql = self.format_args(*actions_list).lstrip("\n") 3618 3619 exists = " IF EXISTS" if expression.args.get("exists") else "" 3620 on_cluster = self.sql(expression, "cluster") 3621 on_cluster = f" {on_cluster}" if on_cluster else "" 3622 only = " ONLY" if expression.args.get("only") else "" 3623 options = self.expressions(expression, key="options") 3624 options = f", {options}" if options else "" 3625 kind = self.sql(expression, "kind") 3626 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3627 check = " WITH CHECK" if expression.args.get("check") else "" 3628 cascade = ( 3629 " CASCADE" 3630 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 3631 else "" 3632 ) 3633 this = self.sql(expression, "this") 3634 this = f" {this}" if this else "" 3635 3636 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 3637 3638 def altersession_sql(self, expression: exp.AlterSession) -> str: 3639 items_sql = self.expressions(expression, flat=True) 3640 keyword = "UNSET" if expression.args.get("unset") else "SET" 3641 return f"{keyword} {items_sql}" 3642 3643 def add_column_sql(self, expression: exp.Expression) -> str: 3644 sql = self.sql(expression) 3645 if isinstance(expression, exp.Schema): 3646 column_text = " COLUMNS" 3647 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3648 column_text = " COLUMN" 3649 else: 3650 column_text = "" 3651 3652 return f"ADD{column_text} {sql}" 3653 3654 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3655 expressions = self.expressions(expression) 3656 exists = " IF EXISTS " if expression.args.get("exists") else " " 3657 return f"DROP{exists}{expressions}" 3658 3659 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3660 return f"ADD {self.expressions(expression, indent=False)}" 3661 3662 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3663 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3664 location = self.sql(expression, "location") 3665 location = f" {location}" if location else "" 3666 return f"ADD {exists}{self.sql(expression.this)}{location}" 3667 3668 def distinct_sql(self, expression: exp.Distinct) -> str: 3669 this = self.expressions(expression, flat=True) 3670 3671 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3672 case = exp.case() 3673 for arg in expression.expressions: 3674 case = case.when(arg.is_(exp.null()), exp.null()) 3675 this = self.sql(case.else_(f"({this})")) 3676 3677 this = f" {this}" if this else "" 3678 3679 on = self.sql(expression, "on") 3680 on = f" ON {on}" if on else "" 3681 return f"DISTINCT{this}{on}" 3682 3683 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3684 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3685 3686 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3687 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3688 3689 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3690 this_sql = self.sql(expression, "this") 3691 expression_sql = self.sql(expression, "expression") 3692 kind = "MAX" if expression.args.get("max") else "MIN" 3693 return f"{this_sql} HAVING {kind} {expression_sql}" 3694 3695 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3696 return self.sql( 3697 exp.Cast( 3698 this=exp.Div(this=expression.this, expression=expression.expression), 3699 to=exp.DataType(this=exp.DataType.Type.INT), 3700 ) 3701 ) 3702 3703 def dpipe_sql(self, expression: exp.DPipe) -> str: 3704 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3705 return self.func( 3706 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3707 ) 3708 return self.binary(expression, "||") 3709 3710 def div_sql(self, expression: exp.Div) -> str: 3711 l, r = expression.left, expression.right 3712 3713 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3714 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3715 3716 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3717 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3718 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3719 3720 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3721 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3722 return self.sql( 3723 exp.cast( 3724 l / r, 3725 to=exp.DataType.Type.BIGINT, 3726 ) 3727 ) 3728 3729 return self.binary(expression, "/") 3730 3731 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3732 n = exp._wrap(expression.this, exp.Binary) 3733 d = exp._wrap(expression.expression, exp.Binary) 3734 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3735 3736 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3737 return self.binary(expression, "OVERLAPS") 3738 3739 def distance_sql(self, expression: exp.Distance) -> str: 3740 return self.binary(expression, "<->") 3741 3742 def dot_sql(self, expression: exp.Dot) -> str: 3743 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3744 3745 def eq_sql(self, expression: exp.EQ) -> str: 3746 return self.binary(expression, "=") 3747 3748 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3749 return self.binary(expression, ":=") 3750 3751 def escape_sql(self, expression: exp.Escape) -> str: 3752 return self.binary(expression, "ESCAPE") 3753 3754 def glob_sql(self, expression: exp.Glob) -> str: 3755 return self.binary(expression, "GLOB") 3756 3757 def gt_sql(self, expression: exp.GT) -> str: 3758 return self.binary(expression, ">") 3759 3760 def gte_sql(self, expression: exp.GTE) -> str: 3761 return self.binary(expression, ">=") 3762 3763 def is_sql(self, expression: exp.Is) -> str: 3764 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3765 return self.sql( 3766 expression.this if expression.expression.this else exp.not_(expression.this) 3767 ) 3768 return self.binary(expression, "IS") 3769 3770 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3771 this = expression.this 3772 rhs = expression.expression 3773 3774 if isinstance(expression, exp.Like): 3775 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3776 op = "LIKE" 3777 else: 3778 exp_class = exp.ILike 3779 op = "ILIKE" 3780 3781 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3782 exprs = rhs.this.unnest() 3783 3784 if isinstance(exprs, exp.Tuple): 3785 exprs = exprs.expressions 3786 3787 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3788 3789 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3790 for expr in exprs[1:]: 3791 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3792 3793 return self.sql(like_expr) 3794 3795 return self.binary(expression, op) 3796 3797 def like_sql(self, expression: exp.Like) -> str: 3798 return self._like_sql(expression) 3799 3800 def ilike_sql(self, expression: exp.ILike) -> str: 3801 return self._like_sql(expression) 3802 3803 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3804 return self.binary(expression, "SIMILAR TO") 3805 3806 def lt_sql(self, expression: exp.LT) -> str: 3807 return self.binary(expression, "<") 3808 3809 def lte_sql(self, expression: exp.LTE) -> str: 3810 return self.binary(expression, "<=") 3811 3812 def mod_sql(self, expression: exp.Mod) -> str: 3813 return self.binary(expression, "%") 3814 3815 def mul_sql(self, expression: exp.Mul) -> str: 3816 return self.binary(expression, "*") 3817 3818 def neq_sql(self, expression: exp.NEQ) -> str: 3819 return self.binary(expression, "<>") 3820 3821 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3822 return self.binary(expression, "IS NOT DISTINCT FROM") 3823 3824 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3825 return self.binary(expression, "IS DISTINCT FROM") 3826 3827 def slice_sql(self, expression: exp.Slice) -> str: 3828 return self.binary(expression, ":") 3829 3830 def sub_sql(self, expression: exp.Sub) -> str: 3831 return self.binary(expression, "-") 3832 3833 def trycast_sql(self, expression: exp.TryCast) -> str: 3834 return self.cast_sql(expression, safe_prefix="TRY_") 3835 3836 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3837 return self.cast_sql(expression) 3838 3839 def try_sql(self, expression: exp.Try) -> str: 3840 if not self.TRY_SUPPORTED: 3841 self.unsupported("Unsupported TRY function") 3842 return self.sql(expression, "this") 3843 3844 return self.func("TRY", expression.this) 3845 3846 def log_sql(self, expression: exp.Log) -> str: 3847 this = expression.this 3848 expr = expression.expression 3849 3850 if self.dialect.LOG_BASE_FIRST is False: 3851 this, expr = expr, this 3852 elif self.dialect.LOG_BASE_FIRST is None and expr: 3853 if this.name in ("2", "10"): 3854 return self.func(f"LOG{this.name}", expr) 3855 3856 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3857 3858 return self.func("LOG", this, expr) 3859 3860 def use_sql(self, expression: exp.Use) -> str: 3861 kind = self.sql(expression, "kind") 3862 kind = f" {kind}" if kind else "" 3863 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3864 this = f" {this}" if this else "" 3865 return f"USE{kind}{this}" 3866 3867 def binary(self, expression: exp.Binary, op: str) -> str: 3868 sqls: t.List[str] = [] 3869 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3870 binary_type = type(expression) 3871 3872 while stack: 3873 node = stack.pop() 3874 3875 if type(node) is binary_type: 3876 op_func = node.args.get("operator") 3877 if op_func: 3878 op = f"OPERATOR({self.sql(op_func)})" 3879 3880 stack.append(node.right) 3881 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3882 stack.append(node.left) 3883 else: 3884 sqls.append(self.sql(node)) 3885 3886 return "".join(sqls) 3887 3888 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3889 to_clause = self.sql(expression, "to") 3890 if to_clause: 3891 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3892 3893 return self.function_fallback_sql(expression) 3894 3895 def function_fallback_sql(self, expression: exp.Func) -> str: 3896 args = [] 3897 3898 for key in expression.arg_types: 3899 arg_value = expression.args.get(key) 3900 3901 if isinstance(arg_value, list): 3902 for value in arg_value: 3903 args.append(value) 3904 elif arg_value is not None: 3905 args.append(arg_value) 3906 3907 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3908 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3909 else: 3910 name = expression.sql_name() 3911 3912 return self.func(name, *args) 3913 3914 def func( 3915 self, 3916 name: str, 3917 *args: t.Optional[exp.Expression | str], 3918 prefix: str = "(", 3919 suffix: str = ")", 3920 normalize: bool = True, 3921 ) -> str: 3922 name = self.normalize_func(name) if normalize else name 3923 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3924 3925 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3926 arg_sqls = tuple( 3927 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3928 ) 3929 if self.pretty and self.too_wide(arg_sqls): 3930 return self.indent( 3931 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3932 ) 3933 return sep.join(arg_sqls) 3934 3935 def too_wide(self, args: t.Iterable) -> bool: 3936 return sum(len(arg) for arg in args) > self.max_text_width 3937 3938 def format_time( 3939 self, 3940 expression: exp.Expression, 3941 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3942 inverse_time_trie: t.Optional[t.Dict] = None, 3943 ) -> t.Optional[str]: 3944 return format_time( 3945 self.sql(expression, "format"), 3946 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3947 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3948 ) 3949 3950 def expressions( 3951 self, 3952 expression: t.Optional[exp.Expression] = None, 3953 key: t.Optional[str] = None, 3954 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3955 flat: bool = False, 3956 indent: bool = True, 3957 skip_first: bool = False, 3958 skip_last: bool = False, 3959 sep: str = ", ", 3960 prefix: str = "", 3961 dynamic: bool = False, 3962 new_line: bool = False, 3963 ) -> str: 3964 expressions = expression.args.get(key or "expressions") if expression else sqls 3965 3966 if not expressions: 3967 return "" 3968 3969 if flat: 3970 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3971 3972 num_sqls = len(expressions) 3973 result_sqls = [] 3974 3975 for i, e in enumerate(expressions): 3976 sql = self.sql(e, comment=False) 3977 if not sql: 3978 continue 3979 3980 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3981 3982 if self.pretty: 3983 if self.leading_comma: 3984 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3985 else: 3986 result_sqls.append( 3987 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3988 ) 3989 else: 3990 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3991 3992 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3993 if new_line: 3994 result_sqls.insert(0, "") 3995 result_sqls.append("") 3996 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3997 else: 3998 result_sql = "".join(result_sqls) 3999 4000 return ( 4001 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4002 if indent 4003 else result_sql 4004 ) 4005 4006 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 4007 flat = flat or isinstance(expression.parent, exp.Properties) 4008 expressions_sql = self.expressions(expression, flat=flat) 4009 if flat: 4010 return f"{op} {expressions_sql}" 4011 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4012 4013 def naked_property(self, expression: exp.Property) -> str: 4014 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4015 if not property_name: 4016 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4017 return f"{property_name} {self.sql(expression, 'this')}" 4018 4019 def tag_sql(self, expression: exp.Tag) -> str: 4020 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4021 4022 def token_sql(self, token_type: TokenType) -> str: 4023 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4024 4025 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4026 this = self.sql(expression, "this") 4027 expressions = self.no_identify(self.expressions, expression) 4028 expressions = ( 4029 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4030 ) 4031 return f"{this}{expressions}" if expressions.strip() != "" else this 4032 4033 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4034 this = self.sql(expression, "this") 4035 expressions = self.expressions(expression, flat=True) 4036 return f"{this}({expressions})" 4037 4038 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4039 return self.binary(expression, "=>") 4040 4041 def when_sql(self, expression: exp.When) -> str: 4042 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4043 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4044 condition = self.sql(expression, "condition") 4045 condition = f" AND {condition}" if condition else "" 4046 4047 then_expression = expression.args.get("then") 4048 if isinstance(then_expression, exp.Insert): 4049 this = self.sql(then_expression, "this") 4050 this = f"INSERT {this}" if this else "INSERT" 4051 then = self.sql(then_expression, "expression") 4052 then = f"{this} VALUES {then}" if then else this 4053 elif isinstance(then_expression, exp.Update): 4054 if isinstance(then_expression.args.get("expressions"), exp.Star): 4055 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4056 else: 4057 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 4058 else: 4059 then = self.sql(then_expression) 4060 return f"WHEN {matched}{source}{condition} THEN {then}" 4061 4062 def whens_sql(self, expression: exp.Whens) -> str: 4063 return self.expressions(expression, sep=" ", indent=False) 4064 4065 def merge_sql(self, expression: exp.Merge) -> str: 4066 table = expression.this 4067 table_alias = "" 4068 4069 hints = table.args.get("hints") 4070 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4071 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4072 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4073 4074 this = self.sql(table) 4075 using = f"USING {self.sql(expression, 'using')}" 4076 on = f"ON {self.sql(expression, 'on')}" 4077 whens = self.sql(expression, "whens") 4078 4079 returning = self.sql(expression, "returning") 4080 if returning: 4081 whens = f"{whens}{returning}" 4082 4083 sep = self.sep() 4084 4085 return self.prepend_ctes( 4086 expression, 4087 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4088 ) 4089 4090 @unsupported_args("format") 4091 def tochar_sql(self, expression: exp.ToChar) -> str: 4092 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4093 4094 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4095 if not self.SUPPORTS_TO_NUMBER: 4096 self.unsupported("Unsupported TO_NUMBER function") 4097 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4098 4099 fmt = expression.args.get("format") 4100 if not fmt: 4101 self.unsupported("Conversion format is required for TO_NUMBER") 4102 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4103 4104 return self.func("TO_NUMBER", expression.this, fmt) 4105 4106 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4107 this = self.sql(expression, "this") 4108 kind = self.sql(expression, "kind") 4109 settings_sql = self.expressions(expression, key="settings", sep=" ") 4110 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4111 return f"{this}({kind}{args})" 4112 4113 def dictrange_sql(self, expression: exp.DictRange) -> str: 4114 this = self.sql(expression, "this") 4115 max = self.sql(expression, "max") 4116 min = self.sql(expression, "min") 4117 return f"{this}(MIN {min} MAX {max})" 4118 4119 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4120 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4121 4122 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4123 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4124 4125 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4126 def uniquekeyproperty_sql( 4127 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4128 ) -> str: 4129 return f"{prefix} ({self.expressions(expression, flat=True)})" 4130 4131 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4132 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4133 expressions = self.expressions(expression, flat=True) 4134 expressions = f" {self.wrap(expressions)}" if expressions else "" 4135 buckets = self.sql(expression, "buckets") 4136 kind = self.sql(expression, "kind") 4137 buckets = f" BUCKETS {buckets}" if buckets else "" 4138 order = self.sql(expression, "order") 4139 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4140 4141 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4142 return "" 4143 4144 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4145 expressions = self.expressions(expression, key="expressions", flat=True) 4146 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4147 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4148 buckets = self.sql(expression, "buckets") 4149 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4150 4151 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4152 this = self.sql(expression, "this") 4153 having = self.sql(expression, "having") 4154 4155 if having: 4156 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4157 4158 return self.func("ANY_VALUE", this) 4159 4160 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4161 transform = self.func("TRANSFORM", *expression.expressions) 4162 row_format_before = self.sql(expression, "row_format_before") 4163 row_format_before = f" {row_format_before}" if row_format_before else "" 4164 record_writer = self.sql(expression, "record_writer") 4165 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4166 using = f" USING {self.sql(expression, 'command_script')}" 4167 schema = self.sql(expression, "schema") 4168 schema = f" AS {schema}" if schema else "" 4169 row_format_after = self.sql(expression, "row_format_after") 4170 row_format_after = f" {row_format_after}" if row_format_after else "" 4171 record_reader = self.sql(expression, "record_reader") 4172 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4173 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4174 4175 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4176 key_block_size = self.sql(expression, "key_block_size") 4177 if key_block_size: 4178 return f"KEY_BLOCK_SIZE = {key_block_size}" 4179 4180 using = self.sql(expression, "using") 4181 if using: 4182 return f"USING {using}" 4183 4184 parser = self.sql(expression, "parser") 4185 if parser: 4186 return f"WITH PARSER {parser}" 4187 4188 comment = self.sql(expression, "comment") 4189 if comment: 4190 return f"COMMENT {comment}" 4191 4192 visible = expression.args.get("visible") 4193 if visible is not None: 4194 return "VISIBLE" if visible else "INVISIBLE" 4195 4196 engine_attr = self.sql(expression, "engine_attr") 4197 if engine_attr: 4198 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4199 4200 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4201 if secondary_engine_attr: 4202 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4203 4204 self.unsupported("Unsupported index constraint option.") 4205 return "" 4206 4207 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4208 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4209 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4210 4211 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4212 kind = self.sql(expression, "kind") 4213 kind = f"{kind} INDEX" if kind else "INDEX" 4214 this = self.sql(expression, "this") 4215 this = f" {this}" if this else "" 4216 index_type = self.sql(expression, "index_type") 4217 index_type = f" USING {index_type}" if index_type else "" 4218 expressions = self.expressions(expression, flat=True) 4219 expressions = f" ({expressions})" if expressions else "" 4220 options = self.expressions(expression, key="options", sep=" ") 4221 options = f" {options}" if options else "" 4222 return f"{kind}{this}{index_type}{expressions}{options}" 4223 4224 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4225 if self.NVL2_SUPPORTED: 4226 return self.function_fallback_sql(expression) 4227 4228 case = exp.Case().when( 4229 expression.this.is_(exp.null()).not_(copy=False), 4230 expression.args["true"], 4231 copy=False, 4232 ) 4233 else_cond = expression.args.get("false") 4234 if else_cond: 4235 case.else_(else_cond, copy=False) 4236 4237 return self.sql(case) 4238 4239 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4240 this = self.sql(expression, "this") 4241 expr = self.sql(expression, "expression") 4242 iterator = self.sql(expression, "iterator") 4243 condition = self.sql(expression, "condition") 4244 condition = f" IF {condition}" if condition else "" 4245 return f"{this} FOR {expr} IN {iterator}{condition}" 4246 4247 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4248 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4249 4250 def opclass_sql(self, expression: exp.Opclass) -> str: 4251 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4252 4253 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4254 model = self.sql(expression, "this") 4255 model = f"MODEL {model}" 4256 expr = expression.expression 4257 if expr: 4258 expr_sql = self.sql(expression, "expression") 4259 expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql 4260 else: 4261 expr_sql = None 4262 4263 parameters = self.sql(expression, "params_struct") or None 4264 4265 return self.func(name, model, expr_sql, parameters) 4266 4267 def predict_sql(self, expression: exp.Predict) -> str: 4268 return self._ml_sql(expression, "PREDICT") 4269 4270 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4271 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4272 return self._ml_sql(expression, name) 4273 4274 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4275 return self._ml_sql(expression, "TRANSLATE") 4276 4277 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4278 return self._ml_sql(expression, "FORECAST") 4279 4280 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4281 this_sql = self.sql(expression, "this") 4282 if isinstance(expression.this, exp.Table): 4283 this_sql = f"TABLE {this_sql}" 4284 4285 return self.func( 4286 "FEATURES_AT_TIME", 4287 this_sql, 4288 expression.args.get("time"), 4289 expression.args.get("num_rows"), 4290 expression.args.get("ignore_feature_nulls"), 4291 ) 4292 4293 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4294 this_sql = self.sql(expression, "this") 4295 if isinstance(expression.this, exp.Table): 4296 this_sql = f"TABLE {this_sql}" 4297 4298 query_table = self.sql(expression, "query_table") 4299 if isinstance(expression.args["query_table"], exp.Table): 4300 query_table = f"TABLE {query_table}" 4301 4302 return self.func( 4303 "VECTOR_SEARCH", 4304 this_sql, 4305 expression.args.get("column_to_search"), 4306 query_table, 4307 expression.args.get("query_column_to_search"), 4308 expression.args.get("top_k"), 4309 expression.args.get("distance_type"), 4310 expression.args.get("options"), 4311 ) 4312 4313 def forin_sql(self, expression: exp.ForIn) -> str: 4314 this = self.sql(expression, "this") 4315 expression_sql = self.sql(expression, "expression") 4316 return f"FOR {this} DO {expression_sql}" 4317 4318 def refresh_sql(self, expression: exp.Refresh) -> str: 4319 this = self.sql(expression, "this") 4320 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4321 return f"REFRESH {table}{this}" 4322 4323 def toarray_sql(self, expression: exp.ToArray) -> str: 4324 arg = expression.this 4325 if not arg.type: 4326 from sqlglot.optimizer.annotate_types import annotate_types 4327 4328 arg = annotate_types(arg, dialect=self.dialect) 4329 4330 if arg.is_type(exp.DataType.Type.ARRAY): 4331 return self.sql(arg) 4332 4333 cond_for_null = arg.is_(exp.null()) 4334 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4335 4336 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4337 this = expression.this 4338 time_format = self.format_time(expression) 4339 4340 if time_format: 4341 return self.sql( 4342 exp.cast( 4343 exp.StrToTime(this=this, format=expression.args["format"]), 4344 exp.DataType.Type.TIME, 4345 ) 4346 ) 4347 4348 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4349 return self.sql(this) 4350 4351 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4352 4353 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4354 this = expression.this 4355 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4356 return self.sql(this) 4357 4358 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4359 4360 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4361 this = expression.this 4362 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4363 return self.sql(this) 4364 4365 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4366 4367 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4368 this = expression.this 4369 time_format = self.format_time(expression) 4370 4371 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4372 return self.sql( 4373 exp.cast( 4374 exp.StrToTime(this=this, format=expression.args["format"]), 4375 exp.DataType.Type.DATE, 4376 ) 4377 ) 4378 4379 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4380 return self.sql(this) 4381 4382 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4383 4384 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4385 return self.sql( 4386 exp.func( 4387 "DATEDIFF", 4388 expression.this, 4389 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4390 "day", 4391 ) 4392 ) 4393 4394 def lastday_sql(self, expression: exp.LastDay) -> str: 4395 if self.LAST_DAY_SUPPORTS_DATE_PART: 4396 return self.function_fallback_sql(expression) 4397 4398 unit = expression.text("unit") 4399 if unit and unit != "MONTH": 4400 self.unsupported("Date parts are not supported in LAST_DAY.") 4401 4402 return self.func("LAST_DAY", expression.this) 4403 4404 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4405 from sqlglot.dialects.dialect import unit_to_str 4406 4407 return self.func( 4408 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4409 ) 4410 4411 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4412 if self.CAN_IMPLEMENT_ARRAY_ANY: 4413 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4414 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4415 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4416 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4417 4418 from sqlglot.dialects import Dialect 4419 4420 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4421 if self.dialect.__class__ != Dialect: 4422 self.unsupported("ARRAY_ANY is unsupported") 4423 4424 return self.function_fallback_sql(expression) 4425 4426 def struct_sql(self, expression: exp.Struct) -> str: 4427 expression.set( 4428 "expressions", 4429 [ 4430 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4431 if isinstance(e, exp.PropertyEQ) 4432 else e 4433 for e in expression.expressions 4434 ], 4435 ) 4436 4437 return self.function_fallback_sql(expression) 4438 4439 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4440 low = self.sql(expression, "this") 4441 high = self.sql(expression, "expression") 4442 4443 return f"{low} TO {high}" 4444 4445 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4446 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4447 tables = f" {self.expressions(expression)}" 4448 4449 exists = " IF EXISTS" if expression.args.get("exists") else "" 4450 4451 on_cluster = self.sql(expression, "cluster") 4452 on_cluster = f" {on_cluster}" if on_cluster else "" 4453 4454 identity = self.sql(expression, "identity") 4455 identity = f" {identity} IDENTITY" if identity else "" 4456 4457 option = self.sql(expression, "option") 4458 option = f" {option}" if option else "" 4459 4460 partition = self.sql(expression, "partition") 4461 partition = f" {partition}" if partition else "" 4462 4463 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4464 4465 # This transpiles T-SQL's CONVERT function 4466 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4467 def convert_sql(self, expression: exp.Convert) -> str: 4468 to = expression.this 4469 value = expression.expression 4470 style = expression.args.get("style") 4471 safe = expression.args.get("safe") 4472 strict = expression.args.get("strict") 4473 4474 if not to or not value: 4475 return "" 4476 4477 # Retrieve length of datatype and override to default if not specified 4478 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4479 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4480 4481 transformed: t.Optional[exp.Expression] = None 4482 cast = exp.Cast if strict else exp.TryCast 4483 4484 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4485 if isinstance(style, exp.Literal) and style.is_int: 4486 from sqlglot.dialects.tsql import TSQL 4487 4488 style_value = style.name 4489 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4490 if not converted_style: 4491 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4492 4493 fmt = exp.Literal.string(converted_style) 4494 4495 if to.this == exp.DataType.Type.DATE: 4496 transformed = exp.StrToDate(this=value, format=fmt) 4497 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4498 transformed = exp.StrToTime(this=value, format=fmt) 4499 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4500 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4501 elif to.this == exp.DataType.Type.TEXT: 4502 transformed = exp.TimeToStr(this=value, format=fmt) 4503 4504 if not transformed: 4505 transformed = cast(this=value, to=to, safe=safe) 4506 4507 return self.sql(transformed) 4508 4509 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4510 this = expression.this 4511 if isinstance(this, exp.JSONPathWildcard): 4512 this = self.json_path_part(this) 4513 return f".{this}" if this else "" 4514 4515 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4516 return f".{this}" 4517 4518 this = self.json_path_part(this) 4519 return ( 4520 f"[{this}]" 4521 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4522 else f".{this}" 4523 ) 4524 4525 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4526 this = self.json_path_part(expression.this) 4527 return f"[{this}]" if this else "" 4528 4529 def _simplify_unless_literal(self, expression: E) -> E: 4530 if not isinstance(expression, exp.Literal): 4531 from sqlglot.optimizer.simplify import simplify 4532 4533 expression = simplify(expression, dialect=self.dialect) 4534 4535 return expression 4536 4537 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4538 this = expression.this 4539 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4540 self.unsupported( 4541 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4542 ) 4543 return self.sql(this) 4544 4545 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4546 # The first modifier here will be the one closest to the AggFunc's arg 4547 mods = sorted( 4548 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4549 key=lambda x: 0 4550 if isinstance(x, exp.HavingMax) 4551 else (1 if isinstance(x, exp.Order) else 2), 4552 ) 4553 4554 if mods: 4555 mod = mods[0] 4556 this = expression.__class__(this=mod.this.copy()) 4557 this.meta["inline"] = True 4558 mod.this.replace(this) 4559 return self.sql(expression.this) 4560 4561 agg_func = expression.find(exp.AggFunc) 4562 4563 if agg_func: 4564 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4565 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4566 4567 return f"{self.sql(expression, 'this')} {text}" 4568 4569 def _replace_line_breaks(self, string: str) -> str: 4570 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4571 if self.pretty: 4572 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4573 return string 4574 4575 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4576 option = self.sql(expression, "this") 4577 4578 if expression.expressions: 4579 upper = option.upper() 4580 4581 # Snowflake FILE_FORMAT options are separated by whitespace 4582 sep = " " if upper == "FILE_FORMAT" else ", " 4583 4584 # Databricks copy/format options do not set their list of values with EQ 4585 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4586 values = self.expressions(expression, flat=True, sep=sep) 4587 return f"{option}{op}({values})" 4588 4589 value = self.sql(expression, "expression") 4590 4591 if not value: 4592 return option 4593 4594 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4595 4596 return f"{option}{op}{value}" 4597 4598 def credentials_sql(self, expression: exp.Credentials) -> str: 4599 cred_expr = expression.args.get("credentials") 4600 if isinstance(cred_expr, exp.Literal): 4601 # Redshift case: CREDENTIALS <string> 4602 credentials = self.sql(expression, "credentials") 4603 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4604 else: 4605 # Snowflake case: CREDENTIALS = (...) 4606 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4607 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4608 4609 storage = self.sql(expression, "storage") 4610 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4611 4612 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4613 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4614 4615 iam_role = self.sql(expression, "iam_role") 4616 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4617 4618 region = self.sql(expression, "region") 4619 region = f" REGION {region}" if region else "" 4620 4621 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4622 4623 def copy_sql(self, expression: exp.Copy) -> str: 4624 this = self.sql(expression, "this") 4625 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4626 4627 credentials = self.sql(expression, "credentials") 4628 credentials = self.seg(credentials) if credentials else "" 4629 files = self.expressions(expression, key="files", flat=True) 4630 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4631 4632 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4633 params = self.expressions( 4634 expression, 4635 key="params", 4636 sep=sep, 4637 new_line=True, 4638 skip_last=True, 4639 skip_first=True, 4640 indent=self.COPY_PARAMS_ARE_WRAPPED, 4641 ) 4642 4643 if params: 4644 if self.COPY_PARAMS_ARE_WRAPPED: 4645 params = f" WITH ({params})" 4646 elif not self.pretty and (files or credentials): 4647 params = f" {params}" 4648 4649 return f"COPY{this}{kind} {files}{credentials}{params}" 4650 4651 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4652 return "" 4653 4654 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4655 on_sql = "ON" if expression.args.get("on") else "OFF" 4656 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4657 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4658 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4659 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4660 4661 if filter_col or retention_period: 4662 on_sql = self.func("ON", filter_col, retention_period) 4663 4664 return f"DATA_DELETION={on_sql}" 4665 4666 def maskingpolicycolumnconstraint_sql( 4667 self, expression: exp.MaskingPolicyColumnConstraint 4668 ) -> str: 4669 this = self.sql(expression, "this") 4670 expressions = self.expressions(expression, flat=True) 4671 expressions = f" USING ({expressions})" if expressions else "" 4672 return f"MASKING POLICY {this}{expressions}" 4673 4674 def gapfill_sql(self, expression: exp.GapFill) -> str: 4675 this = self.sql(expression, "this") 4676 this = f"TABLE {this}" 4677 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4678 4679 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4680 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4681 4682 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4683 this = self.sql(expression, "this") 4684 expr = expression.expression 4685 4686 if isinstance(expr, exp.Func): 4687 # T-SQL's CLR functions are case sensitive 4688 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4689 else: 4690 expr = self.sql(expression, "expression") 4691 4692 return self.scope_resolution(expr, this) 4693 4694 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4695 if self.PARSE_JSON_NAME is None: 4696 return self.sql(expression.this) 4697 4698 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4699 4700 def rand_sql(self, expression: exp.Rand) -> str: 4701 lower = self.sql(expression, "lower") 4702 upper = self.sql(expression, "upper") 4703 4704 if lower and upper: 4705 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4706 return self.func("RAND", expression.this) 4707 4708 def changes_sql(self, expression: exp.Changes) -> str: 4709 information = self.sql(expression, "information") 4710 information = f"INFORMATION => {information}" 4711 at_before = self.sql(expression, "at_before") 4712 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4713 end = self.sql(expression, "end") 4714 end = f"{self.seg('')}{end}" if end else "" 4715 4716 return f"CHANGES ({information}){at_before}{end}" 4717 4718 def pad_sql(self, expression: exp.Pad) -> str: 4719 prefix = "L" if expression.args.get("is_left") else "R" 4720 4721 fill_pattern = self.sql(expression, "fill_pattern") or None 4722 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4723 fill_pattern = "' '" 4724 4725 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4726 4727 def summarize_sql(self, expression: exp.Summarize) -> str: 4728 table = " TABLE" if expression.args.get("table") else "" 4729 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4730 4731 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4732 generate_series = exp.GenerateSeries(**expression.args) 4733 4734 parent = expression.parent 4735 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4736 parent = parent.parent 4737 4738 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4739 return self.sql(exp.Unnest(expressions=[generate_series])) 4740 4741 if isinstance(parent, exp.Select): 4742 self.unsupported("GenerateSeries projection unnesting is not supported.") 4743 4744 return self.sql(generate_series) 4745 4746 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4747 exprs = expression.expressions 4748 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4749 if len(exprs) == 0: 4750 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4751 else: 4752 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4753 else: 4754 rhs = self.expressions(expression) # type: ignore 4755 4756 return self.func(name, expression.this, rhs or None) 4757 4758 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4759 if self.SUPPORTS_CONVERT_TIMEZONE: 4760 return self.function_fallback_sql(expression) 4761 4762 source_tz = expression.args.get("source_tz") 4763 target_tz = expression.args.get("target_tz") 4764 timestamp = expression.args.get("timestamp") 4765 4766 if source_tz and timestamp: 4767 timestamp = exp.AtTimeZone( 4768 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4769 ) 4770 4771 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4772 4773 return self.sql(expr) 4774 4775 def json_sql(self, expression: exp.JSON) -> str: 4776 this = self.sql(expression, "this") 4777 this = f" {this}" if this else "" 4778 4779 _with = expression.args.get("with") 4780 4781 if _with is None: 4782 with_sql = "" 4783 elif not _with: 4784 with_sql = " WITHOUT" 4785 else: 4786 with_sql = " WITH" 4787 4788 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4789 4790 return f"JSON{this}{with_sql}{unique_sql}" 4791 4792 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4793 def _generate_on_options(arg: t.Any) -> str: 4794 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4795 4796 path = self.sql(expression, "path") 4797 returning = self.sql(expression, "returning") 4798 returning = f" RETURNING {returning}" if returning else "" 4799 4800 on_condition = self.sql(expression, "on_condition") 4801 on_condition = f" {on_condition}" if on_condition else "" 4802 4803 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4804 4805 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4806 else_ = "ELSE " if expression.args.get("else_") else "" 4807 condition = self.sql(expression, "expression") 4808 condition = f"WHEN {condition} THEN " if condition else else_ 4809 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4810 return f"{condition}{insert}" 4811 4812 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4813 kind = self.sql(expression, "kind") 4814 expressions = self.seg(self.expressions(expression, sep=" ")) 4815 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4816 return res 4817 4818 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4819 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4820 empty = expression.args.get("empty") 4821 empty = ( 4822 f"DEFAULT {empty} ON EMPTY" 4823 if isinstance(empty, exp.Expression) 4824 else self.sql(expression, "empty") 4825 ) 4826 4827 error = expression.args.get("error") 4828 error = ( 4829 f"DEFAULT {error} ON ERROR" 4830 if isinstance(error, exp.Expression) 4831 else self.sql(expression, "error") 4832 ) 4833 4834 if error and empty: 4835 error = ( 4836 f"{empty} {error}" 4837 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4838 else f"{error} {empty}" 4839 ) 4840 empty = "" 4841 4842 null = self.sql(expression, "null") 4843 4844 return f"{empty}{error}{null}" 4845 4846 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4847 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4848 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4849 4850 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4851 this = self.sql(expression, "this") 4852 path = self.sql(expression, "path") 4853 4854 passing = self.expressions(expression, "passing") 4855 passing = f" PASSING {passing}" if passing else "" 4856 4857 on_condition = self.sql(expression, "on_condition") 4858 on_condition = f" {on_condition}" if on_condition else "" 4859 4860 path = f"{path}{passing}{on_condition}" 4861 4862 return self.func("JSON_EXISTS", this, path) 4863 4864 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4865 array_agg = self.function_fallback_sql(expression) 4866 4867 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4868 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4869 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4870 parent = expression.parent 4871 if isinstance(parent, exp.Filter): 4872 parent_cond = parent.expression.this 4873 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4874 else: 4875 this = expression.this 4876 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4877 if this.find(exp.Column): 4878 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4879 this_sql = ( 4880 self.expressions(this) 4881 if isinstance(this, exp.Distinct) 4882 else self.sql(expression, "this") 4883 ) 4884 4885 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4886 4887 return array_agg 4888 4889 def apply_sql(self, expression: exp.Apply) -> str: 4890 this = self.sql(expression, "this") 4891 expr = self.sql(expression, "expression") 4892 4893 return f"{this} APPLY({expr})" 4894 4895 def _grant_or_revoke_sql( 4896 self, 4897 expression: exp.Grant | exp.Revoke, 4898 keyword: str, 4899 preposition: str, 4900 grant_option_prefix: str = "", 4901 grant_option_suffix: str = "", 4902 ) -> str: 4903 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4904 4905 kind = self.sql(expression, "kind") 4906 kind = f" {kind}" if kind else "" 4907 4908 securable = self.sql(expression, "securable") 4909 securable = f" {securable}" if securable else "" 4910 4911 principals = self.expressions(expression, key="principals", flat=True) 4912 4913 if not expression.args.get("grant_option"): 4914 grant_option_prefix = grant_option_suffix = "" 4915 4916 # cascade for revoke only 4917 cascade = self.sql(expression, "cascade") 4918 cascade = f" {cascade}" if cascade else "" 4919 4920 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 4921 4922 def grant_sql(self, expression: exp.Grant) -> str: 4923 return self._grant_or_revoke_sql( 4924 expression, 4925 keyword="GRANT", 4926 preposition="TO", 4927 grant_option_suffix=" WITH GRANT OPTION", 4928 ) 4929 4930 def revoke_sql(self, expression: exp.Revoke) -> str: 4931 return self._grant_or_revoke_sql( 4932 expression, 4933 keyword="REVOKE", 4934 preposition="FROM", 4935 grant_option_prefix="GRANT OPTION FOR ", 4936 ) 4937 4938 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4939 this = self.sql(expression, "this") 4940 columns = self.expressions(expression, flat=True) 4941 columns = f"({columns})" if columns else "" 4942 4943 return f"{this}{columns}" 4944 4945 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4946 this = self.sql(expression, "this") 4947 4948 kind = self.sql(expression, "kind") 4949 kind = f"{kind} " if kind else "" 4950 4951 return f"{kind}{this}" 4952 4953 def columns_sql(self, expression: exp.Columns): 4954 func = self.function_fallback_sql(expression) 4955 if expression.args.get("unpack"): 4956 func = f"*{func}" 4957 4958 return func 4959 4960 def overlay_sql(self, expression: exp.Overlay): 4961 this = self.sql(expression, "this") 4962 expr = self.sql(expression, "expression") 4963 from_sql = self.sql(expression, "from") 4964 for_sql = self.sql(expression, "for") 4965 for_sql = f" FOR {for_sql}" if for_sql else "" 4966 4967 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4968 4969 @unsupported_args("format") 4970 def todouble_sql(self, expression: exp.ToDouble) -> str: 4971 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4972 4973 def string_sql(self, expression: exp.String) -> str: 4974 this = expression.this 4975 zone = expression.args.get("zone") 4976 4977 if zone: 4978 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4979 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4980 # set for source_tz to transpile the time conversion before the STRING cast 4981 this = exp.ConvertTimezone( 4982 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4983 ) 4984 4985 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4986 4987 def median_sql(self, expression: exp.Median): 4988 if not self.SUPPORTS_MEDIAN: 4989 return self.sql( 4990 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4991 ) 4992 4993 return self.function_fallback_sql(expression) 4994 4995 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4996 filler = self.sql(expression, "this") 4997 filler = f" {filler}" if filler else "" 4998 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4999 return f"TRUNCATE{filler} {with_count}" 5000 5001 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5002 if self.SUPPORTS_UNIX_SECONDS: 5003 return self.function_fallback_sql(expression) 5004 5005 start_ts = exp.cast( 5006 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 5007 ) 5008 5009 return self.sql( 5010 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5011 ) 5012 5013 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5014 dim = expression.expression 5015 5016 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5017 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5018 if not (dim.is_int and dim.name == "1"): 5019 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5020 dim = None 5021 5022 # If dimension is required but not specified, default initialize it 5023 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5024 dim = exp.Literal.number(1) 5025 5026 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5027 5028 def attach_sql(self, expression: exp.Attach) -> str: 5029 this = self.sql(expression, "this") 5030 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5031 expressions = self.expressions(expression) 5032 expressions = f" ({expressions})" if expressions else "" 5033 5034 return f"ATTACH{exists_sql} {this}{expressions}" 5035 5036 def detach_sql(self, expression: exp.Detach) -> str: 5037 this = self.sql(expression, "this") 5038 # the DATABASE keyword is required if IF EXISTS is set 5039 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5040 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5041 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5042 5043 return f"DETACH{exists_sql} {this}" 5044 5045 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5046 this = self.sql(expression, "this") 5047 value = self.sql(expression, "expression") 5048 value = f" {value}" if value else "" 5049 return f"{this}{value}" 5050 5051 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5052 return ( 5053 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5054 ) 5055 5056 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5057 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5058 encode = f"{encode} {self.sql(expression, 'this')}" 5059 5060 properties = expression.args.get("properties") 5061 if properties: 5062 encode = f"{encode} {self.properties(properties)}" 5063 5064 return encode 5065 5066 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5067 this = self.sql(expression, "this") 5068 include = f"INCLUDE {this}" 5069 5070 column_def = self.sql(expression, "column_def") 5071 if column_def: 5072 include = f"{include} {column_def}" 5073 5074 alias = self.sql(expression, "alias") 5075 if alias: 5076 include = f"{include} AS {alias}" 5077 5078 return include 5079 5080 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5081 name = f"NAME {self.sql(expression, 'this')}" 5082 return self.func("XMLELEMENT", name, *expression.expressions) 5083 5084 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5085 this = self.sql(expression, "this") 5086 expr = self.sql(expression, "expression") 5087 expr = f"({expr})" if expr else "" 5088 return f"{this}{expr}" 5089 5090 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5091 partitions = self.expressions(expression, "partition_expressions") 5092 create = self.expressions(expression, "create_expressions") 5093 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5094 5095 def partitionbyrangepropertydynamic_sql( 5096 self, expression: exp.PartitionByRangePropertyDynamic 5097 ) -> str: 5098 start = self.sql(expression, "start") 5099 end = self.sql(expression, "end") 5100 5101 every = expression.args["every"] 5102 if isinstance(every, exp.Interval) and every.this.is_string: 5103 every.this.replace(exp.Literal.number(every.name)) 5104 5105 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5106 5107 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5108 name = self.sql(expression, "this") 5109 values = self.expressions(expression, flat=True) 5110 5111 return f"NAME {name} VALUE {values}" 5112 5113 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5114 kind = self.sql(expression, "kind") 5115 sample = self.sql(expression, "sample") 5116 return f"SAMPLE {sample} {kind}" 5117 5118 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5119 kind = self.sql(expression, "kind") 5120 option = self.sql(expression, "option") 5121 option = f" {option}" if option else "" 5122 this = self.sql(expression, "this") 5123 this = f" {this}" if this else "" 5124 columns = self.expressions(expression) 5125 columns = f" {columns}" if columns else "" 5126 return f"{kind}{option} STATISTICS{this}{columns}" 5127 5128 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5129 this = self.sql(expression, "this") 5130 columns = self.expressions(expression) 5131 inner_expression = self.sql(expression, "expression") 5132 inner_expression = f" {inner_expression}" if inner_expression else "" 5133 update_options = self.sql(expression, "update_options") 5134 update_options = f" {update_options} UPDATE" if update_options else "" 5135 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5136 5137 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5138 kind = self.sql(expression, "kind") 5139 kind = f" {kind}" if kind else "" 5140 return f"DELETE{kind} STATISTICS" 5141 5142 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5143 inner_expression = self.sql(expression, "expression") 5144 return f"LIST CHAINED ROWS{inner_expression}" 5145 5146 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5147 kind = self.sql(expression, "kind") 5148 this = self.sql(expression, "this") 5149 this = f" {this}" if this else "" 5150 inner_expression = self.sql(expression, "expression") 5151 return f"VALIDATE {kind}{this}{inner_expression}" 5152 5153 def analyze_sql(self, expression: exp.Analyze) -> str: 5154 options = self.expressions(expression, key="options", sep=" ") 5155 options = f" {options}" if options else "" 5156 kind = self.sql(expression, "kind") 5157 kind = f" {kind}" if kind else "" 5158 this = self.sql(expression, "this") 5159 this = f" {this}" if this else "" 5160 mode = self.sql(expression, "mode") 5161 mode = f" {mode}" if mode else "" 5162 properties = self.sql(expression, "properties") 5163 properties = f" {properties}" if properties else "" 5164 partition = self.sql(expression, "partition") 5165 partition = f" {partition}" if partition else "" 5166 inner_expression = self.sql(expression, "expression") 5167 inner_expression = f" {inner_expression}" if inner_expression else "" 5168 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5169 5170 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5171 this = self.sql(expression, "this") 5172 namespaces = self.expressions(expression, key="namespaces") 5173 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5174 passing = self.expressions(expression, key="passing") 5175 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5176 columns = self.expressions(expression, key="columns") 5177 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5178 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5179 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5180 5181 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5182 this = self.sql(expression, "this") 5183 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5184 5185 def export_sql(self, expression: exp.Export) -> str: 5186 this = self.sql(expression, "this") 5187 connection = self.sql(expression, "connection") 5188 connection = f"WITH CONNECTION {connection} " if connection else "" 5189 options = self.sql(expression, "options") 5190 return f"EXPORT DATA {connection}{options} AS {this}" 5191 5192 def declare_sql(self, expression: exp.Declare) -> str: 5193 return f"DECLARE {self.expressions(expression, flat=True)}" 5194 5195 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5196 variable = self.sql(expression, "this") 5197 default = self.sql(expression, "default") 5198 default = f" = {default}" if default else "" 5199 5200 kind = self.sql(expression, "kind") 5201 if isinstance(expression.args.get("kind"), exp.Schema): 5202 kind = f"TABLE {kind}" 5203 5204 return f"{variable} AS {kind}{default}" 5205 5206 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5207 kind = self.sql(expression, "kind") 5208 this = self.sql(expression, "this") 5209 set = self.sql(expression, "expression") 5210 using = self.sql(expression, "using") 5211 using = f" USING {using}" if using else "" 5212 5213 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5214 5215 return f"{kind_sql} {this} SET {set}{using}" 5216 5217 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5218 params = self.expressions(expression, key="params", flat=True) 5219 return self.func(expression.name, *expression.expressions) + f"({params})" 5220 5221 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5222 return self.func(expression.name, *expression.expressions) 5223 5224 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5225 return self.anonymousaggfunc_sql(expression) 5226 5227 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5228 return self.parameterizedagg_sql(expression) 5229 5230 def show_sql(self, expression: exp.Show) -> str: 5231 self.unsupported("Unsupported SHOW statement") 5232 return "" 5233 5234 def install_sql(self, expression: exp.Install) -> str: 5235 self.unsupported("Unsupported INSTALL statement") 5236 return "" 5237 5238 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5239 # Snowflake GET/PUT statements: 5240 # PUT <file> <internalStage> <properties> 5241 # GET <internalStage> <file> <properties> 5242 props = expression.args.get("properties") 5243 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5244 this = self.sql(expression, "this") 5245 target = self.sql(expression, "target") 5246 5247 if isinstance(expression, exp.Put): 5248 return f"PUT {this} {target}{props_sql}" 5249 else: 5250 return f"GET {target} {this}{props_sql}" 5251 5252 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5253 this = self.sql(expression, "this") 5254 expr = self.sql(expression, "expression") 5255 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5256 return f"TRANSLATE({this} USING {expr}{with_error})" 5257 5258 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5259 if self.SUPPORTS_DECODE_CASE: 5260 return self.func("DECODE", *expression.expressions) 5261 5262 expression, *expressions = expression.expressions 5263 5264 ifs = [] 5265 for search, result in zip(expressions[::2], expressions[1::2]): 5266 if isinstance(search, exp.Literal): 5267 ifs.append(exp.If(this=expression.eq(search), true=result)) 5268 elif isinstance(search, exp.Null): 5269 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5270 else: 5271 if isinstance(search, exp.Binary): 5272 search = exp.paren(search) 5273 5274 cond = exp.or_( 5275 expression.eq(search), 5276 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5277 copy=False, 5278 ) 5279 ifs.append(exp.If(this=cond, true=result)) 5280 5281 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5282 return self.sql(case) 5283 5284 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5285 this = self.sql(expression, "this") 5286 this = self.seg(this, sep="") 5287 dimensions = self.expressions( 5288 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5289 ) 5290 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5291 metrics = self.expressions( 5292 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5293 ) 5294 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5295 where = self.sql(expression, "where") 5296 where = self.seg(f"WHERE {where}") if where else "" 5297 body = self.indent(this + metrics + dimensions + where, skip_first=True) 5298 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5299 5300 def getextract_sql(self, expression: exp.GetExtract) -> str: 5301 this = expression.this 5302 expr = expression.expression 5303 5304 if not this.type or not expression.type: 5305 from sqlglot.optimizer.annotate_types import annotate_types 5306 5307 this = annotate_types(this, dialect=self.dialect) 5308 5309 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5310 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5311 5312 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5313 5314 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5315 return self.sql( 5316 exp.DateAdd( 5317 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5318 expression=expression.this, 5319 unit=exp.var("DAY"), 5320 ) 5321 ) 5322 5323 def space_sql(self: Generator, expression: exp.Space) -> str: 5324 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5325 5326 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5327 return f"BUILD {self.sql(expression, 'this')}" 5328 5329 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5330 method = self.sql(expression, "method") 5331 kind = expression.args.get("kind") 5332 if not kind: 5333 return f"REFRESH {method}" 5334 5335 every = self.sql(expression, "every") 5336 unit = self.sql(expression, "unit") 5337 every = f" EVERY {every} {unit}" if every else "" 5338 starts = self.sql(expression, "starts") 5339 starts = f" STARTS {starts}" if starts else "" 5340 5341 return f"REFRESH {method} ON {kind}{every}{starts}" 5342 5343 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5344 self.unsupported("The model!attribute syntax is not supported") 5345 return "" 5346 5347 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5348 return self.func("DIRECTORY", expression.this)
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE =
"Argument '{}' is not supported for expression '{}' when targeting {}."
def
unsupported_args( *args: Union[str, Tuple[str, str]]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
30def unsupported_args( 31 *args: t.Union[str, t.Tuple[str, str]], 32) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 33 """ 34 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 35 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 36 """ 37 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 38 for arg in args: 39 if isinstance(arg, str): 40 diagnostic_by_arg[arg] = None 41 else: 42 diagnostic_by_arg[arg[0]] = arg[1] 43 44 def decorator(func: GeneratorMethod) -> GeneratorMethod: 45 @wraps(func) 46 def _func(generator: G, expression: E) -> str: 47 expression_name = expression.__class__.__name__ 48 dialect_name = generator.dialect.__class__.__name__ 49 50 for arg_name, diagnostic in diagnostic_by_arg.items(): 51 if expression.args.get(arg_name): 52 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 53 arg_name, expression_name, dialect_name 54 ) 55 generator.unsupported(diagnostic) 56 57 return func(generator, expression) 58 59 return _func 60 61 return decorator
Decorator that can be used to mark certain args of an Expression subclass as unsupported.
It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
class
Generator:
75class Generator(metaclass=_Generator): 76 """ 77 Generator converts a given syntax tree to the corresponding SQL string. 78 79 Args: 80 pretty: Whether to format the produced SQL string. 81 Default: False. 82 identify: Determines when an identifier should be quoted. Possible values are: 83 False (default): Never quote, except in cases where it's mandatory by the dialect. 84 True or 'always': Always quote. 85 'safe': Only quote identifiers that are case insensitive. 86 normalize: Whether to normalize identifiers to lowercase. 87 Default: False. 88 pad: The pad size in a formatted string. For example, this affects the indentation of 89 a projection in a query, relative to its nesting level. 90 Default: 2. 91 indent: The indentation size in a formatted string. For example, this affects the 92 indentation of subqueries and filters under a `WHERE` clause. 93 Default: 2. 94 normalize_functions: How to normalize function names. Possible values are: 95 "upper" or True (default): Convert names to uppercase. 96 "lower": Convert names to lowercase. 97 False: Disables function name normalization. 98 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 99 Default ErrorLevel.WARN. 100 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 101 This is only relevant if unsupported_level is ErrorLevel.RAISE. 102 Default: 3 103 leading_comma: Whether the comma is leading or trailing in select expressions. 104 This is only relevant when generating in pretty mode. 105 Default: False 106 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 107 The default is on the smaller end because the length only represents a segment and not the true 108 line length. 109 Default: 80 110 comments: Whether to preserve comments in the output SQL code. 111 Default: True 112 """ 113 114 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 115 **JSON_PATH_PART_TRANSFORMS, 116 exp.AllowedValuesProperty: lambda self, 117 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 118 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 119 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 120 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 121 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 122 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 123 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 124 exp.CaseSpecificColumnConstraint: lambda _, 125 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 126 exp.Ceil: lambda self, e: self.ceil_floor(e), 127 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 128 exp.CharacterSetProperty: lambda self, 129 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 130 exp.ClusteredColumnConstraint: lambda self, 131 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 132 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 133 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 134 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 135 exp.ConvertToCharset: lambda self, e: self.func( 136 "CONVERT", e.this, e.args["dest"], e.args.get("source") 137 ), 138 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 139 exp.CredentialsProperty: lambda self, 140 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 141 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 142 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 143 exp.DynamicProperty: lambda *_: "DYNAMIC", 144 exp.EmptyProperty: lambda *_: "EMPTY", 145 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 146 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 147 exp.EphemeralColumnConstraint: lambda self, 148 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 149 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 150 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 151 exp.Except: lambda self, e: self.set_operations(e), 152 exp.ExternalProperty: lambda *_: "EXTERNAL", 153 exp.Floor: lambda self, e: self.ceil_floor(e), 154 exp.Get: lambda self, e: self.get_put_sql(e), 155 exp.GlobalProperty: lambda *_: "GLOBAL", 156 exp.HeapProperty: lambda *_: "HEAP", 157 exp.IcebergProperty: lambda *_: "ICEBERG", 158 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 159 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 160 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 161 exp.Intersect: lambda self, e: self.set_operations(e), 162 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 163 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 164 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 165 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 166 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 167 exp.LanguageProperty: lambda self, e: self.naked_property(e), 168 exp.LocationProperty: lambda self, e: self.naked_property(e), 169 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 170 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 171 exp.NonClusteredColumnConstraint: lambda self, 172 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 173 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 174 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 175 exp.OnCommitProperty: lambda _, 176 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 177 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 178 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 179 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 180 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 181 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 182 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 183 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 184 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 185 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 186 exp.ProjectionPolicyColumnConstraint: lambda self, 187 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 188 exp.Put: lambda self, e: self.get_put_sql(e), 189 exp.RemoteWithConnectionModelProperty: lambda self, 190 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 191 exp.ReturnsProperty: lambda self, e: ( 192 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 193 ), 194 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 195 exp.SecureProperty: lambda *_: "SECURE", 196 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 197 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 198 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 199 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 200 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 201 exp.SqlReadWriteProperty: lambda _, e: e.name, 202 exp.SqlSecurityProperty: lambda _, 203 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 204 exp.StabilityProperty: lambda _, e: e.name, 205 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 206 exp.StreamingTableProperty: lambda *_: "STREAMING", 207 exp.StrictProperty: lambda *_: "STRICT", 208 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 209 exp.TableColumn: lambda self, e: self.sql(e.this), 210 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 211 exp.TemporaryProperty: lambda *_: "TEMPORARY", 212 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 213 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 214 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 215 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 216 exp.TransientProperty: lambda *_: "TRANSIENT", 217 exp.Union: lambda self, e: self.set_operations(e), 218 exp.UnloggedProperty: lambda *_: "UNLOGGED", 219 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 220 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 221 exp.Uuid: lambda *_: "UUID()", 222 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 223 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 224 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 225 exp.UtcTimestamp: lambda self, e: self.sql( 226 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 227 ), 228 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 229 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 230 exp.VolatileProperty: lambda *_: "VOLATILE", 231 exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})", 232 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 233 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 234 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 235 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 236 exp.ForceProperty: lambda *_: "FORCE", 237 } 238 239 # Whether null ordering is supported in order by 240 # True: Full Support, None: No support, False: No support for certain cases 241 # such as window specifications, aggregate functions etc 242 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 243 244 # Whether ignore nulls is inside the agg or outside. 245 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 246 IGNORE_NULLS_IN_FUNC = False 247 248 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 249 LOCKING_READS_SUPPORTED = False 250 251 # Whether the EXCEPT and INTERSECT operations can return duplicates 252 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 253 254 # Wrap derived values in parens, usually standard but spark doesn't support it 255 WRAP_DERIVED_VALUES = True 256 257 # Whether create function uses an AS before the RETURN 258 CREATE_FUNCTION_RETURN_AS = True 259 260 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 261 MATCHED_BY_SOURCE = True 262 263 # Whether the INTERVAL expression works only with values like '1 day' 264 SINGLE_STRING_INTERVAL = False 265 266 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 267 INTERVAL_ALLOWS_PLURAL_FORM = True 268 269 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 270 LIMIT_FETCH = "ALL" 271 272 # Whether limit and fetch allows expresions or just limits 273 LIMIT_ONLY_LITERALS = False 274 275 # Whether a table is allowed to be renamed with a db 276 RENAME_TABLE_WITH_DB = True 277 278 # The separator for grouping sets and rollups 279 GROUPINGS_SEP = "," 280 281 # The string used for creating an index on a table 282 INDEX_ON = "ON" 283 284 # Whether join hints should be generated 285 JOIN_HINTS = True 286 287 # Whether table hints should be generated 288 TABLE_HINTS = True 289 290 # Whether query hints should be generated 291 QUERY_HINTS = True 292 293 # What kind of separator to use for query hints 294 QUERY_HINT_SEP = ", " 295 296 # Whether comparing against booleans (e.g. x IS TRUE) is supported 297 IS_BOOL_ALLOWED = True 298 299 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 300 DUPLICATE_KEY_UPDATE_WITH_SET = True 301 302 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 303 LIMIT_IS_TOP = False 304 305 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 306 RETURNING_END = True 307 308 # Whether to generate an unquoted value for EXTRACT's date part argument 309 EXTRACT_ALLOWS_QUOTES = True 310 311 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 312 TZ_TO_WITH_TIME_ZONE = False 313 314 # Whether the NVL2 function is supported 315 NVL2_SUPPORTED = True 316 317 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 318 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 319 320 # Whether VALUES statements can be used as derived tables. 321 # MySQL 5 and Redshift do not allow this, so when False, it will convert 322 # SELECT * VALUES into SELECT UNION 323 VALUES_AS_TABLE = True 324 325 # Whether the word COLUMN is included when adding a column with ALTER TABLE 326 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 327 328 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 329 UNNEST_WITH_ORDINALITY = True 330 331 # Whether FILTER (WHERE cond) can be used for conditional aggregation 332 AGGREGATE_FILTER_SUPPORTED = True 333 334 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 335 SEMI_ANTI_JOIN_WITH_SIDE = True 336 337 # Whether to include the type of a computed column in the CREATE DDL 338 COMPUTED_COLUMN_WITH_TYPE = True 339 340 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 341 SUPPORTS_TABLE_COPY = True 342 343 # Whether parentheses are required around the table sample's expression 344 TABLESAMPLE_REQUIRES_PARENS = True 345 346 # Whether a table sample clause's size needs to be followed by the ROWS keyword 347 TABLESAMPLE_SIZE_IS_ROWS = True 348 349 # The keyword(s) to use when generating a sample clause 350 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 351 352 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 353 TABLESAMPLE_WITH_METHOD = True 354 355 # The keyword to use when specifying the seed of a sample clause 356 TABLESAMPLE_SEED_KEYWORD = "SEED" 357 358 # Whether COLLATE is a function instead of a binary operator 359 COLLATE_IS_FUNC = False 360 361 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 362 DATA_TYPE_SPECIFIERS_ALLOWED = False 363 364 # Whether conditions require booleans WHERE x = 0 vs WHERE x 365 ENSURE_BOOLS = False 366 367 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 368 CTE_RECURSIVE_KEYWORD_REQUIRED = True 369 370 # Whether CONCAT requires >1 arguments 371 SUPPORTS_SINGLE_ARG_CONCAT = True 372 373 # Whether LAST_DAY function supports a date part argument 374 LAST_DAY_SUPPORTS_DATE_PART = True 375 376 # Whether named columns are allowed in table aliases 377 SUPPORTS_TABLE_ALIAS_COLUMNS = True 378 379 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 380 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 381 382 # What delimiter to use for separating JSON key/value pairs 383 JSON_KEY_VALUE_PAIR_SEP = ":" 384 385 # INSERT OVERWRITE TABLE x override 386 INSERT_OVERWRITE = " OVERWRITE TABLE" 387 388 # Whether the SELECT .. INTO syntax is used instead of CTAS 389 SUPPORTS_SELECT_INTO = False 390 391 # Whether UNLOGGED tables can be created 392 SUPPORTS_UNLOGGED_TABLES = False 393 394 # Whether the CREATE TABLE LIKE statement is supported 395 SUPPORTS_CREATE_TABLE_LIKE = True 396 397 # Whether the LikeProperty needs to be specified inside of the schema clause 398 LIKE_PROPERTY_INSIDE_SCHEMA = False 399 400 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 401 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 402 MULTI_ARG_DISTINCT = True 403 404 # Whether the JSON extraction operators expect a value of type JSON 405 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 406 407 # Whether bracketed keys like ["foo"] are supported in JSON paths 408 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 409 410 # Whether to escape keys using single quotes in JSON paths 411 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 412 413 # The JSONPathPart expressions supported by this dialect 414 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 415 416 # Whether any(f(x) for x in array) can be implemented by this dialect 417 CAN_IMPLEMENT_ARRAY_ANY = False 418 419 # Whether the function TO_NUMBER is supported 420 SUPPORTS_TO_NUMBER = True 421 422 # Whether EXCLUDE in window specification is supported 423 SUPPORTS_WINDOW_EXCLUDE = False 424 425 # Whether or not set op modifiers apply to the outer set op or select. 426 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 427 # True means limit 1 happens after the set op, False means it it happens on y. 428 SET_OP_MODIFIERS = True 429 430 # Whether parameters from COPY statement are wrapped in parentheses 431 COPY_PARAMS_ARE_WRAPPED = True 432 433 # Whether values of params are set with "=" token or empty space 434 COPY_PARAMS_EQ_REQUIRED = False 435 436 # Whether COPY statement has INTO keyword 437 COPY_HAS_INTO_KEYWORD = True 438 439 # Whether the conditional TRY(expression) function is supported 440 TRY_SUPPORTED = True 441 442 # Whether the UESCAPE syntax in unicode strings is supported 443 SUPPORTS_UESCAPE = True 444 445 # Function used to replace escaped unicode codes in unicode strings 446 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 447 448 # The keyword to use when generating a star projection with excluded columns 449 STAR_EXCEPT = "EXCEPT" 450 451 # The HEX function name 452 HEX_FUNC = "HEX" 453 454 # The keywords to use when prefixing & separating WITH based properties 455 WITH_PROPERTIES_PREFIX = "WITH" 456 457 # Whether to quote the generated expression of exp.JsonPath 458 QUOTE_JSON_PATH = True 459 460 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 461 PAD_FILL_PATTERN_IS_REQUIRED = False 462 463 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 464 SUPPORTS_EXPLODING_PROJECTIONS = True 465 466 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 467 ARRAY_CONCAT_IS_VAR_LEN = True 468 469 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 470 SUPPORTS_CONVERT_TIMEZONE = False 471 472 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 473 SUPPORTS_MEDIAN = True 474 475 # Whether UNIX_SECONDS(timestamp) is supported 476 SUPPORTS_UNIX_SECONDS = False 477 478 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 479 ALTER_SET_WRAPPED = False 480 481 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 482 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 483 # TODO: The normalization should be done by default once we've tested it across all dialects. 484 NORMALIZE_EXTRACT_DATE_PARTS = False 485 486 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 487 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 488 489 # The function name of the exp.ArraySize expression 490 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 491 492 # The syntax to use when altering the type of a column 493 ALTER_SET_TYPE = "SET DATA TYPE" 494 495 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 496 # None -> Doesn't support it at all 497 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 498 # True (Postgres) -> Explicitly requires it 499 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 500 501 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 502 SUPPORTS_DECODE_CASE = True 503 504 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 505 SUPPORTS_BETWEEN_FLAGS = False 506 507 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 508 SUPPORTS_LIKE_QUANTIFIERS = True 509 510 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 511 MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None 512 513 TYPE_MAPPING = { 514 exp.DataType.Type.DATETIME2: "TIMESTAMP", 515 exp.DataType.Type.NCHAR: "CHAR", 516 exp.DataType.Type.NVARCHAR: "VARCHAR", 517 exp.DataType.Type.MEDIUMTEXT: "TEXT", 518 exp.DataType.Type.LONGTEXT: "TEXT", 519 exp.DataType.Type.TINYTEXT: "TEXT", 520 exp.DataType.Type.BLOB: "VARBINARY", 521 exp.DataType.Type.MEDIUMBLOB: "BLOB", 522 exp.DataType.Type.LONGBLOB: "BLOB", 523 exp.DataType.Type.TINYBLOB: "BLOB", 524 exp.DataType.Type.INET: "INET", 525 exp.DataType.Type.ROWVERSION: "VARBINARY", 526 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 527 } 528 529 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 530 531 TIME_PART_SINGULARS = { 532 "MICROSECONDS": "MICROSECOND", 533 "SECONDS": "SECOND", 534 "MINUTES": "MINUTE", 535 "HOURS": "HOUR", 536 "DAYS": "DAY", 537 "WEEKS": "WEEK", 538 "MONTHS": "MONTH", 539 "QUARTERS": "QUARTER", 540 "YEARS": "YEAR", 541 } 542 543 AFTER_HAVING_MODIFIER_TRANSFORMS = { 544 "cluster": lambda self, e: self.sql(e, "cluster"), 545 "distribute": lambda self, e: self.sql(e, "distribute"), 546 "sort": lambda self, e: self.sql(e, "sort"), 547 "windows": lambda self, e: ( 548 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 549 if e.args.get("windows") 550 else "" 551 ), 552 "qualify": lambda self, e: self.sql(e, "qualify"), 553 } 554 555 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 556 557 STRUCT_DELIMITER = ("<", ">") 558 559 PARAMETER_TOKEN = "@" 560 NAMED_PLACEHOLDER_TOKEN = ":" 561 562 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 563 564 PROPERTIES_LOCATION = { 565 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 567 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 571 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 572 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 573 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 575 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 576 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 579 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 580 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 582 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 583 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 584 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 585 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 589 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 592 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 593 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 594 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 595 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 596 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 597 exp.HeapProperty: exp.Properties.Location.POST_WITH, 598 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 600 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 603 exp.JournalProperty: exp.Properties.Location.POST_NAME, 604 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 609 exp.LogProperty: exp.Properties.Location.POST_NAME, 610 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 611 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 612 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 613 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 614 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 615 exp.Order: exp.Properties.Location.POST_SCHEMA, 616 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 618 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 620 exp.Property: exp.Properties.Location.POST_WITH, 621 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 626 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 628 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 629 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 630 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 631 exp.Set: exp.Properties.Location.POST_SCHEMA, 632 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 633 exp.SetProperty: exp.Properties.Location.POST_CREATE, 634 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 635 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 636 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 637 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 639 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 640 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 641 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 642 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 643 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 644 exp.Tags: exp.Properties.Location.POST_WITH, 645 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 646 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 647 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 648 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 649 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 650 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 651 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 652 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 653 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 654 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 655 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 656 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 657 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 658 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 659 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 660 } 661 662 # Keywords that can't be used as unquoted identifier names 663 RESERVED_KEYWORDS: t.Set[str] = set() 664 665 # Expressions whose comments are separated from them for better formatting 666 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 667 exp.Command, 668 exp.Create, 669 exp.Describe, 670 exp.Delete, 671 exp.Drop, 672 exp.From, 673 exp.Insert, 674 exp.Join, 675 exp.MultitableInserts, 676 exp.Order, 677 exp.Group, 678 exp.Having, 679 exp.Select, 680 exp.SetOperation, 681 exp.Update, 682 exp.Where, 683 exp.With, 684 ) 685 686 # Expressions that should not have their comments generated in maybe_comment 687 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 688 exp.Binary, 689 exp.SetOperation, 690 ) 691 692 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 693 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 694 exp.Column, 695 exp.Literal, 696 exp.Neg, 697 exp.Paren, 698 ) 699 700 PARAMETERIZABLE_TEXT_TYPES = { 701 exp.DataType.Type.NVARCHAR, 702 exp.DataType.Type.VARCHAR, 703 exp.DataType.Type.CHAR, 704 exp.DataType.Type.NCHAR, 705 } 706 707 # Expressions that need to have all CTEs under them bubbled up to them 708 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 709 710 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 711 712 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 713 714 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 715 716 __slots__ = ( 717 "pretty", 718 "identify", 719 "normalize", 720 "pad", 721 "_indent", 722 "normalize_functions", 723 "unsupported_level", 724 "max_unsupported", 725 "leading_comma", 726 "max_text_width", 727 "comments", 728 "dialect", 729 "unsupported_messages", 730 "_escaped_quote_end", 731 "_escaped_byte_quote_end", 732 "_escaped_identifier_end", 733 "_next_name", 734 "_identifier_start", 735 "_identifier_end", 736 "_quote_json_path_key_using_brackets", 737 ) 738 739 def __init__( 740 self, 741 pretty: t.Optional[bool] = None, 742 identify: str | bool = False, 743 normalize: bool = False, 744 pad: int = 2, 745 indent: int = 2, 746 normalize_functions: t.Optional[str | bool] = None, 747 unsupported_level: ErrorLevel = ErrorLevel.WARN, 748 max_unsupported: int = 3, 749 leading_comma: bool = False, 750 max_text_width: int = 80, 751 comments: bool = True, 752 dialect: DialectType = None, 753 ): 754 import sqlglot 755 from sqlglot.dialects import Dialect 756 757 self.pretty = pretty if pretty is not None else sqlglot.pretty 758 self.identify = identify 759 self.normalize = normalize 760 self.pad = pad 761 self._indent = indent 762 self.unsupported_level = unsupported_level 763 self.max_unsupported = max_unsupported 764 self.leading_comma = leading_comma 765 self.max_text_width = max_text_width 766 self.comments = comments 767 self.dialect = Dialect.get_or_raise(dialect) 768 769 # This is both a Dialect property and a Generator argument, so we prioritize the latter 770 self.normalize_functions = ( 771 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 772 ) 773 774 self.unsupported_messages: t.List[str] = [] 775 self._escaped_quote_end: str = ( 776 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 777 ) 778 self._escaped_byte_quote_end: str = ( 779 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 780 if self.dialect.BYTE_END 781 else "" 782 ) 783 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 784 785 self._next_name = name_sequence("_t") 786 787 self._identifier_start = self.dialect.IDENTIFIER_START 788 self._identifier_end = self.dialect.IDENTIFIER_END 789 790 self._quote_json_path_key_using_brackets = True 791 792 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 793 """ 794 Generates the SQL string corresponding to the given syntax tree. 795 796 Args: 797 expression: The syntax tree. 798 copy: Whether to copy the expression. The generator performs mutations so 799 it is safer to copy. 800 801 Returns: 802 The SQL string corresponding to `expression`. 803 """ 804 if copy: 805 expression = expression.copy() 806 807 expression = self.preprocess(expression) 808 809 self.unsupported_messages = [] 810 sql = self.sql(expression).strip() 811 812 if self.pretty: 813 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 814 815 if self.unsupported_level == ErrorLevel.IGNORE: 816 return sql 817 818 if self.unsupported_level == ErrorLevel.WARN: 819 for msg in self.unsupported_messages: 820 logger.warning(msg) 821 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 822 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 823 824 return sql 825 826 def preprocess(self, expression: exp.Expression) -> exp.Expression: 827 """Apply generic preprocessing transformations to a given expression.""" 828 expression = self._move_ctes_to_top_level(expression) 829 830 if self.ENSURE_BOOLS: 831 from sqlglot.transforms import ensure_bools 832 833 expression = ensure_bools(expression) 834 835 return expression 836 837 def _move_ctes_to_top_level(self, expression: E) -> E: 838 if ( 839 not expression.parent 840 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 841 and any(node.parent is not expression for node in expression.find_all(exp.With)) 842 ): 843 from sqlglot.transforms import move_ctes_to_top_level 844 845 expression = move_ctes_to_top_level(expression) 846 return expression 847 848 def unsupported(self, message: str) -> None: 849 if self.unsupported_level == ErrorLevel.IMMEDIATE: 850 raise UnsupportedError(message) 851 self.unsupported_messages.append(message) 852 853 def sep(self, sep: str = " ") -> str: 854 return f"{sep.strip()}\n" if self.pretty else sep 855 856 def seg(self, sql: str, sep: str = " ") -> str: 857 return f"{self.sep(sep)}{sql}" 858 859 def sanitize_comment(self, comment: str) -> str: 860 comment = " " + comment if comment[0].strip() else comment 861 comment = comment + " " if comment[-1].strip() else comment 862 863 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 864 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 865 comment = comment.replace("*/", "* /") 866 867 return comment 868 869 def maybe_comment( 870 self, 871 sql: str, 872 expression: t.Optional[exp.Expression] = None, 873 comments: t.Optional[t.List[str]] = None, 874 separated: bool = False, 875 ) -> str: 876 comments = ( 877 ((expression and expression.comments) if comments is None else comments) # type: ignore 878 if self.comments 879 else None 880 ) 881 882 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 883 return sql 884 885 comments_sql = " ".join( 886 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 887 ) 888 889 if not comments_sql: 890 return sql 891 892 comments_sql = self._replace_line_breaks(comments_sql) 893 894 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 895 return ( 896 f"{self.sep()}{comments_sql}{sql}" 897 if not sql or sql[0].isspace() 898 else f"{comments_sql}{self.sep()}{sql}" 899 ) 900 901 return f"{sql} {comments_sql}" 902 903 def wrap(self, expression: exp.Expression | str) -> str: 904 this_sql = ( 905 self.sql(expression) 906 if isinstance(expression, exp.UNWRAPPED_QUERIES) 907 else self.sql(expression, "this") 908 ) 909 if not this_sql: 910 return "()" 911 912 this_sql = self.indent(this_sql, level=1, pad=0) 913 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 914 915 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 916 original = self.identify 917 self.identify = False 918 result = func(*args, **kwargs) 919 self.identify = original 920 return result 921 922 def normalize_func(self, name: str) -> str: 923 if self.normalize_functions == "upper" or self.normalize_functions is True: 924 return name.upper() 925 if self.normalize_functions == "lower": 926 return name.lower() 927 return name 928 929 def indent( 930 self, 931 sql: str, 932 level: int = 0, 933 pad: t.Optional[int] = None, 934 skip_first: bool = False, 935 skip_last: bool = False, 936 ) -> str: 937 if not self.pretty or not sql: 938 return sql 939 940 pad = self.pad if pad is None else pad 941 lines = sql.split("\n") 942 943 return "\n".join( 944 ( 945 line 946 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 947 else f"{' ' * (level * self._indent + pad)}{line}" 948 ) 949 for i, line in enumerate(lines) 950 ) 951 952 def sql( 953 self, 954 expression: t.Optional[str | exp.Expression], 955 key: t.Optional[str] = None, 956 comment: bool = True, 957 ) -> str: 958 if not expression: 959 return "" 960 961 if isinstance(expression, str): 962 return expression 963 964 if key: 965 value = expression.args.get(key) 966 if value: 967 return self.sql(value) 968 return "" 969 970 transform = self.TRANSFORMS.get(expression.__class__) 971 972 if callable(transform): 973 sql = transform(self, expression) 974 elif isinstance(expression, exp.Expression): 975 exp_handler_name = f"{expression.key}_sql" 976 977 if hasattr(self, exp_handler_name): 978 sql = getattr(self, exp_handler_name)(expression) 979 elif isinstance(expression, exp.Func): 980 sql = self.function_fallback_sql(expression) 981 elif isinstance(expression, exp.Property): 982 sql = self.property_sql(expression) 983 else: 984 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 985 else: 986 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 987 988 return self.maybe_comment(sql, expression) if self.comments and comment else sql 989 990 def uncache_sql(self, expression: exp.Uncache) -> str: 991 table = self.sql(expression, "this") 992 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 993 return f"UNCACHE TABLE{exists_sql} {table}" 994 995 def cache_sql(self, expression: exp.Cache) -> str: 996 lazy = " LAZY" if expression.args.get("lazy") else "" 997 table = self.sql(expression, "this") 998 options = expression.args.get("options") 999 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1000 sql = self.sql(expression, "expression") 1001 sql = f" AS{self.sep()}{sql}" if sql else "" 1002 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1003 return self.prepend_ctes(expression, sql) 1004 1005 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1006 if isinstance(expression.parent, exp.Cast): 1007 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1008 default = "DEFAULT " if expression.args.get("default") else "" 1009 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1010 1011 def column_parts(self, expression: exp.Column) -> str: 1012 return ".".join( 1013 self.sql(part) 1014 for part in ( 1015 expression.args.get("catalog"), 1016 expression.args.get("db"), 1017 expression.args.get("table"), 1018 expression.args.get("this"), 1019 ) 1020 if part 1021 ) 1022 1023 def column_sql(self, expression: exp.Column) -> str: 1024 join_mark = " (+)" if expression.args.get("join_mark") else "" 1025 1026 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1027 join_mark = "" 1028 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1029 1030 return f"{self.column_parts(expression)}{join_mark}" 1031 1032 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1033 this = self.sql(expression, "this") 1034 this = f" {this}" if this else "" 1035 position = self.sql(expression, "position") 1036 return f"{position}{this}" 1037 1038 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1039 column = self.sql(expression, "this") 1040 kind = self.sql(expression, "kind") 1041 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1042 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1043 kind = f"{sep}{kind}" if kind else "" 1044 constraints = f" {constraints}" if constraints else "" 1045 position = self.sql(expression, "position") 1046 position = f" {position}" if position else "" 1047 1048 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1049 kind = "" 1050 1051 return f"{exists}{column}{kind}{constraints}{position}" 1052 1053 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1054 this = self.sql(expression, "this") 1055 kind_sql = self.sql(expression, "kind").strip() 1056 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1057 1058 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1059 this = self.sql(expression, "this") 1060 if expression.args.get("not_null"): 1061 persisted = " PERSISTED NOT NULL" 1062 elif expression.args.get("persisted"): 1063 persisted = " PERSISTED" 1064 else: 1065 persisted = "" 1066 1067 return f"AS {this}{persisted}" 1068 1069 def autoincrementcolumnconstraint_sql(self, _) -> str: 1070 return self.token_sql(TokenType.AUTO_INCREMENT) 1071 1072 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1073 if isinstance(expression.this, list): 1074 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1075 else: 1076 this = self.sql(expression, "this") 1077 1078 return f"COMPRESS {this}" 1079 1080 def generatedasidentitycolumnconstraint_sql( 1081 self, expression: exp.GeneratedAsIdentityColumnConstraint 1082 ) -> str: 1083 this = "" 1084 if expression.this is not None: 1085 on_null = " ON NULL" if expression.args.get("on_null") else "" 1086 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1087 1088 start = expression.args.get("start") 1089 start = f"START WITH {start}" if start else "" 1090 increment = expression.args.get("increment") 1091 increment = f" INCREMENT BY {increment}" if increment else "" 1092 minvalue = expression.args.get("minvalue") 1093 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1094 maxvalue = expression.args.get("maxvalue") 1095 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1096 cycle = expression.args.get("cycle") 1097 cycle_sql = "" 1098 1099 if cycle is not None: 1100 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1101 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1102 1103 sequence_opts = "" 1104 if start or increment or cycle_sql: 1105 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1106 sequence_opts = f" ({sequence_opts.strip()})" 1107 1108 expr = self.sql(expression, "expression") 1109 expr = f"({expr})" if expr else "IDENTITY" 1110 1111 return f"GENERATED{this} AS {expr}{sequence_opts}" 1112 1113 def generatedasrowcolumnconstraint_sql( 1114 self, expression: exp.GeneratedAsRowColumnConstraint 1115 ) -> str: 1116 start = "START" if expression.args.get("start") else "END" 1117 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1118 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1119 1120 def periodforsystemtimeconstraint_sql( 1121 self, expression: exp.PeriodForSystemTimeConstraint 1122 ) -> str: 1123 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1124 1125 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1126 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1127 1128 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1129 desc = expression.args.get("desc") 1130 if desc is not None: 1131 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1132 options = self.expressions(expression, key="options", flat=True, sep=" ") 1133 options = f" {options}" if options else "" 1134 return f"PRIMARY KEY{options}" 1135 1136 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1137 this = self.sql(expression, "this") 1138 this = f" {this}" if this else "" 1139 index_type = expression.args.get("index_type") 1140 index_type = f" USING {index_type}" if index_type else "" 1141 on_conflict = self.sql(expression, "on_conflict") 1142 on_conflict = f" {on_conflict}" if on_conflict else "" 1143 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1144 options = self.expressions(expression, key="options", flat=True, sep=" ") 1145 options = f" {options}" if options else "" 1146 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1147 1148 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1149 return self.sql(expression, "this") 1150 1151 def create_sql(self, expression: exp.Create) -> str: 1152 kind = self.sql(expression, "kind") 1153 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1154 properties = expression.args.get("properties") 1155 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1156 1157 this = self.createable_sql(expression, properties_locs) 1158 1159 properties_sql = "" 1160 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1161 exp.Properties.Location.POST_WITH 1162 ): 1163 props_ast = exp.Properties( 1164 expressions=[ 1165 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1166 *properties_locs[exp.Properties.Location.POST_WITH], 1167 ] 1168 ) 1169 props_ast.parent = expression 1170 properties_sql = self.sql(props_ast) 1171 1172 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1173 properties_sql = self.sep() + properties_sql 1174 elif not self.pretty: 1175 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1176 properties_sql = f" {properties_sql}" 1177 1178 begin = " BEGIN" if expression.args.get("begin") else "" 1179 end = " END" if expression.args.get("end") else "" 1180 1181 expression_sql = self.sql(expression, "expression") 1182 if expression_sql: 1183 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1184 1185 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1186 postalias_props_sql = "" 1187 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1188 postalias_props_sql = self.properties( 1189 exp.Properties( 1190 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1191 ), 1192 wrapped=False, 1193 ) 1194 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1195 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1196 1197 postindex_props_sql = "" 1198 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1199 postindex_props_sql = self.properties( 1200 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1201 wrapped=False, 1202 prefix=" ", 1203 ) 1204 1205 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1206 indexes = f" {indexes}" if indexes else "" 1207 index_sql = indexes + postindex_props_sql 1208 1209 replace = " OR REPLACE" if expression.args.get("replace") else "" 1210 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1211 unique = " UNIQUE" if expression.args.get("unique") else "" 1212 1213 clustered = expression.args.get("clustered") 1214 if clustered is None: 1215 clustered_sql = "" 1216 elif clustered: 1217 clustered_sql = " CLUSTERED COLUMNSTORE" 1218 else: 1219 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1220 1221 postcreate_props_sql = "" 1222 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1223 postcreate_props_sql = self.properties( 1224 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1225 sep=" ", 1226 prefix=" ", 1227 wrapped=False, 1228 ) 1229 1230 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1231 1232 postexpression_props_sql = "" 1233 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1234 postexpression_props_sql = self.properties( 1235 exp.Properties( 1236 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1237 ), 1238 sep=" ", 1239 prefix=" ", 1240 wrapped=False, 1241 ) 1242 1243 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1244 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1245 no_schema_binding = ( 1246 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1247 ) 1248 1249 clone = self.sql(expression, "clone") 1250 clone = f" {clone}" if clone else "" 1251 1252 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1253 properties_expression = f"{expression_sql}{properties_sql}" 1254 else: 1255 properties_expression = f"{properties_sql}{expression_sql}" 1256 1257 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1258 return self.prepend_ctes(expression, expression_sql) 1259 1260 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1261 start = self.sql(expression, "start") 1262 start = f"START WITH {start}" if start else "" 1263 increment = self.sql(expression, "increment") 1264 increment = f" INCREMENT BY {increment}" if increment else "" 1265 minvalue = self.sql(expression, "minvalue") 1266 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1267 maxvalue = self.sql(expression, "maxvalue") 1268 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1269 owned = self.sql(expression, "owned") 1270 owned = f" OWNED BY {owned}" if owned else "" 1271 1272 cache = expression.args.get("cache") 1273 if cache is None: 1274 cache_str = "" 1275 elif cache is True: 1276 cache_str = " CACHE" 1277 else: 1278 cache_str = f" CACHE {cache}" 1279 1280 options = self.expressions(expression, key="options", flat=True, sep=" ") 1281 options = f" {options}" if options else "" 1282 1283 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1284 1285 def clone_sql(self, expression: exp.Clone) -> str: 1286 this = self.sql(expression, "this") 1287 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1288 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1289 return f"{shallow}{keyword} {this}" 1290 1291 def describe_sql(self, expression: exp.Describe) -> str: 1292 style = expression.args.get("style") 1293 style = f" {style}" if style else "" 1294 partition = self.sql(expression, "partition") 1295 partition = f" {partition}" if partition else "" 1296 format = self.sql(expression, "format") 1297 format = f" {format}" if format else "" 1298 1299 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1300 1301 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1302 tag = self.sql(expression, "tag") 1303 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1304 1305 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1306 with_ = self.sql(expression, "with") 1307 if with_: 1308 sql = f"{with_}{self.sep()}{sql}" 1309 return sql 1310 1311 def with_sql(self, expression: exp.With) -> str: 1312 sql = self.expressions(expression, flat=True) 1313 recursive = ( 1314 "RECURSIVE " 1315 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1316 else "" 1317 ) 1318 search = self.sql(expression, "search") 1319 search = f" {search}" if search else "" 1320 1321 return f"WITH {recursive}{sql}{search}" 1322 1323 def cte_sql(self, expression: exp.CTE) -> str: 1324 alias = expression.args.get("alias") 1325 if alias: 1326 alias.add_comments(expression.pop_comments()) 1327 1328 alias_sql = self.sql(expression, "alias") 1329 1330 materialized = expression.args.get("materialized") 1331 if materialized is False: 1332 materialized = "NOT MATERIALIZED " 1333 elif materialized: 1334 materialized = "MATERIALIZED " 1335 1336 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1337 1338 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1339 alias = self.sql(expression, "this") 1340 columns = self.expressions(expression, key="columns", flat=True) 1341 columns = f"({columns})" if columns else "" 1342 1343 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1344 columns = "" 1345 self.unsupported("Named columns are not supported in table alias.") 1346 1347 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1348 alias = self._next_name() 1349 1350 return f"{alias}{columns}" 1351 1352 def bitstring_sql(self, expression: exp.BitString) -> str: 1353 this = self.sql(expression, "this") 1354 if self.dialect.BIT_START: 1355 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1356 return f"{int(this, 2)}" 1357 1358 def hexstring_sql( 1359 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1360 ) -> str: 1361 this = self.sql(expression, "this") 1362 is_integer_type = expression.args.get("is_integer") 1363 1364 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1365 not self.dialect.HEX_START and not binary_function_repr 1366 ): 1367 # Integer representation will be returned if: 1368 # - The read dialect treats the hex value as integer literal but not the write 1369 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1370 return f"{int(this, 16)}" 1371 1372 if not is_integer_type: 1373 # Read dialect treats the hex value as BINARY/BLOB 1374 if binary_function_repr: 1375 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1376 return self.func(binary_function_repr, exp.Literal.string(this)) 1377 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1378 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1379 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1380 1381 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1382 1383 def bytestring_sql(self, expression: exp.ByteString) -> str: 1384 this = self.sql(expression, "this") 1385 if self.dialect.BYTE_START: 1386 escaped_byte_string = self.escape_str( 1387 this, 1388 escape_backslash=False, 1389 delimiter=self.dialect.BYTE_END, 1390 escaped_delimiter=self._escaped_byte_quote_end, 1391 ) 1392 return f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1393 return this 1394 1395 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1396 this = self.sql(expression, "this") 1397 escape = expression.args.get("escape") 1398 1399 if self.dialect.UNICODE_START: 1400 escape_substitute = r"\\\1" 1401 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1402 else: 1403 escape_substitute = r"\\u\1" 1404 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1405 1406 if escape: 1407 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1408 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1409 else: 1410 escape_pattern = ESCAPED_UNICODE_RE 1411 escape_sql = "" 1412 1413 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1414 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1415 1416 return f"{left_quote}{this}{right_quote}{escape_sql}" 1417 1418 def rawstring_sql(self, expression: exp.RawString) -> str: 1419 string = expression.this 1420 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1421 string = string.replace("\\", "\\\\") 1422 1423 string = self.escape_str(string, escape_backslash=False) 1424 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1425 1426 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1427 this = self.sql(expression, "this") 1428 specifier = self.sql(expression, "expression") 1429 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1430 return f"{this}{specifier}" 1431 1432 def datatype_sql(self, expression: exp.DataType) -> str: 1433 nested = "" 1434 values = "" 1435 interior = self.expressions(expression, flat=True) 1436 1437 type_value = expression.this 1438 if type_value in self.UNSUPPORTED_TYPES: 1439 self.unsupported( 1440 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1441 ) 1442 1443 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1444 type_sql = self.sql(expression, "kind") 1445 else: 1446 type_sql = ( 1447 self.TYPE_MAPPING.get(type_value, type_value.value) 1448 if isinstance(type_value, exp.DataType.Type) 1449 else type_value 1450 ) 1451 1452 if interior: 1453 if expression.args.get("nested"): 1454 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1455 if expression.args.get("values") is not None: 1456 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1457 values = self.expressions(expression, key="values", flat=True) 1458 values = f"{delimiters[0]}{values}{delimiters[1]}" 1459 elif type_value == exp.DataType.Type.INTERVAL: 1460 nested = f" {interior}" 1461 else: 1462 nested = f"({interior})" 1463 1464 type_sql = f"{type_sql}{nested}{values}" 1465 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1466 exp.DataType.Type.TIMETZ, 1467 exp.DataType.Type.TIMESTAMPTZ, 1468 ): 1469 type_sql = f"{type_sql} WITH TIME ZONE" 1470 1471 return type_sql 1472 1473 def directory_sql(self, expression: exp.Directory) -> str: 1474 local = "LOCAL " if expression.args.get("local") else "" 1475 row_format = self.sql(expression, "row_format") 1476 row_format = f" {row_format}" if row_format else "" 1477 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1478 1479 def delete_sql(self, expression: exp.Delete) -> str: 1480 this = self.sql(expression, "this") 1481 this = f" FROM {this}" if this else "" 1482 using = self.sql(expression, "using") 1483 using = f" USING {using}" if using else "" 1484 cluster = self.sql(expression, "cluster") 1485 cluster = f" {cluster}" if cluster else "" 1486 where = self.sql(expression, "where") 1487 returning = self.sql(expression, "returning") 1488 limit = self.sql(expression, "limit") 1489 tables = self.expressions(expression, key="tables") 1490 tables = f" {tables}" if tables else "" 1491 if self.RETURNING_END: 1492 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1493 else: 1494 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1495 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1496 1497 def drop_sql(self, expression: exp.Drop) -> str: 1498 this = self.sql(expression, "this") 1499 expressions = self.expressions(expression, flat=True) 1500 expressions = f" ({expressions})" if expressions else "" 1501 kind = expression.args["kind"] 1502 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1503 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1504 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1505 on_cluster = self.sql(expression, "cluster") 1506 on_cluster = f" {on_cluster}" if on_cluster else "" 1507 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1508 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1509 cascade = " CASCADE" if expression.args.get("cascade") else "" 1510 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1511 purge = " PURGE" if expression.args.get("purge") else "" 1512 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1513 1514 def set_operation(self, expression: exp.SetOperation) -> str: 1515 op_type = type(expression) 1516 op_name = op_type.key.upper() 1517 1518 distinct = expression.args.get("distinct") 1519 if ( 1520 distinct is False 1521 and op_type in (exp.Except, exp.Intersect) 1522 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1523 ): 1524 self.unsupported(f"{op_name} ALL is not supported") 1525 1526 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1527 1528 if distinct is None: 1529 distinct = default_distinct 1530 if distinct is None: 1531 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1532 1533 if distinct is default_distinct: 1534 distinct_or_all = "" 1535 else: 1536 distinct_or_all = " DISTINCT" if distinct else " ALL" 1537 1538 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1539 side_kind = f"{side_kind} " if side_kind else "" 1540 1541 by_name = " BY NAME" if expression.args.get("by_name") else "" 1542 on = self.expressions(expression, key="on", flat=True) 1543 on = f" ON ({on})" if on else "" 1544 1545 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1546 1547 def set_operations(self, expression: exp.SetOperation) -> str: 1548 if not self.SET_OP_MODIFIERS: 1549 limit = expression.args.get("limit") 1550 order = expression.args.get("order") 1551 1552 if limit or order: 1553 select = self._move_ctes_to_top_level( 1554 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1555 ) 1556 1557 if limit: 1558 select = select.limit(limit.pop(), copy=False) 1559 if order: 1560 select = select.order_by(order.pop(), copy=False) 1561 return self.sql(select) 1562 1563 sqls: t.List[str] = [] 1564 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1565 1566 while stack: 1567 node = stack.pop() 1568 1569 if isinstance(node, exp.SetOperation): 1570 stack.append(node.expression) 1571 stack.append( 1572 self.maybe_comment( 1573 self.set_operation(node), comments=node.comments, separated=True 1574 ) 1575 ) 1576 stack.append(node.this) 1577 else: 1578 sqls.append(self.sql(node)) 1579 1580 this = self.sep().join(sqls) 1581 this = self.query_modifiers(expression, this) 1582 return self.prepend_ctes(expression, this) 1583 1584 def fetch_sql(self, expression: exp.Fetch) -> str: 1585 direction = expression.args.get("direction") 1586 direction = f" {direction}" if direction else "" 1587 count = self.sql(expression, "count") 1588 count = f" {count}" if count else "" 1589 limit_options = self.sql(expression, "limit_options") 1590 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1591 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1592 1593 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1594 percent = " PERCENT" if expression.args.get("percent") else "" 1595 rows = " ROWS" if expression.args.get("rows") else "" 1596 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1597 if not with_ties and rows: 1598 with_ties = " ONLY" 1599 return f"{percent}{rows}{with_ties}" 1600 1601 def filter_sql(self, expression: exp.Filter) -> str: 1602 if self.AGGREGATE_FILTER_SUPPORTED: 1603 this = self.sql(expression, "this") 1604 where = self.sql(expression, "expression").strip() 1605 return f"{this} FILTER({where})" 1606 1607 agg = expression.this 1608 agg_arg = agg.this 1609 cond = expression.expression.this 1610 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1611 return self.sql(agg) 1612 1613 def hint_sql(self, expression: exp.Hint) -> str: 1614 if not self.QUERY_HINTS: 1615 self.unsupported("Hints are not supported") 1616 return "" 1617 1618 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1619 1620 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1621 using = self.sql(expression, "using") 1622 using = f" USING {using}" if using else "" 1623 columns = self.expressions(expression, key="columns", flat=True) 1624 columns = f"({columns})" if columns else "" 1625 partition_by = self.expressions(expression, key="partition_by", flat=True) 1626 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1627 where = self.sql(expression, "where") 1628 include = self.expressions(expression, key="include", flat=True) 1629 if include: 1630 include = f" INCLUDE ({include})" 1631 with_storage = self.expressions(expression, key="with_storage", flat=True) 1632 with_storage = f" WITH ({with_storage})" if with_storage else "" 1633 tablespace = self.sql(expression, "tablespace") 1634 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1635 on = self.sql(expression, "on") 1636 on = f" ON {on}" if on else "" 1637 1638 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1639 1640 def index_sql(self, expression: exp.Index) -> str: 1641 unique = "UNIQUE " if expression.args.get("unique") else "" 1642 primary = "PRIMARY " if expression.args.get("primary") else "" 1643 amp = "AMP " if expression.args.get("amp") else "" 1644 name = self.sql(expression, "this") 1645 name = f"{name} " if name else "" 1646 table = self.sql(expression, "table") 1647 table = f"{self.INDEX_ON} {table}" if table else "" 1648 1649 index = "INDEX " if not table else "" 1650 1651 params = self.sql(expression, "params") 1652 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1653 1654 def identifier_sql(self, expression: exp.Identifier) -> str: 1655 text = expression.name 1656 lower = text.lower() 1657 text = lower if self.normalize and not expression.quoted else text 1658 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1659 if ( 1660 expression.quoted 1661 or self.dialect.can_identify(text, self.identify) 1662 or lower in self.RESERVED_KEYWORDS 1663 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1664 ): 1665 text = f"{self._identifier_start}{text}{self._identifier_end}" 1666 return text 1667 1668 def hex_sql(self, expression: exp.Hex) -> str: 1669 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1670 if self.dialect.HEX_LOWERCASE: 1671 text = self.func("LOWER", text) 1672 1673 return text 1674 1675 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1676 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1677 if not self.dialect.HEX_LOWERCASE: 1678 text = self.func("LOWER", text) 1679 return text 1680 1681 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1682 input_format = self.sql(expression, "input_format") 1683 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1684 output_format = self.sql(expression, "output_format") 1685 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1686 return self.sep().join((input_format, output_format)) 1687 1688 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1689 string = self.sql(exp.Literal.string(expression.name)) 1690 return f"{prefix}{string}" 1691 1692 def partition_sql(self, expression: exp.Partition) -> str: 1693 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1694 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1695 1696 def properties_sql(self, expression: exp.Properties) -> str: 1697 root_properties = [] 1698 with_properties = [] 1699 1700 for p in expression.expressions: 1701 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1702 if p_loc == exp.Properties.Location.POST_WITH: 1703 with_properties.append(p) 1704 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1705 root_properties.append(p) 1706 1707 root_props_ast = exp.Properties(expressions=root_properties) 1708 root_props_ast.parent = expression.parent 1709 1710 with_props_ast = exp.Properties(expressions=with_properties) 1711 with_props_ast.parent = expression.parent 1712 1713 root_props = self.root_properties(root_props_ast) 1714 with_props = self.with_properties(with_props_ast) 1715 1716 if root_props and with_props and not self.pretty: 1717 with_props = " " + with_props 1718 1719 return root_props + with_props 1720 1721 def root_properties(self, properties: exp.Properties) -> str: 1722 if properties.expressions: 1723 return self.expressions(properties, indent=False, sep=" ") 1724 return "" 1725 1726 def properties( 1727 self, 1728 properties: exp.Properties, 1729 prefix: str = "", 1730 sep: str = ", ", 1731 suffix: str = "", 1732 wrapped: bool = True, 1733 ) -> str: 1734 if properties.expressions: 1735 expressions = self.expressions(properties, sep=sep, indent=False) 1736 if expressions: 1737 expressions = self.wrap(expressions) if wrapped else expressions 1738 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1739 return "" 1740 1741 def with_properties(self, properties: exp.Properties) -> str: 1742 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1743 1744 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1745 properties_locs = defaultdict(list) 1746 for p in properties.expressions: 1747 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1748 if p_loc != exp.Properties.Location.UNSUPPORTED: 1749 properties_locs[p_loc].append(p) 1750 else: 1751 self.unsupported(f"Unsupported property {p.key}") 1752 1753 return properties_locs 1754 1755 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1756 if isinstance(expression.this, exp.Dot): 1757 return self.sql(expression, "this") 1758 return f"'{expression.name}'" if string_key else expression.name 1759 1760 def property_sql(self, expression: exp.Property) -> str: 1761 property_cls = expression.__class__ 1762 if property_cls == exp.Property: 1763 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1764 1765 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1766 if not property_name: 1767 self.unsupported(f"Unsupported property {expression.key}") 1768 1769 return f"{property_name}={self.sql(expression, 'this')}" 1770 1771 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1772 if self.SUPPORTS_CREATE_TABLE_LIKE: 1773 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1774 options = f" {options}" if options else "" 1775 1776 like = f"LIKE {self.sql(expression, 'this')}{options}" 1777 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1778 like = f"({like})" 1779 1780 return like 1781 1782 if expression.expressions: 1783 self.unsupported("Transpilation of LIKE property options is unsupported") 1784 1785 select = exp.select("*").from_(expression.this).limit(0) 1786 return f"AS {self.sql(select)}" 1787 1788 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1789 no = "NO " if expression.args.get("no") else "" 1790 protection = " PROTECTION" if expression.args.get("protection") else "" 1791 return f"{no}FALLBACK{protection}" 1792 1793 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1794 no = "NO " if expression.args.get("no") else "" 1795 local = expression.args.get("local") 1796 local = f"{local} " if local else "" 1797 dual = "DUAL " if expression.args.get("dual") else "" 1798 before = "BEFORE " if expression.args.get("before") else "" 1799 after = "AFTER " if expression.args.get("after") else "" 1800 return f"{no}{local}{dual}{before}{after}JOURNAL" 1801 1802 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1803 freespace = self.sql(expression, "this") 1804 percent = " PERCENT" if expression.args.get("percent") else "" 1805 return f"FREESPACE={freespace}{percent}" 1806 1807 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1808 if expression.args.get("default"): 1809 property = "DEFAULT" 1810 elif expression.args.get("on"): 1811 property = "ON" 1812 else: 1813 property = "OFF" 1814 return f"CHECKSUM={property}" 1815 1816 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1817 if expression.args.get("no"): 1818 return "NO MERGEBLOCKRATIO" 1819 if expression.args.get("default"): 1820 return "DEFAULT MERGEBLOCKRATIO" 1821 1822 percent = " PERCENT" if expression.args.get("percent") else "" 1823 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1824 1825 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1826 default = expression.args.get("default") 1827 minimum = expression.args.get("minimum") 1828 maximum = expression.args.get("maximum") 1829 if default or minimum or maximum: 1830 if default: 1831 prop = "DEFAULT" 1832 elif minimum: 1833 prop = "MINIMUM" 1834 else: 1835 prop = "MAXIMUM" 1836 return f"{prop} DATABLOCKSIZE" 1837 units = expression.args.get("units") 1838 units = f" {units}" if units else "" 1839 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1840 1841 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1842 autotemp = expression.args.get("autotemp") 1843 always = expression.args.get("always") 1844 default = expression.args.get("default") 1845 manual = expression.args.get("manual") 1846 never = expression.args.get("never") 1847 1848 if autotemp is not None: 1849 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1850 elif always: 1851 prop = "ALWAYS" 1852 elif default: 1853 prop = "DEFAULT" 1854 elif manual: 1855 prop = "MANUAL" 1856 elif never: 1857 prop = "NEVER" 1858 return f"BLOCKCOMPRESSION={prop}" 1859 1860 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1861 no = expression.args.get("no") 1862 no = " NO" if no else "" 1863 concurrent = expression.args.get("concurrent") 1864 concurrent = " CONCURRENT" if concurrent else "" 1865 target = self.sql(expression, "target") 1866 target = f" {target}" if target else "" 1867 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1868 1869 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1870 if isinstance(expression.this, list): 1871 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1872 if expression.this: 1873 modulus = self.sql(expression, "this") 1874 remainder = self.sql(expression, "expression") 1875 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1876 1877 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1878 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1879 return f"FROM ({from_expressions}) TO ({to_expressions})" 1880 1881 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1882 this = self.sql(expression, "this") 1883 1884 for_values_or_default = expression.expression 1885 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1886 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1887 else: 1888 for_values_or_default = " DEFAULT" 1889 1890 return f"PARTITION OF {this}{for_values_or_default}" 1891 1892 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1893 kind = expression.args.get("kind") 1894 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1895 for_or_in = expression.args.get("for_or_in") 1896 for_or_in = f" {for_or_in}" if for_or_in else "" 1897 lock_type = expression.args.get("lock_type") 1898 override = " OVERRIDE" if expression.args.get("override") else "" 1899 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1900 1901 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1902 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1903 statistics = expression.args.get("statistics") 1904 statistics_sql = "" 1905 if statistics is not None: 1906 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1907 return f"{data_sql}{statistics_sql}" 1908 1909 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1910 this = self.sql(expression, "this") 1911 this = f"HISTORY_TABLE={this}" if this else "" 1912 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1913 data_consistency = ( 1914 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1915 ) 1916 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1917 retention_period = ( 1918 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1919 ) 1920 1921 if this: 1922 on_sql = self.func("ON", this, data_consistency, retention_period) 1923 else: 1924 on_sql = "ON" if expression.args.get("on") else "OFF" 1925 1926 sql = f"SYSTEM_VERSIONING={on_sql}" 1927 1928 return f"WITH({sql})" if expression.args.get("with") else sql 1929 1930 def insert_sql(self, expression: exp.Insert) -> str: 1931 hint = self.sql(expression, "hint") 1932 overwrite = expression.args.get("overwrite") 1933 1934 if isinstance(expression.this, exp.Directory): 1935 this = " OVERWRITE" if overwrite else " INTO" 1936 else: 1937 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1938 1939 stored = self.sql(expression, "stored") 1940 stored = f" {stored}" if stored else "" 1941 alternative = expression.args.get("alternative") 1942 alternative = f" OR {alternative}" if alternative else "" 1943 ignore = " IGNORE" if expression.args.get("ignore") else "" 1944 is_function = expression.args.get("is_function") 1945 if is_function: 1946 this = f"{this} FUNCTION" 1947 this = f"{this} {self.sql(expression, 'this')}" 1948 1949 exists = " IF EXISTS" if expression.args.get("exists") else "" 1950 where = self.sql(expression, "where") 1951 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1952 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1953 on_conflict = self.sql(expression, "conflict") 1954 on_conflict = f" {on_conflict}" if on_conflict else "" 1955 by_name = " BY NAME" if expression.args.get("by_name") else "" 1956 returning = self.sql(expression, "returning") 1957 1958 if self.RETURNING_END: 1959 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1960 else: 1961 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1962 1963 partition_by = self.sql(expression, "partition") 1964 partition_by = f" {partition_by}" if partition_by else "" 1965 settings = self.sql(expression, "settings") 1966 settings = f" {settings}" if settings else "" 1967 1968 source = self.sql(expression, "source") 1969 source = f"TABLE {source}" if source else "" 1970 1971 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1972 return self.prepend_ctes(expression, sql) 1973 1974 def introducer_sql(self, expression: exp.Introducer) -> str: 1975 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1976 1977 def kill_sql(self, expression: exp.Kill) -> str: 1978 kind = self.sql(expression, "kind") 1979 kind = f" {kind}" if kind else "" 1980 this = self.sql(expression, "this") 1981 this = f" {this}" if this else "" 1982 return f"KILL{kind}{this}" 1983 1984 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1985 return expression.name 1986 1987 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1988 return expression.name 1989 1990 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1991 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1992 1993 constraint = self.sql(expression, "constraint") 1994 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1995 1996 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1997 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1998 action = self.sql(expression, "action") 1999 2000 expressions = self.expressions(expression, flat=True) 2001 if expressions: 2002 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2003 expressions = f" {set_keyword}{expressions}" 2004 2005 where = self.sql(expression, "where") 2006 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2007 2008 def returning_sql(self, expression: exp.Returning) -> str: 2009 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2010 2011 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2012 fields = self.sql(expression, "fields") 2013 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2014 escaped = self.sql(expression, "escaped") 2015 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2016 items = self.sql(expression, "collection_items") 2017 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2018 keys = self.sql(expression, "map_keys") 2019 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2020 lines = self.sql(expression, "lines") 2021 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2022 null = self.sql(expression, "null") 2023 null = f" NULL DEFINED AS {null}" if null else "" 2024 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2025 2026 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2027 return f"WITH ({self.expressions(expression, flat=True)})" 2028 2029 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2030 this = f"{self.sql(expression, 'this')} INDEX" 2031 target = self.sql(expression, "target") 2032 target = f" FOR {target}" if target else "" 2033 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2034 2035 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2036 this = self.sql(expression, "this") 2037 kind = self.sql(expression, "kind") 2038 expr = self.sql(expression, "expression") 2039 return f"{this} ({kind} => {expr})" 2040 2041 def table_parts(self, expression: exp.Table) -> str: 2042 return ".".join( 2043 self.sql(part) 2044 for part in ( 2045 expression.args.get("catalog"), 2046 expression.args.get("db"), 2047 expression.args.get("this"), 2048 ) 2049 if part is not None 2050 ) 2051 2052 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2053 table = self.table_parts(expression) 2054 only = "ONLY " if expression.args.get("only") else "" 2055 partition = self.sql(expression, "partition") 2056 partition = f" {partition}" if partition else "" 2057 version = self.sql(expression, "version") 2058 version = f" {version}" if version else "" 2059 alias = self.sql(expression, "alias") 2060 alias = f"{sep}{alias}" if alias else "" 2061 2062 sample = self.sql(expression, "sample") 2063 if self.dialect.ALIAS_POST_TABLESAMPLE: 2064 sample_pre_alias = sample 2065 sample_post_alias = "" 2066 else: 2067 sample_pre_alias = "" 2068 sample_post_alias = sample 2069 2070 hints = self.expressions(expression, key="hints", sep=" ") 2071 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2072 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2073 joins = self.indent( 2074 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2075 ) 2076 laterals = self.expressions(expression, key="laterals", sep="") 2077 2078 file_format = self.sql(expression, "format") 2079 if file_format: 2080 pattern = self.sql(expression, "pattern") 2081 pattern = f", PATTERN => {pattern}" if pattern else "" 2082 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2083 2084 ordinality = expression.args.get("ordinality") or "" 2085 if ordinality: 2086 ordinality = f" WITH ORDINALITY{alias}" 2087 alias = "" 2088 2089 when = self.sql(expression, "when") 2090 if when: 2091 table = f"{table} {when}" 2092 2093 changes = self.sql(expression, "changes") 2094 changes = f" {changes}" if changes else "" 2095 2096 rows_from = self.expressions(expression, key="rows_from") 2097 if rows_from: 2098 table = f"ROWS FROM {self.wrap(rows_from)}" 2099 2100 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2101 2102 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2103 table = self.func("TABLE", expression.this) 2104 alias = self.sql(expression, "alias") 2105 alias = f" AS {alias}" if alias else "" 2106 sample = self.sql(expression, "sample") 2107 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2108 joins = self.indent( 2109 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2110 ) 2111 return f"{table}{alias}{pivots}{sample}{joins}" 2112 2113 def tablesample_sql( 2114 self, 2115 expression: exp.TableSample, 2116 tablesample_keyword: t.Optional[str] = None, 2117 ) -> str: 2118 method = self.sql(expression, "method") 2119 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2120 numerator = self.sql(expression, "bucket_numerator") 2121 denominator = self.sql(expression, "bucket_denominator") 2122 field = self.sql(expression, "bucket_field") 2123 field = f" ON {field}" if field else "" 2124 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2125 seed = self.sql(expression, "seed") 2126 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2127 2128 size = self.sql(expression, "size") 2129 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2130 size = f"{size} ROWS" 2131 2132 percent = self.sql(expression, "percent") 2133 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2134 percent = f"{percent} PERCENT" 2135 2136 expr = f"{bucket}{percent}{size}" 2137 if self.TABLESAMPLE_REQUIRES_PARENS: 2138 expr = f"({expr})" 2139 2140 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2141 2142 def pivot_sql(self, expression: exp.Pivot) -> str: 2143 expressions = self.expressions(expression, flat=True) 2144 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2145 2146 group = self.sql(expression, "group") 2147 2148 if expression.this: 2149 this = self.sql(expression, "this") 2150 if not expressions: 2151 return f"UNPIVOT {this}" 2152 2153 on = f"{self.seg('ON')} {expressions}" 2154 into = self.sql(expression, "into") 2155 into = f"{self.seg('INTO')} {into}" if into else "" 2156 using = self.expressions(expression, key="using", flat=True) 2157 using = f"{self.seg('USING')} {using}" if using else "" 2158 return f"{direction} {this}{on}{into}{using}{group}" 2159 2160 alias = self.sql(expression, "alias") 2161 alias = f" AS {alias}" if alias else "" 2162 2163 fields = self.expressions( 2164 expression, 2165 "fields", 2166 sep=" ", 2167 dynamic=True, 2168 new_line=True, 2169 skip_first=True, 2170 skip_last=True, 2171 ) 2172 2173 include_nulls = expression.args.get("include_nulls") 2174 if include_nulls is not None: 2175 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2176 else: 2177 nulls = "" 2178 2179 default_on_null = self.sql(expression, "default_on_null") 2180 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2181 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2182 2183 def version_sql(self, expression: exp.Version) -> str: 2184 this = f"FOR {expression.name}" 2185 kind = expression.text("kind") 2186 expr = self.sql(expression, "expression") 2187 return f"{this} {kind} {expr}" 2188 2189 def tuple_sql(self, expression: exp.Tuple) -> str: 2190 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2191 2192 def update_sql(self, expression: exp.Update) -> str: 2193 this = self.sql(expression, "this") 2194 set_sql = self.expressions(expression, flat=True) 2195 from_sql = self.sql(expression, "from") 2196 where_sql = self.sql(expression, "where") 2197 returning = self.sql(expression, "returning") 2198 order = self.sql(expression, "order") 2199 limit = self.sql(expression, "limit") 2200 if self.RETURNING_END: 2201 expression_sql = f"{from_sql}{where_sql}{returning}" 2202 else: 2203 expression_sql = f"{returning}{from_sql}{where_sql}" 2204 options = self.expressions(expression, key="options") 2205 options = f" OPTION({options})" if options else "" 2206 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}{options}" 2207 return self.prepend_ctes(expression, sql) 2208 2209 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2210 values_as_table = values_as_table and self.VALUES_AS_TABLE 2211 2212 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2213 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2214 args = self.expressions(expression) 2215 alias = self.sql(expression, "alias") 2216 values = f"VALUES{self.seg('')}{args}" 2217 values = ( 2218 f"({values})" 2219 if self.WRAP_DERIVED_VALUES 2220 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2221 else values 2222 ) 2223 return f"{values} AS {alias}" if alias else values 2224 2225 # Converts `VALUES...` expression into a series of select unions. 2226 alias_node = expression.args.get("alias") 2227 column_names = alias_node and alias_node.columns 2228 2229 selects: t.List[exp.Query] = [] 2230 2231 for i, tup in enumerate(expression.expressions): 2232 row = tup.expressions 2233 2234 if i == 0 and column_names: 2235 row = [ 2236 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2237 ] 2238 2239 selects.append(exp.Select(expressions=row)) 2240 2241 if self.pretty: 2242 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2243 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2244 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2245 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2246 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2247 2248 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2249 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2250 return f"({unions}){alias}" 2251 2252 def var_sql(self, expression: exp.Var) -> str: 2253 return self.sql(expression, "this") 2254 2255 @unsupported_args("expressions") 2256 def into_sql(self, expression: exp.Into) -> str: 2257 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2258 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2259 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2260 2261 def from_sql(self, expression: exp.From) -> str: 2262 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2263 2264 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2265 grouping_sets = self.expressions(expression, indent=False) 2266 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2267 2268 def rollup_sql(self, expression: exp.Rollup) -> str: 2269 expressions = self.expressions(expression, indent=False) 2270 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2271 2272 def cube_sql(self, expression: exp.Cube) -> str: 2273 expressions = self.expressions(expression, indent=False) 2274 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2275 2276 def group_sql(self, expression: exp.Group) -> str: 2277 group_by_all = expression.args.get("all") 2278 if group_by_all is True: 2279 modifier = " ALL" 2280 elif group_by_all is False: 2281 modifier = " DISTINCT" 2282 else: 2283 modifier = "" 2284 2285 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2286 2287 grouping_sets = self.expressions(expression, key="grouping_sets") 2288 cube = self.expressions(expression, key="cube") 2289 rollup = self.expressions(expression, key="rollup") 2290 2291 groupings = csv( 2292 self.seg(grouping_sets) if grouping_sets else "", 2293 self.seg(cube) if cube else "", 2294 self.seg(rollup) if rollup else "", 2295 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2296 sep=self.GROUPINGS_SEP, 2297 ) 2298 2299 if ( 2300 expression.expressions 2301 and groupings 2302 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2303 ): 2304 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2305 2306 return f"{group_by}{groupings}" 2307 2308 def having_sql(self, expression: exp.Having) -> str: 2309 this = self.indent(self.sql(expression, "this")) 2310 return f"{self.seg('HAVING')}{self.sep()}{this}" 2311 2312 def connect_sql(self, expression: exp.Connect) -> str: 2313 start = self.sql(expression, "start") 2314 start = self.seg(f"START WITH {start}") if start else "" 2315 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2316 connect = self.sql(expression, "connect") 2317 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2318 return start + connect 2319 2320 def prior_sql(self, expression: exp.Prior) -> str: 2321 return f"PRIOR {self.sql(expression, 'this')}" 2322 2323 def join_sql(self, expression: exp.Join) -> str: 2324 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2325 side = None 2326 else: 2327 side = expression.side 2328 2329 op_sql = " ".join( 2330 op 2331 for op in ( 2332 expression.method, 2333 "GLOBAL" if expression.args.get("global") else None, 2334 side, 2335 expression.kind, 2336 expression.hint if self.JOIN_HINTS else None, 2337 ) 2338 if op 2339 ) 2340 match_cond = self.sql(expression, "match_condition") 2341 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2342 on_sql = self.sql(expression, "on") 2343 using = expression.args.get("using") 2344 2345 if not on_sql and using: 2346 on_sql = csv(*(self.sql(column) for column in using)) 2347 2348 this = expression.this 2349 this_sql = self.sql(this) 2350 2351 exprs = self.expressions(expression) 2352 if exprs: 2353 this_sql = f"{this_sql},{self.seg(exprs)}" 2354 2355 if on_sql: 2356 on_sql = self.indent(on_sql, skip_first=True) 2357 space = self.seg(" " * self.pad) if self.pretty else " " 2358 if using: 2359 on_sql = f"{space}USING ({on_sql})" 2360 else: 2361 on_sql = f"{space}ON {on_sql}" 2362 elif not op_sql: 2363 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2364 return f" {this_sql}" 2365 2366 return f", {this_sql}" 2367 2368 if op_sql != "STRAIGHT_JOIN": 2369 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2370 2371 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2372 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2373 2374 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2375 args = self.expressions(expression, flat=True) 2376 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2377 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2378 2379 def lateral_op(self, expression: exp.Lateral) -> str: 2380 cross_apply = expression.args.get("cross_apply") 2381 2382 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2383 if cross_apply is True: 2384 op = "INNER JOIN " 2385 elif cross_apply is False: 2386 op = "LEFT JOIN " 2387 else: 2388 op = "" 2389 2390 return f"{op}LATERAL" 2391 2392 def lateral_sql(self, expression: exp.Lateral) -> str: 2393 this = self.sql(expression, "this") 2394 2395 if expression.args.get("view"): 2396 alias = expression.args["alias"] 2397 columns = self.expressions(alias, key="columns", flat=True) 2398 table = f" {alias.name}" if alias.name else "" 2399 columns = f" AS {columns}" if columns else "" 2400 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2401 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2402 2403 alias = self.sql(expression, "alias") 2404 alias = f" AS {alias}" if alias else "" 2405 2406 ordinality = expression.args.get("ordinality") or "" 2407 if ordinality: 2408 ordinality = f" WITH ORDINALITY{alias}" 2409 alias = "" 2410 2411 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2412 2413 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2414 this = self.sql(expression, "this") 2415 2416 args = [ 2417 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2418 for e in (expression.args.get(k) for k in ("offset", "expression")) 2419 if e 2420 ] 2421 2422 args_sql = ", ".join(self.sql(e) for e in args) 2423 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2424 expressions = self.expressions(expression, flat=True) 2425 limit_options = self.sql(expression, "limit_options") 2426 expressions = f" BY {expressions}" if expressions else "" 2427 2428 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2429 2430 def offset_sql(self, expression: exp.Offset) -> str: 2431 this = self.sql(expression, "this") 2432 value = expression.expression 2433 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2434 expressions = self.expressions(expression, flat=True) 2435 expressions = f" BY {expressions}" if expressions else "" 2436 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2437 2438 def setitem_sql(self, expression: exp.SetItem) -> str: 2439 kind = self.sql(expression, "kind") 2440 kind = f"{kind} " if kind else "" 2441 this = self.sql(expression, "this") 2442 expressions = self.expressions(expression) 2443 collate = self.sql(expression, "collate") 2444 collate = f" COLLATE {collate}" if collate else "" 2445 global_ = "GLOBAL " if expression.args.get("global") else "" 2446 return f"{global_}{kind}{this}{expressions}{collate}" 2447 2448 def set_sql(self, expression: exp.Set) -> str: 2449 expressions = f" {self.expressions(expression, flat=True)}" 2450 tag = " TAG" if expression.args.get("tag") else "" 2451 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2452 2453 def queryband_sql(self, expression: exp.QueryBand) -> str: 2454 this = self.sql(expression, "this") 2455 update = " UPDATE" if expression.args.get("update") else "" 2456 scope = self.sql(expression, "scope") 2457 scope = f" FOR {scope}" if scope else "" 2458 2459 return f"QUERY_BAND = {this}{update}{scope}" 2460 2461 def pragma_sql(self, expression: exp.Pragma) -> str: 2462 return f"PRAGMA {self.sql(expression, 'this')}" 2463 2464 def lock_sql(self, expression: exp.Lock) -> str: 2465 if not self.LOCKING_READS_SUPPORTED: 2466 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2467 return "" 2468 2469 update = expression.args["update"] 2470 key = expression.args.get("key") 2471 if update: 2472 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2473 else: 2474 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2475 expressions = self.expressions(expression, flat=True) 2476 expressions = f" OF {expressions}" if expressions else "" 2477 wait = expression.args.get("wait") 2478 2479 if wait is not None: 2480 if isinstance(wait, exp.Literal): 2481 wait = f" WAIT {self.sql(wait)}" 2482 else: 2483 wait = " NOWAIT" if wait else " SKIP LOCKED" 2484 2485 return f"{lock_type}{expressions}{wait or ''}" 2486 2487 def literal_sql(self, expression: exp.Literal) -> str: 2488 text = expression.this or "" 2489 if expression.is_string: 2490 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2491 return text 2492 2493 def escape_str( 2494 self, 2495 text: str, 2496 escape_backslash: bool = True, 2497 delimiter: t.Optional[str] = None, 2498 escaped_delimiter: t.Optional[str] = None, 2499 ) -> str: 2500 if self.dialect.ESCAPED_SEQUENCES: 2501 to_escaped = self.dialect.ESCAPED_SEQUENCES 2502 text = "".join( 2503 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2504 ) 2505 2506 delimiter = delimiter or self.dialect.QUOTE_END 2507 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2508 2509 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2510 2511 def loaddata_sql(self, expression: exp.LoadData) -> str: 2512 local = " LOCAL" if expression.args.get("local") else "" 2513 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2514 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2515 this = f" INTO TABLE {self.sql(expression, 'this')}" 2516 partition = self.sql(expression, "partition") 2517 partition = f" {partition}" if partition else "" 2518 input_format = self.sql(expression, "input_format") 2519 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2520 serde = self.sql(expression, "serde") 2521 serde = f" SERDE {serde}" if serde else "" 2522 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2523 2524 def null_sql(self, *_) -> str: 2525 return "NULL" 2526 2527 def boolean_sql(self, expression: exp.Boolean) -> str: 2528 return "TRUE" if expression.this else "FALSE" 2529 2530 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2531 this = self.sql(expression, "this") 2532 this = f"{this} " if this else this 2533 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2534 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2535 2536 def withfill_sql(self, expression: exp.WithFill) -> str: 2537 from_sql = self.sql(expression, "from") 2538 from_sql = f" FROM {from_sql}" if from_sql else "" 2539 to_sql = self.sql(expression, "to") 2540 to_sql = f" TO {to_sql}" if to_sql else "" 2541 step_sql = self.sql(expression, "step") 2542 step_sql = f" STEP {step_sql}" if step_sql else "" 2543 interpolated_values = [ 2544 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2545 if isinstance(e, exp.Alias) 2546 else self.sql(e, "this") 2547 for e in expression.args.get("interpolate") or [] 2548 ] 2549 interpolate = ( 2550 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2551 ) 2552 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2553 2554 def cluster_sql(self, expression: exp.Cluster) -> str: 2555 return self.op_expressions("CLUSTER BY", expression) 2556 2557 def distribute_sql(self, expression: exp.Distribute) -> str: 2558 return self.op_expressions("DISTRIBUTE BY", expression) 2559 2560 def sort_sql(self, expression: exp.Sort) -> str: 2561 return self.op_expressions("SORT BY", expression) 2562 2563 def ordered_sql(self, expression: exp.Ordered) -> str: 2564 desc = expression.args.get("desc") 2565 asc = not desc 2566 2567 nulls_first = expression.args.get("nulls_first") 2568 nulls_last = not nulls_first 2569 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2570 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2571 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2572 2573 this = self.sql(expression, "this") 2574 2575 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2576 nulls_sort_change = "" 2577 if nulls_first and ( 2578 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2579 ): 2580 nulls_sort_change = " NULLS FIRST" 2581 elif ( 2582 nulls_last 2583 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2584 and not nulls_are_last 2585 ): 2586 nulls_sort_change = " NULLS LAST" 2587 2588 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2589 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2590 window = expression.find_ancestor(exp.Window, exp.Select) 2591 if isinstance(window, exp.Window) and window.args.get("spec"): 2592 self.unsupported( 2593 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2594 ) 2595 nulls_sort_change = "" 2596 elif self.NULL_ORDERING_SUPPORTED is False and ( 2597 (asc and nulls_sort_change == " NULLS LAST") 2598 or (desc and nulls_sort_change == " NULLS FIRST") 2599 ): 2600 # BigQuery does not allow these ordering/nulls combinations when used under 2601 # an aggregation func or under a window containing one 2602 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2603 2604 if isinstance(ancestor, exp.Window): 2605 ancestor = ancestor.this 2606 if isinstance(ancestor, exp.AggFunc): 2607 self.unsupported( 2608 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2609 ) 2610 nulls_sort_change = "" 2611 elif self.NULL_ORDERING_SUPPORTED is None: 2612 if expression.this.is_int: 2613 self.unsupported( 2614 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2615 ) 2616 elif not isinstance(expression.this, exp.Rand): 2617 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2618 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2619 nulls_sort_change = "" 2620 2621 with_fill = self.sql(expression, "with_fill") 2622 with_fill = f" {with_fill}" if with_fill else "" 2623 2624 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2625 2626 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2627 window_frame = self.sql(expression, "window_frame") 2628 window_frame = f"{window_frame} " if window_frame else "" 2629 2630 this = self.sql(expression, "this") 2631 2632 return f"{window_frame}{this}" 2633 2634 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2635 partition = self.partition_by_sql(expression) 2636 order = self.sql(expression, "order") 2637 measures = self.expressions(expression, key="measures") 2638 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2639 rows = self.sql(expression, "rows") 2640 rows = self.seg(rows) if rows else "" 2641 after = self.sql(expression, "after") 2642 after = self.seg(after) if after else "" 2643 pattern = self.sql(expression, "pattern") 2644 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2645 definition_sqls = [ 2646 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2647 for definition in expression.args.get("define", []) 2648 ] 2649 definitions = self.expressions(sqls=definition_sqls) 2650 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2651 body = "".join( 2652 ( 2653 partition, 2654 order, 2655 measures, 2656 rows, 2657 after, 2658 pattern, 2659 define, 2660 ) 2661 ) 2662 alias = self.sql(expression, "alias") 2663 alias = f" {alias}" if alias else "" 2664 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2665 2666 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2667 limit = expression.args.get("limit") 2668 2669 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2670 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2671 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2672 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2673 2674 return csv( 2675 *sqls, 2676 *[self.sql(join) for join in expression.args.get("joins") or []], 2677 self.sql(expression, "match"), 2678 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2679 self.sql(expression, "prewhere"), 2680 self.sql(expression, "where"), 2681 self.sql(expression, "connect"), 2682 self.sql(expression, "group"), 2683 self.sql(expression, "having"), 2684 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2685 self.sql(expression, "order"), 2686 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2687 *self.after_limit_modifiers(expression), 2688 self.options_modifier(expression), 2689 self.for_modifiers(expression), 2690 sep="", 2691 ) 2692 2693 def options_modifier(self, expression: exp.Expression) -> str: 2694 options = self.expressions(expression, key="options") 2695 return f" {options}" if options else "" 2696 2697 def for_modifiers(self, expression: exp.Expression) -> str: 2698 for_modifiers = self.expressions(expression, key="for") 2699 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2700 2701 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2702 self.unsupported("Unsupported query option.") 2703 return "" 2704 2705 def offset_limit_modifiers( 2706 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2707 ) -> t.List[str]: 2708 return [ 2709 self.sql(expression, "offset") if fetch else self.sql(limit), 2710 self.sql(limit) if fetch else self.sql(expression, "offset"), 2711 ] 2712 2713 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2714 locks = self.expressions(expression, key="locks", sep=" ") 2715 locks = f" {locks}" if locks else "" 2716 return [locks, self.sql(expression, "sample")] 2717 2718 def select_sql(self, expression: exp.Select) -> str: 2719 into = expression.args.get("into") 2720 if not self.SUPPORTS_SELECT_INTO and into: 2721 into.pop() 2722 2723 hint = self.sql(expression, "hint") 2724 distinct = self.sql(expression, "distinct") 2725 distinct = f" {distinct}" if distinct else "" 2726 kind = self.sql(expression, "kind") 2727 2728 limit = expression.args.get("limit") 2729 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2730 top = self.limit_sql(limit, top=True) 2731 limit.pop() 2732 else: 2733 top = "" 2734 2735 expressions = self.expressions(expression) 2736 2737 if kind: 2738 if kind in self.SELECT_KINDS: 2739 kind = f" AS {kind}" 2740 else: 2741 if kind == "STRUCT": 2742 expressions = self.expressions( 2743 sqls=[ 2744 self.sql( 2745 exp.Struct( 2746 expressions=[ 2747 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2748 if isinstance(e, exp.Alias) 2749 else e 2750 for e in expression.expressions 2751 ] 2752 ) 2753 ) 2754 ] 2755 ) 2756 kind = "" 2757 2758 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2759 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2760 2761 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2762 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2763 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2764 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2765 sql = self.query_modifiers( 2766 expression, 2767 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2768 self.sql(expression, "into", comment=False), 2769 self.sql(expression, "from", comment=False), 2770 ) 2771 2772 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2773 if expression.args.get("with"): 2774 sql = self.maybe_comment(sql, expression) 2775 expression.pop_comments() 2776 2777 sql = self.prepend_ctes(expression, sql) 2778 2779 if not self.SUPPORTS_SELECT_INTO and into: 2780 if into.args.get("temporary"): 2781 table_kind = " TEMPORARY" 2782 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2783 table_kind = " UNLOGGED" 2784 else: 2785 table_kind = "" 2786 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2787 2788 return sql 2789 2790 def schema_sql(self, expression: exp.Schema) -> str: 2791 this = self.sql(expression, "this") 2792 sql = self.schema_columns_sql(expression) 2793 return f"{this} {sql}" if this and sql else this or sql 2794 2795 def schema_columns_sql(self, expression: exp.Schema) -> str: 2796 if expression.expressions: 2797 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2798 return "" 2799 2800 def star_sql(self, expression: exp.Star) -> str: 2801 except_ = self.expressions(expression, key="except", flat=True) 2802 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2803 replace = self.expressions(expression, key="replace", flat=True) 2804 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2805 rename = self.expressions(expression, key="rename", flat=True) 2806 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2807 return f"*{except_}{replace}{rename}" 2808 2809 def parameter_sql(self, expression: exp.Parameter) -> str: 2810 this = self.sql(expression, "this") 2811 return f"{self.PARAMETER_TOKEN}{this}" 2812 2813 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2814 this = self.sql(expression, "this") 2815 kind = expression.text("kind") 2816 if kind: 2817 kind = f"{kind}." 2818 return f"@@{kind}{this}" 2819 2820 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2821 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2822 2823 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2824 alias = self.sql(expression, "alias") 2825 alias = f"{sep}{alias}" if alias else "" 2826 sample = self.sql(expression, "sample") 2827 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2828 alias = f"{sample}{alias}" 2829 2830 # Set to None so it's not generated again by self.query_modifiers() 2831 expression.set("sample", None) 2832 2833 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2834 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2835 return self.prepend_ctes(expression, sql) 2836 2837 def qualify_sql(self, expression: exp.Qualify) -> str: 2838 this = self.indent(self.sql(expression, "this")) 2839 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2840 2841 def unnest_sql(self, expression: exp.Unnest) -> str: 2842 args = self.expressions(expression, flat=True) 2843 2844 alias = expression.args.get("alias") 2845 offset = expression.args.get("offset") 2846 2847 if self.UNNEST_WITH_ORDINALITY: 2848 if alias and isinstance(offset, exp.Expression): 2849 alias.append("columns", offset) 2850 2851 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2852 columns = alias.columns 2853 alias = self.sql(columns[0]) if columns else "" 2854 else: 2855 alias = self.sql(alias) 2856 2857 alias = f" AS {alias}" if alias else alias 2858 if self.UNNEST_WITH_ORDINALITY: 2859 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2860 else: 2861 if isinstance(offset, exp.Expression): 2862 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2863 elif offset: 2864 suffix = f"{alias} WITH OFFSET" 2865 else: 2866 suffix = alias 2867 2868 return f"UNNEST({args}){suffix}" 2869 2870 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2871 return "" 2872 2873 def where_sql(self, expression: exp.Where) -> str: 2874 this = self.indent(self.sql(expression, "this")) 2875 return f"{self.seg('WHERE')}{self.sep()}{this}" 2876 2877 def window_sql(self, expression: exp.Window) -> str: 2878 this = self.sql(expression, "this") 2879 partition = self.partition_by_sql(expression) 2880 order = expression.args.get("order") 2881 order = self.order_sql(order, flat=True) if order else "" 2882 spec = self.sql(expression, "spec") 2883 alias = self.sql(expression, "alias") 2884 over = self.sql(expression, "over") or "OVER" 2885 2886 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2887 2888 first = expression.args.get("first") 2889 if first is None: 2890 first = "" 2891 else: 2892 first = "FIRST" if first else "LAST" 2893 2894 if not partition and not order and not spec and alias: 2895 return f"{this} {alias}" 2896 2897 args = self.format_args( 2898 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2899 ) 2900 return f"{this} ({args})" 2901 2902 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2903 partition = self.expressions(expression, key="partition_by", flat=True) 2904 return f"PARTITION BY {partition}" if partition else "" 2905 2906 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2907 kind = self.sql(expression, "kind") 2908 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2909 end = ( 2910 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2911 or "CURRENT ROW" 2912 ) 2913 2914 window_spec = f"{kind} BETWEEN {start} AND {end}" 2915 2916 exclude = self.sql(expression, "exclude") 2917 if exclude: 2918 if self.SUPPORTS_WINDOW_EXCLUDE: 2919 window_spec += f" EXCLUDE {exclude}" 2920 else: 2921 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2922 2923 return window_spec 2924 2925 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2926 this = self.sql(expression, "this") 2927 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2928 return f"{this} WITHIN GROUP ({expression_sql})" 2929 2930 def between_sql(self, expression: exp.Between) -> str: 2931 this = self.sql(expression, "this") 2932 low = self.sql(expression, "low") 2933 high = self.sql(expression, "high") 2934 symmetric = expression.args.get("symmetric") 2935 2936 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2937 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2938 2939 flag = ( 2940 " SYMMETRIC" 2941 if symmetric 2942 else " ASYMMETRIC" 2943 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2944 else "" # silently drop ASYMMETRIC – semantics identical 2945 ) 2946 return f"{this} BETWEEN{flag} {low} AND {high}" 2947 2948 def bracket_offset_expressions( 2949 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2950 ) -> t.List[exp.Expression]: 2951 return apply_index_offset( 2952 expression.this, 2953 expression.expressions, 2954 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2955 dialect=self.dialect, 2956 ) 2957 2958 def bracket_sql(self, expression: exp.Bracket) -> str: 2959 expressions = self.bracket_offset_expressions(expression) 2960 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2961 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2962 2963 def all_sql(self, expression: exp.All) -> str: 2964 this = self.sql(expression, "this") 2965 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2966 this = self.wrap(this) 2967 return f"ALL {this}" 2968 2969 def any_sql(self, expression: exp.Any) -> str: 2970 this = self.sql(expression, "this") 2971 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2972 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2973 this = self.wrap(this) 2974 return f"ANY{this}" 2975 return f"ANY {this}" 2976 2977 def exists_sql(self, expression: exp.Exists) -> str: 2978 return f"EXISTS{self.wrap(expression)}" 2979 2980 def case_sql(self, expression: exp.Case) -> str: 2981 this = self.sql(expression, "this") 2982 statements = [f"CASE {this}" if this else "CASE"] 2983 2984 for e in expression.args["ifs"]: 2985 statements.append(f"WHEN {self.sql(e, 'this')}") 2986 statements.append(f"THEN {self.sql(e, 'true')}") 2987 2988 default = self.sql(expression, "default") 2989 2990 if default: 2991 statements.append(f"ELSE {default}") 2992 2993 statements.append("END") 2994 2995 if self.pretty and self.too_wide(statements): 2996 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2997 2998 return " ".join(statements) 2999 3000 def constraint_sql(self, expression: exp.Constraint) -> str: 3001 this = self.sql(expression, "this") 3002 expressions = self.expressions(expression, flat=True) 3003 return f"CONSTRAINT {this} {expressions}" 3004 3005 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3006 order = expression.args.get("order") 3007 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3008 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3009 3010 def extract_sql(self, expression: exp.Extract) -> str: 3011 from sqlglot.dialects.dialect import map_date_part 3012 3013 this = ( 3014 map_date_part(expression.this, self.dialect) 3015 if self.NORMALIZE_EXTRACT_DATE_PARTS 3016 else expression.this 3017 ) 3018 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3019 expression_sql = self.sql(expression, "expression") 3020 3021 return f"EXTRACT({this_sql} FROM {expression_sql})" 3022 3023 def trim_sql(self, expression: exp.Trim) -> str: 3024 trim_type = self.sql(expression, "position") 3025 3026 if trim_type == "LEADING": 3027 func_name = "LTRIM" 3028 elif trim_type == "TRAILING": 3029 func_name = "RTRIM" 3030 else: 3031 func_name = "TRIM" 3032 3033 return self.func(func_name, expression.this, expression.expression) 3034 3035 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3036 args = expression.expressions 3037 if isinstance(expression, exp.ConcatWs): 3038 args = args[1:] # Skip the delimiter 3039 3040 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3041 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3042 3043 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3044 3045 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3046 if not e.type: 3047 from sqlglot.optimizer.annotate_types import annotate_types 3048 3049 e = annotate_types(e, dialect=self.dialect) 3050 3051 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3052 return e 3053 3054 return exp.func("coalesce", e, exp.Literal.string("")) 3055 3056 args = [_wrap_with_coalesce(e) for e in args] 3057 3058 return args 3059 3060 def concat_sql(self, expression: exp.Concat) -> str: 3061 expressions = self.convert_concat_args(expression) 3062 3063 # Some dialects don't allow a single-argument CONCAT call 3064 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3065 return self.sql(expressions[0]) 3066 3067 return self.func("CONCAT", *expressions) 3068 3069 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3070 return self.func( 3071 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3072 ) 3073 3074 def check_sql(self, expression: exp.Check) -> str: 3075 this = self.sql(expression, key="this") 3076 return f"CHECK ({this})" 3077 3078 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3079 expressions = self.expressions(expression, flat=True) 3080 expressions = f" ({expressions})" if expressions else "" 3081 reference = self.sql(expression, "reference") 3082 reference = f" {reference}" if reference else "" 3083 delete = self.sql(expression, "delete") 3084 delete = f" ON DELETE {delete}" if delete else "" 3085 update = self.sql(expression, "update") 3086 update = f" ON UPDATE {update}" if update else "" 3087 options = self.expressions(expression, key="options", flat=True, sep=" ") 3088 options = f" {options}" if options else "" 3089 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3090 3091 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3092 expressions = self.expressions(expression, flat=True) 3093 include = self.sql(expression, "include") 3094 options = self.expressions(expression, key="options", flat=True, sep=" ") 3095 options = f" {options}" if options else "" 3096 return f"PRIMARY KEY ({expressions}){include}{options}" 3097 3098 def if_sql(self, expression: exp.If) -> str: 3099 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3100 3101 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3102 if self.MATCH_AGAINST_TABLE_PREFIX: 3103 expressions = [] 3104 for expr in expression.expressions: 3105 if isinstance(expr, exp.Table): 3106 expressions.append(f"TABLE {self.sql(expr)}") 3107 else: 3108 expressions.append(expr) 3109 else: 3110 expressions = expression.expressions 3111 3112 modifier = expression.args.get("modifier") 3113 modifier = f" {modifier}" if modifier else "" 3114 return ( 3115 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3116 ) 3117 3118 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3119 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3120 3121 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3122 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3123 3124 if expression.args.get("escape"): 3125 path = self.escape_str(path) 3126 3127 if self.QUOTE_JSON_PATH: 3128 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3129 3130 return path 3131 3132 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3133 if isinstance(expression, exp.JSONPathPart): 3134 transform = self.TRANSFORMS.get(expression.__class__) 3135 if not callable(transform): 3136 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3137 return "" 3138 3139 return transform(self, expression) 3140 3141 if isinstance(expression, int): 3142 return str(expression) 3143 3144 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3145 escaped = expression.replace("'", "\\'") 3146 escaped = f"\\'{expression}\\'" 3147 else: 3148 escaped = expression.replace('"', '\\"') 3149 escaped = f'"{escaped}"' 3150 3151 return escaped 3152 3153 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3154 return f"{self.sql(expression, 'this')} FORMAT JSON" 3155 3156 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3157 # Output the Teradata column FORMAT override. 3158 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3159 this = self.sql(expression, "this") 3160 fmt = self.sql(expression, "format") 3161 return f"{this} (FORMAT {fmt})" 3162 3163 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3164 null_handling = expression.args.get("null_handling") 3165 null_handling = f" {null_handling}" if null_handling else "" 3166 3167 unique_keys = expression.args.get("unique_keys") 3168 if unique_keys is not None: 3169 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3170 else: 3171 unique_keys = "" 3172 3173 return_type = self.sql(expression, "return_type") 3174 return_type = f" RETURNING {return_type}" if return_type else "" 3175 encoding = self.sql(expression, "encoding") 3176 encoding = f" ENCODING {encoding}" if encoding else "" 3177 3178 return self.func( 3179 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3180 *expression.expressions, 3181 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3182 ) 3183 3184 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3185 return self.jsonobject_sql(expression) 3186 3187 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3188 null_handling = expression.args.get("null_handling") 3189 null_handling = f" {null_handling}" if null_handling else "" 3190 return_type = self.sql(expression, "return_type") 3191 return_type = f" RETURNING {return_type}" if return_type else "" 3192 strict = " STRICT" if expression.args.get("strict") else "" 3193 return self.func( 3194 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3195 ) 3196 3197 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3198 this = self.sql(expression, "this") 3199 order = self.sql(expression, "order") 3200 null_handling = expression.args.get("null_handling") 3201 null_handling = f" {null_handling}" if null_handling else "" 3202 return_type = self.sql(expression, "return_type") 3203 return_type = f" RETURNING {return_type}" if return_type else "" 3204 strict = " STRICT" if expression.args.get("strict") else "" 3205 return self.func( 3206 "JSON_ARRAYAGG", 3207 this, 3208 suffix=f"{order}{null_handling}{return_type}{strict})", 3209 ) 3210 3211 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3212 path = self.sql(expression, "path") 3213 path = f" PATH {path}" if path else "" 3214 nested_schema = self.sql(expression, "nested_schema") 3215 3216 if nested_schema: 3217 return f"NESTED{path} {nested_schema}" 3218 3219 this = self.sql(expression, "this") 3220 kind = self.sql(expression, "kind") 3221 kind = f" {kind}" if kind else "" 3222 3223 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3224 return f"{this}{kind}{path}{ordinality}" 3225 3226 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3227 return self.func("COLUMNS", *expression.expressions) 3228 3229 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3230 this = self.sql(expression, "this") 3231 path = self.sql(expression, "path") 3232 path = f", {path}" if path else "" 3233 error_handling = expression.args.get("error_handling") 3234 error_handling = f" {error_handling}" if error_handling else "" 3235 empty_handling = expression.args.get("empty_handling") 3236 empty_handling = f" {empty_handling}" if empty_handling else "" 3237 schema = self.sql(expression, "schema") 3238 return self.func( 3239 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3240 ) 3241 3242 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3243 this = self.sql(expression, "this") 3244 kind = self.sql(expression, "kind") 3245 path = self.sql(expression, "path") 3246 path = f" {path}" if path else "" 3247 as_json = " AS JSON" if expression.args.get("as_json") else "" 3248 return f"{this} {kind}{path}{as_json}" 3249 3250 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3251 this = self.sql(expression, "this") 3252 path = self.sql(expression, "path") 3253 path = f", {path}" if path else "" 3254 expressions = self.expressions(expression) 3255 with_ = ( 3256 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3257 if expressions 3258 else "" 3259 ) 3260 return f"OPENJSON({this}{path}){with_}" 3261 3262 def in_sql(self, expression: exp.In) -> str: 3263 query = expression.args.get("query") 3264 unnest = expression.args.get("unnest") 3265 field = expression.args.get("field") 3266 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3267 3268 if query: 3269 in_sql = self.sql(query) 3270 elif unnest: 3271 in_sql = self.in_unnest_op(unnest) 3272 elif field: 3273 in_sql = self.sql(field) 3274 else: 3275 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3276 3277 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3278 3279 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3280 return f"(SELECT {self.sql(unnest)})" 3281 3282 def interval_sql(self, expression: exp.Interval) -> str: 3283 unit_expression = expression.args.get("unit") 3284 unit = self.sql(unit_expression) if unit_expression else "" 3285 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3286 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3287 unit = f" {unit}" if unit else "" 3288 3289 if self.SINGLE_STRING_INTERVAL: 3290 this = expression.this.name if expression.this else "" 3291 if this: 3292 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3293 return f"INTERVAL '{this}'{unit}" 3294 return f"INTERVAL '{this}{unit}'" 3295 return f"INTERVAL{unit}" 3296 3297 this = self.sql(expression, "this") 3298 if this: 3299 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3300 this = f" {this}" if unwrapped else f" ({this})" 3301 3302 return f"INTERVAL{this}{unit}" 3303 3304 def return_sql(self, expression: exp.Return) -> str: 3305 return f"RETURN {self.sql(expression, 'this')}" 3306 3307 def reference_sql(self, expression: exp.Reference) -> str: 3308 this = self.sql(expression, "this") 3309 expressions = self.expressions(expression, flat=True) 3310 expressions = f"({expressions})" if expressions else "" 3311 options = self.expressions(expression, key="options", flat=True, sep=" ") 3312 options = f" {options}" if options else "" 3313 return f"REFERENCES {this}{expressions}{options}" 3314 3315 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3316 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3317 parent = expression.parent 3318 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3319 return self.func( 3320 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3321 ) 3322 3323 def paren_sql(self, expression: exp.Paren) -> str: 3324 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3325 return f"({sql}{self.seg(')', sep='')}" 3326 3327 def neg_sql(self, expression: exp.Neg) -> str: 3328 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3329 this_sql = self.sql(expression, "this") 3330 sep = " " if this_sql[0] == "-" else "" 3331 return f"-{sep}{this_sql}" 3332 3333 def not_sql(self, expression: exp.Not) -> str: 3334 return f"NOT {self.sql(expression, 'this')}" 3335 3336 def alias_sql(self, expression: exp.Alias) -> str: 3337 alias = self.sql(expression, "alias") 3338 alias = f" AS {alias}" if alias else "" 3339 return f"{self.sql(expression, 'this')}{alias}" 3340 3341 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3342 alias = expression.args["alias"] 3343 3344 parent = expression.parent 3345 pivot = parent and parent.parent 3346 3347 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3348 identifier_alias = isinstance(alias, exp.Identifier) 3349 literal_alias = isinstance(alias, exp.Literal) 3350 3351 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3352 alias.replace(exp.Literal.string(alias.output_name)) 3353 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3354 alias.replace(exp.to_identifier(alias.output_name)) 3355 3356 return self.alias_sql(expression) 3357 3358 def aliases_sql(self, expression: exp.Aliases) -> str: 3359 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3360 3361 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3362 this = self.sql(expression, "this") 3363 index = self.sql(expression, "expression") 3364 return f"{this} AT {index}" 3365 3366 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3367 this = self.sql(expression, "this") 3368 zone = self.sql(expression, "zone") 3369 return f"{this} AT TIME ZONE {zone}" 3370 3371 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3372 this = self.sql(expression, "this") 3373 zone = self.sql(expression, "zone") 3374 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3375 3376 def add_sql(self, expression: exp.Add) -> str: 3377 return self.binary(expression, "+") 3378 3379 def and_sql( 3380 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3381 ) -> str: 3382 return self.connector_sql(expression, "AND", stack) 3383 3384 def or_sql( 3385 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3386 ) -> str: 3387 return self.connector_sql(expression, "OR", stack) 3388 3389 def xor_sql( 3390 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3391 ) -> str: 3392 return self.connector_sql(expression, "XOR", stack) 3393 3394 def connector_sql( 3395 self, 3396 expression: exp.Connector, 3397 op: str, 3398 stack: t.Optional[t.List[str | exp.Expression]] = None, 3399 ) -> str: 3400 if stack is not None: 3401 if expression.expressions: 3402 stack.append(self.expressions(expression, sep=f" {op} ")) 3403 else: 3404 stack.append(expression.right) 3405 if expression.comments and self.comments: 3406 for comment in expression.comments: 3407 if comment: 3408 op += f" /*{self.sanitize_comment(comment)}*/" 3409 stack.extend((op, expression.left)) 3410 return op 3411 3412 stack = [expression] 3413 sqls: t.List[str] = [] 3414 ops = set() 3415 3416 while stack: 3417 node = stack.pop() 3418 if isinstance(node, exp.Connector): 3419 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3420 else: 3421 sql = self.sql(node) 3422 if sqls and sqls[-1] in ops: 3423 sqls[-1] += f" {sql}" 3424 else: 3425 sqls.append(sql) 3426 3427 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3428 return sep.join(sqls) 3429 3430 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3431 return self.binary(expression, "&") 3432 3433 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3434 return self.binary(expression, "<<") 3435 3436 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3437 return f"~{self.sql(expression, 'this')}" 3438 3439 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3440 return self.binary(expression, "|") 3441 3442 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3443 return self.binary(expression, ">>") 3444 3445 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3446 return self.binary(expression, "^") 3447 3448 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3449 format_sql = self.sql(expression, "format") 3450 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3451 to_sql = self.sql(expression, "to") 3452 to_sql = f" {to_sql}" if to_sql else "" 3453 action = self.sql(expression, "action") 3454 action = f" {action}" if action else "" 3455 default = self.sql(expression, "default") 3456 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3457 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3458 3459 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3460 zone = self.sql(expression, "this") 3461 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3462 3463 def collate_sql(self, expression: exp.Collate) -> str: 3464 if self.COLLATE_IS_FUNC: 3465 return self.function_fallback_sql(expression) 3466 return self.binary(expression, "COLLATE") 3467 3468 def command_sql(self, expression: exp.Command) -> str: 3469 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3470 3471 def comment_sql(self, expression: exp.Comment) -> str: 3472 this = self.sql(expression, "this") 3473 kind = expression.args["kind"] 3474 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3475 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3476 expression_sql = self.sql(expression, "expression") 3477 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3478 3479 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3480 this = self.sql(expression, "this") 3481 delete = " DELETE" if expression.args.get("delete") else "" 3482 recompress = self.sql(expression, "recompress") 3483 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3484 to_disk = self.sql(expression, "to_disk") 3485 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3486 to_volume = self.sql(expression, "to_volume") 3487 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3488 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3489 3490 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3491 where = self.sql(expression, "where") 3492 group = self.sql(expression, "group") 3493 aggregates = self.expressions(expression, key="aggregates") 3494 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3495 3496 if not (where or group or aggregates) and len(expression.expressions) == 1: 3497 return f"TTL {self.expressions(expression, flat=True)}" 3498 3499 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3500 3501 def transaction_sql(self, expression: exp.Transaction) -> str: 3502 modes = self.expressions(expression, key="modes") 3503 modes = f" {modes}" if modes else "" 3504 return f"BEGIN{modes}" 3505 3506 def commit_sql(self, expression: exp.Commit) -> str: 3507 chain = expression.args.get("chain") 3508 if chain is not None: 3509 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3510 3511 return f"COMMIT{chain or ''}" 3512 3513 def rollback_sql(self, expression: exp.Rollback) -> str: 3514 savepoint = expression.args.get("savepoint") 3515 savepoint = f" TO {savepoint}" if savepoint else "" 3516 return f"ROLLBACK{savepoint}" 3517 3518 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3519 this = self.sql(expression, "this") 3520 3521 dtype = self.sql(expression, "dtype") 3522 if dtype: 3523 collate = self.sql(expression, "collate") 3524 collate = f" COLLATE {collate}" if collate else "" 3525 using = self.sql(expression, "using") 3526 using = f" USING {using}" if using else "" 3527 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3528 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3529 3530 default = self.sql(expression, "default") 3531 if default: 3532 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3533 3534 comment = self.sql(expression, "comment") 3535 if comment: 3536 return f"ALTER COLUMN {this} COMMENT {comment}" 3537 3538 visible = expression.args.get("visible") 3539 if visible: 3540 return f"ALTER COLUMN {this} SET {visible}" 3541 3542 allow_null = expression.args.get("allow_null") 3543 drop = expression.args.get("drop") 3544 3545 if not drop and not allow_null: 3546 self.unsupported("Unsupported ALTER COLUMN syntax") 3547 3548 if allow_null is not None: 3549 keyword = "DROP" if drop else "SET" 3550 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3551 3552 return f"ALTER COLUMN {this} DROP DEFAULT" 3553 3554 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3555 this = self.sql(expression, "this") 3556 3557 visible = expression.args.get("visible") 3558 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3559 3560 return f"ALTER INDEX {this} {visible_sql}" 3561 3562 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3563 this = self.sql(expression, "this") 3564 if not isinstance(expression.this, exp.Var): 3565 this = f"KEY DISTKEY {this}" 3566 return f"ALTER DISTSTYLE {this}" 3567 3568 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3569 compound = " COMPOUND" if expression.args.get("compound") else "" 3570 this = self.sql(expression, "this") 3571 expressions = self.expressions(expression, flat=True) 3572 expressions = f"({expressions})" if expressions else "" 3573 return f"ALTER{compound} SORTKEY {this or expressions}" 3574 3575 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3576 if not self.RENAME_TABLE_WITH_DB: 3577 # Remove db from tables 3578 expression = expression.transform( 3579 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3580 ).assert_is(exp.AlterRename) 3581 this = self.sql(expression, "this") 3582 to_kw = " TO" if include_to else "" 3583 return f"RENAME{to_kw} {this}" 3584 3585 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3586 exists = " IF EXISTS" if expression.args.get("exists") else "" 3587 old_column = self.sql(expression, "this") 3588 new_column = self.sql(expression, "to") 3589 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3590 3591 def alterset_sql(self, expression: exp.AlterSet) -> str: 3592 exprs = self.expressions(expression, flat=True) 3593 if self.ALTER_SET_WRAPPED: 3594 exprs = f"({exprs})" 3595 3596 return f"SET {exprs}" 3597 3598 def alter_sql(self, expression: exp.Alter) -> str: 3599 actions = expression.args["actions"] 3600 3601 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3602 actions[0], exp.ColumnDef 3603 ): 3604 actions_sql = self.expressions(expression, key="actions", flat=True) 3605 actions_sql = f"ADD {actions_sql}" 3606 else: 3607 actions_list = [] 3608 for action in actions: 3609 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3610 action_sql = self.add_column_sql(action) 3611 else: 3612 action_sql = self.sql(action) 3613 if isinstance(action, exp.Query): 3614 action_sql = f"AS {action_sql}" 3615 3616 actions_list.append(action_sql) 3617 3618 actions_sql = self.format_args(*actions_list).lstrip("\n") 3619 3620 exists = " IF EXISTS" if expression.args.get("exists") else "" 3621 on_cluster = self.sql(expression, "cluster") 3622 on_cluster = f" {on_cluster}" if on_cluster else "" 3623 only = " ONLY" if expression.args.get("only") else "" 3624 options = self.expressions(expression, key="options") 3625 options = f", {options}" if options else "" 3626 kind = self.sql(expression, "kind") 3627 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3628 check = " WITH CHECK" if expression.args.get("check") else "" 3629 cascade = ( 3630 " CASCADE" 3631 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 3632 else "" 3633 ) 3634 this = self.sql(expression, "this") 3635 this = f" {this}" if this else "" 3636 3637 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 3638 3639 def altersession_sql(self, expression: exp.AlterSession) -> str: 3640 items_sql = self.expressions(expression, flat=True) 3641 keyword = "UNSET" if expression.args.get("unset") else "SET" 3642 return f"{keyword} {items_sql}" 3643 3644 def add_column_sql(self, expression: exp.Expression) -> str: 3645 sql = self.sql(expression) 3646 if isinstance(expression, exp.Schema): 3647 column_text = " COLUMNS" 3648 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3649 column_text = " COLUMN" 3650 else: 3651 column_text = "" 3652 3653 return f"ADD{column_text} {sql}" 3654 3655 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3656 expressions = self.expressions(expression) 3657 exists = " IF EXISTS " if expression.args.get("exists") else " " 3658 return f"DROP{exists}{expressions}" 3659 3660 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3661 return f"ADD {self.expressions(expression, indent=False)}" 3662 3663 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3664 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3665 location = self.sql(expression, "location") 3666 location = f" {location}" if location else "" 3667 return f"ADD {exists}{self.sql(expression.this)}{location}" 3668 3669 def distinct_sql(self, expression: exp.Distinct) -> str: 3670 this = self.expressions(expression, flat=True) 3671 3672 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3673 case = exp.case() 3674 for arg in expression.expressions: 3675 case = case.when(arg.is_(exp.null()), exp.null()) 3676 this = self.sql(case.else_(f"({this})")) 3677 3678 this = f" {this}" if this else "" 3679 3680 on = self.sql(expression, "on") 3681 on = f" ON {on}" if on else "" 3682 return f"DISTINCT{this}{on}" 3683 3684 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3685 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3686 3687 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3688 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3689 3690 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3691 this_sql = self.sql(expression, "this") 3692 expression_sql = self.sql(expression, "expression") 3693 kind = "MAX" if expression.args.get("max") else "MIN" 3694 return f"{this_sql} HAVING {kind} {expression_sql}" 3695 3696 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3697 return self.sql( 3698 exp.Cast( 3699 this=exp.Div(this=expression.this, expression=expression.expression), 3700 to=exp.DataType(this=exp.DataType.Type.INT), 3701 ) 3702 ) 3703 3704 def dpipe_sql(self, expression: exp.DPipe) -> str: 3705 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3706 return self.func( 3707 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3708 ) 3709 return self.binary(expression, "||") 3710 3711 def div_sql(self, expression: exp.Div) -> str: 3712 l, r = expression.left, expression.right 3713 3714 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3715 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3716 3717 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3718 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3719 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3720 3721 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3722 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3723 return self.sql( 3724 exp.cast( 3725 l / r, 3726 to=exp.DataType.Type.BIGINT, 3727 ) 3728 ) 3729 3730 return self.binary(expression, "/") 3731 3732 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3733 n = exp._wrap(expression.this, exp.Binary) 3734 d = exp._wrap(expression.expression, exp.Binary) 3735 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3736 3737 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3738 return self.binary(expression, "OVERLAPS") 3739 3740 def distance_sql(self, expression: exp.Distance) -> str: 3741 return self.binary(expression, "<->") 3742 3743 def dot_sql(self, expression: exp.Dot) -> str: 3744 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3745 3746 def eq_sql(self, expression: exp.EQ) -> str: 3747 return self.binary(expression, "=") 3748 3749 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3750 return self.binary(expression, ":=") 3751 3752 def escape_sql(self, expression: exp.Escape) -> str: 3753 return self.binary(expression, "ESCAPE") 3754 3755 def glob_sql(self, expression: exp.Glob) -> str: 3756 return self.binary(expression, "GLOB") 3757 3758 def gt_sql(self, expression: exp.GT) -> str: 3759 return self.binary(expression, ">") 3760 3761 def gte_sql(self, expression: exp.GTE) -> str: 3762 return self.binary(expression, ">=") 3763 3764 def is_sql(self, expression: exp.Is) -> str: 3765 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3766 return self.sql( 3767 expression.this if expression.expression.this else exp.not_(expression.this) 3768 ) 3769 return self.binary(expression, "IS") 3770 3771 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3772 this = expression.this 3773 rhs = expression.expression 3774 3775 if isinstance(expression, exp.Like): 3776 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3777 op = "LIKE" 3778 else: 3779 exp_class = exp.ILike 3780 op = "ILIKE" 3781 3782 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3783 exprs = rhs.this.unnest() 3784 3785 if isinstance(exprs, exp.Tuple): 3786 exprs = exprs.expressions 3787 3788 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3789 3790 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3791 for expr in exprs[1:]: 3792 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3793 3794 return self.sql(like_expr) 3795 3796 return self.binary(expression, op) 3797 3798 def like_sql(self, expression: exp.Like) -> str: 3799 return self._like_sql(expression) 3800 3801 def ilike_sql(self, expression: exp.ILike) -> str: 3802 return self._like_sql(expression) 3803 3804 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3805 return self.binary(expression, "SIMILAR TO") 3806 3807 def lt_sql(self, expression: exp.LT) -> str: 3808 return self.binary(expression, "<") 3809 3810 def lte_sql(self, expression: exp.LTE) -> str: 3811 return self.binary(expression, "<=") 3812 3813 def mod_sql(self, expression: exp.Mod) -> str: 3814 return self.binary(expression, "%") 3815 3816 def mul_sql(self, expression: exp.Mul) -> str: 3817 return self.binary(expression, "*") 3818 3819 def neq_sql(self, expression: exp.NEQ) -> str: 3820 return self.binary(expression, "<>") 3821 3822 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3823 return self.binary(expression, "IS NOT DISTINCT FROM") 3824 3825 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3826 return self.binary(expression, "IS DISTINCT FROM") 3827 3828 def slice_sql(self, expression: exp.Slice) -> str: 3829 return self.binary(expression, ":") 3830 3831 def sub_sql(self, expression: exp.Sub) -> str: 3832 return self.binary(expression, "-") 3833 3834 def trycast_sql(self, expression: exp.TryCast) -> str: 3835 return self.cast_sql(expression, safe_prefix="TRY_") 3836 3837 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3838 return self.cast_sql(expression) 3839 3840 def try_sql(self, expression: exp.Try) -> str: 3841 if not self.TRY_SUPPORTED: 3842 self.unsupported("Unsupported TRY function") 3843 return self.sql(expression, "this") 3844 3845 return self.func("TRY", expression.this) 3846 3847 def log_sql(self, expression: exp.Log) -> str: 3848 this = expression.this 3849 expr = expression.expression 3850 3851 if self.dialect.LOG_BASE_FIRST is False: 3852 this, expr = expr, this 3853 elif self.dialect.LOG_BASE_FIRST is None and expr: 3854 if this.name in ("2", "10"): 3855 return self.func(f"LOG{this.name}", expr) 3856 3857 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3858 3859 return self.func("LOG", this, expr) 3860 3861 def use_sql(self, expression: exp.Use) -> str: 3862 kind = self.sql(expression, "kind") 3863 kind = f" {kind}" if kind else "" 3864 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3865 this = f" {this}" if this else "" 3866 return f"USE{kind}{this}" 3867 3868 def binary(self, expression: exp.Binary, op: str) -> str: 3869 sqls: t.List[str] = [] 3870 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3871 binary_type = type(expression) 3872 3873 while stack: 3874 node = stack.pop() 3875 3876 if type(node) is binary_type: 3877 op_func = node.args.get("operator") 3878 if op_func: 3879 op = f"OPERATOR({self.sql(op_func)})" 3880 3881 stack.append(node.right) 3882 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3883 stack.append(node.left) 3884 else: 3885 sqls.append(self.sql(node)) 3886 3887 return "".join(sqls) 3888 3889 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3890 to_clause = self.sql(expression, "to") 3891 if to_clause: 3892 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3893 3894 return self.function_fallback_sql(expression) 3895 3896 def function_fallback_sql(self, expression: exp.Func) -> str: 3897 args = [] 3898 3899 for key in expression.arg_types: 3900 arg_value = expression.args.get(key) 3901 3902 if isinstance(arg_value, list): 3903 for value in arg_value: 3904 args.append(value) 3905 elif arg_value is not None: 3906 args.append(arg_value) 3907 3908 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3909 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3910 else: 3911 name = expression.sql_name() 3912 3913 return self.func(name, *args) 3914 3915 def func( 3916 self, 3917 name: str, 3918 *args: t.Optional[exp.Expression | str], 3919 prefix: str = "(", 3920 suffix: str = ")", 3921 normalize: bool = True, 3922 ) -> str: 3923 name = self.normalize_func(name) if normalize else name 3924 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3925 3926 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3927 arg_sqls = tuple( 3928 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3929 ) 3930 if self.pretty and self.too_wide(arg_sqls): 3931 return self.indent( 3932 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3933 ) 3934 return sep.join(arg_sqls) 3935 3936 def too_wide(self, args: t.Iterable) -> bool: 3937 return sum(len(arg) for arg in args) > self.max_text_width 3938 3939 def format_time( 3940 self, 3941 expression: exp.Expression, 3942 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3943 inverse_time_trie: t.Optional[t.Dict] = None, 3944 ) -> t.Optional[str]: 3945 return format_time( 3946 self.sql(expression, "format"), 3947 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3948 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3949 ) 3950 3951 def expressions( 3952 self, 3953 expression: t.Optional[exp.Expression] = None, 3954 key: t.Optional[str] = None, 3955 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3956 flat: bool = False, 3957 indent: bool = True, 3958 skip_first: bool = False, 3959 skip_last: bool = False, 3960 sep: str = ", ", 3961 prefix: str = "", 3962 dynamic: bool = False, 3963 new_line: bool = False, 3964 ) -> str: 3965 expressions = expression.args.get(key or "expressions") if expression else sqls 3966 3967 if not expressions: 3968 return "" 3969 3970 if flat: 3971 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3972 3973 num_sqls = len(expressions) 3974 result_sqls = [] 3975 3976 for i, e in enumerate(expressions): 3977 sql = self.sql(e, comment=False) 3978 if not sql: 3979 continue 3980 3981 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3982 3983 if self.pretty: 3984 if self.leading_comma: 3985 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3986 else: 3987 result_sqls.append( 3988 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3989 ) 3990 else: 3991 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3992 3993 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3994 if new_line: 3995 result_sqls.insert(0, "") 3996 result_sqls.append("") 3997 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3998 else: 3999 result_sql = "".join(result_sqls) 4000 4001 return ( 4002 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4003 if indent 4004 else result_sql 4005 ) 4006 4007 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 4008 flat = flat or isinstance(expression.parent, exp.Properties) 4009 expressions_sql = self.expressions(expression, flat=flat) 4010 if flat: 4011 return f"{op} {expressions_sql}" 4012 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4013 4014 def naked_property(self, expression: exp.Property) -> str: 4015 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4016 if not property_name: 4017 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4018 return f"{property_name} {self.sql(expression, 'this')}" 4019 4020 def tag_sql(self, expression: exp.Tag) -> str: 4021 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4022 4023 def token_sql(self, token_type: TokenType) -> str: 4024 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4025 4026 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4027 this = self.sql(expression, "this") 4028 expressions = self.no_identify(self.expressions, expression) 4029 expressions = ( 4030 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4031 ) 4032 return f"{this}{expressions}" if expressions.strip() != "" else this 4033 4034 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4035 this = self.sql(expression, "this") 4036 expressions = self.expressions(expression, flat=True) 4037 return f"{this}({expressions})" 4038 4039 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4040 return self.binary(expression, "=>") 4041 4042 def when_sql(self, expression: exp.When) -> str: 4043 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4044 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4045 condition = self.sql(expression, "condition") 4046 condition = f" AND {condition}" if condition else "" 4047 4048 then_expression = expression.args.get("then") 4049 if isinstance(then_expression, exp.Insert): 4050 this = self.sql(then_expression, "this") 4051 this = f"INSERT {this}" if this else "INSERT" 4052 then = self.sql(then_expression, "expression") 4053 then = f"{this} VALUES {then}" if then else this 4054 elif isinstance(then_expression, exp.Update): 4055 if isinstance(then_expression.args.get("expressions"), exp.Star): 4056 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4057 else: 4058 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 4059 else: 4060 then = self.sql(then_expression) 4061 return f"WHEN {matched}{source}{condition} THEN {then}" 4062 4063 def whens_sql(self, expression: exp.Whens) -> str: 4064 return self.expressions(expression, sep=" ", indent=False) 4065 4066 def merge_sql(self, expression: exp.Merge) -> str: 4067 table = expression.this 4068 table_alias = "" 4069 4070 hints = table.args.get("hints") 4071 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4072 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4073 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4074 4075 this = self.sql(table) 4076 using = f"USING {self.sql(expression, 'using')}" 4077 on = f"ON {self.sql(expression, 'on')}" 4078 whens = self.sql(expression, "whens") 4079 4080 returning = self.sql(expression, "returning") 4081 if returning: 4082 whens = f"{whens}{returning}" 4083 4084 sep = self.sep() 4085 4086 return self.prepend_ctes( 4087 expression, 4088 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4089 ) 4090 4091 @unsupported_args("format") 4092 def tochar_sql(self, expression: exp.ToChar) -> str: 4093 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4094 4095 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4096 if not self.SUPPORTS_TO_NUMBER: 4097 self.unsupported("Unsupported TO_NUMBER function") 4098 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4099 4100 fmt = expression.args.get("format") 4101 if not fmt: 4102 self.unsupported("Conversion format is required for TO_NUMBER") 4103 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4104 4105 return self.func("TO_NUMBER", expression.this, fmt) 4106 4107 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4108 this = self.sql(expression, "this") 4109 kind = self.sql(expression, "kind") 4110 settings_sql = self.expressions(expression, key="settings", sep=" ") 4111 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4112 return f"{this}({kind}{args})" 4113 4114 def dictrange_sql(self, expression: exp.DictRange) -> str: 4115 this = self.sql(expression, "this") 4116 max = self.sql(expression, "max") 4117 min = self.sql(expression, "min") 4118 return f"{this}(MIN {min} MAX {max})" 4119 4120 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4121 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4122 4123 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4124 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4125 4126 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4127 def uniquekeyproperty_sql( 4128 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4129 ) -> str: 4130 return f"{prefix} ({self.expressions(expression, flat=True)})" 4131 4132 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4133 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4134 expressions = self.expressions(expression, flat=True) 4135 expressions = f" {self.wrap(expressions)}" if expressions else "" 4136 buckets = self.sql(expression, "buckets") 4137 kind = self.sql(expression, "kind") 4138 buckets = f" BUCKETS {buckets}" if buckets else "" 4139 order = self.sql(expression, "order") 4140 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4141 4142 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4143 return "" 4144 4145 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4146 expressions = self.expressions(expression, key="expressions", flat=True) 4147 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4148 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4149 buckets = self.sql(expression, "buckets") 4150 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4151 4152 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4153 this = self.sql(expression, "this") 4154 having = self.sql(expression, "having") 4155 4156 if having: 4157 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4158 4159 return self.func("ANY_VALUE", this) 4160 4161 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4162 transform = self.func("TRANSFORM", *expression.expressions) 4163 row_format_before = self.sql(expression, "row_format_before") 4164 row_format_before = f" {row_format_before}" if row_format_before else "" 4165 record_writer = self.sql(expression, "record_writer") 4166 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4167 using = f" USING {self.sql(expression, 'command_script')}" 4168 schema = self.sql(expression, "schema") 4169 schema = f" AS {schema}" if schema else "" 4170 row_format_after = self.sql(expression, "row_format_after") 4171 row_format_after = f" {row_format_after}" if row_format_after else "" 4172 record_reader = self.sql(expression, "record_reader") 4173 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4174 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4175 4176 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4177 key_block_size = self.sql(expression, "key_block_size") 4178 if key_block_size: 4179 return f"KEY_BLOCK_SIZE = {key_block_size}" 4180 4181 using = self.sql(expression, "using") 4182 if using: 4183 return f"USING {using}" 4184 4185 parser = self.sql(expression, "parser") 4186 if parser: 4187 return f"WITH PARSER {parser}" 4188 4189 comment = self.sql(expression, "comment") 4190 if comment: 4191 return f"COMMENT {comment}" 4192 4193 visible = expression.args.get("visible") 4194 if visible is not None: 4195 return "VISIBLE" if visible else "INVISIBLE" 4196 4197 engine_attr = self.sql(expression, "engine_attr") 4198 if engine_attr: 4199 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4200 4201 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4202 if secondary_engine_attr: 4203 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4204 4205 self.unsupported("Unsupported index constraint option.") 4206 return "" 4207 4208 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4209 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4210 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4211 4212 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4213 kind = self.sql(expression, "kind") 4214 kind = f"{kind} INDEX" if kind else "INDEX" 4215 this = self.sql(expression, "this") 4216 this = f" {this}" if this else "" 4217 index_type = self.sql(expression, "index_type") 4218 index_type = f" USING {index_type}" if index_type else "" 4219 expressions = self.expressions(expression, flat=True) 4220 expressions = f" ({expressions})" if expressions else "" 4221 options = self.expressions(expression, key="options", sep=" ") 4222 options = f" {options}" if options else "" 4223 return f"{kind}{this}{index_type}{expressions}{options}" 4224 4225 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4226 if self.NVL2_SUPPORTED: 4227 return self.function_fallback_sql(expression) 4228 4229 case = exp.Case().when( 4230 expression.this.is_(exp.null()).not_(copy=False), 4231 expression.args["true"], 4232 copy=False, 4233 ) 4234 else_cond = expression.args.get("false") 4235 if else_cond: 4236 case.else_(else_cond, copy=False) 4237 4238 return self.sql(case) 4239 4240 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4241 this = self.sql(expression, "this") 4242 expr = self.sql(expression, "expression") 4243 iterator = self.sql(expression, "iterator") 4244 condition = self.sql(expression, "condition") 4245 condition = f" IF {condition}" if condition else "" 4246 return f"{this} FOR {expr} IN {iterator}{condition}" 4247 4248 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4249 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4250 4251 def opclass_sql(self, expression: exp.Opclass) -> str: 4252 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4253 4254 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4255 model = self.sql(expression, "this") 4256 model = f"MODEL {model}" 4257 expr = expression.expression 4258 if expr: 4259 expr_sql = self.sql(expression, "expression") 4260 expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql 4261 else: 4262 expr_sql = None 4263 4264 parameters = self.sql(expression, "params_struct") or None 4265 4266 return self.func(name, model, expr_sql, parameters) 4267 4268 def predict_sql(self, expression: exp.Predict) -> str: 4269 return self._ml_sql(expression, "PREDICT") 4270 4271 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4272 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4273 return self._ml_sql(expression, name) 4274 4275 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4276 return self._ml_sql(expression, "TRANSLATE") 4277 4278 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4279 return self._ml_sql(expression, "FORECAST") 4280 4281 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4282 this_sql = self.sql(expression, "this") 4283 if isinstance(expression.this, exp.Table): 4284 this_sql = f"TABLE {this_sql}" 4285 4286 return self.func( 4287 "FEATURES_AT_TIME", 4288 this_sql, 4289 expression.args.get("time"), 4290 expression.args.get("num_rows"), 4291 expression.args.get("ignore_feature_nulls"), 4292 ) 4293 4294 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4295 this_sql = self.sql(expression, "this") 4296 if isinstance(expression.this, exp.Table): 4297 this_sql = f"TABLE {this_sql}" 4298 4299 query_table = self.sql(expression, "query_table") 4300 if isinstance(expression.args["query_table"], exp.Table): 4301 query_table = f"TABLE {query_table}" 4302 4303 return self.func( 4304 "VECTOR_SEARCH", 4305 this_sql, 4306 expression.args.get("column_to_search"), 4307 query_table, 4308 expression.args.get("query_column_to_search"), 4309 expression.args.get("top_k"), 4310 expression.args.get("distance_type"), 4311 expression.args.get("options"), 4312 ) 4313 4314 def forin_sql(self, expression: exp.ForIn) -> str: 4315 this = self.sql(expression, "this") 4316 expression_sql = self.sql(expression, "expression") 4317 return f"FOR {this} DO {expression_sql}" 4318 4319 def refresh_sql(self, expression: exp.Refresh) -> str: 4320 this = self.sql(expression, "this") 4321 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4322 return f"REFRESH {table}{this}" 4323 4324 def toarray_sql(self, expression: exp.ToArray) -> str: 4325 arg = expression.this 4326 if not arg.type: 4327 from sqlglot.optimizer.annotate_types import annotate_types 4328 4329 arg = annotate_types(arg, dialect=self.dialect) 4330 4331 if arg.is_type(exp.DataType.Type.ARRAY): 4332 return self.sql(arg) 4333 4334 cond_for_null = arg.is_(exp.null()) 4335 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4336 4337 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4338 this = expression.this 4339 time_format = self.format_time(expression) 4340 4341 if time_format: 4342 return self.sql( 4343 exp.cast( 4344 exp.StrToTime(this=this, format=expression.args["format"]), 4345 exp.DataType.Type.TIME, 4346 ) 4347 ) 4348 4349 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4350 return self.sql(this) 4351 4352 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4353 4354 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4355 this = expression.this 4356 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4357 return self.sql(this) 4358 4359 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4360 4361 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4362 this = expression.this 4363 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4364 return self.sql(this) 4365 4366 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4367 4368 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4369 this = expression.this 4370 time_format = self.format_time(expression) 4371 4372 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4373 return self.sql( 4374 exp.cast( 4375 exp.StrToTime(this=this, format=expression.args["format"]), 4376 exp.DataType.Type.DATE, 4377 ) 4378 ) 4379 4380 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4381 return self.sql(this) 4382 4383 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4384 4385 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4386 return self.sql( 4387 exp.func( 4388 "DATEDIFF", 4389 expression.this, 4390 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4391 "day", 4392 ) 4393 ) 4394 4395 def lastday_sql(self, expression: exp.LastDay) -> str: 4396 if self.LAST_DAY_SUPPORTS_DATE_PART: 4397 return self.function_fallback_sql(expression) 4398 4399 unit = expression.text("unit") 4400 if unit and unit != "MONTH": 4401 self.unsupported("Date parts are not supported in LAST_DAY.") 4402 4403 return self.func("LAST_DAY", expression.this) 4404 4405 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4406 from sqlglot.dialects.dialect import unit_to_str 4407 4408 return self.func( 4409 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4410 ) 4411 4412 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4413 if self.CAN_IMPLEMENT_ARRAY_ANY: 4414 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4415 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4416 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4417 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4418 4419 from sqlglot.dialects import Dialect 4420 4421 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4422 if self.dialect.__class__ != Dialect: 4423 self.unsupported("ARRAY_ANY is unsupported") 4424 4425 return self.function_fallback_sql(expression) 4426 4427 def struct_sql(self, expression: exp.Struct) -> str: 4428 expression.set( 4429 "expressions", 4430 [ 4431 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4432 if isinstance(e, exp.PropertyEQ) 4433 else e 4434 for e in expression.expressions 4435 ], 4436 ) 4437 4438 return self.function_fallback_sql(expression) 4439 4440 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4441 low = self.sql(expression, "this") 4442 high = self.sql(expression, "expression") 4443 4444 return f"{low} TO {high}" 4445 4446 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4447 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4448 tables = f" {self.expressions(expression)}" 4449 4450 exists = " IF EXISTS" if expression.args.get("exists") else "" 4451 4452 on_cluster = self.sql(expression, "cluster") 4453 on_cluster = f" {on_cluster}" if on_cluster else "" 4454 4455 identity = self.sql(expression, "identity") 4456 identity = f" {identity} IDENTITY" if identity else "" 4457 4458 option = self.sql(expression, "option") 4459 option = f" {option}" if option else "" 4460 4461 partition = self.sql(expression, "partition") 4462 partition = f" {partition}" if partition else "" 4463 4464 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4465 4466 # This transpiles T-SQL's CONVERT function 4467 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4468 def convert_sql(self, expression: exp.Convert) -> str: 4469 to = expression.this 4470 value = expression.expression 4471 style = expression.args.get("style") 4472 safe = expression.args.get("safe") 4473 strict = expression.args.get("strict") 4474 4475 if not to or not value: 4476 return "" 4477 4478 # Retrieve length of datatype and override to default if not specified 4479 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4480 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4481 4482 transformed: t.Optional[exp.Expression] = None 4483 cast = exp.Cast if strict else exp.TryCast 4484 4485 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4486 if isinstance(style, exp.Literal) and style.is_int: 4487 from sqlglot.dialects.tsql import TSQL 4488 4489 style_value = style.name 4490 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4491 if not converted_style: 4492 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4493 4494 fmt = exp.Literal.string(converted_style) 4495 4496 if to.this == exp.DataType.Type.DATE: 4497 transformed = exp.StrToDate(this=value, format=fmt) 4498 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4499 transformed = exp.StrToTime(this=value, format=fmt) 4500 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4501 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4502 elif to.this == exp.DataType.Type.TEXT: 4503 transformed = exp.TimeToStr(this=value, format=fmt) 4504 4505 if not transformed: 4506 transformed = cast(this=value, to=to, safe=safe) 4507 4508 return self.sql(transformed) 4509 4510 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4511 this = expression.this 4512 if isinstance(this, exp.JSONPathWildcard): 4513 this = self.json_path_part(this) 4514 return f".{this}" if this else "" 4515 4516 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4517 return f".{this}" 4518 4519 this = self.json_path_part(this) 4520 return ( 4521 f"[{this}]" 4522 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4523 else f".{this}" 4524 ) 4525 4526 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4527 this = self.json_path_part(expression.this) 4528 return f"[{this}]" if this else "" 4529 4530 def _simplify_unless_literal(self, expression: E) -> E: 4531 if not isinstance(expression, exp.Literal): 4532 from sqlglot.optimizer.simplify import simplify 4533 4534 expression = simplify(expression, dialect=self.dialect) 4535 4536 return expression 4537 4538 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4539 this = expression.this 4540 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4541 self.unsupported( 4542 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4543 ) 4544 return self.sql(this) 4545 4546 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4547 # The first modifier here will be the one closest to the AggFunc's arg 4548 mods = sorted( 4549 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4550 key=lambda x: 0 4551 if isinstance(x, exp.HavingMax) 4552 else (1 if isinstance(x, exp.Order) else 2), 4553 ) 4554 4555 if mods: 4556 mod = mods[0] 4557 this = expression.__class__(this=mod.this.copy()) 4558 this.meta["inline"] = True 4559 mod.this.replace(this) 4560 return self.sql(expression.this) 4561 4562 agg_func = expression.find(exp.AggFunc) 4563 4564 if agg_func: 4565 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4566 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4567 4568 return f"{self.sql(expression, 'this')} {text}" 4569 4570 def _replace_line_breaks(self, string: str) -> str: 4571 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4572 if self.pretty: 4573 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4574 return string 4575 4576 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4577 option = self.sql(expression, "this") 4578 4579 if expression.expressions: 4580 upper = option.upper() 4581 4582 # Snowflake FILE_FORMAT options are separated by whitespace 4583 sep = " " if upper == "FILE_FORMAT" else ", " 4584 4585 # Databricks copy/format options do not set their list of values with EQ 4586 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4587 values = self.expressions(expression, flat=True, sep=sep) 4588 return f"{option}{op}({values})" 4589 4590 value = self.sql(expression, "expression") 4591 4592 if not value: 4593 return option 4594 4595 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4596 4597 return f"{option}{op}{value}" 4598 4599 def credentials_sql(self, expression: exp.Credentials) -> str: 4600 cred_expr = expression.args.get("credentials") 4601 if isinstance(cred_expr, exp.Literal): 4602 # Redshift case: CREDENTIALS <string> 4603 credentials = self.sql(expression, "credentials") 4604 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4605 else: 4606 # Snowflake case: CREDENTIALS = (...) 4607 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4608 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4609 4610 storage = self.sql(expression, "storage") 4611 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4612 4613 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4614 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4615 4616 iam_role = self.sql(expression, "iam_role") 4617 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4618 4619 region = self.sql(expression, "region") 4620 region = f" REGION {region}" if region else "" 4621 4622 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4623 4624 def copy_sql(self, expression: exp.Copy) -> str: 4625 this = self.sql(expression, "this") 4626 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4627 4628 credentials = self.sql(expression, "credentials") 4629 credentials = self.seg(credentials) if credentials else "" 4630 files = self.expressions(expression, key="files", flat=True) 4631 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4632 4633 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4634 params = self.expressions( 4635 expression, 4636 key="params", 4637 sep=sep, 4638 new_line=True, 4639 skip_last=True, 4640 skip_first=True, 4641 indent=self.COPY_PARAMS_ARE_WRAPPED, 4642 ) 4643 4644 if params: 4645 if self.COPY_PARAMS_ARE_WRAPPED: 4646 params = f" WITH ({params})" 4647 elif not self.pretty and (files or credentials): 4648 params = f" {params}" 4649 4650 return f"COPY{this}{kind} {files}{credentials}{params}" 4651 4652 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4653 return "" 4654 4655 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4656 on_sql = "ON" if expression.args.get("on") else "OFF" 4657 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4658 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4659 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4660 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4661 4662 if filter_col or retention_period: 4663 on_sql = self.func("ON", filter_col, retention_period) 4664 4665 return f"DATA_DELETION={on_sql}" 4666 4667 def maskingpolicycolumnconstraint_sql( 4668 self, expression: exp.MaskingPolicyColumnConstraint 4669 ) -> str: 4670 this = self.sql(expression, "this") 4671 expressions = self.expressions(expression, flat=True) 4672 expressions = f" USING ({expressions})" if expressions else "" 4673 return f"MASKING POLICY {this}{expressions}" 4674 4675 def gapfill_sql(self, expression: exp.GapFill) -> str: 4676 this = self.sql(expression, "this") 4677 this = f"TABLE {this}" 4678 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4679 4680 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4681 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4682 4683 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4684 this = self.sql(expression, "this") 4685 expr = expression.expression 4686 4687 if isinstance(expr, exp.Func): 4688 # T-SQL's CLR functions are case sensitive 4689 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4690 else: 4691 expr = self.sql(expression, "expression") 4692 4693 return self.scope_resolution(expr, this) 4694 4695 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4696 if self.PARSE_JSON_NAME is None: 4697 return self.sql(expression.this) 4698 4699 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4700 4701 def rand_sql(self, expression: exp.Rand) -> str: 4702 lower = self.sql(expression, "lower") 4703 upper = self.sql(expression, "upper") 4704 4705 if lower and upper: 4706 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4707 return self.func("RAND", expression.this) 4708 4709 def changes_sql(self, expression: exp.Changes) -> str: 4710 information = self.sql(expression, "information") 4711 information = f"INFORMATION => {information}" 4712 at_before = self.sql(expression, "at_before") 4713 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4714 end = self.sql(expression, "end") 4715 end = f"{self.seg('')}{end}" if end else "" 4716 4717 return f"CHANGES ({information}){at_before}{end}" 4718 4719 def pad_sql(self, expression: exp.Pad) -> str: 4720 prefix = "L" if expression.args.get("is_left") else "R" 4721 4722 fill_pattern = self.sql(expression, "fill_pattern") or None 4723 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4724 fill_pattern = "' '" 4725 4726 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4727 4728 def summarize_sql(self, expression: exp.Summarize) -> str: 4729 table = " TABLE" if expression.args.get("table") else "" 4730 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4731 4732 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4733 generate_series = exp.GenerateSeries(**expression.args) 4734 4735 parent = expression.parent 4736 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4737 parent = parent.parent 4738 4739 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4740 return self.sql(exp.Unnest(expressions=[generate_series])) 4741 4742 if isinstance(parent, exp.Select): 4743 self.unsupported("GenerateSeries projection unnesting is not supported.") 4744 4745 return self.sql(generate_series) 4746 4747 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4748 exprs = expression.expressions 4749 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4750 if len(exprs) == 0: 4751 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4752 else: 4753 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4754 else: 4755 rhs = self.expressions(expression) # type: ignore 4756 4757 return self.func(name, expression.this, rhs or None) 4758 4759 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4760 if self.SUPPORTS_CONVERT_TIMEZONE: 4761 return self.function_fallback_sql(expression) 4762 4763 source_tz = expression.args.get("source_tz") 4764 target_tz = expression.args.get("target_tz") 4765 timestamp = expression.args.get("timestamp") 4766 4767 if source_tz and timestamp: 4768 timestamp = exp.AtTimeZone( 4769 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4770 ) 4771 4772 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4773 4774 return self.sql(expr) 4775 4776 def json_sql(self, expression: exp.JSON) -> str: 4777 this = self.sql(expression, "this") 4778 this = f" {this}" if this else "" 4779 4780 _with = expression.args.get("with") 4781 4782 if _with is None: 4783 with_sql = "" 4784 elif not _with: 4785 with_sql = " WITHOUT" 4786 else: 4787 with_sql = " WITH" 4788 4789 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4790 4791 return f"JSON{this}{with_sql}{unique_sql}" 4792 4793 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4794 def _generate_on_options(arg: t.Any) -> str: 4795 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4796 4797 path = self.sql(expression, "path") 4798 returning = self.sql(expression, "returning") 4799 returning = f" RETURNING {returning}" if returning else "" 4800 4801 on_condition = self.sql(expression, "on_condition") 4802 on_condition = f" {on_condition}" if on_condition else "" 4803 4804 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4805 4806 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4807 else_ = "ELSE " if expression.args.get("else_") else "" 4808 condition = self.sql(expression, "expression") 4809 condition = f"WHEN {condition} THEN " if condition else else_ 4810 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4811 return f"{condition}{insert}" 4812 4813 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4814 kind = self.sql(expression, "kind") 4815 expressions = self.seg(self.expressions(expression, sep=" ")) 4816 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4817 return res 4818 4819 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4820 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4821 empty = expression.args.get("empty") 4822 empty = ( 4823 f"DEFAULT {empty} ON EMPTY" 4824 if isinstance(empty, exp.Expression) 4825 else self.sql(expression, "empty") 4826 ) 4827 4828 error = expression.args.get("error") 4829 error = ( 4830 f"DEFAULT {error} ON ERROR" 4831 if isinstance(error, exp.Expression) 4832 else self.sql(expression, "error") 4833 ) 4834 4835 if error and empty: 4836 error = ( 4837 f"{empty} {error}" 4838 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4839 else f"{error} {empty}" 4840 ) 4841 empty = "" 4842 4843 null = self.sql(expression, "null") 4844 4845 return f"{empty}{error}{null}" 4846 4847 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4848 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4849 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4850 4851 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4852 this = self.sql(expression, "this") 4853 path = self.sql(expression, "path") 4854 4855 passing = self.expressions(expression, "passing") 4856 passing = f" PASSING {passing}" if passing else "" 4857 4858 on_condition = self.sql(expression, "on_condition") 4859 on_condition = f" {on_condition}" if on_condition else "" 4860 4861 path = f"{path}{passing}{on_condition}" 4862 4863 return self.func("JSON_EXISTS", this, path) 4864 4865 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4866 array_agg = self.function_fallback_sql(expression) 4867 4868 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4869 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4870 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4871 parent = expression.parent 4872 if isinstance(parent, exp.Filter): 4873 parent_cond = parent.expression.this 4874 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4875 else: 4876 this = expression.this 4877 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4878 if this.find(exp.Column): 4879 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4880 this_sql = ( 4881 self.expressions(this) 4882 if isinstance(this, exp.Distinct) 4883 else self.sql(expression, "this") 4884 ) 4885 4886 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4887 4888 return array_agg 4889 4890 def apply_sql(self, expression: exp.Apply) -> str: 4891 this = self.sql(expression, "this") 4892 expr = self.sql(expression, "expression") 4893 4894 return f"{this} APPLY({expr})" 4895 4896 def _grant_or_revoke_sql( 4897 self, 4898 expression: exp.Grant | exp.Revoke, 4899 keyword: str, 4900 preposition: str, 4901 grant_option_prefix: str = "", 4902 grant_option_suffix: str = "", 4903 ) -> str: 4904 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4905 4906 kind = self.sql(expression, "kind") 4907 kind = f" {kind}" if kind else "" 4908 4909 securable = self.sql(expression, "securable") 4910 securable = f" {securable}" if securable else "" 4911 4912 principals = self.expressions(expression, key="principals", flat=True) 4913 4914 if not expression.args.get("grant_option"): 4915 grant_option_prefix = grant_option_suffix = "" 4916 4917 # cascade for revoke only 4918 cascade = self.sql(expression, "cascade") 4919 cascade = f" {cascade}" if cascade else "" 4920 4921 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 4922 4923 def grant_sql(self, expression: exp.Grant) -> str: 4924 return self._grant_or_revoke_sql( 4925 expression, 4926 keyword="GRANT", 4927 preposition="TO", 4928 grant_option_suffix=" WITH GRANT OPTION", 4929 ) 4930 4931 def revoke_sql(self, expression: exp.Revoke) -> str: 4932 return self._grant_or_revoke_sql( 4933 expression, 4934 keyword="REVOKE", 4935 preposition="FROM", 4936 grant_option_prefix="GRANT OPTION FOR ", 4937 ) 4938 4939 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4940 this = self.sql(expression, "this") 4941 columns = self.expressions(expression, flat=True) 4942 columns = f"({columns})" if columns else "" 4943 4944 return f"{this}{columns}" 4945 4946 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4947 this = self.sql(expression, "this") 4948 4949 kind = self.sql(expression, "kind") 4950 kind = f"{kind} " if kind else "" 4951 4952 return f"{kind}{this}" 4953 4954 def columns_sql(self, expression: exp.Columns): 4955 func = self.function_fallback_sql(expression) 4956 if expression.args.get("unpack"): 4957 func = f"*{func}" 4958 4959 return func 4960 4961 def overlay_sql(self, expression: exp.Overlay): 4962 this = self.sql(expression, "this") 4963 expr = self.sql(expression, "expression") 4964 from_sql = self.sql(expression, "from") 4965 for_sql = self.sql(expression, "for") 4966 for_sql = f" FOR {for_sql}" if for_sql else "" 4967 4968 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4969 4970 @unsupported_args("format") 4971 def todouble_sql(self, expression: exp.ToDouble) -> str: 4972 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4973 4974 def string_sql(self, expression: exp.String) -> str: 4975 this = expression.this 4976 zone = expression.args.get("zone") 4977 4978 if zone: 4979 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4980 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4981 # set for source_tz to transpile the time conversion before the STRING cast 4982 this = exp.ConvertTimezone( 4983 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4984 ) 4985 4986 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4987 4988 def median_sql(self, expression: exp.Median): 4989 if not self.SUPPORTS_MEDIAN: 4990 return self.sql( 4991 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4992 ) 4993 4994 return self.function_fallback_sql(expression) 4995 4996 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4997 filler = self.sql(expression, "this") 4998 filler = f" {filler}" if filler else "" 4999 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5000 return f"TRUNCATE{filler} {with_count}" 5001 5002 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5003 if self.SUPPORTS_UNIX_SECONDS: 5004 return self.function_fallback_sql(expression) 5005 5006 start_ts = exp.cast( 5007 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 5008 ) 5009 5010 return self.sql( 5011 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5012 ) 5013 5014 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5015 dim = expression.expression 5016 5017 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5018 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5019 if not (dim.is_int and dim.name == "1"): 5020 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5021 dim = None 5022 5023 # If dimension is required but not specified, default initialize it 5024 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5025 dim = exp.Literal.number(1) 5026 5027 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5028 5029 def attach_sql(self, expression: exp.Attach) -> str: 5030 this = self.sql(expression, "this") 5031 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5032 expressions = self.expressions(expression) 5033 expressions = f" ({expressions})" if expressions else "" 5034 5035 return f"ATTACH{exists_sql} {this}{expressions}" 5036 5037 def detach_sql(self, expression: exp.Detach) -> str: 5038 this = self.sql(expression, "this") 5039 # the DATABASE keyword is required if IF EXISTS is set 5040 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5041 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5042 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5043 5044 return f"DETACH{exists_sql} {this}" 5045 5046 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5047 this = self.sql(expression, "this") 5048 value = self.sql(expression, "expression") 5049 value = f" {value}" if value else "" 5050 return f"{this}{value}" 5051 5052 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5053 return ( 5054 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5055 ) 5056 5057 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5058 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5059 encode = f"{encode} {self.sql(expression, 'this')}" 5060 5061 properties = expression.args.get("properties") 5062 if properties: 5063 encode = f"{encode} {self.properties(properties)}" 5064 5065 return encode 5066 5067 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5068 this = self.sql(expression, "this") 5069 include = f"INCLUDE {this}" 5070 5071 column_def = self.sql(expression, "column_def") 5072 if column_def: 5073 include = f"{include} {column_def}" 5074 5075 alias = self.sql(expression, "alias") 5076 if alias: 5077 include = f"{include} AS {alias}" 5078 5079 return include 5080 5081 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5082 name = f"NAME {self.sql(expression, 'this')}" 5083 return self.func("XMLELEMENT", name, *expression.expressions) 5084 5085 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5086 this = self.sql(expression, "this") 5087 expr = self.sql(expression, "expression") 5088 expr = f"({expr})" if expr else "" 5089 return f"{this}{expr}" 5090 5091 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5092 partitions = self.expressions(expression, "partition_expressions") 5093 create = self.expressions(expression, "create_expressions") 5094 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5095 5096 def partitionbyrangepropertydynamic_sql( 5097 self, expression: exp.PartitionByRangePropertyDynamic 5098 ) -> str: 5099 start = self.sql(expression, "start") 5100 end = self.sql(expression, "end") 5101 5102 every = expression.args["every"] 5103 if isinstance(every, exp.Interval) and every.this.is_string: 5104 every.this.replace(exp.Literal.number(every.name)) 5105 5106 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5107 5108 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5109 name = self.sql(expression, "this") 5110 values = self.expressions(expression, flat=True) 5111 5112 return f"NAME {name} VALUE {values}" 5113 5114 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5115 kind = self.sql(expression, "kind") 5116 sample = self.sql(expression, "sample") 5117 return f"SAMPLE {sample} {kind}" 5118 5119 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5120 kind = self.sql(expression, "kind") 5121 option = self.sql(expression, "option") 5122 option = f" {option}" if option else "" 5123 this = self.sql(expression, "this") 5124 this = f" {this}" if this else "" 5125 columns = self.expressions(expression) 5126 columns = f" {columns}" if columns else "" 5127 return f"{kind}{option} STATISTICS{this}{columns}" 5128 5129 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5130 this = self.sql(expression, "this") 5131 columns = self.expressions(expression) 5132 inner_expression = self.sql(expression, "expression") 5133 inner_expression = f" {inner_expression}" if inner_expression else "" 5134 update_options = self.sql(expression, "update_options") 5135 update_options = f" {update_options} UPDATE" if update_options else "" 5136 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5137 5138 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5139 kind = self.sql(expression, "kind") 5140 kind = f" {kind}" if kind else "" 5141 return f"DELETE{kind} STATISTICS" 5142 5143 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5144 inner_expression = self.sql(expression, "expression") 5145 return f"LIST CHAINED ROWS{inner_expression}" 5146 5147 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5148 kind = self.sql(expression, "kind") 5149 this = self.sql(expression, "this") 5150 this = f" {this}" if this else "" 5151 inner_expression = self.sql(expression, "expression") 5152 return f"VALIDATE {kind}{this}{inner_expression}" 5153 5154 def analyze_sql(self, expression: exp.Analyze) -> str: 5155 options = self.expressions(expression, key="options", sep=" ") 5156 options = f" {options}" if options else "" 5157 kind = self.sql(expression, "kind") 5158 kind = f" {kind}" if kind else "" 5159 this = self.sql(expression, "this") 5160 this = f" {this}" if this else "" 5161 mode = self.sql(expression, "mode") 5162 mode = f" {mode}" if mode else "" 5163 properties = self.sql(expression, "properties") 5164 properties = f" {properties}" if properties else "" 5165 partition = self.sql(expression, "partition") 5166 partition = f" {partition}" if partition else "" 5167 inner_expression = self.sql(expression, "expression") 5168 inner_expression = f" {inner_expression}" if inner_expression else "" 5169 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5170 5171 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5172 this = self.sql(expression, "this") 5173 namespaces = self.expressions(expression, key="namespaces") 5174 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5175 passing = self.expressions(expression, key="passing") 5176 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5177 columns = self.expressions(expression, key="columns") 5178 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5179 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5180 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5181 5182 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5183 this = self.sql(expression, "this") 5184 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5185 5186 def export_sql(self, expression: exp.Export) -> str: 5187 this = self.sql(expression, "this") 5188 connection = self.sql(expression, "connection") 5189 connection = f"WITH CONNECTION {connection} " if connection else "" 5190 options = self.sql(expression, "options") 5191 return f"EXPORT DATA {connection}{options} AS {this}" 5192 5193 def declare_sql(self, expression: exp.Declare) -> str: 5194 return f"DECLARE {self.expressions(expression, flat=True)}" 5195 5196 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5197 variable = self.sql(expression, "this") 5198 default = self.sql(expression, "default") 5199 default = f" = {default}" if default else "" 5200 5201 kind = self.sql(expression, "kind") 5202 if isinstance(expression.args.get("kind"), exp.Schema): 5203 kind = f"TABLE {kind}" 5204 5205 return f"{variable} AS {kind}{default}" 5206 5207 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5208 kind = self.sql(expression, "kind") 5209 this = self.sql(expression, "this") 5210 set = self.sql(expression, "expression") 5211 using = self.sql(expression, "using") 5212 using = f" USING {using}" if using else "" 5213 5214 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5215 5216 return f"{kind_sql} {this} SET {set}{using}" 5217 5218 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5219 params = self.expressions(expression, key="params", flat=True) 5220 return self.func(expression.name, *expression.expressions) + f"({params})" 5221 5222 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5223 return self.func(expression.name, *expression.expressions) 5224 5225 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5226 return self.anonymousaggfunc_sql(expression) 5227 5228 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5229 return self.parameterizedagg_sql(expression) 5230 5231 def show_sql(self, expression: exp.Show) -> str: 5232 self.unsupported("Unsupported SHOW statement") 5233 return "" 5234 5235 def install_sql(self, expression: exp.Install) -> str: 5236 self.unsupported("Unsupported INSTALL statement") 5237 return "" 5238 5239 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5240 # Snowflake GET/PUT statements: 5241 # PUT <file> <internalStage> <properties> 5242 # GET <internalStage> <file> <properties> 5243 props = expression.args.get("properties") 5244 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5245 this = self.sql(expression, "this") 5246 target = self.sql(expression, "target") 5247 5248 if isinstance(expression, exp.Put): 5249 return f"PUT {this} {target}{props_sql}" 5250 else: 5251 return f"GET {target} {this}{props_sql}" 5252 5253 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5254 this = self.sql(expression, "this") 5255 expr = self.sql(expression, "expression") 5256 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5257 return f"TRANSLATE({this} USING {expr}{with_error})" 5258 5259 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5260 if self.SUPPORTS_DECODE_CASE: 5261 return self.func("DECODE", *expression.expressions) 5262 5263 expression, *expressions = expression.expressions 5264 5265 ifs = [] 5266 for search, result in zip(expressions[::2], expressions[1::2]): 5267 if isinstance(search, exp.Literal): 5268 ifs.append(exp.If(this=expression.eq(search), true=result)) 5269 elif isinstance(search, exp.Null): 5270 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5271 else: 5272 if isinstance(search, exp.Binary): 5273 search = exp.paren(search) 5274 5275 cond = exp.or_( 5276 expression.eq(search), 5277 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5278 copy=False, 5279 ) 5280 ifs.append(exp.If(this=cond, true=result)) 5281 5282 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5283 return self.sql(case) 5284 5285 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5286 this = self.sql(expression, "this") 5287 this = self.seg(this, sep="") 5288 dimensions = self.expressions( 5289 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5290 ) 5291 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5292 metrics = self.expressions( 5293 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5294 ) 5295 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5296 where = self.sql(expression, "where") 5297 where = self.seg(f"WHERE {where}") if where else "" 5298 body = self.indent(this + metrics + dimensions + where, skip_first=True) 5299 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5300 5301 def getextract_sql(self, expression: exp.GetExtract) -> str: 5302 this = expression.this 5303 expr = expression.expression 5304 5305 if not this.type or not expression.type: 5306 from sqlglot.optimizer.annotate_types import annotate_types 5307 5308 this = annotate_types(this, dialect=self.dialect) 5309 5310 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5311 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5312 5313 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5314 5315 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5316 return self.sql( 5317 exp.DateAdd( 5318 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5319 expression=expression.this, 5320 unit=exp.var("DAY"), 5321 ) 5322 ) 5323 5324 def space_sql(self: Generator, expression: exp.Space) -> str: 5325 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5326 5327 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5328 return f"BUILD {self.sql(expression, 'this')}" 5329 5330 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5331 method = self.sql(expression, "method") 5332 kind = expression.args.get("kind") 5333 if not kind: 5334 return f"REFRESH {method}" 5335 5336 every = self.sql(expression, "every") 5337 unit = self.sql(expression, "unit") 5338 every = f" EVERY {every} {unit}" if every else "" 5339 starts = self.sql(expression, "starts") 5340 starts = f" STARTS {starts}" if starts else "" 5341 5342 return f"REFRESH {method} ON {kind}{every}{starts}" 5343 5344 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5345 self.unsupported("The model!attribute syntax is not supported") 5346 return "" 5347 5348 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5349 return self.func("DIRECTORY", expression.this)
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether to format the produced SQL string. Default: False.
- identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether to normalize identifiers to lowercase. Default: False.
- pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
- indent: The indentation size in a formatted string. For example, this affects the
indentation of subqueries and filters under a
WHEREclause. Default: 2. - normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
- unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, Type[sqlglot.dialects.Dialect], NoneType] = None)
739 def __init__( 740 self, 741 pretty: t.Optional[bool] = None, 742 identify: str | bool = False, 743 normalize: bool = False, 744 pad: int = 2, 745 indent: int = 2, 746 normalize_functions: t.Optional[str | bool] = None, 747 unsupported_level: ErrorLevel = ErrorLevel.WARN, 748 max_unsupported: int = 3, 749 leading_comma: bool = False, 750 max_text_width: int = 80, 751 comments: bool = True, 752 dialect: DialectType = None, 753 ): 754 import sqlglot 755 from sqlglot.dialects import Dialect 756 757 self.pretty = pretty if pretty is not None else sqlglot.pretty 758 self.identify = identify 759 self.normalize = normalize 760 self.pad = pad 761 self._indent = indent 762 self.unsupported_level = unsupported_level 763 self.max_unsupported = max_unsupported 764 self.leading_comma = leading_comma 765 self.max_text_width = max_text_width 766 self.comments = comments 767 self.dialect = Dialect.get_or_raise(dialect) 768 769 # This is both a Dialect property and a Generator argument, so we prioritize the latter 770 self.normalize_functions = ( 771 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 772 ) 773 774 self.unsupported_messages: t.List[str] = [] 775 self._escaped_quote_end: str = ( 776 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 777 ) 778 self._escaped_byte_quote_end: str = ( 779 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 780 if self.dialect.BYTE_END 781 else "" 782 ) 783 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 784 785 self._next_name = name_sequence("_t") 786 787 self._identifier_start = self.dialect.IDENTIFIER_START 788 self._identifier_end = self.dialect.IDENTIFIER_END 789 790 self._quote_json_path_key_using_brackets = True
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] =
{<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Uuid'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WeekStart'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.JSONPathRoot'>, <class 'sqlglot.expressions.JSONPathRecursive'>, <class 'sqlglot.expressions.JSONPathKey'>, <class 'sqlglot.expressions.JSONPathWildcard'>, <class 'sqlglot.expressions.JSONPathFilter'>, <class 'sqlglot.expressions.JSONPathUnion'>, <class 'sqlglot.expressions.JSONPathSubscript'>, <class 'sqlglot.expressions.JSONPathSelector'>, <class 'sqlglot.expressions.JSONPathSlice'>, <class 'sqlglot.expressions.JSONPathScript'>}
TYPE_MAPPING =
{<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TIME_PART_SINGULARS =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
PROPERTIES_LOCATION =
{<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Command'>, <class 'sqlglot.expressions.Create'>, <class 'sqlglot.expressions.Describe'>, <class 'sqlglot.expressions.Delete'>, <class 'sqlglot.expressions.Drop'>, <class 'sqlglot.expressions.From'>, <class 'sqlglot.expressions.Insert'>, <class 'sqlglot.expressions.Join'>, <class 'sqlglot.expressions.MultitableInserts'>, <class 'sqlglot.expressions.Order'>, <class 'sqlglot.expressions.Group'>, <class 'sqlglot.expressions.Having'>, <class 'sqlglot.expressions.Select'>, <class 'sqlglot.expressions.SetOperation'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES =
{<Type.CHAR: 'CHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.NCHAR: 'NCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>}
792 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 793 """ 794 Generates the SQL string corresponding to the given syntax tree. 795 796 Args: 797 expression: The syntax tree. 798 copy: Whether to copy the expression. The generator performs mutations so 799 it is safer to copy. 800 801 Returns: 802 The SQL string corresponding to `expression`. 803 """ 804 if copy: 805 expression = expression.copy() 806 807 expression = self.preprocess(expression) 808 809 self.unsupported_messages = [] 810 sql = self.sql(expression).strip() 811 812 if self.pretty: 813 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 814 815 if self.unsupported_level == ErrorLevel.IGNORE: 816 return sql 817 818 if self.unsupported_level == ErrorLevel.WARN: 819 for msg in self.unsupported_messages: 820 logger.warning(msg) 821 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 822 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 823 824 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:
826 def preprocess(self, expression: exp.Expression) -> exp.Expression: 827 """Apply generic preprocessing transformations to a given expression.""" 828 expression = self._move_ctes_to_top_level(expression) 829 830 if self.ENSURE_BOOLS: 831 from sqlglot.transforms import ensure_bools 832 833 expression = ensure_bools(expression) 834 835 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
859 def sanitize_comment(self, comment: str) -> str: 860 comment = " " + comment if comment[0].strip() else comment 861 comment = comment + " " if comment[-1].strip() else comment 862 863 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 864 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 865 comment = comment.replace("*/", "* /") 866 867 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
869 def maybe_comment( 870 self, 871 sql: str, 872 expression: t.Optional[exp.Expression] = None, 873 comments: t.Optional[t.List[str]] = None, 874 separated: bool = False, 875 ) -> str: 876 comments = ( 877 ((expression and expression.comments) if comments is None else comments) # type: ignore 878 if self.comments 879 else None 880 ) 881 882 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 883 return sql 884 885 comments_sql = " ".join( 886 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 887 ) 888 889 if not comments_sql: 890 return sql 891 892 comments_sql = self._replace_line_breaks(comments_sql) 893 894 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 895 return ( 896 f"{self.sep()}{comments_sql}{sql}" 897 if not sql or sql[0].isspace() 898 else f"{comments_sql}{self.sep()}{sql}" 899 ) 900 901 return f"{sql} {comments_sql}"
903 def wrap(self, expression: exp.Expression | str) -> str: 904 this_sql = ( 905 self.sql(expression) 906 if isinstance(expression, exp.UNWRAPPED_QUERIES) 907 else self.sql(expression, "this") 908 ) 909 if not this_sql: 910 return "()" 911 912 this_sql = self.indent(this_sql, level=1, pad=0) 913 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:
929 def indent( 930 self, 931 sql: str, 932 level: int = 0, 933 pad: t.Optional[int] = None, 934 skip_first: bool = False, 935 skip_last: bool = False, 936 ) -> str: 937 if not self.pretty or not sql: 938 return sql 939 940 pad = self.pad if pad is None else pad 941 lines = sql.split("\n") 942 943 return "\n".join( 944 ( 945 line 946 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 947 else f"{' ' * (level * self._indent + pad)}{line}" 948 ) 949 for i, line in enumerate(lines) 950 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
952 def sql( 953 self, 954 expression: t.Optional[str | exp.Expression], 955 key: t.Optional[str] = None, 956 comment: bool = True, 957 ) -> str: 958 if not expression: 959 return "" 960 961 if isinstance(expression, str): 962 return expression 963 964 if key: 965 value = expression.args.get(key) 966 if value: 967 return self.sql(value) 968 return "" 969 970 transform = self.TRANSFORMS.get(expression.__class__) 971 972 if callable(transform): 973 sql = transform(self, expression) 974 elif isinstance(expression, exp.Expression): 975 exp_handler_name = f"{expression.key}_sql" 976 977 if hasattr(self, exp_handler_name): 978 sql = getattr(self, exp_handler_name)(expression) 979 elif isinstance(expression, exp.Func): 980 sql = self.function_fallback_sql(expression) 981 elif isinstance(expression, exp.Property): 982 sql = self.property_sql(expression) 983 else: 984 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 985 else: 986 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 987 988 return self.maybe_comment(sql, expression) if self.comments and comment else sql
995 def cache_sql(self, expression: exp.Cache) -> str: 996 lazy = " LAZY" if expression.args.get("lazy") else "" 997 table = self.sql(expression, "this") 998 options = expression.args.get("options") 999 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1000 sql = self.sql(expression, "expression") 1001 sql = f" AS{self.sep()}{sql}" if sql else "" 1002 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1003 return self.prepend_ctes(expression, sql)
1005 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1006 if isinstance(expression.parent, exp.Cast): 1007 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1008 default = "DEFAULT " if expression.args.get("default") else "" 1009 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1023 def column_sql(self, expression: exp.Column) -> str: 1024 join_mark = " (+)" if expression.args.get("join_mark") else "" 1025 1026 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1027 join_mark = "" 1028 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1029 1030 return f"{self.column_parts(expression)}{join_mark}"
1038 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1039 column = self.sql(expression, "this") 1040 kind = self.sql(expression, "kind") 1041 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1042 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1043 kind = f"{sep}{kind}" if kind else "" 1044 constraints = f" {constraints}" if constraints else "" 1045 position = self.sql(expression, "position") 1046 position = f" {position}" if position else "" 1047 1048 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1049 kind = "" 1050 1051 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1058 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1059 this = self.sql(expression, "this") 1060 if expression.args.get("not_null"): 1061 persisted = " PERSISTED NOT NULL" 1062 elif expression.args.get("persisted"): 1063 persisted = " PERSISTED" 1064 else: 1065 persisted = "" 1066 1067 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1080 def generatedasidentitycolumnconstraint_sql( 1081 self, expression: exp.GeneratedAsIdentityColumnConstraint 1082 ) -> str: 1083 this = "" 1084 if expression.this is not None: 1085 on_null = " ON NULL" if expression.args.get("on_null") else "" 1086 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1087 1088 start = expression.args.get("start") 1089 start = f"START WITH {start}" if start else "" 1090 increment = expression.args.get("increment") 1091 increment = f" INCREMENT BY {increment}" if increment else "" 1092 minvalue = expression.args.get("minvalue") 1093 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1094 maxvalue = expression.args.get("maxvalue") 1095 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1096 cycle = expression.args.get("cycle") 1097 cycle_sql = "" 1098 1099 if cycle is not None: 1100 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1101 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1102 1103 sequence_opts = "" 1104 if start or increment or cycle_sql: 1105 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1106 sequence_opts = f" ({sequence_opts.strip()})" 1107 1108 expr = self.sql(expression, "expression") 1109 expr = f"({expr})" if expr else "IDENTITY" 1110 1111 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1113 def generatedasrowcolumnconstraint_sql( 1114 self, expression: exp.GeneratedAsRowColumnConstraint 1115 ) -> str: 1116 start = "START" if expression.args.get("start") else "END" 1117 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1118 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:
1128 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1129 desc = expression.args.get("desc") 1130 if desc is not None: 1131 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1132 options = self.expressions(expression, key="options", flat=True, sep=" ") 1133 options = f" {options}" if options else "" 1134 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1136 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1137 this = self.sql(expression, "this") 1138 this = f" {this}" if this else "" 1139 index_type = expression.args.get("index_type") 1140 index_type = f" USING {index_type}" if index_type else "" 1141 on_conflict = self.sql(expression, "on_conflict") 1142 on_conflict = f" {on_conflict}" if on_conflict else "" 1143 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1144 options = self.expressions(expression, key="options", flat=True, sep=" ") 1145 options = f" {options}" if options else "" 1146 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1151 def create_sql(self, expression: exp.Create) -> str: 1152 kind = self.sql(expression, "kind") 1153 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1154 properties = expression.args.get("properties") 1155 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1156 1157 this = self.createable_sql(expression, properties_locs) 1158 1159 properties_sql = "" 1160 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1161 exp.Properties.Location.POST_WITH 1162 ): 1163 props_ast = exp.Properties( 1164 expressions=[ 1165 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1166 *properties_locs[exp.Properties.Location.POST_WITH], 1167 ] 1168 ) 1169 props_ast.parent = expression 1170 properties_sql = self.sql(props_ast) 1171 1172 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1173 properties_sql = self.sep() + properties_sql 1174 elif not self.pretty: 1175 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1176 properties_sql = f" {properties_sql}" 1177 1178 begin = " BEGIN" if expression.args.get("begin") else "" 1179 end = " END" if expression.args.get("end") else "" 1180 1181 expression_sql = self.sql(expression, "expression") 1182 if expression_sql: 1183 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1184 1185 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1186 postalias_props_sql = "" 1187 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1188 postalias_props_sql = self.properties( 1189 exp.Properties( 1190 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1191 ), 1192 wrapped=False, 1193 ) 1194 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1195 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1196 1197 postindex_props_sql = "" 1198 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1199 postindex_props_sql = self.properties( 1200 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1201 wrapped=False, 1202 prefix=" ", 1203 ) 1204 1205 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1206 indexes = f" {indexes}" if indexes else "" 1207 index_sql = indexes + postindex_props_sql 1208 1209 replace = " OR REPLACE" if expression.args.get("replace") else "" 1210 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1211 unique = " UNIQUE" if expression.args.get("unique") else "" 1212 1213 clustered = expression.args.get("clustered") 1214 if clustered is None: 1215 clustered_sql = "" 1216 elif clustered: 1217 clustered_sql = " CLUSTERED COLUMNSTORE" 1218 else: 1219 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1220 1221 postcreate_props_sql = "" 1222 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1223 postcreate_props_sql = self.properties( 1224 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1225 sep=" ", 1226 prefix=" ", 1227 wrapped=False, 1228 ) 1229 1230 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1231 1232 postexpression_props_sql = "" 1233 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1234 postexpression_props_sql = self.properties( 1235 exp.Properties( 1236 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1237 ), 1238 sep=" ", 1239 prefix=" ", 1240 wrapped=False, 1241 ) 1242 1243 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1244 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1245 no_schema_binding = ( 1246 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1247 ) 1248 1249 clone = self.sql(expression, "clone") 1250 clone = f" {clone}" if clone else "" 1251 1252 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1253 properties_expression = f"{expression_sql}{properties_sql}" 1254 else: 1255 properties_expression = f"{properties_sql}{expression_sql}" 1256 1257 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1258 return self.prepend_ctes(expression, expression_sql)
1260 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1261 start = self.sql(expression, "start") 1262 start = f"START WITH {start}" if start else "" 1263 increment = self.sql(expression, "increment") 1264 increment = f" INCREMENT BY {increment}" if increment else "" 1265 minvalue = self.sql(expression, "minvalue") 1266 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1267 maxvalue = self.sql(expression, "maxvalue") 1268 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1269 owned = self.sql(expression, "owned") 1270 owned = f" OWNED BY {owned}" if owned else "" 1271 1272 cache = expression.args.get("cache") 1273 if cache is None: 1274 cache_str = "" 1275 elif cache is True: 1276 cache_str = " CACHE" 1277 else: 1278 cache_str = f" CACHE {cache}" 1279 1280 options = self.expressions(expression, key="options", flat=True, sep=" ") 1281 options = f" {options}" if options else "" 1282 1283 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1285 def clone_sql(self, expression: exp.Clone) -> str: 1286 this = self.sql(expression, "this") 1287 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1288 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1289 return f"{shallow}{keyword} {this}"
1291 def describe_sql(self, expression: exp.Describe) -> str: 1292 style = expression.args.get("style") 1293 style = f" {style}" if style else "" 1294 partition = self.sql(expression, "partition") 1295 partition = f" {partition}" if partition else "" 1296 format = self.sql(expression, "format") 1297 format = f" {format}" if format else "" 1298 1299 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1311 def with_sql(self, expression: exp.With) -> str: 1312 sql = self.expressions(expression, flat=True) 1313 recursive = ( 1314 "RECURSIVE " 1315 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1316 else "" 1317 ) 1318 search = self.sql(expression, "search") 1319 search = f" {search}" if search else "" 1320 1321 return f"WITH {recursive}{sql}{search}"
1323 def cte_sql(self, expression: exp.CTE) -> str: 1324 alias = expression.args.get("alias") 1325 if alias: 1326 alias.add_comments(expression.pop_comments()) 1327 1328 alias_sql = self.sql(expression, "alias") 1329 1330 materialized = expression.args.get("materialized") 1331 if materialized is False: 1332 materialized = "NOT MATERIALIZED " 1333 elif materialized: 1334 materialized = "MATERIALIZED " 1335 1336 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1338 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1339 alias = self.sql(expression, "this") 1340 columns = self.expressions(expression, key="columns", flat=True) 1341 columns = f"({columns})" if columns else "" 1342 1343 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1344 columns = "" 1345 self.unsupported("Named columns are not supported in table alias.") 1346 1347 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1348 alias = self._next_name() 1349 1350 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1358 def hexstring_sql( 1359 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1360 ) -> str: 1361 this = self.sql(expression, "this") 1362 is_integer_type = expression.args.get("is_integer") 1363 1364 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1365 not self.dialect.HEX_START and not binary_function_repr 1366 ): 1367 # Integer representation will be returned if: 1368 # - The read dialect treats the hex value as integer literal but not the write 1369 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1370 return f"{int(this, 16)}" 1371 1372 if not is_integer_type: 1373 # Read dialect treats the hex value as BINARY/BLOB 1374 if binary_function_repr: 1375 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1376 return self.func(binary_function_repr, exp.Literal.string(this)) 1377 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1378 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1379 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1380 1381 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1383 def bytestring_sql(self, expression: exp.ByteString) -> str: 1384 this = self.sql(expression, "this") 1385 if self.dialect.BYTE_START: 1386 escaped_byte_string = self.escape_str( 1387 this, 1388 escape_backslash=False, 1389 delimiter=self.dialect.BYTE_END, 1390 escaped_delimiter=self._escaped_byte_quote_end, 1391 ) 1392 return f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1393 return this
1395 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1396 this = self.sql(expression, "this") 1397 escape = expression.args.get("escape") 1398 1399 if self.dialect.UNICODE_START: 1400 escape_substitute = r"\\\1" 1401 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1402 else: 1403 escape_substitute = r"\\u\1" 1404 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1405 1406 if escape: 1407 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1408 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1409 else: 1410 escape_pattern = ESCAPED_UNICODE_RE 1411 escape_sql = "" 1412 1413 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1414 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1415 1416 return f"{left_quote}{this}{right_quote}{escape_sql}"
1418 def rawstring_sql(self, expression: exp.RawString) -> str: 1419 string = expression.this 1420 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1421 string = string.replace("\\", "\\\\") 1422 1423 string = self.escape_str(string, escape_backslash=False) 1424 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1432 def datatype_sql(self, expression: exp.DataType) -> str: 1433 nested = "" 1434 values = "" 1435 interior = self.expressions(expression, flat=True) 1436 1437 type_value = expression.this 1438 if type_value in self.UNSUPPORTED_TYPES: 1439 self.unsupported( 1440 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1441 ) 1442 1443 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1444 type_sql = self.sql(expression, "kind") 1445 else: 1446 type_sql = ( 1447 self.TYPE_MAPPING.get(type_value, type_value.value) 1448 if isinstance(type_value, exp.DataType.Type) 1449 else type_value 1450 ) 1451 1452 if interior: 1453 if expression.args.get("nested"): 1454 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1455 if expression.args.get("values") is not None: 1456 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1457 values = self.expressions(expression, key="values", flat=True) 1458 values = f"{delimiters[0]}{values}{delimiters[1]}" 1459 elif type_value == exp.DataType.Type.INTERVAL: 1460 nested = f" {interior}" 1461 else: 1462 nested = f"({interior})" 1463 1464 type_sql = f"{type_sql}{nested}{values}" 1465 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1466 exp.DataType.Type.TIMETZ, 1467 exp.DataType.Type.TIMESTAMPTZ, 1468 ): 1469 type_sql = f"{type_sql} WITH TIME ZONE" 1470 1471 return type_sql
1473 def directory_sql(self, expression: exp.Directory) -> str: 1474 local = "LOCAL " if expression.args.get("local") else "" 1475 row_format = self.sql(expression, "row_format") 1476 row_format = f" {row_format}" if row_format else "" 1477 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1479 def delete_sql(self, expression: exp.Delete) -> str: 1480 this = self.sql(expression, "this") 1481 this = f" FROM {this}" if this else "" 1482 using = self.sql(expression, "using") 1483 using = f" USING {using}" if using else "" 1484 cluster = self.sql(expression, "cluster") 1485 cluster = f" {cluster}" if cluster else "" 1486 where = self.sql(expression, "where") 1487 returning = self.sql(expression, "returning") 1488 limit = self.sql(expression, "limit") 1489 tables = self.expressions(expression, key="tables") 1490 tables = f" {tables}" if tables else "" 1491 if self.RETURNING_END: 1492 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1493 else: 1494 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1495 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1497 def drop_sql(self, expression: exp.Drop) -> str: 1498 this = self.sql(expression, "this") 1499 expressions = self.expressions(expression, flat=True) 1500 expressions = f" ({expressions})" if expressions else "" 1501 kind = expression.args["kind"] 1502 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1503 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1504 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1505 on_cluster = self.sql(expression, "cluster") 1506 on_cluster = f" {on_cluster}" if on_cluster else "" 1507 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1508 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1509 cascade = " CASCADE" if expression.args.get("cascade") else "" 1510 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1511 purge = " PURGE" if expression.args.get("purge") else "" 1512 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1514 def set_operation(self, expression: exp.SetOperation) -> str: 1515 op_type = type(expression) 1516 op_name = op_type.key.upper() 1517 1518 distinct = expression.args.get("distinct") 1519 if ( 1520 distinct is False 1521 and op_type in (exp.Except, exp.Intersect) 1522 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1523 ): 1524 self.unsupported(f"{op_name} ALL is not supported") 1525 1526 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1527 1528 if distinct is None: 1529 distinct = default_distinct 1530 if distinct is None: 1531 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1532 1533 if distinct is default_distinct: 1534 distinct_or_all = "" 1535 else: 1536 distinct_or_all = " DISTINCT" if distinct else " ALL" 1537 1538 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1539 side_kind = f"{side_kind} " if side_kind else "" 1540 1541 by_name = " BY NAME" if expression.args.get("by_name") else "" 1542 on = self.expressions(expression, key="on", flat=True) 1543 on = f" ON ({on})" if on else "" 1544 1545 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1547 def set_operations(self, expression: exp.SetOperation) -> str: 1548 if not self.SET_OP_MODIFIERS: 1549 limit = expression.args.get("limit") 1550 order = expression.args.get("order") 1551 1552 if limit or order: 1553 select = self._move_ctes_to_top_level( 1554 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1555 ) 1556 1557 if limit: 1558 select = select.limit(limit.pop(), copy=False) 1559 if order: 1560 select = select.order_by(order.pop(), copy=False) 1561 return self.sql(select) 1562 1563 sqls: t.List[str] = [] 1564 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1565 1566 while stack: 1567 node = stack.pop() 1568 1569 if isinstance(node, exp.SetOperation): 1570 stack.append(node.expression) 1571 stack.append( 1572 self.maybe_comment( 1573 self.set_operation(node), comments=node.comments, separated=True 1574 ) 1575 ) 1576 stack.append(node.this) 1577 else: 1578 sqls.append(self.sql(node)) 1579 1580 this = self.sep().join(sqls) 1581 this = self.query_modifiers(expression, this) 1582 return self.prepend_ctes(expression, this)
1584 def fetch_sql(self, expression: exp.Fetch) -> str: 1585 direction = expression.args.get("direction") 1586 direction = f" {direction}" if direction else "" 1587 count = self.sql(expression, "count") 1588 count = f" {count}" if count else "" 1589 limit_options = self.sql(expression, "limit_options") 1590 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1591 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1593 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1594 percent = " PERCENT" if expression.args.get("percent") else "" 1595 rows = " ROWS" if expression.args.get("rows") else "" 1596 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1597 if not with_ties and rows: 1598 with_ties = " ONLY" 1599 return f"{percent}{rows}{with_ties}"
1601 def filter_sql(self, expression: exp.Filter) -> str: 1602 if self.AGGREGATE_FILTER_SUPPORTED: 1603 this = self.sql(expression, "this") 1604 where = self.sql(expression, "expression").strip() 1605 return f"{this} FILTER({where})" 1606 1607 agg = expression.this 1608 agg_arg = agg.this 1609 cond = expression.expression.this 1610 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1611 return self.sql(agg)
1620 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1621 using = self.sql(expression, "using") 1622 using = f" USING {using}" if using else "" 1623 columns = self.expressions(expression, key="columns", flat=True) 1624 columns = f"({columns})" if columns else "" 1625 partition_by = self.expressions(expression, key="partition_by", flat=True) 1626 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1627 where = self.sql(expression, "where") 1628 include = self.expressions(expression, key="include", flat=True) 1629 if include: 1630 include = f" INCLUDE ({include})" 1631 with_storage = self.expressions(expression, key="with_storage", flat=True) 1632 with_storage = f" WITH ({with_storage})" if with_storage else "" 1633 tablespace = self.sql(expression, "tablespace") 1634 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1635 on = self.sql(expression, "on") 1636 on = f" ON {on}" if on else "" 1637 1638 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1640 def index_sql(self, expression: exp.Index) -> str: 1641 unique = "UNIQUE " if expression.args.get("unique") else "" 1642 primary = "PRIMARY " if expression.args.get("primary") else "" 1643 amp = "AMP " if expression.args.get("amp") else "" 1644 name = self.sql(expression, "this") 1645 name = f"{name} " if name else "" 1646 table = self.sql(expression, "table") 1647 table = f"{self.INDEX_ON} {table}" if table else "" 1648 1649 index = "INDEX " if not table else "" 1650 1651 params = self.sql(expression, "params") 1652 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1654 def identifier_sql(self, expression: exp.Identifier) -> str: 1655 text = expression.name 1656 lower = text.lower() 1657 text = lower if self.normalize and not expression.quoted else text 1658 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1659 if ( 1660 expression.quoted 1661 or self.dialect.can_identify(text, self.identify) 1662 or lower in self.RESERVED_KEYWORDS 1663 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1664 ): 1665 text = f"{self._identifier_start}{text}{self._identifier_end}" 1666 return text
1681 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1682 input_format = self.sql(expression, "input_format") 1683 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1684 output_format = self.sql(expression, "output_format") 1685 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1686 return self.sep().join((input_format, output_format))
1696 def properties_sql(self, expression: exp.Properties) -> str: 1697 root_properties = [] 1698 with_properties = [] 1699 1700 for p in expression.expressions: 1701 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1702 if p_loc == exp.Properties.Location.POST_WITH: 1703 with_properties.append(p) 1704 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1705 root_properties.append(p) 1706 1707 root_props_ast = exp.Properties(expressions=root_properties) 1708 root_props_ast.parent = expression.parent 1709 1710 with_props_ast = exp.Properties(expressions=with_properties) 1711 with_props_ast.parent = expression.parent 1712 1713 root_props = self.root_properties(root_props_ast) 1714 with_props = self.with_properties(with_props_ast) 1715 1716 if root_props and with_props and not self.pretty: 1717 with_props = " " + with_props 1718 1719 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1726 def properties( 1727 self, 1728 properties: exp.Properties, 1729 prefix: str = "", 1730 sep: str = ", ", 1731 suffix: str = "", 1732 wrapped: bool = True, 1733 ) -> str: 1734 if properties.expressions: 1735 expressions = self.expressions(properties, sep=sep, indent=False) 1736 if expressions: 1737 expressions = self.wrap(expressions) if wrapped else expressions 1738 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1739 return ""
1744 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1745 properties_locs = defaultdict(list) 1746 for p in properties.expressions: 1747 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1748 if p_loc != exp.Properties.Location.UNSUPPORTED: 1749 properties_locs[p_loc].append(p) 1750 else: 1751 self.unsupported(f"Unsupported property {p.key}") 1752 1753 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1760 def property_sql(self, expression: exp.Property) -> str: 1761 property_cls = expression.__class__ 1762 if property_cls == exp.Property: 1763 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1764 1765 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1766 if not property_name: 1767 self.unsupported(f"Unsupported property {expression.key}") 1768 1769 return f"{property_name}={self.sql(expression, 'this')}"
1771 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1772 if self.SUPPORTS_CREATE_TABLE_LIKE: 1773 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1774 options = f" {options}" if options else "" 1775 1776 like = f"LIKE {self.sql(expression, 'this')}{options}" 1777 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1778 like = f"({like})" 1779 1780 return like 1781 1782 if expression.expressions: 1783 self.unsupported("Transpilation of LIKE property options is unsupported") 1784 1785 select = exp.select("*").from_(expression.this).limit(0) 1786 return f"AS {self.sql(select)}"
1793 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1794 no = "NO " if expression.args.get("no") else "" 1795 local = expression.args.get("local") 1796 local = f"{local} " if local else "" 1797 dual = "DUAL " if expression.args.get("dual") else "" 1798 before = "BEFORE " if expression.args.get("before") else "" 1799 after = "AFTER " if expression.args.get("after") else "" 1800 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1816 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1817 if expression.args.get("no"): 1818 return "NO MERGEBLOCKRATIO" 1819 if expression.args.get("default"): 1820 return "DEFAULT MERGEBLOCKRATIO" 1821 1822 percent = " PERCENT" if expression.args.get("percent") else "" 1823 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1825 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1826 default = expression.args.get("default") 1827 minimum = expression.args.get("minimum") 1828 maximum = expression.args.get("maximum") 1829 if default or minimum or maximum: 1830 if default: 1831 prop = "DEFAULT" 1832 elif minimum: 1833 prop = "MINIMUM" 1834 else: 1835 prop = "MAXIMUM" 1836 return f"{prop} DATABLOCKSIZE" 1837 units = expression.args.get("units") 1838 units = f" {units}" if units else "" 1839 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1841 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1842 autotemp = expression.args.get("autotemp") 1843 always = expression.args.get("always") 1844 default = expression.args.get("default") 1845 manual = expression.args.get("manual") 1846 never = expression.args.get("never") 1847 1848 if autotemp is not None: 1849 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1850 elif always: 1851 prop = "ALWAYS" 1852 elif default: 1853 prop = "DEFAULT" 1854 elif manual: 1855 prop = "MANUAL" 1856 elif never: 1857 prop = "NEVER" 1858 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1860 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1861 no = expression.args.get("no") 1862 no = " NO" if no else "" 1863 concurrent = expression.args.get("concurrent") 1864 concurrent = " CONCURRENT" if concurrent else "" 1865 target = self.sql(expression, "target") 1866 target = f" {target}" if target else "" 1867 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1869 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1870 if isinstance(expression.this, list): 1871 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1872 if expression.this: 1873 modulus = self.sql(expression, "this") 1874 remainder = self.sql(expression, "expression") 1875 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1876 1877 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1878 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1879 return f"FROM ({from_expressions}) TO ({to_expressions})"
1881 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1882 this = self.sql(expression, "this") 1883 1884 for_values_or_default = expression.expression 1885 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1886 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1887 else: 1888 for_values_or_default = " DEFAULT" 1889 1890 return f"PARTITION OF {this}{for_values_or_default}"
1892 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1893 kind = expression.args.get("kind") 1894 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1895 for_or_in = expression.args.get("for_or_in") 1896 for_or_in = f" {for_or_in}" if for_or_in else "" 1897 lock_type = expression.args.get("lock_type") 1898 override = " OVERRIDE" if expression.args.get("override") else "" 1899 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1901 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1902 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1903 statistics = expression.args.get("statistics") 1904 statistics_sql = "" 1905 if statistics is not None: 1906 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1907 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1909 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1910 this = self.sql(expression, "this") 1911 this = f"HISTORY_TABLE={this}" if this else "" 1912 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1913 data_consistency = ( 1914 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1915 ) 1916 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1917 retention_period = ( 1918 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1919 ) 1920 1921 if this: 1922 on_sql = self.func("ON", this, data_consistency, retention_period) 1923 else: 1924 on_sql = "ON" if expression.args.get("on") else "OFF" 1925 1926 sql = f"SYSTEM_VERSIONING={on_sql}" 1927 1928 return f"WITH({sql})" if expression.args.get("with") else sql
1930 def insert_sql(self, expression: exp.Insert) -> str: 1931 hint = self.sql(expression, "hint") 1932 overwrite = expression.args.get("overwrite") 1933 1934 if isinstance(expression.this, exp.Directory): 1935 this = " OVERWRITE" if overwrite else " INTO" 1936 else: 1937 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1938 1939 stored = self.sql(expression, "stored") 1940 stored = f" {stored}" if stored else "" 1941 alternative = expression.args.get("alternative") 1942 alternative = f" OR {alternative}" if alternative else "" 1943 ignore = " IGNORE" if expression.args.get("ignore") else "" 1944 is_function = expression.args.get("is_function") 1945 if is_function: 1946 this = f"{this} FUNCTION" 1947 this = f"{this} {self.sql(expression, 'this')}" 1948 1949 exists = " IF EXISTS" if expression.args.get("exists") else "" 1950 where = self.sql(expression, "where") 1951 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1952 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1953 on_conflict = self.sql(expression, "conflict") 1954 on_conflict = f" {on_conflict}" if on_conflict else "" 1955 by_name = " BY NAME" if expression.args.get("by_name") else "" 1956 returning = self.sql(expression, "returning") 1957 1958 if self.RETURNING_END: 1959 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1960 else: 1961 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1962 1963 partition_by = self.sql(expression, "partition") 1964 partition_by = f" {partition_by}" if partition_by else "" 1965 settings = self.sql(expression, "settings") 1966 settings = f" {settings}" if settings else "" 1967 1968 source = self.sql(expression, "source") 1969 source = f"TABLE {source}" if source else "" 1970 1971 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1972 return self.prepend_ctes(expression, sql)
1990 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1991 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1992 1993 constraint = self.sql(expression, "constraint") 1994 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1995 1996 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1997 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1998 action = self.sql(expression, "action") 1999 2000 expressions = self.expressions(expression, flat=True) 2001 if expressions: 2002 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2003 expressions = f" {set_keyword}{expressions}" 2004 2005 where = self.sql(expression, "where") 2006 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
2011 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2012 fields = self.sql(expression, "fields") 2013 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2014 escaped = self.sql(expression, "escaped") 2015 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2016 items = self.sql(expression, "collection_items") 2017 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2018 keys = self.sql(expression, "map_keys") 2019 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2020 lines = self.sql(expression, "lines") 2021 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2022 null = self.sql(expression, "null") 2023 null = f" NULL DEFINED AS {null}" if null else "" 2024 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2052 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2053 table = self.table_parts(expression) 2054 only = "ONLY " if expression.args.get("only") else "" 2055 partition = self.sql(expression, "partition") 2056 partition = f" {partition}" if partition else "" 2057 version = self.sql(expression, "version") 2058 version = f" {version}" if version else "" 2059 alias = self.sql(expression, "alias") 2060 alias = f"{sep}{alias}" if alias else "" 2061 2062 sample = self.sql(expression, "sample") 2063 if self.dialect.ALIAS_POST_TABLESAMPLE: 2064 sample_pre_alias = sample 2065 sample_post_alias = "" 2066 else: 2067 sample_pre_alias = "" 2068 sample_post_alias = sample 2069 2070 hints = self.expressions(expression, key="hints", sep=" ") 2071 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2072 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2073 joins = self.indent( 2074 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2075 ) 2076 laterals = self.expressions(expression, key="laterals", sep="") 2077 2078 file_format = self.sql(expression, "format") 2079 if file_format: 2080 pattern = self.sql(expression, "pattern") 2081 pattern = f", PATTERN => {pattern}" if pattern else "" 2082 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2083 2084 ordinality = expression.args.get("ordinality") or "" 2085 if ordinality: 2086 ordinality = f" WITH ORDINALITY{alias}" 2087 alias = "" 2088 2089 when = self.sql(expression, "when") 2090 if when: 2091 table = f"{table} {when}" 2092 2093 changes = self.sql(expression, "changes") 2094 changes = f" {changes}" if changes else "" 2095 2096 rows_from = self.expressions(expression, key="rows_from") 2097 if rows_from: 2098 table = f"ROWS FROM {self.wrap(rows_from)}" 2099 2100 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2102 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2103 table = self.func("TABLE", expression.this) 2104 alias = self.sql(expression, "alias") 2105 alias = f" AS {alias}" if alias else "" 2106 sample = self.sql(expression, "sample") 2107 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2108 joins = self.indent( 2109 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2110 ) 2111 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2113 def tablesample_sql( 2114 self, 2115 expression: exp.TableSample, 2116 tablesample_keyword: t.Optional[str] = None, 2117 ) -> str: 2118 method = self.sql(expression, "method") 2119 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2120 numerator = self.sql(expression, "bucket_numerator") 2121 denominator = self.sql(expression, "bucket_denominator") 2122 field = self.sql(expression, "bucket_field") 2123 field = f" ON {field}" if field else "" 2124 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2125 seed = self.sql(expression, "seed") 2126 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2127 2128 size = self.sql(expression, "size") 2129 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2130 size = f"{size} ROWS" 2131 2132 percent = self.sql(expression, "percent") 2133 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2134 percent = f"{percent} PERCENT" 2135 2136 expr = f"{bucket}{percent}{size}" 2137 if self.TABLESAMPLE_REQUIRES_PARENS: 2138 expr = f"({expr})" 2139 2140 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2142 def pivot_sql(self, expression: exp.Pivot) -> str: 2143 expressions = self.expressions(expression, flat=True) 2144 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2145 2146 group = self.sql(expression, "group") 2147 2148 if expression.this: 2149 this = self.sql(expression, "this") 2150 if not expressions: 2151 return f"UNPIVOT {this}" 2152 2153 on = f"{self.seg('ON')} {expressions}" 2154 into = self.sql(expression, "into") 2155 into = f"{self.seg('INTO')} {into}" if into else "" 2156 using = self.expressions(expression, key="using", flat=True) 2157 using = f"{self.seg('USING')} {using}" if using else "" 2158 return f"{direction} {this}{on}{into}{using}{group}" 2159 2160 alias = self.sql(expression, "alias") 2161 alias = f" AS {alias}" if alias else "" 2162 2163 fields = self.expressions( 2164 expression, 2165 "fields", 2166 sep=" ", 2167 dynamic=True, 2168 new_line=True, 2169 skip_first=True, 2170 skip_last=True, 2171 ) 2172 2173 include_nulls = expression.args.get("include_nulls") 2174 if include_nulls is not None: 2175 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2176 else: 2177 nulls = "" 2178 2179 default_on_null = self.sql(expression, "default_on_null") 2180 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2181 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2192 def update_sql(self, expression: exp.Update) -> str: 2193 this = self.sql(expression, "this") 2194 set_sql = self.expressions(expression, flat=True) 2195 from_sql = self.sql(expression, "from") 2196 where_sql = self.sql(expression, "where") 2197 returning = self.sql(expression, "returning") 2198 order = self.sql(expression, "order") 2199 limit = self.sql(expression, "limit") 2200 if self.RETURNING_END: 2201 expression_sql = f"{from_sql}{where_sql}{returning}" 2202 else: 2203 expression_sql = f"{returning}{from_sql}{where_sql}" 2204 options = self.expressions(expression, key="options") 2205 options = f" OPTION({options})" if options else "" 2206 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}{options}" 2207 return self.prepend_ctes(expression, sql)
2209 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2210 values_as_table = values_as_table and self.VALUES_AS_TABLE 2211 2212 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2213 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2214 args = self.expressions(expression) 2215 alias = self.sql(expression, "alias") 2216 values = f"VALUES{self.seg('')}{args}" 2217 values = ( 2218 f"({values})" 2219 if self.WRAP_DERIVED_VALUES 2220 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2221 else values 2222 ) 2223 return f"{values} AS {alias}" if alias else values 2224 2225 # Converts `VALUES...` expression into a series of select unions. 2226 alias_node = expression.args.get("alias") 2227 column_names = alias_node and alias_node.columns 2228 2229 selects: t.List[exp.Query] = [] 2230 2231 for i, tup in enumerate(expression.expressions): 2232 row = tup.expressions 2233 2234 if i == 0 and column_names: 2235 row = [ 2236 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2237 ] 2238 2239 selects.append(exp.Select(expressions=row)) 2240 2241 if self.pretty: 2242 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2243 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2244 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2245 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2246 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2247 2248 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2249 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2250 return f"({unions}){alias}"
2255 @unsupported_args("expressions") 2256 def into_sql(self, expression: exp.Into) -> str: 2257 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2258 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2259 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2276 def group_sql(self, expression: exp.Group) -> str: 2277 group_by_all = expression.args.get("all") 2278 if group_by_all is True: 2279 modifier = " ALL" 2280 elif group_by_all is False: 2281 modifier = " DISTINCT" 2282 else: 2283 modifier = "" 2284 2285 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2286 2287 grouping_sets = self.expressions(expression, key="grouping_sets") 2288 cube = self.expressions(expression, key="cube") 2289 rollup = self.expressions(expression, key="rollup") 2290 2291 groupings = csv( 2292 self.seg(grouping_sets) if grouping_sets else "", 2293 self.seg(cube) if cube else "", 2294 self.seg(rollup) if rollup else "", 2295 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2296 sep=self.GROUPINGS_SEP, 2297 ) 2298 2299 if ( 2300 expression.expressions 2301 and groupings 2302 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2303 ): 2304 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2305 2306 return f"{group_by}{groupings}"
2312 def connect_sql(self, expression: exp.Connect) -> str: 2313 start = self.sql(expression, "start") 2314 start = self.seg(f"START WITH {start}") if start else "" 2315 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2316 connect = self.sql(expression, "connect") 2317 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2318 return start + connect
2323 def join_sql(self, expression: exp.Join) -> str: 2324 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2325 side = None 2326 else: 2327 side = expression.side 2328 2329 op_sql = " ".join( 2330 op 2331 for op in ( 2332 expression.method, 2333 "GLOBAL" if expression.args.get("global") else None, 2334 side, 2335 expression.kind, 2336 expression.hint if self.JOIN_HINTS else None, 2337 ) 2338 if op 2339 ) 2340 match_cond = self.sql(expression, "match_condition") 2341 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2342 on_sql = self.sql(expression, "on") 2343 using = expression.args.get("using") 2344 2345 if not on_sql and using: 2346 on_sql = csv(*(self.sql(column) for column in using)) 2347 2348 this = expression.this 2349 this_sql = self.sql(this) 2350 2351 exprs = self.expressions(expression) 2352 if exprs: 2353 this_sql = f"{this_sql},{self.seg(exprs)}" 2354 2355 if on_sql: 2356 on_sql = self.indent(on_sql, skip_first=True) 2357 space = self.seg(" " * self.pad) if self.pretty else " " 2358 if using: 2359 on_sql = f"{space}USING ({on_sql})" 2360 else: 2361 on_sql = f"{space}ON {on_sql}" 2362 elif not op_sql: 2363 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2364 return f" {this_sql}" 2365 2366 return f", {this_sql}" 2367 2368 if op_sql != "STRAIGHT_JOIN": 2369 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2370 2371 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2372 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:
2379 def lateral_op(self, expression: exp.Lateral) -> str: 2380 cross_apply = expression.args.get("cross_apply") 2381 2382 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2383 if cross_apply is True: 2384 op = "INNER JOIN " 2385 elif cross_apply is False: 2386 op = "LEFT JOIN " 2387 else: 2388 op = "" 2389 2390 return f"{op}LATERAL"
2392 def lateral_sql(self, expression: exp.Lateral) -> str: 2393 this = self.sql(expression, "this") 2394 2395 if expression.args.get("view"): 2396 alias = expression.args["alias"] 2397 columns = self.expressions(alias, key="columns", flat=True) 2398 table = f" {alias.name}" if alias.name else "" 2399 columns = f" AS {columns}" if columns else "" 2400 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2401 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2402 2403 alias = self.sql(expression, "alias") 2404 alias = f" AS {alias}" if alias else "" 2405 2406 ordinality = expression.args.get("ordinality") or "" 2407 if ordinality: 2408 ordinality = f" WITH ORDINALITY{alias}" 2409 alias = "" 2410 2411 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2413 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2414 this = self.sql(expression, "this") 2415 2416 args = [ 2417 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2418 for e in (expression.args.get(k) for k in ("offset", "expression")) 2419 if e 2420 ] 2421 2422 args_sql = ", ".join(self.sql(e) for e in args) 2423 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2424 expressions = self.expressions(expression, flat=True) 2425 limit_options = self.sql(expression, "limit_options") 2426 expressions = f" BY {expressions}" if expressions else "" 2427 2428 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2430 def offset_sql(self, expression: exp.Offset) -> str: 2431 this = self.sql(expression, "this") 2432 value = expression.expression 2433 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2434 expressions = self.expressions(expression, flat=True) 2435 expressions = f" BY {expressions}" if expressions else "" 2436 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2438 def setitem_sql(self, expression: exp.SetItem) -> str: 2439 kind = self.sql(expression, "kind") 2440 kind = f"{kind} " if kind else "" 2441 this = self.sql(expression, "this") 2442 expressions = self.expressions(expression) 2443 collate = self.sql(expression, "collate") 2444 collate = f" COLLATE {collate}" if collate else "" 2445 global_ = "GLOBAL " if expression.args.get("global") else "" 2446 return f"{global_}{kind}{this}{expressions}{collate}"
2453 def queryband_sql(self, expression: exp.QueryBand) -> str: 2454 this = self.sql(expression, "this") 2455 update = " UPDATE" if expression.args.get("update") else "" 2456 scope = self.sql(expression, "scope") 2457 scope = f" FOR {scope}" if scope else "" 2458 2459 return f"QUERY_BAND = {this}{update}{scope}"
2464 def lock_sql(self, expression: exp.Lock) -> str: 2465 if not self.LOCKING_READS_SUPPORTED: 2466 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2467 return "" 2468 2469 update = expression.args["update"] 2470 key = expression.args.get("key") 2471 if update: 2472 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2473 else: 2474 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2475 expressions = self.expressions(expression, flat=True) 2476 expressions = f" OF {expressions}" if expressions else "" 2477 wait = expression.args.get("wait") 2478 2479 if wait is not None: 2480 if isinstance(wait, exp.Literal): 2481 wait = f" WAIT {self.sql(wait)}" 2482 else: 2483 wait = " NOWAIT" if wait else " SKIP LOCKED" 2484 2485 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str( self, text: str, escape_backslash: bool = True, delimiter: Optional[str] = None, escaped_delimiter: Optional[str] = None) -> str:
2493 def escape_str( 2494 self, 2495 text: str, 2496 escape_backslash: bool = True, 2497 delimiter: t.Optional[str] = None, 2498 escaped_delimiter: t.Optional[str] = None, 2499 ) -> str: 2500 if self.dialect.ESCAPED_SEQUENCES: 2501 to_escaped = self.dialect.ESCAPED_SEQUENCES 2502 text = "".join( 2503 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2504 ) 2505 2506 delimiter = delimiter or self.dialect.QUOTE_END 2507 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2508 2509 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2511 def loaddata_sql(self, expression: exp.LoadData) -> str: 2512 local = " LOCAL" if expression.args.get("local") else "" 2513 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2514 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2515 this = f" INTO TABLE {self.sql(expression, 'this')}" 2516 partition = self.sql(expression, "partition") 2517 partition = f" {partition}" if partition else "" 2518 input_format = self.sql(expression, "input_format") 2519 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2520 serde = self.sql(expression, "serde") 2521 serde = f" SERDE {serde}" if serde else "" 2522 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2530 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2531 this = self.sql(expression, "this") 2532 this = f"{this} " if this else this 2533 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2534 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2536 def withfill_sql(self, expression: exp.WithFill) -> str: 2537 from_sql = self.sql(expression, "from") 2538 from_sql = f" FROM {from_sql}" if from_sql else "" 2539 to_sql = self.sql(expression, "to") 2540 to_sql = f" TO {to_sql}" if to_sql else "" 2541 step_sql = self.sql(expression, "step") 2542 step_sql = f" STEP {step_sql}" if step_sql else "" 2543 interpolated_values = [ 2544 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2545 if isinstance(e, exp.Alias) 2546 else self.sql(e, "this") 2547 for e in expression.args.get("interpolate") or [] 2548 ] 2549 interpolate = ( 2550 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2551 ) 2552 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2563 def ordered_sql(self, expression: exp.Ordered) -> str: 2564 desc = expression.args.get("desc") 2565 asc = not desc 2566 2567 nulls_first = expression.args.get("nulls_first") 2568 nulls_last = not nulls_first 2569 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2570 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2571 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2572 2573 this = self.sql(expression, "this") 2574 2575 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2576 nulls_sort_change = "" 2577 if nulls_first and ( 2578 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2579 ): 2580 nulls_sort_change = " NULLS FIRST" 2581 elif ( 2582 nulls_last 2583 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2584 and not nulls_are_last 2585 ): 2586 nulls_sort_change = " NULLS LAST" 2587 2588 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2589 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2590 window = expression.find_ancestor(exp.Window, exp.Select) 2591 if isinstance(window, exp.Window) and window.args.get("spec"): 2592 self.unsupported( 2593 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2594 ) 2595 nulls_sort_change = "" 2596 elif self.NULL_ORDERING_SUPPORTED is False and ( 2597 (asc and nulls_sort_change == " NULLS LAST") 2598 or (desc and nulls_sort_change == " NULLS FIRST") 2599 ): 2600 # BigQuery does not allow these ordering/nulls combinations when used under 2601 # an aggregation func or under a window containing one 2602 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2603 2604 if isinstance(ancestor, exp.Window): 2605 ancestor = ancestor.this 2606 if isinstance(ancestor, exp.AggFunc): 2607 self.unsupported( 2608 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2609 ) 2610 nulls_sort_change = "" 2611 elif self.NULL_ORDERING_SUPPORTED is None: 2612 if expression.this.is_int: 2613 self.unsupported( 2614 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2615 ) 2616 elif not isinstance(expression.this, exp.Rand): 2617 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2618 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2619 nulls_sort_change = "" 2620 2621 with_fill = self.sql(expression, "with_fill") 2622 with_fill = f" {with_fill}" if with_fill else "" 2623 2624 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2634 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2635 partition = self.partition_by_sql(expression) 2636 order = self.sql(expression, "order") 2637 measures = self.expressions(expression, key="measures") 2638 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2639 rows = self.sql(expression, "rows") 2640 rows = self.seg(rows) if rows else "" 2641 after = self.sql(expression, "after") 2642 after = self.seg(after) if after else "" 2643 pattern = self.sql(expression, "pattern") 2644 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2645 definition_sqls = [ 2646 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2647 for definition in expression.args.get("define", []) 2648 ] 2649 definitions = self.expressions(sqls=definition_sqls) 2650 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2651 body = "".join( 2652 ( 2653 partition, 2654 order, 2655 measures, 2656 rows, 2657 after, 2658 pattern, 2659 define, 2660 ) 2661 ) 2662 alias = self.sql(expression, "alias") 2663 alias = f" {alias}" if alias else "" 2664 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2666 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2667 limit = expression.args.get("limit") 2668 2669 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2670 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2671 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2672 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2673 2674 return csv( 2675 *sqls, 2676 *[self.sql(join) for join in expression.args.get("joins") or []], 2677 self.sql(expression, "match"), 2678 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2679 self.sql(expression, "prewhere"), 2680 self.sql(expression, "where"), 2681 self.sql(expression, "connect"), 2682 self.sql(expression, "group"), 2683 self.sql(expression, "having"), 2684 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2685 self.sql(expression, "order"), 2686 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2687 *self.after_limit_modifiers(expression), 2688 self.options_modifier(expression), 2689 self.for_modifiers(expression), 2690 sep="", 2691 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2705 def offset_limit_modifiers( 2706 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2707 ) -> t.List[str]: 2708 return [ 2709 self.sql(expression, "offset") if fetch else self.sql(limit), 2710 self.sql(limit) if fetch else self.sql(expression, "offset"), 2711 ]
2718 def select_sql(self, expression: exp.Select) -> str: 2719 into = expression.args.get("into") 2720 if not self.SUPPORTS_SELECT_INTO and into: 2721 into.pop() 2722 2723 hint = self.sql(expression, "hint") 2724 distinct = self.sql(expression, "distinct") 2725 distinct = f" {distinct}" if distinct else "" 2726 kind = self.sql(expression, "kind") 2727 2728 limit = expression.args.get("limit") 2729 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2730 top = self.limit_sql(limit, top=True) 2731 limit.pop() 2732 else: 2733 top = "" 2734 2735 expressions = self.expressions(expression) 2736 2737 if kind: 2738 if kind in self.SELECT_KINDS: 2739 kind = f" AS {kind}" 2740 else: 2741 if kind == "STRUCT": 2742 expressions = self.expressions( 2743 sqls=[ 2744 self.sql( 2745 exp.Struct( 2746 expressions=[ 2747 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2748 if isinstance(e, exp.Alias) 2749 else e 2750 for e in expression.expressions 2751 ] 2752 ) 2753 ) 2754 ] 2755 ) 2756 kind = "" 2757 2758 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2759 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2760 2761 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2762 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2763 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2764 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2765 sql = self.query_modifiers( 2766 expression, 2767 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2768 self.sql(expression, "into", comment=False), 2769 self.sql(expression, "from", comment=False), 2770 ) 2771 2772 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2773 if expression.args.get("with"): 2774 sql = self.maybe_comment(sql, expression) 2775 expression.pop_comments() 2776 2777 sql = self.prepend_ctes(expression, sql) 2778 2779 if not self.SUPPORTS_SELECT_INTO and into: 2780 if into.args.get("temporary"): 2781 table_kind = " TEMPORARY" 2782 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2783 table_kind = " UNLOGGED" 2784 else: 2785 table_kind = "" 2786 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2787 2788 return sql
2800 def star_sql(self, expression: exp.Star) -> str: 2801 except_ = self.expressions(expression, key="except", flat=True) 2802 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2803 replace = self.expressions(expression, key="replace", flat=True) 2804 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2805 rename = self.expressions(expression, key="rename", flat=True) 2806 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2807 return f"*{except_}{replace}{rename}"
2823 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2824 alias = self.sql(expression, "alias") 2825 alias = f"{sep}{alias}" if alias else "" 2826 sample = self.sql(expression, "sample") 2827 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2828 alias = f"{sample}{alias}" 2829 2830 # Set to None so it's not generated again by self.query_modifiers() 2831 expression.set("sample", None) 2832 2833 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2834 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2835 return self.prepend_ctes(expression, sql)
2841 def unnest_sql(self, expression: exp.Unnest) -> str: 2842 args = self.expressions(expression, flat=True) 2843 2844 alias = expression.args.get("alias") 2845 offset = expression.args.get("offset") 2846 2847 if self.UNNEST_WITH_ORDINALITY: 2848 if alias and isinstance(offset, exp.Expression): 2849 alias.append("columns", offset) 2850 2851 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2852 columns = alias.columns 2853 alias = self.sql(columns[0]) if columns else "" 2854 else: 2855 alias = self.sql(alias) 2856 2857 alias = f" AS {alias}" if alias else alias 2858 if self.UNNEST_WITH_ORDINALITY: 2859 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2860 else: 2861 if isinstance(offset, exp.Expression): 2862 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2863 elif offset: 2864 suffix = f"{alias} WITH OFFSET" 2865 else: 2866 suffix = alias 2867 2868 return f"UNNEST({args}){suffix}"
2877 def window_sql(self, expression: exp.Window) -> str: 2878 this = self.sql(expression, "this") 2879 partition = self.partition_by_sql(expression) 2880 order = expression.args.get("order") 2881 order = self.order_sql(order, flat=True) if order else "" 2882 spec = self.sql(expression, "spec") 2883 alias = self.sql(expression, "alias") 2884 over = self.sql(expression, "over") or "OVER" 2885 2886 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2887 2888 first = expression.args.get("first") 2889 if first is None: 2890 first = "" 2891 else: 2892 first = "FIRST" if first else "LAST" 2893 2894 if not partition and not order and not spec and alias: 2895 return f"{this} {alias}" 2896 2897 args = self.format_args( 2898 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2899 ) 2900 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2906 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2907 kind = self.sql(expression, "kind") 2908 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2909 end = ( 2910 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2911 or "CURRENT ROW" 2912 ) 2913 2914 window_spec = f"{kind} BETWEEN {start} AND {end}" 2915 2916 exclude = self.sql(expression, "exclude") 2917 if exclude: 2918 if self.SUPPORTS_WINDOW_EXCLUDE: 2919 window_spec += f" EXCLUDE {exclude}" 2920 else: 2921 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2922 2923 return window_spec
2930 def between_sql(self, expression: exp.Between) -> str: 2931 this = self.sql(expression, "this") 2932 low = self.sql(expression, "low") 2933 high = self.sql(expression, "high") 2934 symmetric = expression.args.get("symmetric") 2935 2936 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2937 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2938 2939 flag = ( 2940 " SYMMETRIC" 2941 if symmetric 2942 else " ASYMMETRIC" 2943 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2944 else "" # silently drop ASYMMETRIC – semantics identical 2945 ) 2946 return f"{this} BETWEEN{flag} {low} AND {high}"
def
bracket_offset_expressions( self, expression: sqlglot.expressions.Bracket, index_offset: Optional[int] = None) -> List[sqlglot.expressions.Expression]:
2948 def bracket_offset_expressions( 2949 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2950 ) -> t.List[exp.Expression]: 2951 return apply_index_offset( 2952 expression.this, 2953 expression.expressions, 2954 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2955 dialect=self.dialect, 2956 )
2969 def any_sql(self, expression: exp.Any) -> str: 2970 this = self.sql(expression, "this") 2971 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2972 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2973 this = self.wrap(this) 2974 return f"ANY{this}" 2975 return f"ANY {this}"
2980 def case_sql(self, expression: exp.Case) -> str: 2981 this = self.sql(expression, "this") 2982 statements = [f"CASE {this}" if this else "CASE"] 2983 2984 for e in expression.args["ifs"]: 2985 statements.append(f"WHEN {self.sql(e, 'this')}") 2986 statements.append(f"THEN {self.sql(e, 'true')}") 2987 2988 default = self.sql(expression, "default") 2989 2990 if default: 2991 statements.append(f"ELSE {default}") 2992 2993 statements.append("END") 2994 2995 if self.pretty and self.too_wide(statements): 2996 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2997 2998 return " ".join(statements)
3010 def extract_sql(self, expression: exp.Extract) -> str: 3011 from sqlglot.dialects.dialect import map_date_part 3012 3013 this = ( 3014 map_date_part(expression.this, self.dialect) 3015 if self.NORMALIZE_EXTRACT_DATE_PARTS 3016 else expression.this 3017 ) 3018 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3019 expression_sql = self.sql(expression, "expression") 3020 3021 return f"EXTRACT({this_sql} FROM {expression_sql})"
3023 def trim_sql(self, expression: exp.Trim) -> str: 3024 trim_type = self.sql(expression, "position") 3025 3026 if trim_type == "LEADING": 3027 func_name = "LTRIM" 3028 elif trim_type == "TRAILING": 3029 func_name = "RTRIM" 3030 else: 3031 func_name = "TRIM" 3032 3033 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]:
3035 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3036 args = expression.expressions 3037 if isinstance(expression, exp.ConcatWs): 3038 args = args[1:] # Skip the delimiter 3039 3040 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3041 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3042 3043 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3044 3045 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3046 if not e.type: 3047 from sqlglot.optimizer.annotate_types import annotate_types 3048 3049 e = annotate_types(e, dialect=self.dialect) 3050 3051 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3052 return e 3053 3054 return exp.func("coalesce", e, exp.Literal.string("")) 3055 3056 args = [_wrap_with_coalesce(e) for e in args] 3057 3058 return args
3060 def concat_sql(self, expression: exp.Concat) -> str: 3061 expressions = self.convert_concat_args(expression) 3062 3063 # Some dialects don't allow a single-argument CONCAT call 3064 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3065 return self.sql(expressions[0]) 3066 3067 return self.func("CONCAT", *expressions)
3078 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3079 expressions = self.expressions(expression, flat=True) 3080 expressions = f" ({expressions})" if expressions else "" 3081 reference = self.sql(expression, "reference") 3082 reference = f" {reference}" if reference else "" 3083 delete = self.sql(expression, "delete") 3084 delete = f" ON DELETE {delete}" if delete else "" 3085 update = self.sql(expression, "update") 3086 update = f" ON UPDATE {update}" if update else "" 3087 options = self.expressions(expression, key="options", flat=True, sep=" ") 3088 options = f" {options}" if options else "" 3089 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3091 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3092 expressions = self.expressions(expression, flat=True) 3093 include = self.sql(expression, "include") 3094 options = self.expressions(expression, key="options", flat=True, sep=" ") 3095 options = f" {options}" if options else "" 3096 return f"PRIMARY KEY ({expressions}){include}{options}"
3101 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3102 if self.MATCH_AGAINST_TABLE_PREFIX: 3103 expressions = [] 3104 for expr in expression.expressions: 3105 if isinstance(expr, exp.Table): 3106 expressions.append(f"TABLE {self.sql(expr)}") 3107 else: 3108 expressions.append(expr) 3109 else: 3110 expressions = expression.expressions 3111 3112 modifier = expression.args.get("modifier") 3113 modifier = f" {modifier}" if modifier else "" 3114 return ( 3115 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3116 )
3121 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3122 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3123 3124 if expression.args.get("escape"): 3125 path = self.escape_str(path) 3126 3127 if self.QUOTE_JSON_PATH: 3128 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3129 3130 return path
3132 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3133 if isinstance(expression, exp.JSONPathPart): 3134 transform = self.TRANSFORMS.get(expression.__class__) 3135 if not callable(transform): 3136 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3137 return "" 3138 3139 return transform(self, expression) 3140 3141 if isinstance(expression, int): 3142 return str(expression) 3143 3144 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3145 escaped = expression.replace("'", "\\'") 3146 escaped = f"\\'{expression}\\'" 3147 else: 3148 escaped = expression.replace('"', '\\"') 3149 escaped = f'"{escaped}"' 3150 3151 return escaped
3156 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3157 # Output the Teradata column FORMAT override. 3158 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3159 this = self.sql(expression, "this") 3160 fmt = self.sql(expression, "format") 3161 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3163 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3164 null_handling = expression.args.get("null_handling") 3165 null_handling = f" {null_handling}" if null_handling else "" 3166 3167 unique_keys = expression.args.get("unique_keys") 3168 if unique_keys is not None: 3169 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3170 else: 3171 unique_keys = "" 3172 3173 return_type = self.sql(expression, "return_type") 3174 return_type = f" RETURNING {return_type}" if return_type else "" 3175 encoding = self.sql(expression, "encoding") 3176 encoding = f" ENCODING {encoding}" if encoding else "" 3177 3178 return self.func( 3179 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3180 *expression.expressions, 3181 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3182 )
3187 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3188 null_handling = expression.args.get("null_handling") 3189 null_handling = f" {null_handling}" if null_handling else "" 3190 return_type = self.sql(expression, "return_type") 3191 return_type = f" RETURNING {return_type}" if return_type else "" 3192 strict = " STRICT" if expression.args.get("strict") else "" 3193 return self.func( 3194 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3195 )
3197 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3198 this = self.sql(expression, "this") 3199 order = self.sql(expression, "order") 3200 null_handling = expression.args.get("null_handling") 3201 null_handling = f" {null_handling}" if null_handling else "" 3202 return_type = self.sql(expression, "return_type") 3203 return_type = f" RETURNING {return_type}" if return_type else "" 3204 strict = " STRICT" if expression.args.get("strict") else "" 3205 return self.func( 3206 "JSON_ARRAYAGG", 3207 this, 3208 suffix=f"{order}{null_handling}{return_type}{strict})", 3209 )
3211 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3212 path = self.sql(expression, "path") 3213 path = f" PATH {path}" if path else "" 3214 nested_schema = self.sql(expression, "nested_schema") 3215 3216 if nested_schema: 3217 return f"NESTED{path} {nested_schema}" 3218 3219 this = self.sql(expression, "this") 3220 kind = self.sql(expression, "kind") 3221 kind = f" {kind}" if kind else "" 3222 3223 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3224 return f"{this}{kind}{path}{ordinality}"
3229 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3230 this = self.sql(expression, "this") 3231 path = self.sql(expression, "path") 3232 path = f", {path}" if path else "" 3233 error_handling = expression.args.get("error_handling") 3234 error_handling = f" {error_handling}" if error_handling else "" 3235 empty_handling = expression.args.get("empty_handling") 3236 empty_handling = f" {empty_handling}" if empty_handling else "" 3237 schema = self.sql(expression, "schema") 3238 return self.func( 3239 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3240 )
3242 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3243 this = self.sql(expression, "this") 3244 kind = self.sql(expression, "kind") 3245 path = self.sql(expression, "path") 3246 path = f" {path}" if path else "" 3247 as_json = " AS JSON" if expression.args.get("as_json") else "" 3248 return f"{this} {kind}{path}{as_json}"
3250 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3251 this = self.sql(expression, "this") 3252 path = self.sql(expression, "path") 3253 path = f", {path}" if path else "" 3254 expressions = self.expressions(expression) 3255 with_ = ( 3256 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3257 if expressions 3258 else "" 3259 ) 3260 return f"OPENJSON({this}{path}){with_}"
3262 def in_sql(self, expression: exp.In) -> str: 3263 query = expression.args.get("query") 3264 unnest = expression.args.get("unnest") 3265 field = expression.args.get("field") 3266 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3267 3268 if query: 3269 in_sql = self.sql(query) 3270 elif unnest: 3271 in_sql = self.in_unnest_op(unnest) 3272 elif field: 3273 in_sql = self.sql(field) 3274 else: 3275 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3276 3277 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3282 def interval_sql(self, expression: exp.Interval) -> str: 3283 unit_expression = expression.args.get("unit") 3284 unit = self.sql(unit_expression) if unit_expression else "" 3285 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3286 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3287 unit = f" {unit}" if unit else "" 3288 3289 if self.SINGLE_STRING_INTERVAL: 3290 this = expression.this.name if expression.this else "" 3291 if this: 3292 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3293 return f"INTERVAL '{this}'{unit}" 3294 return f"INTERVAL '{this}{unit}'" 3295 return f"INTERVAL{unit}" 3296 3297 this = self.sql(expression, "this") 3298 if this: 3299 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3300 this = f" {this}" if unwrapped else f" ({this})" 3301 3302 return f"INTERVAL{this}{unit}"
3307 def reference_sql(self, expression: exp.Reference) -> str: 3308 this = self.sql(expression, "this") 3309 expressions = self.expressions(expression, flat=True) 3310 expressions = f"({expressions})" if expressions else "" 3311 options = self.expressions(expression, key="options", flat=True, sep=" ") 3312 options = f" {options}" if options else "" 3313 return f"REFERENCES {this}{expressions}{options}"
3315 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3316 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3317 parent = expression.parent 3318 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3319 return self.func( 3320 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3321 )
3341 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3342 alias = expression.args["alias"] 3343 3344 parent = expression.parent 3345 pivot = parent and parent.parent 3346 3347 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3348 identifier_alias = isinstance(alias, exp.Identifier) 3349 literal_alias = isinstance(alias, exp.Literal) 3350 3351 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3352 alias.replace(exp.Literal.string(alias.output_name)) 3353 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3354 alias.replace(exp.to_identifier(alias.output_name)) 3355 3356 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:
3394 def connector_sql( 3395 self, 3396 expression: exp.Connector, 3397 op: str, 3398 stack: t.Optional[t.List[str | exp.Expression]] = None, 3399 ) -> str: 3400 if stack is not None: 3401 if expression.expressions: 3402 stack.append(self.expressions(expression, sep=f" {op} ")) 3403 else: 3404 stack.append(expression.right) 3405 if expression.comments and self.comments: 3406 for comment in expression.comments: 3407 if comment: 3408 op += f" /*{self.sanitize_comment(comment)}*/" 3409 stack.extend((op, expression.left)) 3410 return op 3411 3412 stack = [expression] 3413 sqls: t.List[str] = [] 3414 ops = set() 3415 3416 while stack: 3417 node = stack.pop() 3418 if isinstance(node, exp.Connector): 3419 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3420 else: 3421 sql = self.sql(node) 3422 if sqls and sqls[-1] in ops: 3423 sqls[-1] += f" {sql}" 3424 else: 3425 sqls.append(sql) 3426 3427 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3428 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3448 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3449 format_sql = self.sql(expression, "format") 3450 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3451 to_sql = self.sql(expression, "to") 3452 to_sql = f" {to_sql}" if to_sql else "" 3453 action = self.sql(expression, "action") 3454 action = f" {action}" if action else "" 3455 default = self.sql(expression, "default") 3456 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3457 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3471 def comment_sql(self, expression: exp.Comment) -> str: 3472 this = self.sql(expression, "this") 3473 kind = expression.args["kind"] 3474 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3475 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3476 expression_sql = self.sql(expression, "expression") 3477 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3479 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3480 this = self.sql(expression, "this") 3481 delete = " DELETE" if expression.args.get("delete") else "" 3482 recompress = self.sql(expression, "recompress") 3483 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3484 to_disk = self.sql(expression, "to_disk") 3485 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3486 to_volume = self.sql(expression, "to_volume") 3487 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3488 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3490 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3491 where = self.sql(expression, "where") 3492 group = self.sql(expression, "group") 3493 aggregates = self.expressions(expression, key="aggregates") 3494 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3495 3496 if not (where or group or aggregates) and len(expression.expressions) == 1: 3497 return f"TTL {self.expressions(expression, flat=True)}" 3498 3499 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3518 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3519 this = self.sql(expression, "this") 3520 3521 dtype = self.sql(expression, "dtype") 3522 if dtype: 3523 collate = self.sql(expression, "collate") 3524 collate = f" COLLATE {collate}" if collate else "" 3525 using = self.sql(expression, "using") 3526 using = f" USING {using}" if using else "" 3527 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3528 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3529 3530 default = self.sql(expression, "default") 3531 if default: 3532 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3533 3534 comment = self.sql(expression, "comment") 3535 if comment: 3536 return f"ALTER COLUMN {this} COMMENT {comment}" 3537 3538 visible = expression.args.get("visible") 3539 if visible: 3540 return f"ALTER COLUMN {this} SET {visible}" 3541 3542 allow_null = expression.args.get("allow_null") 3543 drop = expression.args.get("drop") 3544 3545 if not drop and not allow_null: 3546 self.unsupported("Unsupported ALTER COLUMN syntax") 3547 3548 if allow_null is not None: 3549 keyword = "DROP" if drop else "SET" 3550 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3551 3552 return f"ALTER COLUMN {this} DROP DEFAULT"
3568 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3569 compound = " COMPOUND" if expression.args.get("compound") else "" 3570 this = self.sql(expression, "this") 3571 expressions = self.expressions(expression, flat=True) 3572 expressions = f"({expressions})" if expressions else "" 3573 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.AlterRename, include_to: bool = True) -> str:
3575 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3576 if not self.RENAME_TABLE_WITH_DB: 3577 # Remove db from tables 3578 expression = expression.transform( 3579 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3580 ).assert_is(exp.AlterRename) 3581 this = self.sql(expression, "this") 3582 to_kw = " TO" if include_to else "" 3583 return f"RENAME{to_kw} {this}"
3598 def alter_sql(self, expression: exp.Alter) -> str: 3599 actions = expression.args["actions"] 3600 3601 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3602 actions[0], exp.ColumnDef 3603 ): 3604 actions_sql = self.expressions(expression, key="actions", flat=True) 3605 actions_sql = f"ADD {actions_sql}" 3606 else: 3607 actions_list = [] 3608 for action in actions: 3609 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3610 action_sql = self.add_column_sql(action) 3611 else: 3612 action_sql = self.sql(action) 3613 if isinstance(action, exp.Query): 3614 action_sql = f"AS {action_sql}" 3615 3616 actions_list.append(action_sql) 3617 3618 actions_sql = self.format_args(*actions_list).lstrip("\n") 3619 3620 exists = " IF EXISTS" if expression.args.get("exists") else "" 3621 on_cluster = self.sql(expression, "cluster") 3622 on_cluster = f" {on_cluster}" if on_cluster else "" 3623 only = " ONLY" if expression.args.get("only") else "" 3624 options = self.expressions(expression, key="options") 3625 options = f", {options}" if options else "" 3626 kind = self.sql(expression, "kind") 3627 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3628 check = " WITH CHECK" if expression.args.get("check") else "" 3629 cascade = ( 3630 " CASCADE" 3631 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 3632 else "" 3633 ) 3634 this = self.sql(expression, "this") 3635 this = f" {this}" if this else "" 3636 3637 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
3644 def add_column_sql(self, expression: exp.Expression) -> str: 3645 sql = self.sql(expression) 3646 if isinstance(expression, exp.Schema): 3647 column_text = " COLUMNS" 3648 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3649 column_text = " COLUMN" 3650 else: 3651 column_text = "" 3652 3653 return f"ADD{column_text} {sql}"
3663 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3664 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3665 location = self.sql(expression, "location") 3666 location = f" {location}" if location else "" 3667 return f"ADD {exists}{self.sql(expression.this)}{location}"
3669 def distinct_sql(self, expression: exp.Distinct) -> str: 3670 this = self.expressions(expression, flat=True) 3671 3672 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3673 case = exp.case() 3674 for arg in expression.expressions: 3675 case = case.when(arg.is_(exp.null()), exp.null()) 3676 this = self.sql(case.else_(f"({this})")) 3677 3678 this = f" {this}" if this else "" 3679 3680 on = self.sql(expression, "on") 3681 on = f" ON {on}" if on else "" 3682 return f"DISTINCT{this}{on}"
3711 def div_sql(self, expression: exp.Div) -> str: 3712 l, r = expression.left, expression.right 3713 3714 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3715 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3716 3717 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3718 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3719 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3720 3721 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3722 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3723 return self.sql( 3724 exp.cast( 3725 l / r, 3726 to=exp.DataType.Type.BIGINT, 3727 ) 3728 ) 3729 3730 return self.binary(expression, "/")
3847 def log_sql(self, expression: exp.Log) -> str: 3848 this = expression.this 3849 expr = expression.expression 3850 3851 if self.dialect.LOG_BASE_FIRST is False: 3852 this, expr = expr, this 3853 elif self.dialect.LOG_BASE_FIRST is None and expr: 3854 if this.name in ("2", "10"): 3855 return self.func(f"LOG{this.name}", expr) 3856 3857 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3858 3859 return self.func("LOG", this, expr)
3868 def binary(self, expression: exp.Binary, op: str) -> str: 3869 sqls: t.List[str] = [] 3870 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3871 binary_type = type(expression) 3872 3873 while stack: 3874 node = stack.pop() 3875 3876 if type(node) is binary_type: 3877 op_func = node.args.get("operator") 3878 if op_func: 3879 op = f"OPERATOR({self.sql(op_func)})" 3880 3881 stack.append(node.right) 3882 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3883 stack.append(node.left) 3884 else: 3885 sqls.append(self.sql(node)) 3886 3887 return "".join(sqls)
3896 def function_fallback_sql(self, expression: exp.Func) -> str: 3897 args = [] 3898 3899 for key in expression.arg_types: 3900 arg_value = expression.args.get(key) 3901 3902 if isinstance(arg_value, list): 3903 for value in arg_value: 3904 args.append(value) 3905 elif arg_value is not None: 3906 args.append(arg_value) 3907 3908 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3909 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3910 else: 3911 name = expression.sql_name() 3912 3913 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:
3915 def func( 3916 self, 3917 name: str, 3918 *args: t.Optional[exp.Expression | str], 3919 prefix: str = "(", 3920 suffix: str = ")", 3921 normalize: bool = True, 3922 ) -> str: 3923 name = self.normalize_func(name) if normalize else name 3924 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3926 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3927 arg_sqls = tuple( 3928 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3929 ) 3930 if self.pretty and self.too_wide(arg_sqls): 3931 return self.indent( 3932 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3933 ) 3934 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]:
3939 def format_time( 3940 self, 3941 expression: exp.Expression, 3942 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3943 inverse_time_trie: t.Optional[t.Dict] = None, 3944 ) -> t.Optional[str]: 3945 return format_time( 3946 self.sql(expression, "format"), 3947 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3948 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3949 )
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:
3951 def expressions( 3952 self, 3953 expression: t.Optional[exp.Expression] = None, 3954 key: t.Optional[str] = None, 3955 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3956 flat: bool = False, 3957 indent: bool = True, 3958 skip_first: bool = False, 3959 skip_last: bool = False, 3960 sep: str = ", ", 3961 prefix: str = "", 3962 dynamic: bool = False, 3963 new_line: bool = False, 3964 ) -> str: 3965 expressions = expression.args.get(key or "expressions") if expression else sqls 3966 3967 if not expressions: 3968 return "" 3969 3970 if flat: 3971 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3972 3973 num_sqls = len(expressions) 3974 result_sqls = [] 3975 3976 for i, e in enumerate(expressions): 3977 sql = self.sql(e, comment=False) 3978 if not sql: 3979 continue 3980 3981 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3982 3983 if self.pretty: 3984 if self.leading_comma: 3985 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3986 else: 3987 result_sqls.append( 3988 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3989 ) 3990 else: 3991 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3992 3993 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3994 if new_line: 3995 result_sqls.insert(0, "") 3996 result_sqls.append("") 3997 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3998 else: 3999 result_sql = "".join(result_sqls) 4000 4001 return ( 4002 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4003 if indent 4004 else result_sql 4005 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
4007 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 4008 flat = flat or isinstance(expression.parent, exp.Properties) 4009 expressions_sql = self.expressions(expression, flat=flat) 4010 if flat: 4011 return f"{op} {expressions_sql}" 4012 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4014 def naked_property(self, expression: exp.Property) -> str: 4015 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4016 if not property_name: 4017 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4018 return f"{property_name} {self.sql(expression, 'this')}"
4026 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4027 this = self.sql(expression, "this") 4028 expressions = self.no_identify(self.expressions, expression) 4029 expressions = ( 4030 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4031 ) 4032 return f"{this}{expressions}" if expressions.strip() != "" else this
4042 def when_sql(self, expression: exp.When) -> str: 4043 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4044 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4045 condition = self.sql(expression, "condition") 4046 condition = f" AND {condition}" if condition else "" 4047 4048 then_expression = expression.args.get("then") 4049 if isinstance(then_expression, exp.Insert): 4050 this = self.sql(then_expression, "this") 4051 this = f"INSERT {this}" if this else "INSERT" 4052 then = self.sql(then_expression, "expression") 4053 then = f"{this} VALUES {then}" if then else this 4054 elif isinstance(then_expression, exp.Update): 4055 if isinstance(then_expression.args.get("expressions"), exp.Star): 4056 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4057 else: 4058 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 4059 else: 4060 then = self.sql(then_expression) 4061 return f"WHEN {matched}{source}{condition} THEN {then}"
4066 def merge_sql(self, expression: exp.Merge) -> str: 4067 table = expression.this 4068 table_alias = "" 4069 4070 hints = table.args.get("hints") 4071 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4072 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4073 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4074 4075 this = self.sql(table) 4076 using = f"USING {self.sql(expression, 'using')}" 4077 on = f"ON {self.sql(expression, 'on')}" 4078 whens = self.sql(expression, "whens") 4079 4080 returning = self.sql(expression, "returning") 4081 if returning: 4082 whens = f"{whens}{returning}" 4083 4084 sep = self.sep() 4085 4086 return self.prepend_ctes( 4087 expression, 4088 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4089 )
4095 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4096 if not self.SUPPORTS_TO_NUMBER: 4097 self.unsupported("Unsupported TO_NUMBER function") 4098 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4099 4100 fmt = expression.args.get("format") 4101 if not fmt: 4102 self.unsupported("Conversion format is required for TO_NUMBER") 4103 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4104 4105 return self.func("TO_NUMBER", expression.this, fmt)
4107 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4108 this = self.sql(expression, "this") 4109 kind = self.sql(expression, "kind") 4110 settings_sql = self.expressions(expression, key="settings", sep=" ") 4111 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4112 return f"{this}({kind}{args})"
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
4133 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4134 expressions = self.expressions(expression, flat=True) 4135 expressions = f" {self.wrap(expressions)}" if expressions else "" 4136 buckets = self.sql(expression, "buckets") 4137 kind = self.sql(expression, "kind") 4138 buckets = f" BUCKETS {buckets}" if buckets else "" 4139 order = self.sql(expression, "order") 4140 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4145 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4146 expressions = self.expressions(expression, key="expressions", flat=True) 4147 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4148 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4149 buckets = self.sql(expression, "buckets") 4150 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4152 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4153 this = self.sql(expression, "this") 4154 having = self.sql(expression, "having") 4155 4156 if having: 4157 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4158 4159 return self.func("ANY_VALUE", this)
4161 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4162 transform = self.func("TRANSFORM", *expression.expressions) 4163 row_format_before = self.sql(expression, "row_format_before") 4164 row_format_before = f" {row_format_before}" if row_format_before else "" 4165 record_writer = self.sql(expression, "record_writer") 4166 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4167 using = f" USING {self.sql(expression, 'command_script')}" 4168 schema = self.sql(expression, "schema") 4169 schema = f" AS {schema}" if schema else "" 4170 row_format_after = self.sql(expression, "row_format_after") 4171 row_format_after = f" {row_format_after}" if row_format_after else "" 4172 record_reader = self.sql(expression, "record_reader") 4173 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4174 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4176 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4177 key_block_size = self.sql(expression, "key_block_size") 4178 if key_block_size: 4179 return f"KEY_BLOCK_SIZE = {key_block_size}" 4180 4181 using = self.sql(expression, "using") 4182 if using: 4183 return f"USING {using}" 4184 4185 parser = self.sql(expression, "parser") 4186 if parser: 4187 return f"WITH PARSER {parser}" 4188 4189 comment = self.sql(expression, "comment") 4190 if comment: 4191 return f"COMMENT {comment}" 4192 4193 visible = expression.args.get("visible") 4194 if visible is not None: 4195 return "VISIBLE" if visible else "INVISIBLE" 4196 4197 engine_attr = self.sql(expression, "engine_attr") 4198 if engine_attr: 4199 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4200 4201 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4202 if secondary_engine_attr: 4203 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4204 4205 self.unsupported("Unsupported index constraint option.") 4206 return ""
4212 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4213 kind = self.sql(expression, "kind") 4214 kind = f"{kind} INDEX" if kind else "INDEX" 4215 this = self.sql(expression, "this") 4216 this = f" {this}" if this else "" 4217 index_type = self.sql(expression, "index_type") 4218 index_type = f" USING {index_type}" if index_type else "" 4219 expressions = self.expressions(expression, flat=True) 4220 expressions = f" ({expressions})" if expressions else "" 4221 options = self.expressions(expression, key="options", sep=" ") 4222 options = f" {options}" if options else "" 4223 return f"{kind}{this}{index_type}{expressions}{options}"
4225 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4226 if self.NVL2_SUPPORTED: 4227 return self.function_fallback_sql(expression) 4228 4229 case = exp.Case().when( 4230 expression.this.is_(exp.null()).not_(copy=False), 4231 expression.args["true"], 4232 copy=False, 4233 ) 4234 else_cond = expression.args.get("false") 4235 if else_cond: 4236 case.else_(else_cond, copy=False) 4237 4238 return self.sql(case)
4240 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4241 this = self.sql(expression, "this") 4242 expr = self.sql(expression, "expression") 4243 iterator = self.sql(expression, "iterator") 4244 condition = self.sql(expression, "condition") 4245 condition = f" IF {condition}" if condition else "" 4246 return f"{this} FOR {expr} IN {iterator}{condition}"
4281 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4282 this_sql = self.sql(expression, "this") 4283 if isinstance(expression.this, exp.Table): 4284 this_sql = f"TABLE {this_sql}" 4285 4286 return self.func( 4287 "FEATURES_AT_TIME", 4288 this_sql, 4289 expression.args.get("time"), 4290 expression.args.get("num_rows"), 4291 expression.args.get("ignore_feature_nulls"), 4292 )
4294 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4295 this_sql = self.sql(expression, "this") 4296 if isinstance(expression.this, exp.Table): 4297 this_sql = f"TABLE {this_sql}" 4298 4299 query_table = self.sql(expression, "query_table") 4300 if isinstance(expression.args["query_table"], exp.Table): 4301 query_table = f"TABLE {query_table}" 4302 4303 return self.func( 4304 "VECTOR_SEARCH", 4305 this_sql, 4306 expression.args.get("column_to_search"), 4307 query_table, 4308 expression.args.get("query_column_to_search"), 4309 expression.args.get("top_k"), 4310 expression.args.get("distance_type"), 4311 expression.args.get("options"), 4312 )
4324 def toarray_sql(self, expression: exp.ToArray) -> str: 4325 arg = expression.this 4326 if not arg.type: 4327 from sqlglot.optimizer.annotate_types import annotate_types 4328 4329 arg = annotate_types(arg, dialect=self.dialect) 4330 4331 if arg.is_type(exp.DataType.Type.ARRAY): 4332 return self.sql(arg) 4333 4334 cond_for_null = arg.is_(exp.null()) 4335 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4337 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4338 this = expression.this 4339 time_format = self.format_time(expression) 4340 4341 if time_format: 4342 return self.sql( 4343 exp.cast( 4344 exp.StrToTime(this=this, format=expression.args["format"]), 4345 exp.DataType.Type.TIME, 4346 ) 4347 ) 4348 4349 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4350 return self.sql(this) 4351 4352 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4354 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4355 this = expression.this 4356 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4357 return self.sql(this) 4358 4359 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4361 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4362 this = expression.this 4363 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4364 return self.sql(this) 4365 4366 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4368 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4369 this = expression.this 4370 time_format = self.format_time(expression) 4371 4372 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4373 return self.sql( 4374 exp.cast( 4375 exp.StrToTime(this=this, format=expression.args["format"]), 4376 exp.DataType.Type.DATE, 4377 ) 4378 ) 4379 4380 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4381 return self.sql(this) 4382 4383 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4395 def lastday_sql(self, expression: exp.LastDay) -> str: 4396 if self.LAST_DAY_SUPPORTS_DATE_PART: 4397 return self.function_fallback_sql(expression) 4398 4399 unit = expression.text("unit") 4400 if unit and unit != "MONTH": 4401 self.unsupported("Date parts are not supported in LAST_DAY.") 4402 4403 return self.func("LAST_DAY", expression.this)
4412 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4413 if self.CAN_IMPLEMENT_ARRAY_ANY: 4414 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4415 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4416 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4417 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4418 4419 from sqlglot.dialects import Dialect 4420 4421 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4422 if self.dialect.__class__ != Dialect: 4423 self.unsupported("ARRAY_ANY is unsupported") 4424 4425 return self.function_fallback_sql(expression)
4427 def struct_sql(self, expression: exp.Struct) -> str: 4428 expression.set( 4429 "expressions", 4430 [ 4431 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4432 if isinstance(e, exp.PropertyEQ) 4433 else e 4434 for e in expression.expressions 4435 ], 4436 ) 4437 4438 return self.function_fallback_sql(expression)
4446 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4447 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4448 tables = f" {self.expressions(expression)}" 4449 4450 exists = " IF EXISTS" if expression.args.get("exists") else "" 4451 4452 on_cluster = self.sql(expression, "cluster") 4453 on_cluster = f" {on_cluster}" if on_cluster else "" 4454 4455 identity = self.sql(expression, "identity") 4456 identity = f" {identity} IDENTITY" if identity else "" 4457 4458 option = self.sql(expression, "option") 4459 option = f" {option}" if option else "" 4460 4461 partition = self.sql(expression, "partition") 4462 partition = f" {partition}" if partition else "" 4463 4464 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4468 def convert_sql(self, expression: exp.Convert) -> str: 4469 to = expression.this 4470 value = expression.expression 4471 style = expression.args.get("style") 4472 safe = expression.args.get("safe") 4473 strict = expression.args.get("strict") 4474 4475 if not to or not value: 4476 return "" 4477 4478 # Retrieve length of datatype and override to default if not specified 4479 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4480 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4481 4482 transformed: t.Optional[exp.Expression] = None 4483 cast = exp.Cast if strict else exp.TryCast 4484 4485 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4486 if isinstance(style, exp.Literal) and style.is_int: 4487 from sqlglot.dialects.tsql import TSQL 4488 4489 style_value = style.name 4490 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4491 if not converted_style: 4492 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4493 4494 fmt = exp.Literal.string(converted_style) 4495 4496 if to.this == exp.DataType.Type.DATE: 4497 transformed = exp.StrToDate(this=value, format=fmt) 4498 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4499 transformed = exp.StrToTime(this=value, format=fmt) 4500 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4501 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4502 elif to.this == exp.DataType.Type.TEXT: 4503 transformed = exp.TimeToStr(this=value, format=fmt) 4504 4505 if not transformed: 4506 transformed = cast(this=value, to=to, safe=safe) 4507 4508 return self.sql(transformed)
4576 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4577 option = self.sql(expression, "this") 4578 4579 if expression.expressions: 4580 upper = option.upper() 4581 4582 # Snowflake FILE_FORMAT options are separated by whitespace 4583 sep = " " if upper == "FILE_FORMAT" else ", " 4584 4585 # Databricks copy/format options do not set their list of values with EQ 4586 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4587 values = self.expressions(expression, flat=True, sep=sep) 4588 return f"{option}{op}({values})" 4589 4590 value = self.sql(expression, "expression") 4591 4592 if not value: 4593 return option 4594 4595 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4596 4597 return f"{option}{op}{value}"
4599 def credentials_sql(self, expression: exp.Credentials) -> str: 4600 cred_expr = expression.args.get("credentials") 4601 if isinstance(cred_expr, exp.Literal): 4602 # Redshift case: CREDENTIALS <string> 4603 credentials = self.sql(expression, "credentials") 4604 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4605 else: 4606 # Snowflake case: CREDENTIALS = (...) 4607 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4608 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4609 4610 storage = self.sql(expression, "storage") 4611 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4612 4613 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4614 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4615 4616 iam_role = self.sql(expression, "iam_role") 4617 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4618 4619 region = self.sql(expression, "region") 4620 region = f" REGION {region}" if region else "" 4621 4622 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4624 def copy_sql(self, expression: exp.Copy) -> str: 4625 this = self.sql(expression, "this") 4626 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4627 4628 credentials = self.sql(expression, "credentials") 4629 credentials = self.seg(credentials) if credentials else "" 4630 files = self.expressions(expression, key="files", flat=True) 4631 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4632 4633 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4634 params = self.expressions( 4635 expression, 4636 key="params", 4637 sep=sep, 4638 new_line=True, 4639 skip_last=True, 4640 skip_first=True, 4641 indent=self.COPY_PARAMS_ARE_WRAPPED, 4642 ) 4643 4644 if params: 4645 if self.COPY_PARAMS_ARE_WRAPPED: 4646 params = f" WITH ({params})" 4647 elif not self.pretty and (files or credentials): 4648 params = f" {params}" 4649 4650 return f"COPY{this}{kind} {files}{credentials}{params}"
4655 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4656 on_sql = "ON" if expression.args.get("on") else "OFF" 4657 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4658 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4659 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4660 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4661 4662 if filter_col or retention_period: 4663 on_sql = self.func("ON", filter_col, retention_period) 4664 4665 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4667 def maskingpolicycolumnconstraint_sql( 4668 self, expression: exp.MaskingPolicyColumnConstraint 4669 ) -> str: 4670 this = self.sql(expression, "this") 4671 expressions = self.expressions(expression, flat=True) 4672 expressions = f" USING ({expressions})" if expressions else "" 4673 return f"MASKING POLICY {this}{expressions}"
4683 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4684 this = self.sql(expression, "this") 4685 expr = expression.expression 4686 4687 if isinstance(expr, exp.Func): 4688 # T-SQL's CLR functions are case sensitive 4689 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4690 else: 4691 expr = self.sql(expression, "expression") 4692 4693 return self.scope_resolution(expr, this)
4701 def rand_sql(self, expression: exp.Rand) -> str: 4702 lower = self.sql(expression, "lower") 4703 upper = self.sql(expression, "upper") 4704 4705 if lower and upper: 4706 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4707 return self.func("RAND", expression.this)
4709 def changes_sql(self, expression: exp.Changes) -> str: 4710 information = self.sql(expression, "information") 4711 information = f"INFORMATION => {information}" 4712 at_before = self.sql(expression, "at_before") 4713 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4714 end = self.sql(expression, "end") 4715 end = f"{self.seg('')}{end}" if end else "" 4716 4717 return f"CHANGES ({information}){at_before}{end}"
4719 def pad_sql(self, expression: exp.Pad) -> str: 4720 prefix = "L" if expression.args.get("is_left") else "R" 4721 4722 fill_pattern = self.sql(expression, "fill_pattern") or None 4723 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4724 fill_pattern = "' '" 4725 4726 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4732 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4733 generate_series = exp.GenerateSeries(**expression.args) 4734 4735 parent = expression.parent 4736 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4737 parent = parent.parent 4738 4739 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4740 return self.sql(exp.Unnest(expressions=[generate_series])) 4741 4742 if isinstance(parent, exp.Select): 4743 self.unsupported("GenerateSeries projection unnesting is not supported.") 4744 4745 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4747 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4748 exprs = expression.expressions 4749 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4750 if len(exprs) == 0: 4751 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4752 else: 4753 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4754 else: 4755 rhs = self.expressions(expression) # type: ignore 4756 4757 return self.func(name, expression.this, rhs or None)
4759 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4760 if self.SUPPORTS_CONVERT_TIMEZONE: 4761 return self.function_fallback_sql(expression) 4762 4763 source_tz = expression.args.get("source_tz") 4764 target_tz = expression.args.get("target_tz") 4765 timestamp = expression.args.get("timestamp") 4766 4767 if source_tz and timestamp: 4768 timestamp = exp.AtTimeZone( 4769 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4770 ) 4771 4772 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4773 4774 return self.sql(expr)
4776 def json_sql(self, expression: exp.JSON) -> str: 4777 this = self.sql(expression, "this") 4778 this = f" {this}" if this else "" 4779 4780 _with = expression.args.get("with") 4781 4782 if _with is None: 4783 with_sql = "" 4784 elif not _with: 4785 with_sql = " WITHOUT" 4786 else: 4787 with_sql = " WITH" 4788 4789 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4790 4791 return f"JSON{this}{with_sql}{unique_sql}"
4793 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4794 def _generate_on_options(arg: t.Any) -> str: 4795 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4796 4797 path = self.sql(expression, "path") 4798 returning = self.sql(expression, "returning") 4799 returning = f" RETURNING {returning}" if returning else "" 4800 4801 on_condition = self.sql(expression, "on_condition") 4802 on_condition = f" {on_condition}" if on_condition else "" 4803 4804 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4806 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4807 else_ = "ELSE " if expression.args.get("else_") else "" 4808 condition = self.sql(expression, "expression") 4809 condition = f"WHEN {condition} THEN " if condition else else_ 4810 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4811 return f"{condition}{insert}"
4819 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4820 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4821 empty = expression.args.get("empty") 4822 empty = ( 4823 f"DEFAULT {empty} ON EMPTY" 4824 if isinstance(empty, exp.Expression) 4825 else self.sql(expression, "empty") 4826 ) 4827 4828 error = expression.args.get("error") 4829 error = ( 4830 f"DEFAULT {error} ON ERROR" 4831 if isinstance(error, exp.Expression) 4832 else self.sql(expression, "error") 4833 ) 4834 4835 if error and empty: 4836 error = ( 4837 f"{empty} {error}" 4838 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4839 else f"{error} {empty}" 4840 ) 4841 empty = "" 4842 4843 null = self.sql(expression, "null") 4844 4845 return f"{empty}{error}{null}"
4851 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4852 this = self.sql(expression, "this") 4853 path = self.sql(expression, "path") 4854 4855 passing = self.expressions(expression, "passing") 4856 passing = f" PASSING {passing}" if passing else "" 4857 4858 on_condition = self.sql(expression, "on_condition") 4859 on_condition = f" {on_condition}" if on_condition else "" 4860 4861 path = f"{path}{passing}{on_condition}" 4862 4863 return self.func("JSON_EXISTS", this, path)
4865 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4866 array_agg = self.function_fallback_sql(expression) 4867 4868 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4869 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4870 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4871 parent = expression.parent 4872 if isinstance(parent, exp.Filter): 4873 parent_cond = parent.expression.this 4874 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4875 else: 4876 this = expression.this 4877 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4878 if this.find(exp.Column): 4879 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4880 this_sql = ( 4881 self.expressions(this) 4882 if isinstance(this, exp.Distinct) 4883 else self.sql(expression, "this") 4884 ) 4885 4886 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4887 4888 return array_agg
4961 def overlay_sql(self, expression: exp.Overlay): 4962 this = self.sql(expression, "this") 4963 expr = self.sql(expression, "expression") 4964 from_sql = self.sql(expression, "from") 4965 for_sql = self.sql(expression, "for") 4966 for_sql = f" FOR {for_sql}" if for_sql else "" 4967 4968 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4974 def string_sql(self, expression: exp.String) -> str: 4975 this = expression.this 4976 zone = expression.args.get("zone") 4977 4978 if zone: 4979 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4980 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4981 # set for source_tz to transpile the time conversion before the STRING cast 4982 this = exp.ConvertTimezone( 4983 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4984 ) 4985 4986 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4996 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4997 filler = self.sql(expression, "this") 4998 filler = f" {filler}" if filler else "" 4999 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5000 return f"TRUNCATE{filler} {with_count}"
5002 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5003 if self.SUPPORTS_UNIX_SECONDS: 5004 return self.function_fallback_sql(expression) 5005 5006 start_ts = exp.cast( 5007 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 5008 ) 5009 5010 return self.sql( 5011 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5012 )
5014 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5015 dim = expression.expression 5016 5017 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5018 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5019 if not (dim.is_int and dim.name == "1"): 5020 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5021 dim = None 5022 5023 # If dimension is required but not specified, default initialize it 5024 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5025 dim = exp.Literal.number(1) 5026 5027 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5029 def attach_sql(self, expression: exp.Attach) -> str: 5030 this = self.sql(expression, "this") 5031 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5032 expressions = self.expressions(expression) 5033 expressions = f" ({expressions})" if expressions else "" 5034 5035 return f"ATTACH{exists_sql} {this}{expressions}"
5037 def detach_sql(self, expression: exp.Detach) -> str: 5038 this = self.sql(expression, "this") 5039 # the DATABASE keyword is required if IF EXISTS is set 5040 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5041 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5042 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5043 5044 return f"DETACH{exists_sql} {this}"
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
5057 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5058 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5059 encode = f"{encode} {self.sql(expression, 'this')}" 5060 5061 properties = expression.args.get("properties") 5062 if properties: 5063 encode = f"{encode} {self.properties(properties)}" 5064 5065 return encode
5067 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5068 this = self.sql(expression, "this") 5069 include = f"INCLUDE {this}" 5070 5071 column_def = self.sql(expression, "column_def") 5072 if column_def: 5073 include = f"{include} {column_def}" 5074 5075 alias = self.sql(expression, "alias") 5076 if alias: 5077 include = f"{include} AS {alias}" 5078 5079 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
5091 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5092 partitions = self.expressions(expression, "partition_expressions") 5093 create = self.expressions(expression, "create_expressions") 5094 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
5096 def partitionbyrangepropertydynamic_sql( 5097 self, expression: exp.PartitionByRangePropertyDynamic 5098 ) -> str: 5099 start = self.sql(expression, "start") 5100 end = self.sql(expression, "end") 5101 5102 every = expression.args["every"] 5103 if isinstance(every, exp.Interval) and every.this.is_string: 5104 every.this.replace(exp.Literal.number(every.name)) 5105 5106 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5119 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5120 kind = self.sql(expression, "kind") 5121 option = self.sql(expression, "option") 5122 option = f" {option}" if option else "" 5123 this = self.sql(expression, "this") 5124 this = f" {this}" if this else "" 5125 columns = self.expressions(expression) 5126 columns = f" {columns}" if columns else "" 5127 return f"{kind}{option} STATISTICS{this}{columns}"
5129 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5130 this = self.sql(expression, "this") 5131 columns = self.expressions(expression) 5132 inner_expression = self.sql(expression, "expression") 5133 inner_expression = f" {inner_expression}" if inner_expression else "" 5134 update_options = self.sql(expression, "update_options") 5135 update_options = f" {update_options} UPDATE" if update_options else "" 5136 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
5147 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5148 kind = self.sql(expression, "kind") 5149 this = self.sql(expression, "this") 5150 this = f" {this}" if this else "" 5151 inner_expression = self.sql(expression, "expression") 5152 return f"VALIDATE {kind}{this}{inner_expression}"
5154 def analyze_sql(self, expression: exp.Analyze) -> str: 5155 options = self.expressions(expression, key="options", sep=" ") 5156 options = f" {options}" if options else "" 5157 kind = self.sql(expression, "kind") 5158 kind = f" {kind}" if kind else "" 5159 this = self.sql(expression, "this") 5160 this = f" {this}" if this else "" 5161 mode = self.sql(expression, "mode") 5162 mode = f" {mode}" if mode else "" 5163 properties = self.sql(expression, "properties") 5164 properties = f" {properties}" if properties else "" 5165 partition = self.sql(expression, "partition") 5166 partition = f" {partition}" if partition else "" 5167 inner_expression = self.sql(expression, "expression") 5168 inner_expression = f" {inner_expression}" if inner_expression else "" 5169 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5171 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5172 this = self.sql(expression, "this") 5173 namespaces = self.expressions(expression, key="namespaces") 5174 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5175 passing = self.expressions(expression, key="passing") 5176 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5177 columns = self.expressions(expression, key="columns") 5178 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5179 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5180 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5186 def export_sql(self, expression: exp.Export) -> str: 5187 this = self.sql(expression, "this") 5188 connection = self.sql(expression, "connection") 5189 connection = f"WITH CONNECTION {connection} " if connection else "" 5190 options = self.sql(expression, "options") 5191 return f"EXPORT DATA {connection}{options} AS {this}"
5196 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5197 variable = self.sql(expression, "this") 5198 default = self.sql(expression, "default") 5199 default = f" = {default}" if default else "" 5200 5201 kind = self.sql(expression, "kind") 5202 if isinstance(expression.args.get("kind"), exp.Schema): 5203 kind = f"TABLE {kind}" 5204 5205 return f"{variable} AS {kind}{default}"
5207 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5208 kind = self.sql(expression, "kind") 5209 this = self.sql(expression, "this") 5210 set = self.sql(expression, "expression") 5211 using = self.sql(expression, "using") 5212 using = f" USING {using}" if using else "" 5213 5214 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5215 5216 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5239 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5240 # Snowflake GET/PUT statements: 5241 # PUT <file> <internalStage> <properties> 5242 # GET <internalStage> <file> <properties> 5243 props = expression.args.get("properties") 5244 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5245 this = self.sql(expression, "this") 5246 target = self.sql(expression, "target") 5247 5248 if isinstance(expression, exp.Put): 5249 return f"PUT {this} {target}{props_sql}" 5250 else: 5251 return f"GET {target} {this}{props_sql}"
5259 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5260 if self.SUPPORTS_DECODE_CASE: 5261 return self.func("DECODE", *expression.expressions) 5262 5263 expression, *expressions = expression.expressions 5264 5265 ifs = [] 5266 for search, result in zip(expressions[::2], expressions[1::2]): 5267 if isinstance(search, exp.Literal): 5268 ifs.append(exp.If(this=expression.eq(search), true=result)) 5269 elif isinstance(search, exp.Null): 5270 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5271 else: 5272 if isinstance(search, exp.Binary): 5273 search = exp.paren(search) 5274 5275 cond = exp.or_( 5276 expression.eq(search), 5277 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5278 copy=False, 5279 ) 5280 ifs.append(exp.If(this=cond, true=result)) 5281 5282 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5283 return self.sql(case)
5285 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5286 this = self.sql(expression, "this") 5287 this = self.seg(this, sep="") 5288 dimensions = self.expressions( 5289 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5290 ) 5291 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5292 metrics = self.expressions( 5293 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5294 ) 5295 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5296 where = self.sql(expression, "where") 5297 where = self.seg(f"WHERE {where}") if where else "" 5298 body = self.indent(this + metrics + dimensions + where, skip_first=True) 5299 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5301 def getextract_sql(self, expression: exp.GetExtract) -> str: 5302 this = expression.this 5303 expr = expression.expression 5304 5305 if not this.type or not expression.type: 5306 from sqlglot.optimizer.annotate_types import annotate_types 5307 5308 this = annotate_types(this, dialect=self.dialect) 5309 5310 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5311 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5312 5313 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql(self, expression: sqlglot.expressions.RefreshTriggerProperty) -> str:
5330 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5331 method = self.sql(expression, "method") 5332 kind = expression.args.get("kind") 5333 if not kind: 5334 return f"REFRESH {method}" 5335 5336 every = self.sql(expression, "every") 5337 unit = self.sql(expression, "unit") 5338 every = f" EVERY {every} {unit}" if every else "" 5339 starts = self.sql(expression, "starts") 5340 starts = f" STARTS {starts}" if starts else "" 5341 5342 return f"REFRESH {method} ON {kind}{every}{starts}"