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: Always quote except for specials cases. 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.Adjacent: lambda self, e: self.binary(e, "-|-"), 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.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 142 exp.SessionUser: lambda *_: "SESSION_USER", 143 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 144 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 145 exp.DynamicProperty: lambda *_: "DYNAMIC", 146 exp.EmptyProperty: lambda *_: "EMPTY", 147 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 148 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 149 exp.EphemeralColumnConstraint: lambda self, 150 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 151 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 152 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 153 exp.Except: lambda self, e: self.set_operations(e), 154 exp.ExternalProperty: lambda *_: "EXTERNAL", 155 exp.Floor: lambda self, e: self.ceil_floor(e), 156 exp.Get: lambda self, e: self.get_put_sql(e), 157 exp.GlobalProperty: lambda *_: "GLOBAL", 158 exp.HeapProperty: lambda *_: "HEAP", 159 exp.IcebergProperty: lambda *_: "ICEBERG", 160 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 161 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 162 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 163 exp.Intersect: lambda self, e: self.set_operations(e), 164 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 165 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 166 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 167 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 168 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 169 exp.LanguageProperty: lambda self, e: self.naked_property(e), 170 exp.LocationProperty: lambda self, e: self.naked_property(e), 171 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 172 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 173 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 174 exp.NonClusteredColumnConstraint: lambda self, 175 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 176 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 177 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 178 exp.OnCommitProperty: lambda _, 179 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 180 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 181 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 182 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 183 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 184 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 185 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 186 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 187 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 188 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 189 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 190 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 191 exp.ProjectionPolicyColumnConstraint: lambda self, 192 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 193 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 194 exp.Put: lambda self, e: self.get_put_sql(e), 195 exp.RemoteWithConnectionModelProperty: lambda self, 196 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 197 exp.ReturnsProperty: lambda self, e: ( 198 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 199 ), 200 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 201 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 202 exp.SecureProperty: lambda *_: "SECURE", 203 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 204 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 205 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 206 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 207 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 208 exp.SqlReadWriteProperty: lambda _, e: e.name, 209 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 210 exp.StabilityProperty: lambda _, e: e.name, 211 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 212 exp.StreamingTableProperty: lambda *_: "STREAMING", 213 exp.StrictProperty: lambda *_: "STRICT", 214 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 215 exp.TableColumn: lambda self, e: self.sql(e.this), 216 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 217 exp.TemporaryProperty: lambda *_: "TEMPORARY", 218 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 219 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 220 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 221 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 222 exp.TransientProperty: lambda *_: "TRANSIENT", 223 exp.Union: lambda self, e: self.set_operations(e), 224 exp.UnloggedProperty: lambda *_: "UNLOGGED", 225 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 226 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 227 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 228 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 229 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 230 exp.UtcTimestamp: lambda self, e: self.sql( 231 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 232 ), 233 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 234 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 235 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 236 exp.VolatileProperty: lambda *_: "VOLATILE", 237 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 238 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 239 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 240 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 241 exp.ForceProperty: lambda *_: "FORCE", 242 } 243 244 # Whether null ordering is supported in order by 245 # True: Full Support, None: No support, False: No support for certain cases 246 # such as window specifications, aggregate functions etc 247 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 248 249 # Whether ignore nulls is inside the agg or outside. 250 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 251 IGNORE_NULLS_IN_FUNC = False 252 253 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 254 LOCKING_READS_SUPPORTED = False 255 256 # Whether the EXCEPT and INTERSECT operations can return duplicates 257 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 258 259 # Wrap derived values in parens, usually standard but spark doesn't support it 260 WRAP_DERIVED_VALUES = True 261 262 # Whether create function uses an AS before the RETURN 263 CREATE_FUNCTION_RETURN_AS = True 264 265 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 266 MATCHED_BY_SOURCE = True 267 268 # Whether the INTERVAL expression works only with values like '1 day' 269 SINGLE_STRING_INTERVAL = False 270 271 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 272 INTERVAL_ALLOWS_PLURAL_FORM = True 273 274 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 275 LIMIT_FETCH = "ALL" 276 277 # Whether limit and fetch allows expresions or just limits 278 LIMIT_ONLY_LITERALS = False 279 280 # Whether a table is allowed to be renamed with a db 281 RENAME_TABLE_WITH_DB = True 282 283 # The separator for grouping sets and rollups 284 GROUPINGS_SEP = "," 285 286 # The string used for creating an index on a table 287 INDEX_ON = "ON" 288 289 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 290 INOUT_SEPARATOR = " " 291 292 # Whether join hints should be generated 293 JOIN_HINTS = True 294 295 # Whether directed joins are supported 296 DIRECTED_JOINS = False 297 298 # Whether table hints should be generated 299 TABLE_HINTS = True 300 301 # Whether query hints should be generated 302 QUERY_HINTS = True 303 304 # What kind of separator to use for query hints 305 QUERY_HINT_SEP = ", " 306 307 # Whether comparing against booleans (e.g. x IS TRUE) is supported 308 IS_BOOL_ALLOWED = True 309 310 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 311 DUPLICATE_KEY_UPDATE_WITH_SET = True 312 313 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 314 LIMIT_IS_TOP = False 315 316 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 317 RETURNING_END = True 318 319 # Whether to generate an unquoted value for EXTRACT's date part argument 320 EXTRACT_ALLOWS_QUOTES = True 321 322 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 323 TZ_TO_WITH_TIME_ZONE = False 324 325 # Whether the NVL2 function is supported 326 NVL2_SUPPORTED = True 327 328 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 329 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 330 331 # Whether VALUES statements can be used as derived tables. 332 # MySQL 5 and Redshift do not allow this, so when False, it will convert 333 # SELECT * VALUES into SELECT UNION 334 VALUES_AS_TABLE = True 335 336 # Whether the word COLUMN is included when adding a column with ALTER TABLE 337 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 338 339 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 340 UNNEST_WITH_ORDINALITY = True 341 342 # Whether FILTER (WHERE cond) can be used for conditional aggregation 343 AGGREGATE_FILTER_SUPPORTED = True 344 345 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 346 SEMI_ANTI_JOIN_WITH_SIDE = True 347 348 # Whether to include the type of a computed column in the CREATE DDL 349 COMPUTED_COLUMN_WITH_TYPE = True 350 351 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 352 SUPPORTS_TABLE_COPY = True 353 354 # Whether parentheses are required around the table sample's expression 355 TABLESAMPLE_REQUIRES_PARENS = True 356 357 # Whether a table sample clause's size needs to be followed by the ROWS keyword 358 TABLESAMPLE_SIZE_IS_ROWS = True 359 360 # The keyword(s) to use when generating a sample clause 361 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 362 363 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 364 TABLESAMPLE_WITH_METHOD = True 365 366 # The keyword to use when specifying the seed of a sample clause 367 TABLESAMPLE_SEED_KEYWORD = "SEED" 368 369 # Whether COLLATE is a function instead of a binary operator 370 COLLATE_IS_FUNC = False 371 372 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 373 DATA_TYPE_SPECIFIERS_ALLOWED = False 374 375 # Whether conditions require booleans WHERE x = 0 vs WHERE x 376 ENSURE_BOOLS = False 377 378 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 379 CTE_RECURSIVE_KEYWORD_REQUIRED = True 380 381 # Whether CONCAT requires >1 arguments 382 SUPPORTS_SINGLE_ARG_CONCAT = True 383 384 # Whether LAST_DAY function supports a date part argument 385 LAST_DAY_SUPPORTS_DATE_PART = True 386 387 # Whether named columns are allowed in table aliases 388 SUPPORTS_TABLE_ALIAS_COLUMNS = True 389 390 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 391 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 392 393 # What delimiter to use for separating JSON key/value pairs 394 JSON_KEY_VALUE_PAIR_SEP = ":" 395 396 # INSERT OVERWRITE TABLE x override 397 INSERT_OVERWRITE = " OVERWRITE TABLE" 398 399 # Whether the SELECT .. INTO syntax is used instead of CTAS 400 SUPPORTS_SELECT_INTO = False 401 402 # Whether UNLOGGED tables can be created 403 SUPPORTS_UNLOGGED_TABLES = False 404 405 # Whether the CREATE TABLE LIKE statement is supported 406 SUPPORTS_CREATE_TABLE_LIKE = True 407 408 # Whether the LikeProperty needs to be specified inside of the schema clause 409 LIKE_PROPERTY_INSIDE_SCHEMA = False 410 411 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 412 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 413 MULTI_ARG_DISTINCT = True 414 415 # Whether the JSON extraction operators expect a value of type JSON 416 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 417 418 # Whether bracketed keys like ["foo"] are supported in JSON paths 419 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 420 421 # Whether to escape keys using single quotes in JSON paths 422 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 423 424 # The JSONPathPart expressions supported by this dialect 425 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 426 427 # Whether any(f(x) for x in array) can be implemented by this dialect 428 CAN_IMPLEMENT_ARRAY_ANY = False 429 430 # Whether the function TO_NUMBER is supported 431 SUPPORTS_TO_NUMBER = True 432 433 # Whether EXCLUDE in window specification is supported 434 SUPPORTS_WINDOW_EXCLUDE = False 435 436 # Whether or not set op modifiers apply to the outer set op or select. 437 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 438 # True means limit 1 happens after the set op, False means it it happens on y. 439 SET_OP_MODIFIERS = True 440 441 # Whether parameters from COPY statement are wrapped in parentheses 442 COPY_PARAMS_ARE_WRAPPED = True 443 444 # Whether values of params are set with "=" token or empty space 445 COPY_PARAMS_EQ_REQUIRED = False 446 447 # Whether COPY statement has INTO keyword 448 COPY_HAS_INTO_KEYWORD = True 449 450 # Whether the conditional TRY(expression) function is supported 451 TRY_SUPPORTED = True 452 453 # Whether the UESCAPE syntax in unicode strings is supported 454 SUPPORTS_UESCAPE = True 455 456 # Function used to replace escaped unicode codes in unicode strings 457 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 458 459 # The keyword to use when generating a star projection with excluded columns 460 STAR_EXCEPT = "EXCEPT" 461 462 # The HEX function name 463 HEX_FUNC = "HEX" 464 465 # The keywords to use when prefixing & separating WITH based properties 466 WITH_PROPERTIES_PREFIX = "WITH" 467 468 # Whether to quote the generated expression of exp.JsonPath 469 QUOTE_JSON_PATH = True 470 471 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 472 PAD_FILL_PATTERN_IS_REQUIRED = False 473 474 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 475 SUPPORTS_EXPLODING_PROJECTIONS = True 476 477 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 478 ARRAY_CONCAT_IS_VAR_LEN = True 479 480 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 481 SUPPORTS_CONVERT_TIMEZONE = False 482 483 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 484 SUPPORTS_MEDIAN = True 485 486 # Whether UNIX_SECONDS(timestamp) is supported 487 SUPPORTS_UNIX_SECONDS = False 488 489 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 490 ALTER_SET_WRAPPED = False 491 492 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 493 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 494 # TODO: The normalization should be done by default once we've tested it across all dialects. 495 NORMALIZE_EXTRACT_DATE_PARTS = False 496 497 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 498 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 499 500 # The function name of the exp.ArraySize expression 501 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 502 503 # The syntax to use when altering the type of a column 504 ALTER_SET_TYPE = "SET DATA TYPE" 505 506 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 507 # None -> Doesn't support it at all 508 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 509 # True (Postgres) -> Explicitly requires it 510 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 511 512 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 513 SUPPORTS_DECODE_CASE = True 514 515 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 516 SUPPORTS_BETWEEN_FLAGS = False 517 518 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 519 SUPPORTS_LIKE_QUANTIFIERS = True 520 521 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 522 MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None 523 524 # Whether to include the VARIABLE keyword for SET assignments 525 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 526 527 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 528 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 529 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 530 UPDATE_STATEMENT_SUPPORTS_FROM = True 531 532 TYPE_MAPPING = { 533 exp.DataType.Type.DATETIME2: "TIMESTAMP", 534 exp.DataType.Type.NCHAR: "CHAR", 535 exp.DataType.Type.NVARCHAR: "VARCHAR", 536 exp.DataType.Type.MEDIUMTEXT: "TEXT", 537 exp.DataType.Type.LONGTEXT: "TEXT", 538 exp.DataType.Type.TINYTEXT: "TEXT", 539 exp.DataType.Type.BLOB: "VARBINARY", 540 exp.DataType.Type.MEDIUMBLOB: "BLOB", 541 exp.DataType.Type.LONGBLOB: "BLOB", 542 exp.DataType.Type.TINYBLOB: "BLOB", 543 exp.DataType.Type.INET: "INET", 544 exp.DataType.Type.ROWVERSION: "VARBINARY", 545 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 546 } 547 548 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 549 550 TIME_PART_SINGULARS = { 551 "MICROSECONDS": "MICROSECOND", 552 "SECONDS": "SECOND", 553 "MINUTES": "MINUTE", 554 "HOURS": "HOUR", 555 "DAYS": "DAY", 556 "WEEKS": "WEEK", 557 "MONTHS": "MONTH", 558 "QUARTERS": "QUARTER", 559 "YEARS": "YEAR", 560 } 561 562 AFTER_HAVING_MODIFIER_TRANSFORMS = { 563 "cluster": lambda self, e: self.sql(e, "cluster"), 564 "distribute": lambda self, e: self.sql(e, "distribute"), 565 "sort": lambda self, e: self.sql(e, "sort"), 566 "windows": lambda self, e: ( 567 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 568 if e.args.get("windows") 569 else "" 570 ), 571 "qualify": lambda self, e: self.sql(e, "qualify"), 572 } 573 574 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 575 576 STRUCT_DELIMITER = ("<", ">") 577 578 PARAMETER_TOKEN = "@" 579 NAMED_PLACEHOLDER_TOKEN = ":" 580 581 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 582 583 PROPERTIES_LOCATION = { 584 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 586 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 590 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 592 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 593 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 594 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 595 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 596 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 599 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 600 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 601 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 602 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 604 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 608 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 609 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 611 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 612 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 613 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 614 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 615 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 616 exp.HeapProperty: exp.Properties.Location.POST_WITH, 617 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 619 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 620 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 622 exp.JournalProperty: exp.Properties.Location.POST_NAME, 623 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 626 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 628 exp.LogProperty: exp.Properties.Location.POST_NAME, 629 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 630 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 631 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 632 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 633 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 634 exp.Order: exp.Properties.Location.POST_SCHEMA, 635 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 636 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 637 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 639 exp.Property: exp.Properties.Location.POST_WITH, 640 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 641 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 642 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 643 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 644 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 645 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 646 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 647 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 648 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 649 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 650 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 651 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 652 exp.Set: exp.Properties.Location.POST_SCHEMA, 653 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 654 exp.SetProperty: exp.Properties.Location.POST_CREATE, 655 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 656 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 657 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 658 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 659 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 660 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 661 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 662 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 663 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 664 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 665 exp.Tags: exp.Properties.Location.POST_WITH, 666 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 667 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 668 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 669 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 670 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 671 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 672 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 673 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 674 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 675 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 676 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 677 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 678 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 679 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 680 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 681 } 682 683 # Keywords that can't be used as unquoted identifier names 684 RESERVED_KEYWORDS: t.Set[str] = set() 685 686 # Expressions whose comments are separated from them for better formatting 687 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 688 exp.Command, 689 exp.Create, 690 exp.Describe, 691 exp.Delete, 692 exp.Drop, 693 exp.From, 694 exp.Insert, 695 exp.Join, 696 exp.MultitableInserts, 697 exp.Order, 698 exp.Group, 699 exp.Having, 700 exp.Select, 701 exp.SetOperation, 702 exp.Update, 703 exp.Where, 704 exp.With, 705 ) 706 707 # Expressions that should not have their comments generated in maybe_comment 708 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 709 exp.Binary, 710 exp.SetOperation, 711 ) 712 713 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 714 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 715 exp.Column, 716 exp.Literal, 717 exp.Neg, 718 exp.Paren, 719 ) 720 721 PARAMETERIZABLE_TEXT_TYPES = { 722 exp.DataType.Type.NVARCHAR, 723 exp.DataType.Type.VARCHAR, 724 exp.DataType.Type.CHAR, 725 exp.DataType.Type.NCHAR, 726 } 727 728 # Expressions that need to have all CTEs under them bubbled up to them 729 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 730 731 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 732 733 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 734 735 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 736 737 __slots__ = ( 738 "pretty", 739 "identify", 740 "normalize", 741 "pad", 742 "_indent", 743 "normalize_functions", 744 "unsupported_level", 745 "max_unsupported", 746 "leading_comma", 747 "max_text_width", 748 "comments", 749 "dialect", 750 "unsupported_messages", 751 "_escaped_quote_end", 752 "_escaped_byte_quote_end", 753 "_escaped_identifier_end", 754 "_next_name", 755 "_identifier_start", 756 "_identifier_end", 757 "_quote_json_path_key_using_brackets", 758 ) 759 760 def __init__( 761 self, 762 pretty: t.Optional[bool] = None, 763 identify: str | bool = False, 764 normalize: bool = False, 765 pad: int = 2, 766 indent: int = 2, 767 normalize_functions: t.Optional[str | bool] = None, 768 unsupported_level: ErrorLevel = ErrorLevel.WARN, 769 max_unsupported: int = 3, 770 leading_comma: bool = False, 771 max_text_width: int = 80, 772 comments: bool = True, 773 dialect: DialectType = None, 774 ): 775 import sqlglot 776 from sqlglot.dialects import Dialect 777 778 self.pretty = pretty if pretty is not None else sqlglot.pretty 779 self.identify = identify 780 self.normalize = normalize 781 self.pad = pad 782 self._indent = indent 783 self.unsupported_level = unsupported_level 784 self.max_unsupported = max_unsupported 785 self.leading_comma = leading_comma 786 self.max_text_width = max_text_width 787 self.comments = comments 788 self.dialect = Dialect.get_or_raise(dialect) 789 790 # This is both a Dialect property and a Generator argument, so we prioritize the latter 791 self.normalize_functions = ( 792 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 793 ) 794 795 self.unsupported_messages: t.List[str] = [] 796 self._escaped_quote_end: str = ( 797 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 798 ) 799 self._escaped_byte_quote_end: str = ( 800 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 801 if self.dialect.BYTE_END 802 else "" 803 ) 804 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 805 806 self._next_name = name_sequence("_t") 807 808 self._identifier_start = self.dialect.IDENTIFIER_START 809 self._identifier_end = self.dialect.IDENTIFIER_END 810 811 self._quote_json_path_key_using_brackets = True 812 813 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 814 """ 815 Generates the SQL string corresponding to the given syntax tree. 816 817 Args: 818 expression: The syntax tree. 819 copy: Whether to copy the expression. The generator performs mutations so 820 it is safer to copy. 821 822 Returns: 823 The SQL string corresponding to `expression`. 824 """ 825 if copy: 826 expression = expression.copy() 827 828 expression = self.preprocess(expression) 829 830 self.unsupported_messages = [] 831 sql = self.sql(expression).strip() 832 833 if self.pretty: 834 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 835 836 if self.unsupported_level == ErrorLevel.IGNORE: 837 return sql 838 839 if self.unsupported_level == ErrorLevel.WARN: 840 for msg in self.unsupported_messages: 841 logger.warning(msg) 842 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 843 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 844 845 return sql 846 847 def preprocess(self, expression: exp.Expression) -> exp.Expression: 848 """Apply generic preprocessing transformations to a given expression.""" 849 expression = self._move_ctes_to_top_level(expression) 850 851 if self.ENSURE_BOOLS: 852 from sqlglot.transforms import ensure_bools 853 854 expression = ensure_bools(expression) 855 856 return expression 857 858 def _move_ctes_to_top_level(self, expression: E) -> E: 859 if ( 860 not expression.parent 861 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 862 and any(node.parent is not expression for node in expression.find_all(exp.With)) 863 ): 864 from sqlglot.transforms import move_ctes_to_top_level 865 866 expression = move_ctes_to_top_level(expression) 867 return expression 868 869 def unsupported(self, message: str) -> None: 870 if self.unsupported_level == ErrorLevel.IMMEDIATE: 871 raise UnsupportedError(message) 872 self.unsupported_messages.append(message) 873 874 def sep(self, sep: str = " ") -> str: 875 return f"{sep.strip()}\n" if self.pretty else sep 876 877 def seg(self, sql: str, sep: str = " ") -> str: 878 return f"{self.sep(sep)}{sql}" 879 880 def sanitize_comment(self, comment: str) -> str: 881 comment = " " + comment if comment[0].strip() else comment 882 comment = comment + " " if comment[-1].strip() else comment 883 884 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 885 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 886 comment = comment.replace("*/", "* /") 887 888 return comment 889 890 def maybe_comment( 891 self, 892 sql: str, 893 expression: t.Optional[exp.Expression] = None, 894 comments: t.Optional[t.List[str]] = None, 895 separated: bool = False, 896 ) -> str: 897 comments = ( 898 ((expression and expression.comments) if comments is None else comments) # type: ignore 899 if self.comments 900 else None 901 ) 902 903 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 904 return sql 905 906 comments_sql = " ".join( 907 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 908 ) 909 910 if not comments_sql: 911 return sql 912 913 comments_sql = self._replace_line_breaks(comments_sql) 914 915 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 916 return ( 917 f"{self.sep()}{comments_sql}{sql}" 918 if not sql or sql[0].isspace() 919 else f"{comments_sql}{self.sep()}{sql}" 920 ) 921 922 return f"{sql} {comments_sql}" 923 924 def wrap(self, expression: exp.Expression | str) -> str: 925 this_sql = ( 926 self.sql(expression) 927 if isinstance(expression, exp.UNWRAPPED_QUERIES) 928 else self.sql(expression, "this") 929 ) 930 if not this_sql: 931 return "()" 932 933 this_sql = self.indent(this_sql, level=1, pad=0) 934 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 935 936 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 937 original = self.identify 938 self.identify = False 939 result = func(*args, **kwargs) 940 self.identify = original 941 return result 942 943 def normalize_func(self, name: str) -> str: 944 if self.normalize_functions == "upper" or self.normalize_functions is True: 945 return name.upper() 946 if self.normalize_functions == "lower": 947 return name.lower() 948 return name 949 950 def indent( 951 self, 952 sql: str, 953 level: int = 0, 954 pad: t.Optional[int] = None, 955 skip_first: bool = False, 956 skip_last: bool = False, 957 ) -> str: 958 if not self.pretty or not sql: 959 return sql 960 961 pad = self.pad if pad is None else pad 962 lines = sql.split("\n") 963 964 return "\n".join( 965 ( 966 line 967 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 968 else f"{' ' * (level * self._indent + pad)}{line}" 969 ) 970 for i, line in enumerate(lines) 971 ) 972 973 def sql( 974 self, 975 expression: t.Optional[str | exp.Expression], 976 key: t.Optional[str] = None, 977 comment: bool = True, 978 ) -> str: 979 if not expression: 980 return "" 981 982 if isinstance(expression, str): 983 return expression 984 985 if key: 986 value = expression.args.get(key) 987 if value: 988 return self.sql(value) 989 return "" 990 991 transform = self.TRANSFORMS.get(expression.__class__) 992 993 if callable(transform): 994 sql = transform(self, expression) 995 elif isinstance(expression, exp.Expression): 996 exp_handler_name = f"{expression.key}_sql" 997 998 if hasattr(self, exp_handler_name): 999 sql = getattr(self, exp_handler_name)(expression) 1000 elif isinstance(expression, exp.Func): 1001 sql = self.function_fallback_sql(expression) 1002 elif isinstance(expression, exp.Property): 1003 sql = self.property_sql(expression) 1004 else: 1005 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1006 else: 1007 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 1008 1009 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1010 1011 def uncache_sql(self, expression: exp.Uncache) -> str: 1012 table = self.sql(expression, "this") 1013 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1014 return f"UNCACHE TABLE{exists_sql} {table}" 1015 1016 def cache_sql(self, expression: exp.Cache) -> str: 1017 lazy = " LAZY" if expression.args.get("lazy") else "" 1018 table = self.sql(expression, "this") 1019 options = expression.args.get("options") 1020 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1021 sql = self.sql(expression, "expression") 1022 sql = f" AS{self.sep()}{sql}" if sql else "" 1023 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1024 return self.prepend_ctes(expression, sql) 1025 1026 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1027 if isinstance(expression.parent, exp.Cast): 1028 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1029 default = "DEFAULT " if expression.args.get("default") else "" 1030 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1031 1032 def column_parts(self, expression: exp.Column) -> str: 1033 return ".".join( 1034 self.sql(part) 1035 for part in ( 1036 expression.args.get("catalog"), 1037 expression.args.get("db"), 1038 expression.args.get("table"), 1039 expression.args.get("this"), 1040 ) 1041 if part 1042 ) 1043 1044 def column_sql(self, expression: exp.Column) -> str: 1045 join_mark = " (+)" if expression.args.get("join_mark") else "" 1046 1047 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1048 join_mark = "" 1049 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1050 1051 return f"{self.column_parts(expression)}{join_mark}" 1052 1053 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1054 return self.column_sql(expression) 1055 1056 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1057 this = self.sql(expression, "this") 1058 this = f" {this}" if this else "" 1059 position = self.sql(expression, "position") 1060 return f"{position}{this}" 1061 1062 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1063 column = self.sql(expression, "this") 1064 kind = self.sql(expression, "kind") 1065 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1066 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1067 kind = f"{sep}{kind}" if kind else "" 1068 constraints = f" {constraints}" if constraints else "" 1069 position = self.sql(expression, "position") 1070 position = f" {position}" if position else "" 1071 1072 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1073 kind = "" 1074 1075 return f"{exists}{column}{kind}{constraints}{position}" 1076 1077 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1078 this = self.sql(expression, "this") 1079 kind_sql = self.sql(expression, "kind").strip() 1080 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1081 1082 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1083 this = self.sql(expression, "this") 1084 if expression.args.get("not_null"): 1085 persisted = " PERSISTED NOT NULL" 1086 elif expression.args.get("persisted"): 1087 persisted = " PERSISTED" 1088 else: 1089 persisted = "" 1090 1091 return f"AS {this}{persisted}" 1092 1093 def autoincrementcolumnconstraint_sql(self, _) -> str: 1094 return self.token_sql(TokenType.AUTO_INCREMENT) 1095 1096 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1097 if isinstance(expression.this, list): 1098 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1099 else: 1100 this = self.sql(expression, "this") 1101 1102 return f"COMPRESS {this}" 1103 1104 def generatedasidentitycolumnconstraint_sql( 1105 self, expression: exp.GeneratedAsIdentityColumnConstraint 1106 ) -> str: 1107 this = "" 1108 if expression.this is not None: 1109 on_null = " ON NULL" if expression.args.get("on_null") else "" 1110 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1111 1112 start = expression.args.get("start") 1113 start = f"START WITH {start}" if start else "" 1114 increment = expression.args.get("increment") 1115 increment = f" INCREMENT BY {increment}" if increment else "" 1116 minvalue = expression.args.get("minvalue") 1117 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1118 maxvalue = expression.args.get("maxvalue") 1119 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1120 cycle = expression.args.get("cycle") 1121 cycle_sql = "" 1122 1123 if cycle is not None: 1124 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1125 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1126 1127 sequence_opts = "" 1128 if start or increment or cycle_sql: 1129 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1130 sequence_opts = f" ({sequence_opts.strip()})" 1131 1132 expr = self.sql(expression, "expression") 1133 expr = f"({expr})" if expr else "IDENTITY" 1134 1135 return f"GENERATED{this} AS {expr}{sequence_opts}" 1136 1137 def generatedasrowcolumnconstraint_sql( 1138 self, expression: exp.GeneratedAsRowColumnConstraint 1139 ) -> str: 1140 start = "START" if expression.args.get("start") else "END" 1141 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1142 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1143 1144 def periodforsystemtimeconstraint_sql( 1145 self, expression: exp.PeriodForSystemTimeConstraint 1146 ) -> str: 1147 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1148 1149 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1150 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1151 1152 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1153 desc = expression.args.get("desc") 1154 if desc is not None: 1155 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1156 options = self.expressions(expression, key="options", flat=True, sep=" ") 1157 options = f" {options}" if options else "" 1158 return f"PRIMARY KEY{options}" 1159 1160 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1161 this = self.sql(expression, "this") 1162 this = f" {this}" if this else "" 1163 index_type = expression.args.get("index_type") 1164 index_type = f" USING {index_type}" if index_type else "" 1165 on_conflict = self.sql(expression, "on_conflict") 1166 on_conflict = f" {on_conflict}" if on_conflict else "" 1167 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1168 options = self.expressions(expression, key="options", flat=True, sep=" ") 1169 options = f" {options}" if options else "" 1170 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1171 1172 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1173 input_ = expression.args.get("input_") 1174 output = expression.args.get("output") 1175 variadic = expression.args.get("variadic") 1176 1177 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1178 if variadic: 1179 return "VARIADIC" 1180 1181 if input_ and output: 1182 return f"IN{self.INOUT_SEPARATOR}OUT" 1183 if input_: 1184 return "IN" 1185 if output: 1186 return "OUT" 1187 1188 return "" 1189 1190 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1191 return self.sql(expression, "this") 1192 1193 def create_sql(self, expression: exp.Create) -> str: 1194 kind = self.sql(expression, "kind") 1195 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1196 properties = expression.args.get("properties") 1197 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1198 1199 this = self.createable_sql(expression, properties_locs) 1200 1201 properties_sql = "" 1202 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1203 exp.Properties.Location.POST_WITH 1204 ): 1205 props_ast = exp.Properties( 1206 expressions=[ 1207 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1208 *properties_locs[exp.Properties.Location.POST_WITH], 1209 ] 1210 ) 1211 props_ast.parent = expression 1212 properties_sql = self.sql(props_ast) 1213 1214 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1215 properties_sql = self.sep() + properties_sql 1216 elif not self.pretty: 1217 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1218 properties_sql = f" {properties_sql}" 1219 1220 begin = " BEGIN" if expression.args.get("begin") else "" 1221 end = " END" if expression.args.get("end") else "" 1222 1223 expression_sql = self.sql(expression, "expression") 1224 if expression_sql: 1225 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1226 1227 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1228 postalias_props_sql = "" 1229 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1230 postalias_props_sql = self.properties( 1231 exp.Properties( 1232 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1233 ), 1234 wrapped=False, 1235 ) 1236 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1237 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1238 1239 postindex_props_sql = "" 1240 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1241 postindex_props_sql = self.properties( 1242 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1243 wrapped=False, 1244 prefix=" ", 1245 ) 1246 1247 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1248 indexes = f" {indexes}" if indexes else "" 1249 index_sql = indexes + postindex_props_sql 1250 1251 replace = " OR REPLACE" if expression.args.get("replace") else "" 1252 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1253 unique = " UNIQUE" if expression.args.get("unique") else "" 1254 1255 clustered = expression.args.get("clustered") 1256 if clustered is None: 1257 clustered_sql = "" 1258 elif clustered: 1259 clustered_sql = " CLUSTERED COLUMNSTORE" 1260 else: 1261 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1262 1263 postcreate_props_sql = "" 1264 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1265 postcreate_props_sql = self.properties( 1266 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1267 sep=" ", 1268 prefix=" ", 1269 wrapped=False, 1270 ) 1271 1272 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1273 1274 postexpression_props_sql = "" 1275 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1276 postexpression_props_sql = self.properties( 1277 exp.Properties( 1278 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1279 ), 1280 sep=" ", 1281 prefix=" ", 1282 wrapped=False, 1283 ) 1284 1285 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1286 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1287 no_schema_binding = ( 1288 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1289 ) 1290 1291 clone = self.sql(expression, "clone") 1292 clone = f" {clone}" if clone else "" 1293 1294 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1295 properties_expression = f"{expression_sql}{properties_sql}" 1296 else: 1297 properties_expression = f"{properties_sql}{expression_sql}" 1298 1299 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1300 return self.prepend_ctes(expression, expression_sql) 1301 1302 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1303 start = self.sql(expression, "start") 1304 start = f"START WITH {start}" if start else "" 1305 increment = self.sql(expression, "increment") 1306 increment = f" INCREMENT BY {increment}" if increment else "" 1307 minvalue = self.sql(expression, "minvalue") 1308 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1309 maxvalue = self.sql(expression, "maxvalue") 1310 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1311 owned = self.sql(expression, "owned") 1312 owned = f" OWNED BY {owned}" if owned else "" 1313 1314 cache = expression.args.get("cache") 1315 if cache is None: 1316 cache_str = "" 1317 elif cache is True: 1318 cache_str = " CACHE" 1319 else: 1320 cache_str = f" CACHE {cache}" 1321 1322 options = self.expressions(expression, key="options", flat=True, sep=" ") 1323 options = f" {options}" if options else "" 1324 1325 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1326 1327 def clone_sql(self, expression: exp.Clone) -> str: 1328 this = self.sql(expression, "this") 1329 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1330 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1331 return f"{shallow}{keyword} {this}" 1332 1333 def describe_sql(self, expression: exp.Describe) -> str: 1334 style = expression.args.get("style") 1335 style = f" {style}" if style else "" 1336 partition = self.sql(expression, "partition") 1337 partition = f" {partition}" if partition else "" 1338 format = self.sql(expression, "format") 1339 format = f" {format}" if format else "" 1340 as_json = " AS JSON" if expression.args.get("as_json") else "" 1341 1342 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1343 1344 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1345 tag = self.sql(expression, "tag") 1346 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1347 1348 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1349 with_ = self.sql(expression, "with_") 1350 if with_: 1351 sql = f"{with_}{self.sep()}{sql}" 1352 return sql 1353 1354 def with_sql(self, expression: exp.With) -> str: 1355 sql = self.expressions(expression, flat=True) 1356 recursive = ( 1357 "RECURSIVE " 1358 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1359 else "" 1360 ) 1361 search = self.sql(expression, "search") 1362 search = f" {search}" if search else "" 1363 1364 return f"WITH {recursive}{sql}{search}" 1365 1366 def cte_sql(self, expression: exp.CTE) -> str: 1367 alias = expression.args.get("alias") 1368 if alias: 1369 alias.add_comments(expression.pop_comments()) 1370 1371 alias_sql = self.sql(expression, "alias") 1372 1373 materialized = expression.args.get("materialized") 1374 if materialized is False: 1375 materialized = "NOT MATERIALIZED " 1376 elif materialized: 1377 materialized = "MATERIALIZED " 1378 1379 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1380 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1381 1382 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1383 1384 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1385 alias = self.sql(expression, "this") 1386 columns = self.expressions(expression, key="columns", flat=True) 1387 columns = f"({columns})" if columns else "" 1388 1389 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1390 columns = "" 1391 self.unsupported("Named columns are not supported in table alias.") 1392 1393 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1394 alias = self._next_name() 1395 1396 return f"{alias}{columns}" 1397 1398 def bitstring_sql(self, expression: exp.BitString) -> str: 1399 this = self.sql(expression, "this") 1400 if self.dialect.BIT_START: 1401 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1402 return f"{int(this, 2)}" 1403 1404 def hexstring_sql( 1405 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1406 ) -> str: 1407 this = self.sql(expression, "this") 1408 is_integer_type = expression.args.get("is_integer") 1409 1410 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1411 not self.dialect.HEX_START and not binary_function_repr 1412 ): 1413 # Integer representation will be returned if: 1414 # - The read dialect treats the hex value as integer literal but not the write 1415 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1416 return f"{int(this, 16)}" 1417 1418 if not is_integer_type: 1419 # Read dialect treats the hex value as BINARY/BLOB 1420 if binary_function_repr: 1421 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1422 return self.func(binary_function_repr, exp.Literal.string(this)) 1423 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1424 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1425 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1426 1427 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1428 1429 def bytestring_sql(self, expression: exp.ByteString) -> str: 1430 this = self.sql(expression, "this") 1431 if self.dialect.BYTE_START: 1432 escaped_byte_string = self.escape_str( 1433 this, 1434 escape_backslash=False, 1435 delimiter=self.dialect.BYTE_END, 1436 escaped_delimiter=self._escaped_byte_quote_end, 1437 is_byte_string=True, 1438 ) 1439 is_bytes = expression.args.get("is_bytes", False) 1440 delimited_byte_string = ( 1441 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1442 ) 1443 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1444 return self.sql( 1445 exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect) 1446 ) 1447 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1448 return self.sql( 1449 exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect) 1450 ) 1451 1452 return delimited_byte_string 1453 return this 1454 1455 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1456 this = self.sql(expression, "this") 1457 escape = expression.args.get("escape") 1458 1459 if self.dialect.UNICODE_START: 1460 escape_substitute = r"\\\1" 1461 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1462 else: 1463 escape_substitute = r"\\u\1" 1464 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1465 1466 if escape: 1467 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1468 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1469 else: 1470 escape_pattern = ESCAPED_UNICODE_RE 1471 escape_sql = "" 1472 1473 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1474 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1475 1476 return f"{left_quote}{this}{right_quote}{escape_sql}" 1477 1478 def rawstring_sql(self, expression: exp.RawString) -> str: 1479 string = expression.this 1480 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1481 string = string.replace("\\", "\\\\") 1482 1483 string = self.escape_str(string, escape_backslash=False) 1484 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1485 1486 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1487 this = self.sql(expression, "this") 1488 specifier = self.sql(expression, "expression") 1489 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1490 return f"{this}{specifier}" 1491 1492 def datatype_sql(self, expression: exp.DataType) -> str: 1493 nested = "" 1494 values = "" 1495 1496 expr_nested = expression.args.get("nested") 1497 interior = ( 1498 self.expressions( 1499 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1500 ) 1501 if expr_nested and self.pretty 1502 else self.expressions(expression, flat=True) 1503 ) 1504 1505 type_value = expression.this 1506 if type_value in self.UNSUPPORTED_TYPES: 1507 self.unsupported( 1508 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1509 ) 1510 1511 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1512 type_sql = self.sql(expression, "kind") 1513 else: 1514 type_sql = ( 1515 self.TYPE_MAPPING.get(type_value, type_value.value) 1516 if isinstance(type_value, exp.DataType.Type) 1517 else type_value 1518 ) 1519 1520 if interior: 1521 if expr_nested: 1522 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1523 if expression.args.get("values") is not None: 1524 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1525 values = self.expressions(expression, key="values", flat=True) 1526 values = f"{delimiters[0]}{values}{delimiters[1]}" 1527 elif type_value == exp.DataType.Type.INTERVAL: 1528 nested = f" {interior}" 1529 else: 1530 nested = f"({interior})" 1531 1532 type_sql = f"{type_sql}{nested}{values}" 1533 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1534 exp.DataType.Type.TIMETZ, 1535 exp.DataType.Type.TIMESTAMPTZ, 1536 ): 1537 type_sql = f"{type_sql} WITH TIME ZONE" 1538 1539 return type_sql 1540 1541 def directory_sql(self, expression: exp.Directory) -> str: 1542 local = "LOCAL " if expression.args.get("local") else "" 1543 row_format = self.sql(expression, "row_format") 1544 row_format = f" {row_format}" if row_format else "" 1545 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1546 1547 def delete_sql(self, expression: exp.Delete) -> str: 1548 this = self.sql(expression, "this") 1549 this = f" FROM {this}" if this else "" 1550 using = self.expressions(expression, key="using") 1551 using = f" USING {using}" if using else "" 1552 cluster = self.sql(expression, "cluster") 1553 cluster = f" {cluster}" if cluster else "" 1554 where = self.sql(expression, "where") 1555 returning = self.sql(expression, "returning") 1556 order = self.sql(expression, "order") 1557 limit = self.sql(expression, "limit") 1558 tables = self.expressions(expression, key="tables") 1559 tables = f" {tables}" if tables else "" 1560 if self.RETURNING_END: 1561 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1562 else: 1563 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1564 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1565 1566 def drop_sql(self, expression: exp.Drop) -> str: 1567 this = self.sql(expression, "this") 1568 expressions = self.expressions(expression, flat=True) 1569 expressions = f" ({expressions})" if expressions else "" 1570 kind = expression.args["kind"] 1571 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1572 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1573 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1574 on_cluster = self.sql(expression, "cluster") 1575 on_cluster = f" {on_cluster}" if on_cluster else "" 1576 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1577 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1578 cascade = " CASCADE" if expression.args.get("cascade") else "" 1579 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1580 purge = " PURGE" if expression.args.get("purge") else "" 1581 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1582 1583 def set_operation(self, expression: exp.SetOperation) -> str: 1584 op_type = type(expression) 1585 op_name = op_type.key.upper() 1586 1587 distinct = expression.args.get("distinct") 1588 if ( 1589 distinct is False 1590 and op_type in (exp.Except, exp.Intersect) 1591 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1592 ): 1593 self.unsupported(f"{op_name} ALL is not supported") 1594 1595 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1596 1597 if distinct is None: 1598 distinct = default_distinct 1599 if distinct is None: 1600 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1601 1602 if distinct is default_distinct: 1603 distinct_or_all = "" 1604 else: 1605 distinct_or_all = " DISTINCT" if distinct else " ALL" 1606 1607 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1608 side_kind = f"{side_kind} " if side_kind else "" 1609 1610 by_name = " BY NAME" if expression.args.get("by_name") else "" 1611 on = self.expressions(expression, key="on", flat=True) 1612 on = f" ON ({on})" if on else "" 1613 1614 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1615 1616 def set_operations(self, expression: exp.SetOperation) -> str: 1617 if not self.SET_OP_MODIFIERS: 1618 limit = expression.args.get("limit") 1619 order = expression.args.get("order") 1620 1621 if limit or order: 1622 select = self._move_ctes_to_top_level( 1623 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1624 ) 1625 1626 if limit: 1627 select = select.limit(limit.pop(), copy=False) 1628 if order: 1629 select = select.order_by(order.pop(), copy=False) 1630 return self.sql(select) 1631 1632 sqls: t.List[str] = [] 1633 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1634 1635 while stack: 1636 node = stack.pop() 1637 1638 if isinstance(node, exp.SetOperation): 1639 stack.append(node.expression) 1640 stack.append( 1641 self.maybe_comment( 1642 self.set_operation(node), comments=node.comments, separated=True 1643 ) 1644 ) 1645 stack.append(node.this) 1646 else: 1647 sqls.append(self.sql(node)) 1648 1649 this = self.sep().join(sqls) 1650 this = self.query_modifiers(expression, this) 1651 return self.prepend_ctes(expression, this) 1652 1653 def fetch_sql(self, expression: exp.Fetch) -> str: 1654 direction = expression.args.get("direction") 1655 direction = f" {direction}" if direction else "" 1656 count = self.sql(expression, "count") 1657 count = f" {count}" if count else "" 1658 limit_options = self.sql(expression, "limit_options") 1659 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1660 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1661 1662 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1663 percent = " PERCENT" if expression.args.get("percent") else "" 1664 rows = " ROWS" if expression.args.get("rows") else "" 1665 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1666 if not with_ties and rows: 1667 with_ties = " ONLY" 1668 return f"{percent}{rows}{with_ties}" 1669 1670 def filter_sql(self, expression: exp.Filter) -> str: 1671 if self.AGGREGATE_FILTER_SUPPORTED: 1672 this = self.sql(expression, "this") 1673 where = self.sql(expression, "expression").strip() 1674 return f"{this} FILTER({where})" 1675 1676 agg = expression.this 1677 agg_arg = agg.this 1678 cond = expression.expression.this 1679 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1680 return self.sql(agg) 1681 1682 def hint_sql(self, expression: exp.Hint) -> str: 1683 if not self.QUERY_HINTS: 1684 self.unsupported("Hints are not supported") 1685 return "" 1686 1687 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1688 1689 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1690 using = self.sql(expression, "using") 1691 using = f" USING {using}" if using else "" 1692 columns = self.expressions(expression, key="columns", flat=True) 1693 columns = f"({columns})" if columns else "" 1694 partition_by = self.expressions(expression, key="partition_by", flat=True) 1695 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1696 where = self.sql(expression, "where") 1697 include = self.expressions(expression, key="include", flat=True) 1698 if include: 1699 include = f" INCLUDE ({include})" 1700 with_storage = self.expressions(expression, key="with_storage", flat=True) 1701 with_storage = f" WITH ({with_storage})" if with_storage else "" 1702 tablespace = self.sql(expression, "tablespace") 1703 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1704 on = self.sql(expression, "on") 1705 on = f" ON {on}" if on else "" 1706 1707 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1708 1709 def index_sql(self, expression: exp.Index) -> str: 1710 unique = "UNIQUE " if expression.args.get("unique") else "" 1711 primary = "PRIMARY " if expression.args.get("primary") else "" 1712 amp = "AMP " if expression.args.get("amp") else "" 1713 name = self.sql(expression, "this") 1714 name = f"{name} " if name else "" 1715 table = self.sql(expression, "table") 1716 table = f"{self.INDEX_ON} {table}" if table else "" 1717 1718 index = "INDEX " if not table else "" 1719 1720 params = self.sql(expression, "params") 1721 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1722 1723 def identifier_sql(self, expression: exp.Identifier) -> str: 1724 text = expression.name 1725 lower = text.lower() 1726 text = lower if self.normalize and not expression.quoted else text 1727 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1728 if ( 1729 expression.quoted 1730 or self.dialect.can_quote(expression, self.identify) 1731 or lower in self.RESERVED_KEYWORDS 1732 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1733 ): 1734 text = f"{self._identifier_start}{text}{self._identifier_end}" 1735 return text 1736 1737 def hex_sql(self, expression: exp.Hex) -> str: 1738 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1739 if self.dialect.HEX_LOWERCASE: 1740 text = self.func("LOWER", text) 1741 1742 return text 1743 1744 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1745 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1746 if not self.dialect.HEX_LOWERCASE: 1747 text = self.func("LOWER", text) 1748 return text 1749 1750 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1751 input_format = self.sql(expression, "input_format") 1752 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1753 output_format = self.sql(expression, "output_format") 1754 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1755 return self.sep().join((input_format, output_format)) 1756 1757 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1758 string = self.sql(exp.Literal.string(expression.name)) 1759 return f"{prefix}{string}" 1760 1761 def partition_sql(self, expression: exp.Partition) -> str: 1762 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1763 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1764 1765 def properties_sql(self, expression: exp.Properties) -> str: 1766 root_properties = [] 1767 with_properties = [] 1768 1769 for p in expression.expressions: 1770 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1771 if p_loc == exp.Properties.Location.POST_WITH: 1772 with_properties.append(p) 1773 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1774 root_properties.append(p) 1775 1776 root_props_ast = exp.Properties(expressions=root_properties) 1777 root_props_ast.parent = expression.parent 1778 1779 with_props_ast = exp.Properties(expressions=with_properties) 1780 with_props_ast.parent = expression.parent 1781 1782 root_props = self.root_properties(root_props_ast) 1783 with_props = self.with_properties(with_props_ast) 1784 1785 if root_props and with_props and not self.pretty: 1786 with_props = " " + with_props 1787 1788 return root_props + with_props 1789 1790 def root_properties(self, properties: exp.Properties) -> str: 1791 if properties.expressions: 1792 return self.expressions(properties, indent=False, sep=" ") 1793 return "" 1794 1795 def properties( 1796 self, 1797 properties: exp.Properties, 1798 prefix: str = "", 1799 sep: str = ", ", 1800 suffix: str = "", 1801 wrapped: bool = True, 1802 ) -> str: 1803 if properties.expressions: 1804 expressions = self.expressions(properties, sep=sep, indent=False) 1805 if expressions: 1806 expressions = self.wrap(expressions) if wrapped else expressions 1807 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1808 return "" 1809 1810 def with_properties(self, properties: exp.Properties) -> str: 1811 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1812 1813 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1814 properties_locs = defaultdict(list) 1815 for p in properties.expressions: 1816 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1817 if p_loc != exp.Properties.Location.UNSUPPORTED: 1818 properties_locs[p_loc].append(p) 1819 else: 1820 self.unsupported(f"Unsupported property {p.key}") 1821 1822 return properties_locs 1823 1824 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1825 if isinstance(expression.this, exp.Dot): 1826 return self.sql(expression, "this") 1827 return f"'{expression.name}'" if string_key else expression.name 1828 1829 def property_sql(self, expression: exp.Property) -> str: 1830 property_cls = expression.__class__ 1831 if property_cls == exp.Property: 1832 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1833 1834 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1835 if not property_name: 1836 self.unsupported(f"Unsupported property {expression.key}") 1837 1838 return f"{property_name}={self.sql(expression, 'this')}" 1839 1840 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1841 if self.SUPPORTS_CREATE_TABLE_LIKE: 1842 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1843 options = f" {options}" if options else "" 1844 1845 like = f"LIKE {self.sql(expression, 'this')}{options}" 1846 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1847 like = f"({like})" 1848 1849 return like 1850 1851 if expression.expressions: 1852 self.unsupported("Transpilation of LIKE property options is unsupported") 1853 1854 select = exp.select("*").from_(expression.this).limit(0) 1855 return f"AS {self.sql(select)}" 1856 1857 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1858 no = "NO " if expression.args.get("no") else "" 1859 protection = " PROTECTION" if expression.args.get("protection") else "" 1860 return f"{no}FALLBACK{protection}" 1861 1862 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1863 no = "NO " if expression.args.get("no") else "" 1864 local = expression.args.get("local") 1865 local = f"{local} " if local else "" 1866 dual = "DUAL " if expression.args.get("dual") else "" 1867 before = "BEFORE " if expression.args.get("before") else "" 1868 after = "AFTER " if expression.args.get("after") else "" 1869 return f"{no}{local}{dual}{before}{after}JOURNAL" 1870 1871 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1872 freespace = self.sql(expression, "this") 1873 percent = " PERCENT" if expression.args.get("percent") else "" 1874 return f"FREESPACE={freespace}{percent}" 1875 1876 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1877 if expression.args.get("default"): 1878 property = "DEFAULT" 1879 elif expression.args.get("on"): 1880 property = "ON" 1881 else: 1882 property = "OFF" 1883 return f"CHECKSUM={property}" 1884 1885 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1886 if expression.args.get("no"): 1887 return "NO MERGEBLOCKRATIO" 1888 if expression.args.get("default"): 1889 return "DEFAULT MERGEBLOCKRATIO" 1890 1891 percent = " PERCENT" if expression.args.get("percent") else "" 1892 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1893 1894 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1895 default = expression.args.get("default") 1896 minimum = expression.args.get("minimum") 1897 maximum = expression.args.get("maximum") 1898 if default or minimum or maximum: 1899 if default: 1900 prop = "DEFAULT" 1901 elif minimum: 1902 prop = "MINIMUM" 1903 else: 1904 prop = "MAXIMUM" 1905 return f"{prop} DATABLOCKSIZE" 1906 units = expression.args.get("units") 1907 units = f" {units}" if units else "" 1908 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1909 1910 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1911 autotemp = expression.args.get("autotemp") 1912 always = expression.args.get("always") 1913 default = expression.args.get("default") 1914 manual = expression.args.get("manual") 1915 never = expression.args.get("never") 1916 1917 if autotemp is not None: 1918 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1919 elif always: 1920 prop = "ALWAYS" 1921 elif default: 1922 prop = "DEFAULT" 1923 elif manual: 1924 prop = "MANUAL" 1925 elif never: 1926 prop = "NEVER" 1927 return f"BLOCKCOMPRESSION={prop}" 1928 1929 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1930 no = expression.args.get("no") 1931 no = " NO" if no else "" 1932 concurrent = expression.args.get("concurrent") 1933 concurrent = " CONCURRENT" if concurrent else "" 1934 target = self.sql(expression, "target") 1935 target = f" {target}" if target else "" 1936 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1937 1938 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1939 if isinstance(expression.this, list): 1940 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1941 if expression.this: 1942 modulus = self.sql(expression, "this") 1943 remainder = self.sql(expression, "expression") 1944 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1945 1946 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1947 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1948 return f"FROM ({from_expressions}) TO ({to_expressions})" 1949 1950 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1951 this = self.sql(expression, "this") 1952 1953 for_values_or_default = expression.expression 1954 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1955 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1956 else: 1957 for_values_or_default = " DEFAULT" 1958 1959 return f"PARTITION OF {this}{for_values_or_default}" 1960 1961 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1962 kind = expression.args.get("kind") 1963 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1964 for_or_in = expression.args.get("for_or_in") 1965 for_or_in = f" {for_or_in}" if for_or_in else "" 1966 lock_type = expression.args.get("lock_type") 1967 override = " OVERRIDE" if expression.args.get("override") else "" 1968 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1969 1970 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1971 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1972 statistics = expression.args.get("statistics") 1973 statistics_sql = "" 1974 if statistics is not None: 1975 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1976 return f"{data_sql}{statistics_sql}" 1977 1978 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1979 this = self.sql(expression, "this") 1980 this = f"HISTORY_TABLE={this}" if this else "" 1981 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1982 data_consistency = ( 1983 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1984 ) 1985 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1986 retention_period = ( 1987 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1988 ) 1989 1990 if this: 1991 on_sql = self.func("ON", this, data_consistency, retention_period) 1992 else: 1993 on_sql = "ON" if expression.args.get("on") else "OFF" 1994 1995 sql = f"SYSTEM_VERSIONING={on_sql}" 1996 1997 return f"WITH({sql})" if expression.args.get("with_") else sql 1998 1999 def insert_sql(self, expression: exp.Insert) -> str: 2000 hint = self.sql(expression, "hint") 2001 overwrite = expression.args.get("overwrite") 2002 2003 if isinstance(expression.this, exp.Directory): 2004 this = " OVERWRITE" if overwrite else " INTO" 2005 else: 2006 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2007 2008 stored = self.sql(expression, "stored") 2009 stored = f" {stored}" if stored else "" 2010 alternative = expression.args.get("alternative") 2011 alternative = f" OR {alternative}" if alternative else "" 2012 ignore = " IGNORE" if expression.args.get("ignore") else "" 2013 is_function = expression.args.get("is_function") 2014 if is_function: 2015 this = f"{this} FUNCTION" 2016 this = f"{this} {self.sql(expression, 'this')}" 2017 2018 exists = " IF EXISTS" if expression.args.get("exists") else "" 2019 where = self.sql(expression, "where") 2020 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2021 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2022 on_conflict = self.sql(expression, "conflict") 2023 on_conflict = f" {on_conflict}" if on_conflict else "" 2024 by_name = " BY NAME" if expression.args.get("by_name") else "" 2025 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2026 returning = self.sql(expression, "returning") 2027 2028 if self.RETURNING_END: 2029 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2030 else: 2031 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2032 2033 partition_by = self.sql(expression, "partition") 2034 partition_by = f" {partition_by}" if partition_by else "" 2035 settings = self.sql(expression, "settings") 2036 settings = f" {settings}" if settings else "" 2037 2038 source = self.sql(expression, "source") 2039 source = f"TABLE {source}" if source else "" 2040 2041 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2042 return self.prepend_ctes(expression, sql) 2043 2044 def introducer_sql(self, expression: exp.Introducer) -> str: 2045 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2046 2047 def kill_sql(self, expression: exp.Kill) -> str: 2048 kind = self.sql(expression, "kind") 2049 kind = f" {kind}" if kind else "" 2050 this = self.sql(expression, "this") 2051 this = f" {this}" if this else "" 2052 return f"KILL{kind}{this}" 2053 2054 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2055 return expression.name 2056 2057 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2058 return expression.name 2059 2060 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2061 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2062 2063 constraint = self.sql(expression, "constraint") 2064 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2065 2066 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2067 if conflict_keys: 2068 conflict_keys = f"({conflict_keys})" 2069 2070 index_predicate = self.sql(expression, "index_predicate") 2071 conflict_keys = f"{conflict_keys}{index_predicate} " 2072 2073 action = self.sql(expression, "action") 2074 2075 expressions = self.expressions(expression, flat=True) 2076 if expressions: 2077 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2078 expressions = f" {set_keyword}{expressions}" 2079 2080 where = self.sql(expression, "where") 2081 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2082 2083 def returning_sql(self, expression: exp.Returning) -> str: 2084 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2085 2086 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2087 fields = self.sql(expression, "fields") 2088 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2089 escaped = self.sql(expression, "escaped") 2090 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2091 items = self.sql(expression, "collection_items") 2092 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2093 keys = self.sql(expression, "map_keys") 2094 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2095 lines = self.sql(expression, "lines") 2096 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2097 null = self.sql(expression, "null") 2098 null = f" NULL DEFINED AS {null}" if null else "" 2099 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2100 2101 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2102 return f"WITH ({self.expressions(expression, flat=True)})" 2103 2104 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2105 this = f"{self.sql(expression, 'this')} INDEX" 2106 target = self.sql(expression, "target") 2107 target = f" FOR {target}" if target else "" 2108 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2109 2110 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2111 this = self.sql(expression, "this") 2112 kind = self.sql(expression, "kind") 2113 expr = self.sql(expression, "expression") 2114 return f"{this} ({kind} => {expr})" 2115 2116 def table_parts(self, expression: exp.Table) -> str: 2117 return ".".join( 2118 self.sql(part) 2119 for part in ( 2120 expression.args.get("catalog"), 2121 expression.args.get("db"), 2122 expression.args.get("this"), 2123 ) 2124 if part is not None 2125 ) 2126 2127 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2128 table = self.table_parts(expression) 2129 only = "ONLY " if expression.args.get("only") else "" 2130 partition = self.sql(expression, "partition") 2131 partition = f" {partition}" if partition else "" 2132 version = self.sql(expression, "version") 2133 version = f" {version}" if version else "" 2134 alias = self.sql(expression, "alias") 2135 alias = f"{sep}{alias}" if alias else "" 2136 2137 sample = self.sql(expression, "sample") 2138 if self.dialect.ALIAS_POST_TABLESAMPLE: 2139 sample_pre_alias = sample 2140 sample_post_alias = "" 2141 else: 2142 sample_pre_alias = "" 2143 sample_post_alias = sample 2144 2145 hints = self.expressions(expression, key="hints", sep=" ") 2146 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2147 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2148 joins = self.indent( 2149 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2150 ) 2151 laterals = self.expressions(expression, key="laterals", sep="") 2152 2153 file_format = self.sql(expression, "format") 2154 if file_format: 2155 pattern = self.sql(expression, "pattern") 2156 pattern = f", PATTERN => {pattern}" if pattern else "" 2157 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2158 2159 ordinality = expression.args.get("ordinality") or "" 2160 if ordinality: 2161 ordinality = f" WITH ORDINALITY{alias}" 2162 alias = "" 2163 2164 when = self.sql(expression, "when") 2165 if when: 2166 table = f"{table} {when}" 2167 2168 changes = self.sql(expression, "changes") 2169 changes = f" {changes}" if changes else "" 2170 2171 rows_from = self.expressions(expression, key="rows_from") 2172 if rows_from: 2173 table = f"ROWS FROM {self.wrap(rows_from)}" 2174 2175 indexed = expression.args.get("indexed") 2176 if indexed is not None: 2177 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2178 else: 2179 indexed = "" 2180 2181 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{indexed}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2182 2183 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2184 table = self.func("TABLE", expression.this) 2185 alias = self.sql(expression, "alias") 2186 alias = f" AS {alias}" if alias else "" 2187 sample = self.sql(expression, "sample") 2188 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2189 joins = self.indent( 2190 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2191 ) 2192 return f"{table}{alias}{pivots}{sample}{joins}" 2193 2194 def tablesample_sql( 2195 self, 2196 expression: exp.TableSample, 2197 tablesample_keyword: t.Optional[str] = None, 2198 ) -> str: 2199 method = self.sql(expression, "method") 2200 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2201 numerator = self.sql(expression, "bucket_numerator") 2202 denominator = self.sql(expression, "bucket_denominator") 2203 field = self.sql(expression, "bucket_field") 2204 field = f" ON {field}" if field else "" 2205 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2206 seed = self.sql(expression, "seed") 2207 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2208 2209 size = self.sql(expression, "size") 2210 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2211 size = f"{size} ROWS" 2212 2213 percent = self.sql(expression, "percent") 2214 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2215 percent = f"{percent} PERCENT" 2216 2217 expr = f"{bucket}{percent}{size}" 2218 if self.TABLESAMPLE_REQUIRES_PARENS: 2219 expr = f"({expr})" 2220 2221 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2222 2223 def pivot_sql(self, expression: exp.Pivot) -> str: 2224 expressions = self.expressions(expression, flat=True) 2225 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2226 2227 group = self.sql(expression, "group") 2228 2229 if expression.this: 2230 this = self.sql(expression, "this") 2231 if not expressions: 2232 sql = f"UNPIVOT {this}" 2233 else: 2234 on = f"{self.seg('ON')} {expressions}" 2235 into = self.sql(expression, "into") 2236 into = f"{self.seg('INTO')} {into}" if into else "" 2237 using = self.expressions(expression, key="using", flat=True) 2238 using = f"{self.seg('USING')} {using}" if using else "" 2239 sql = f"{direction} {this}{on}{into}{using}{group}" 2240 return self.prepend_ctes(expression, sql) 2241 2242 alias = self.sql(expression, "alias") 2243 alias = f" AS {alias}" if alias else "" 2244 2245 fields = self.expressions( 2246 expression, 2247 "fields", 2248 sep=" ", 2249 dynamic=True, 2250 new_line=True, 2251 skip_first=True, 2252 skip_last=True, 2253 ) 2254 2255 include_nulls = expression.args.get("include_nulls") 2256 if include_nulls is not None: 2257 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2258 else: 2259 nulls = "" 2260 2261 default_on_null = self.sql(expression, "default_on_null") 2262 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2263 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2264 return self.prepend_ctes(expression, sql) 2265 2266 def version_sql(self, expression: exp.Version) -> str: 2267 this = f"FOR {expression.name}" 2268 kind = expression.text("kind") 2269 expr = self.sql(expression, "expression") 2270 return f"{this} {kind} {expr}" 2271 2272 def tuple_sql(self, expression: exp.Tuple) -> str: 2273 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2274 2275 def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]: 2276 """ 2277 Returns (join_sql, from_sql) for UPDATE statements. 2278 - join_sql: placed after UPDATE table, before SET 2279 - from_sql: placed after SET clause (standard position) 2280 Dialects like MySQL need to convert FROM to JOIN syntax. 2281 """ 2282 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2283 return ("", self.sql(expression, "from_")) 2284 2285 # Qualify unqualified columns in SET clause with the target table 2286 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2287 target_table = expression.this 2288 if isinstance(target_table, exp.Table): 2289 target_name = exp.to_identifier(target_table.alias_or_name) 2290 for eq in expression.expressions: 2291 col = eq.this 2292 if isinstance(col, exp.Column) and not col.table: 2293 col.set("table", target_name) 2294 2295 table = from_expr.this 2296 if nested_joins := table.args.get("joins", []): 2297 table.set("joins", None) 2298 2299 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2300 for nested in nested_joins: 2301 if not nested.args.get("on") and not nested.args.get("using"): 2302 nested.set("on", exp.true()) 2303 join_sql += self.sql(nested) 2304 2305 return (join_sql, "") 2306 2307 def update_sql(self, expression: exp.Update) -> str: 2308 this = self.sql(expression, "this") 2309 join_sql, from_sql = self._update_from_joins_sql(expression) 2310 set_sql = self.expressions(expression, flat=True) 2311 where_sql = self.sql(expression, "where") 2312 returning = self.sql(expression, "returning") 2313 order = self.sql(expression, "order") 2314 limit = self.sql(expression, "limit") 2315 if self.RETURNING_END: 2316 expression_sql = f"{from_sql}{where_sql}{returning}" 2317 else: 2318 expression_sql = f"{returning}{from_sql}{where_sql}" 2319 options = self.expressions(expression, key="options") 2320 options = f" OPTION({options})" if options else "" 2321 sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2322 return self.prepend_ctes(expression, sql) 2323 2324 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2325 values_as_table = values_as_table and self.VALUES_AS_TABLE 2326 2327 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2328 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2329 args = self.expressions(expression) 2330 alias = self.sql(expression, "alias") 2331 values = f"VALUES{self.seg('')}{args}" 2332 values = ( 2333 f"({values})" 2334 if self.WRAP_DERIVED_VALUES 2335 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2336 else values 2337 ) 2338 values = self.query_modifiers(expression, values) 2339 return f"{values} AS {alias}" if alias else values 2340 2341 # Converts `VALUES...` expression into a series of select unions. 2342 alias_node = expression.args.get("alias") 2343 column_names = alias_node and alias_node.columns 2344 2345 selects: t.List[exp.Query] = [] 2346 2347 for i, tup in enumerate(expression.expressions): 2348 row = tup.expressions 2349 2350 if i == 0 and column_names: 2351 row = [ 2352 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2353 ] 2354 2355 selects.append(exp.Select(expressions=row)) 2356 2357 if self.pretty: 2358 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2359 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2360 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2361 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2362 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2363 2364 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2365 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2366 return f"({unions}){alias}" 2367 2368 def var_sql(self, expression: exp.Var) -> str: 2369 return self.sql(expression, "this") 2370 2371 @unsupported_args("expressions") 2372 def into_sql(self, expression: exp.Into) -> str: 2373 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2374 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2375 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2376 2377 def from_sql(self, expression: exp.From) -> str: 2378 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2379 2380 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2381 grouping_sets = self.expressions(expression, indent=False) 2382 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2383 2384 def rollup_sql(self, expression: exp.Rollup) -> str: 2385 expressions = self.expressions(expression, indent=False) 2386 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2387 2388 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2389 this = self.sql(expression, "this") 2390 2391 columns = self.expressions(expression, flat=True) 2392 2393 from_sql = self.sql(expression, "from_index") 2394 from_sql = f" FROM {from_sql}" if from_sql else "" 2395 2396 properties = expression.args.get("properties") 2397 properties_sql = ( 2398 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2399 ) 2400 2401 return f"{this}({columns}){from_sql}{properties_sql}" 2402 2403 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2404 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2405 2406 def cube_sql(self, expression: exp.Cube) -> str: 2407 expressions = self.expressions(expression, indent=False) 2408 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2409 2410 def group_sql(self, expression: exp.Group) -> str: 2411 group_by_all = expression.args.get("all") 2412 if group_by_all is True: 2413 modifier = " ALL" 2414 elif group_by_all is False: 2415 modifier = " DISTINCT" 2416 else: 2417 modifier = "" 2418 2419 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2420 2421 grouping_sets = self.expressions(expression, key="grouping_sets") 2422 cube = self.expressions(expression, key="cube") 2423 rollup = self.expressions(expression, key="rollup") 2424 2425 groupings = csv( 2426 self.seg(grouping_sets) if grouping_sets else "", 2427 self.seg(cube) if cube else "", 2428 self.seg(rollup) if rollup else "", 2429 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2430 sep=self.GROUPINGS_SEP, 2431 ) 2432 2433 if ( 2434 expression.expressions 2435 and groupings 2436 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2437 ): 2438 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2439 2440 return f"{group_by}{groupings}" 2441 2442 def having_sql(self, expression: exp.Having) -> str: 2443 this = self.indent(self.sql(expression, "this")) 2444 return f"{self.seg('HAVING')}{self.sep()}{this}" 2445 2446 def connect_sql(self, expression: exp.Connect) -> str: 2447 start = self.sql(expression, "start") 2448 start = self.seg(f"START WITH {start}") if start else "" 2449 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2450 connect = self.sql(expression, "connect") 2451 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2452 return start + connect 2453 2454 def prior_sql(self, expression: exp.Prior) -> str: 2455 return f"PRIOR {self.sql(expression, 'this')}" 2456 2457 def join_sql(self, expression: exp.Join) -> str: 2458 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2459 side = None 2460 else: 2461 side = expression.side 2462 2463 op_sql = " ".join( 2464 op 2465 for op in ( 2466 expression.method, 2467 "GLOBAL" if expression.args.get("global_") else None, 2468 side, 2469 expression.kind, 2470 expression.hint if self.JOIN_HINTS else None, 2471 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2472 ) 2473 if op 2474 ) 2475 match_cond = self.sql(expression, "match_condition") 2476 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2477 on_sql = self.sql(expression, "on") 2478 using = expression.args.get("using") 2479 2480 if not on_sql and using: 2481 on_sql = csv(*(self.sql(column) for column in using)) 2482 2483 this = expression.this 2484 this_sql = self.sql(this) 2485 2486 exprs = self.expressions(expression) 2487 if exprs: 2488 this_sql = f"{this_sql},{self.seg(exprs)}" 2489 2490 if on_sql: 2491 on_sql = self.indent(on_sql, skip_first=True) 2492 space = self.seg(" " * self.pad) if self.pretty else " " 2493 if using: 2494 on_sql = f"{space}USING ({on_sql})" 2495 else: 2496 on_sql = f"{space}ON {on_sql}" 2497 elif not op_sql: 2498 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2499 return f" {this_sql}" 2500 2501 return f", {this_sql}" 2502 2503 if op_sql != "STRAIGHT_JOIN": 2504 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2505 2506 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2507 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2508 2509 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2510 args = self.expressions(expression, flat=True) 2511 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2512 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2513 2514 def lateral_op(self, expression: exp.Lateral) -> str: 2515 cross_apply = expression.args.get("cross_apply") 2516 2517 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2518 if cross_apply is True: 2519 op = "INNER JOIN " 2520 elif cross_apply is False: 2521 op = "LEFT JOIN " 2522 else: 2523 op = "" 2524 2525 return f"{op}LATERAL" 2526 2527 def lateral_sql(self, expression: exp.Lateral) -> str: 2528 this = self.sql(expression, "this") 2529 2530 if expression.args.get("view"): 2531 alias = expression.args["alias"] 2532 columns = self.expressions(alias, key="columns", flat=True) 2533 table = f" {alias.name}" if alias.name else "" 2534 columns = f" AS {columns}" if columns else "" 2535 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2536 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2537 2538 alias = self.sql(expression, "alias") 2539 alias = f" AS {alias}" if alias else "" 2540 2541 ordinality = expression.args.get("ordinality") or "" 2542 if ordinality: 2543 ordinality = f" WITH ORDINALITY{alias}" 2544 alias = "" 2545 2546 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2547 2548 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2549 this = self.sql(expression, "this") 2550 2551 args = [ 2552 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2553 for e in (expression.args.get(k) for k in ("offset", "expression")) 2554 if e 2555 ] 2556 2557 args_sql = ", ".join(self.sql(e) for e in args) 2558 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2559 expressions = self.expressions(expression, flat=True) 2560 limit_options = self.sql(expression, "limit_options") 2561 expressions = f" BY {expressions}" if expressions else "" 2562 2563 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2564 2565 def offset_sql(self, expression: exp.Offset) -> str: 2566 this = self.sql(expression, "this") 2567 value = expression.expression 2568 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2569 expressions = self.expressions(expression, flat=True) 2570 expressions = f" BY {expressions}" if expressions else "" 2571 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2572 2573 def setitem_sql(self, expression: exp.SetItem) -> str: 2574 kind = self.sql(expression, "kind") 2575 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2576 kind = "" 2577 else: 2578 kind = f"{kind} " if kind else "" 2579 this = self.sql(expression, "this") 2580 expressions = self.expressions(expression) 2581 collate = self.sql(expression, "collate") 2582 collate = f" COLLATE {collate}" if collate else "" 2583 global_ = "GLOBAL " if expression.args.get("global_") else "" 2584 return f"{global_}{kind}{this}{expressions}{collate}" 2585 2586 def set_sql(self, expression: exp.Set) -> str: 2587 expressions = f" {self.expressions(expression, flat=True)}" 2588 tag = " TAG" if expression.args.get("tag") else "" 2589 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2590 2591 def queryband_sql(self, expression: exp.QueryBand) -> str: 2592 this = self.sql(expression, "this") 2593 update = " UPDATE" if expression.args.get("update") else "" 2594 scope = self.sql(expression, "scope") 2595 scope = f" FOR {scope}" if scope else "" 2596 2597 return f"QUERY_BAND = {this}{update}{scope}" 2598 2599 def pragma_sql(self, expression: exp.Pragma) -> str: 2600 return f"PRAGMA {self.sql(expression, 'this')}" 2601 2602 def lock_sql(self, expression: exp.Lock) -> str: 2603 if not self.LOCKING_READS_SUPPORTED: 2604 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2605 return "" 2606 2607 update = expression.args["update"] 2608 key = expression.args.get("key") 2609 if update: 2610 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2611 else: 2612 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2613 expressions = self.expressions(expression, flat=True) 2614 expressions = f" OF {expressions}" if expressions else "" 2615 wait = expression.args.get("wait") 2616 2617 if wait is not None: 2618 if isinstance(wait, exp.Literal): 2619 wait = f" WAIT {self.sql(wait)}" 2620 else: 2621 wait = " NOWAIT" if wait else " SKIP LOCKED" 2622 2623 return f"{lock_type}{expressions}{wait or ''}" 2624 2625 def literal_sql(self, expression: exp.Literal) -> str: 2626 text = expression.this or "" 2627 if expression.is_string: 2628 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2629 return text 2630 2631 def escape_str( 2632 self, 2633 text: str, 2634 escape_backslash: bool = True, 2635 delimiter: t.Optional[str] = None, 2636 escaped_delimiter: t.Optional[str] = None, 2637 is_byte_string: bool = False, 2638 ) -> str: 2639 if is_byte_string: 2640 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2641 else: 2642 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2643 2644 if supports_escape_sequences: 2645 text = "".join( 2646 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2647 for ch in text 2648 ) 2649 2650 delimiter = delimiter or self.dialect.QUOTE_END 2651 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2652 2653 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2654 2655 def loaddata_sql(self, expression: exp.LoadData) -> str: 2656 local = " LOCAL" if expression.args.get("local") else "" 2657 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2658 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2659 this = f" INTO TABLE {self.sql(expression, 'this')}" 2660 partition = self.sql(expression, "partition") 2661 partition = f" {partition}" if partition else "" 2662 input_format = self.sql(expression, "input_format") 2663 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2664 serde = self.sql(expression, "serde") 2665 serde = f" SERDE {serde}" if serde else "" 2666 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2667 2668 def null_sql(self, *_) -> str: 2669 return "NULL" 2670 2671 def boolean_sql(self, expression: exp.Boolean) -> str: 2672 return "TRUE" if expression.this else "FALSE" 2673 2674 def booland_sql(self, expression: exp.Booland) -> str: 2675 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 2676 2677 def boolor_sql(self, expression: exp.Boolor) -> str: 2678 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 2679 2680 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2681 this = self.sql(expression, "this") 2682 this = f"{this} " if this else this 2683 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2684 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2685 2686 def withfill_sql(self, expression: exp.WithFill) -> str: 2687 from_sql = self.sql(expression, "from_") 2688 from_sql = f" FROM {from_sql}" if from_sql else "" 2689 to_sql = self.sql(expression, "to") 2690 to_sql = f" TO {to_sql}" if to_sql else "" 2691 step_sql = self.sql(expression, "step") 2692 step_sql = f" STEP {step_sql}" if step_sql else "" 2693 interpolated_values = [ 2694 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2695 if isinstance(e, exp.Alias) 2696 else self.sql(e, "this") 2697 for e in expression.args.get("interpolate") or [] 2698 ] 2699 interpolate = ( 2700 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2701 ) 2702 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2703 2704 def cluster_sql(self, expression: exp.Cluster) -> str: 2705 return self.op_expressions("CLUSTER BY", expression) 2706 2707 def distribute_sql(self, expression: exp.Distribute) -> str: 2708 return self.op_expressions("DISTRIBUTE BY", expression) 2709 2710 def sort_sql(self, expression: exp.Sort) -> str: 2711 return self.op_expressions("SORT BY", expression) 2712 2713 def ordered_sql(self, expression: exp.Ordered) -> str: 2714 desc = expression.args.get("desc") 2715 asc = not desc 2716 2717 nulls_first = expression.args.get("nulls_first") 2718 nulls_last = not nulls_first 2719 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2720 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2721 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2722 2723 this = self.sql(expression, "this") 2724 2725 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2726 nulls_sort_change = "" 2727 if nulls_first and ( 2728 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2729 ): 2730 nulls_sort_change = " NULLS FIRST" 2731 elif ( 2732 nulls_last 2733 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2734 and not nulls_are_last 2735 ): 2736 nulls_sort_change = " NULLS LAST" 2737 2738 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2739 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2740 window = expression.find_ancestor(exp.Window, exp.Select) 2741 if isinstance(window, exp.Window) and window.args.get("spec"): 2742 self.unsupported( 2743 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2744 ) 2745 nulls_sort_change = "" 2746 elif self.NULL_ORDERING_SUPPORTED is False and ( 2747 (asc and nulls_sort_change == " NULLS LAST") 2748 or (desc and nulls_sort_change == " NULLS FIRST") 2749 ): 2750 # BigQuery does not allow these ordering/nulls combinations when used under 2751 # an aggregation func or under a window containing one 2752 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2753 2754 if isinstance(ancestor, exp.Window): 2755 ancestor = ancestor.this 2756 if isinstance(ancestor, exp.AggFunc): 2757 self.unsupported( 2758 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2759 ) 2760 nulls_sort_change = "" 2761 elif self.NULL_ORDERING_SUPPORTED is None: 2762 if expression.this.is_int: 2763 self.unsupported( 2764 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2765 ) 2766 elif not isinstance(expression.this, exp.Rand): 2767 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2768 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2769 nulls_sort_change = "" 2770 2771 with_fill = self.sql(expression, "with_fill") 2772 with_fill = f" {with_fill}" if with_fill else "" 2773 2774 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2775 2776 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2777 window_frame = self.sql(expression, "window_frame") 2778 window_frame = f"{window_frame} " if window_frame else "" 2779 2780 this = self.sql(expression, "this") 2781 2782 return f"{window_frame}{this}" 2783 2784 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2785 partition = self.partition_by_sql(expression) 2786 order = self.sql(expression, "order") 2787 measures = self.expressions(expression, key="measures") 2788 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2789 rows = self.sql(expression, "rows") 2790 rows = self.seg(rows) if rows else "" 2791 after = self.sql(expression, "after") 2792 after = self.seg(after) if after else "" 2793 pattern = self.sql(expression, "pattern") 2794 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2795 definition_sqls = [ 2796 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2797 for definition in expression.args.get("define", []) 2798 ] 2799 definitions = self.expressions(sqls=definition_sqls) 2800 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2801 body = "".join( 2802 ( 2803 partition, 2804 order, 2805 measures, 2806 rows, 2807 after, 2808 pattern, 2809 define, 2810 ) 2811 ) 2812 alias = self.sql(expression, "alias") 2813 alias = f" {alias}" if alias else "" 2814 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2815 2816 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2817 limit = expression.args.get("limit") 2818 2819 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2820 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2821 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2822 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2823 2824 return csv( 2825 *sqls, 2826 *[self.sql(join) for join in expression.args.get("joins") or []], 2827 self.sql(expression, "match"), 2828 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2829 self.sql(expression, "prewhere"), 2830 self.sql(expression, "where"), 2831 self.sql(expression, "connect"), 2832 self.sql(expression, "group"), 2833 self.sql(expression, "having"), 2834 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2835 self.sql(expression, "order"), 2836 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2837 *self.after_limit_modifiers(expression), 2838 self.options_modifier(expression), 2839 self.for_modifiers(expression), 2840 sep="", 2841 ) 2842 2843 def options_modifier(self, expression: exp.Expression) -> str: 2844 options = self.expressions(expression, key="options") 2845 return f" {options}" if options else "" 2846 2847 def for_modifiers(self, expression: exp.Expression) -> str: 2848 for_modifiers = self.expressions(expression, key="for_") 2849 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2850 2851 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2852 self.unsupported("Unsupported query option.") 2853 return "" 2854 2855 def offset_limit_modifiers( 2856 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2857 ) -> t.List[str]: 2858 return [ 2859 self.sql(expression, "offset") if fetch else self.sql(limit), 2860 self.sql(limit) if fetch else self.sql(expression, "offset"), 2861 ] 2862 2863 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2864 locks = self.expressions(expression, key="locks", sep=" ") 2865 locks = f" {locks}" if locks else "" 2866 return [locks, self.sql(expression, "sample")] 2867 2868 def select_sql(self, expression: exp.Select) -> str: 2869 into = expression.args.get("into") 2870 if not self.SUPPORTS_SELECT_INTO and into: 2871 into.pop() 2872 2873 hint = self.sql(expression, "hint") 2874 distinct = self.sql(expression, "distinct") 2875 distinct = f" {distinct}" if distinct else "" 2876 kind = self.sql(expression, "kind") 2877 2878 limit = expression.args.get("limit") 2879 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2880 top = self.limit_sql(limit, top=True) 2881 limit.pop() 2882 else: 2883 top = "" 2884 2885 expressions = self.expressions(expression) 2886 2887 if kind: 2888 if kind in self.SELECT_KINDS: 2889 kind = f" AS {kind}" 2890 else: 2891 if kind == "STRUCT": 2892 expressions = self.expressions( 2893 sqls=[ 2894 self.sql( 2895 exp.Struct( 2896 expressions=[ 2897 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2898 if isinstance(e, exp.Alias) 2899 else e 2900 for e in expression.expressions 2901 ] 2902 ) 2903 ) 2904 ] 2905 ) 2906 kind = "" 2907 2908 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2909 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2910 2911 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2912 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2913 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2914 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2915 sql = self.query_modifiers( 2916 expression, 2917 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2918 self.sql(expression, "into", comment=False), 2919 self.sql(expression, "from_", comment=False), 2920 ) 2921 2922 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2923 if expression.args.get("with_"): 2924 sql = self.maybe_comment(sql, expression) 2925 expression.pop_comments() 2926 2927 sql = self.prepend_ctes(expression, sql) 2928 2929 if not self.SUPPORTS_SELECT_INTO and into: 2930 if into.args.get("temporary"): 2931 table_kind = " TEMPORARY" 2932 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2933 table_kind = " UNLOGGED" 2934 else: 2935 table_kind = "" 2936 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2937 2938 return sql 2939 2940 def schema_sql(self, expression: exp.Schema) -> str: 2941 this = self.sql(expression, "this") 2942 sql = self.schema_columns_sql(expression) 2943 return f"{this} {sql}" if this and sql else this or sql 2944 2945 def schema_columns_sql(self, expression: exp.Schema) -> str: 2946 if expression.expressions: 2947 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2948 return "" 2949 2950 def star_sql(self, expression: exp.Star) -> str: 2951 except_ = self.expressions(expression, key="except_", flat=True) 2952 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2953 replace = self.expressions(expression, key="replace", flat=True) 2954 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2955 rename = self.expressions(expression, key="rename", flat=True) 2956 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2957 return f"*{except_}{replace}{rename}" 2958 2959 def parameter_sql(self, expression: exp.Parameter) -> str: 2960 this = self.sql(expression, "this") 2961 return f"{self.PARAMETER_TOKEN}{this}" 2962 2963 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2964 this = self.sql(expression, "this") 2965 kind = expression.text("kind") 2966 if kind: 2967 kind = f"{kind}." 2968 return f"@@{kind}{this}" 2969 2970 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2971 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2972 2973 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2974 alias = self.sql(expression, "alias") 2975 alias = f"{sep}{alias}" if alias else "" 2976 sample = self.sql(expression, "sample") 2977 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2978 alias = f"{sample}{alias}" 2979 2980 # Set to None so it's not generated again by self.query_modifiers() 2981 expression.set("sample", None) 2982 2983 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2984 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2985 return self.prepend_ctes(expression, sql) 2986 2987 def qualify_sql(self, expression: exp.Qualify) -> str: 2988 this = self.indent(self.sql(expression, "this")) 2989 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2990 2991 def unnest_sql(self, expression: exp.Unnest) -> str: 2992 args = self.expressions(expression, flat=True) 2993 2994 alias = expression.args.get("alias") 2995 offset = expression.args.get("offset") 2996 2997 if self.UNNEST_WITH_ORDINALITY: 2998 if alias and isinstance(offset, exp.Expression): 2999 alias.append("columns", offset) 3000 3001 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3002 columns = alias.columns 3003 alias = self.sql(columns[0]) if columns else "" 3004 else: 3005 alias = self.sql(alias) 3006 3007 alias = f" AS {alias}" if alias else alias 3008 if self.UNNEST_WITH_ORDINALITY: 3009 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3010 else: 3011 if isinstance(offset, exp.Expression): 3012 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3013 elif offset: 3014 suffix = f"{alias} WITH OFFSET" 3015 else: 3016 suffix = alias 3017 3018 return f"UNNEST({args}){suffix}" 3019 3020 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3021 return "" 3022 3023 def where_sql(self, expression: exp.Where) -> str: 3024 this = self.indent(self.sql(expression, "this")) 3025 return f"{self.seg('WHERE')}{self.sep()}{this}" 3026 3027 def window_sql(self, expression: exp.Window) -> str: 3028 this = self.sql(expression, "this") 3029 partition = self.partition_by_sql(expression) 3030 order = expression.args.get("order") 3031 order = self.order_sql(order, flat=True) if order else "" 3032 spec = self.sql(expression, "spec") 3033 alias = self.sql(expression, "alias") 3034 over = self.sql(expression, "over") or "OVER" 3035 3036 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3037 3038 first = expression.args.get("first") 3039 if first is None: 3040 first = "" 3041 else: 3042 first = "FIRST" if first else "LAST" 3043 3044 if not partition and not order and not spec and alias: 3045 return f"{this} {alias}" 3046 3047 args = self.format_args( 3048 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3049 ) 3050 return f"{this} ({args})" 3051 3052 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3053 partition = self.expressions(expression, key="partition_by", flat=True) 3054 return f"PARTITION BY {partition}" if partition else "" 3055 3056 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3057 kind = self.sql(expression, "kind") 3058 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3059 end = ( 3060 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3061 or "CURRENT ROW" 3062 ) 3063 3064 window_spec = f"{kind} BETWEEN {start} AND {end}" 3065 3066 exclude = self.sql(expression, "exclude") 3067 if exclude: 3068 if self.SUPPORTS_WINDOW_EXCLUDE: 3069 window_spec += f" EXCLUDE {exclude}" 3070 else: 3071 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3072 3073 return window_spec 3074 3075 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3076 this = self.sql(expression, "this") 3077 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3078 return f"{this} WITHIN GROUP ({expression_sql})" 3079 3080 def between_sql(self, expression: exp.Between) -> str: 3081 this = self.sql(expression, "this") 3082 low = self.sql(expression, "low") 3083 high = self.sql(expression, "high") 3084 symmetric = expression.args.get("symmetric") 3085 3086 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3087 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3088 3089 flag = ( 3090 " SYMMETRIC" 3091 if symmetric 3092 else " ASYMMETRIC" 3093 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3094 else "" # silently drop ASYMMETRIC – semantics identical 3095 ) 3096 return f"{this} BETWEEN{flag} {low} AND {high}" 3097 3098 def bracket_offset_expressions( 3099 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 3100 ) -> t.List[exp.Expression]: 3101 return apply_index_offset( 3102 expression.this, 3103 expression.expressions, 3104 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3105 dialect=self.dialect, 3106 ) 3107 3108 def bracket_sql(self, expression: exp.Bracket) -> str: 3109 expressions = self.bracket_offset_expressions(expression) 3110 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3111 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3112 3113 def all_sql(self, expression: exp.All) -> str: 3114 this = self.sql(expression, "this") 3115 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3116 this = self.wrap(this) 3117 return f"ALL {this}" 3118 3119 def any_sql(self, expression: exp.Any) -> str: 3120 this = self.sql(expression, "this") 3121 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3122 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3123 this = self.wrap(this) 3124 return f"ANY{this}" 3125 return f"ANY {this}" 3126 3127 def exists_sql(self, expression: exp.Exists) -> str: 3128 return f"EXISTS{self.wrap(expression)}" 3129 3130 def case_sql(self, expression: exp.Case) -> str: 3131 this = self.sql(expression, "this") 3132 statements = [f"CASE {this}" if this else "CASE"] 3133 3134 for e in expression.args["ifs"]: 3135 statements.append(f"WHEN {self.sql(e, 'this')}") 3136 statements.append(f"THEN {self.sql(e, 'true')}") 3137 3138 default = self.sql(expression, "default") 3139 3140 if default: 3141 statements.append(f"ELSE {default}") 3142 3143 statements.append("END") 3144 3145 if self.pretty and self.too_wide(statements): 3146 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3147 3148 return " ".join(statements) 3149 3150 def constraint_sql(self, expression: exp.Constraint) -> str: 3151 this = self.sql(expression, "this") 3152 expressions = self.expressions(expression, flat=True) 3153 return f"CONSTRAINT {this} {expressions}" 3154 3155 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3156 order = expression.args.get("order") 3157 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3158 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3159 3160 def extract_sql(self, expression: exp.Extract) -> str: 3161 from sqlglot.dialects.dialect import map_date_part 3162 3163 this = ( 3164 map_date_part(expression.this, self.dialect) 3165 if self.NORMALIZE_EXTRACT_DATE_PARTS 3166 else expression.this 3167 ) 3168 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3169 expression_sql = self.sql(expression, "expression") 3170 3171 return f"EXTRACT({this_sql} FROM {expression_sql})" 3172 3173 def trim_sql(self, expression: exp.Trim) -> str: 3174 trim_type = self.sql(expression, "position") 3175 3176 if trim_type == "LEADING": 3177 func_name = "LTRIM" 3178 elif trim_type == "TRAILING": 3179 func_name = "RTRIM" 3180 else: 3181 func_name = "TRIM" 3182 3183 return self.func(func_name, expression.this, expression.expression) 3184 3185 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3186 args = expression.expressions 3187 if isinstance(expression, exp.ConcatWs): 3188 args = args[1:] # Skip the delimiter 3189 3190 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3191 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3192 3193 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3194 3195 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3196 if not e.type: 3197 from sqlglot.optimizer.annotate_types import annotate_types 3198 3199 e = annotate_types(e, dialect=self.dialect) 3200 3201 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3202 return e 3203 3204 return exp.func("coalesce", e, exp.Literal.string("")) 3205 3206 args = [_wrap_with_coalesce(e) for e in args] 3207 3208 return args 3209 3210 def concat_sql(self, expression: exp.Concat) -> str: 3211 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3212 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3213 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3214 # instead of coalescing them to empty string. 3215 from sqlglot.dialects.dialect import concat_to_dpipe_sql 3216 3217 return concat_to_dpipe_sql(self, expression) 3218 3219 expressions = self.convert_concat_args(expression) 3220 3221 # Some dialects don't allow a single-argument CONCAT call 3222 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3223 return self.sql(expressions[0]) 3224 3225 return self.func("CONCAT", *expressions) 3226 3227 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3228 return self.func( 3229 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3230 ) 3231 3232 def check_sql(self, expression: exp.Check) -> str: 3233 this = self.sql(expression, key="this") 3234 return f"CHECK ({this})" 3235 3236 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3237 expressions = self.expressions(expression, flat=True) 3238 expressions = f" ({expressions})" if expressions else "" 3239 reference = self.sql(expression, "reference") 3240 reference = f" {reference}" if reference else "" 3241 delete = self.sql(expression, "delete") 3242 delete = f" ON DELETE {delete}" if delete else "" 3243 update = self.sql(expression, "update") 3244 update = f" ON UPDATE {update}" if update else "" 3245 options = self.expressions(expression, key="options", flat=True, sep=" ") 3246 options = f" {options}" if options else "" 3247 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3248 3249 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3250 this = self.sql(expression, "this") 3251 this = f" {this}" if this else "" 3252 expressions = self.expressions(expression, flat=True) 3253 include = self.sql(expression, "include") 3254 options = self.expressions(expression, key="options", flat=True, sep=" ") 3255 options = f" {options}" if options else "" 3256 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3257 3258 def if_sql(self, expression: exp.If) -> str: 3259 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3260 3261 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3262 if self.MATCH_AGAINST_TABLE_PREFIX: 3263 expressions = [] 3264 for expr in expression.expressions: 3265 if isinstance(expr, exp.Table): 3266 expressions.append(f"TABLE {self.sql(expr)}") 3267 else: 3268 expressions.append(expr) 3269 else: 3270 expressions = expression.expressions 3271 3272 modifier = expression.args.get("modifier") 3273 modifier = f" {modifier}" if modifier else "" 3274 return ( 3275 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3276 ) 3277 3278 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3279 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3280 3281 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3282 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3283 3284 if expression.args.get("escape"): 3285 path = self.escape_str(path) 3286 3287 if self.QUOTE_JSON_PATH: 3288 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3289 3290 return path 3291 3292 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3293 if isinstance(expression, exp.JSONPathPart): 3294 transform = self.TRANSFORMS.get(expression.__class__) 3295 if not callable(transform): 3296 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3297 return "" 3298 3299 return transform(self, expression) 3300 3301 if isinstance(expression, int): 3302 return str(expression) 3303 3304 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3305 escaped = expression.replace("'", "\\'") 3306 escaped = f"\\'{expression}\\'" 3307 else: 3308 escaped = expression.replace('"', '\\"') 3309 escaped = f'"{escaped}"' 3310 3311 return escaped 3312 3313 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3314 return f"{self.sql(expression, 'this')} FORMAT JSON" 3315 3316 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3317 # Output the Teradata column FORMAT override. 3318 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3319 this = self.sql(expression, "this") 3320 fmt = self.sql(expression, "format") 3321 return f"{this} (FORMAT {fmt})" 3322 3323 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3324 null_handling = expression.args.get("null_handling") 3325 null_handling = f" {null_handling}" if null_handling else "" 3326 3327 unique_keys = expression.args.get("unique_keys") 3328 if unique_keys is not None: 3329 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3330 else: 3331 unique_keys = "" 3332 3333 return_type = self.sql(expression, "return_type") 3334 return_type = f" RETURNING {return_type}" if return_type else "" 3335 encoding = self.sql(expression, "encoding") 3336 encoding = f" ENCODING {encoding}" if encoding else "" 3337 3338 return self.func( 3339 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3340 *expression.expressions, 3341 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3342 ) 3343 3344 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3345 return self.jsonobject_sql(expression) 3346 3347 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3348 null_handling = expression.args.get("null_handling") 3349 null_handling = f" {null_handling}" if null_handling else "" 3350 return_type = self.sql(expression, "return_type") 3351 return_type = f" RETURNING {return_type}" if return_type else "" 3352 strict = " STRICT" if expression.args.get("strict") else "" 3353 return self.func( 3354 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3355 ) 3356 3357 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3358 this = self.sql(expression, "this") 3359 order = self.sql(expression, "order") 3360 null_handling = expression.args.get("null_handling") 3361 null_handling = f" {null_handling}" if null_handling else "" 3362 return_type = self.sql(expression, "return_type") 3363 return_type = f" RETURNING {return_type}" if return_type else "" 3364 strict = " STRICT" if expression.args.get("strict") else "" 3365 return self.func( 3366 "JSON_ARRAYAGG", 3367 this, 3368 suffix=f"{order}{null_handling}{return_type}{strict})", 3369 ) 3370 3371 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3372 path = self.sql(expression, "path") 3373 path = f" PATH {path}" if path else "" 3374 nested_schema = self.sql(expression, "nested_schema") 3375 3376 if nested_schema: 3377 return f"NESTED{path} {nested_schema}" 3378 3379 this = self.sql(expression, "this") 3380 kind = self.sql(expression, "kind") 3381 kind = f" {kind}" if kind else "" 3382 3383 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3384 return f"{this}{kind}{path}{ordinality}" 3385 3386 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3387 return self.func("COLUMNS", *expression.expressions) 3388 3389 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3390 this = self.sql(expression, "this") 3391 path = self.sql(expression, "path") 3392 path = f", {path}" if path else "" 3393 error_handling = expression.args.get("error_handling") 3394 error_handling = f" {error_handling}" if error_handling else "" 3395 empty_handling = expression.args.get("empty_handling") 3396 empty_handling = f" {empty_handling}" if empty_handling else "" 3397 schema = self.sql(expression, "schema") 3398 return self.func( 3399 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3400 ) 3401 3402 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3403 this = self.sql(expression, "this") 3404 kind = self.sql(expression, "kind") 3405 path = self.sql(expression, "path") 3406 path = f" {path}" if path else "" 3407 as_json = " AS JSON" if expression.args.get("as_json") else "" 3408 return f"{this} {kind}{path}{as_json}" 3409 3410 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3411 this = self.sql(expression, "this") 3412 path = self.sql(expression, "path") 3413 path = f", {path}" if path else "" 3414 expressions = self.expressions(expression) 3415 with_ = ( 3416 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3417 if expressions 3418 else "" 3419 ) 3420 return f"OPENJSON({this}{path}){with_}" 3421 3422 def in_sql(self, expression: exp.In) -> str: 3423 query = expression.args.get("query") 3424 unnest = expression.args.get("unnest") 3425 field = expression.args.get("field") 3426 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3427 3428 if query: 3429 in_sql = self.sql(query) 3430 elif unnest: 3431 in_sql = self.in_unnest_op(unnest) 3432 elif field: 3433 in_sql = self.sql(field) 3434 else: 3435 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3436 3437 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3438 3439 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3440 return f"(SELECT {self.sql(unnest)})" 3441 3442 def interval_sql(self, expression: exp.Interval) -> str: 3443 unit_expression = expression.args.get("unit") 3444 unit = self.sql(unit_expression) if unit_expression else "" 3445 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3446 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3447 unit = f" {unit}" if unit else "" 3448 3449 if self.SINGLE_STRING_INTERVAL: 3450 this = expression.this.name if expression.this else "" 3451 if this: 3452 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3453 return f"INTERVAL '{this}'{unit}" 3454 return f"INTERVAL '{this}{unit}'" 3455 return f"INTERVAL{unit}" 3456 3457 this = self.sql(expression, "this") 3458 if this: 3459 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3460 this = f" {this}" if unwrapped else f" ({this})" 3461 3462 return f"INTERVAL{this}{unit}" 3463 3464 def return_sql(self, expression: exp.Return) -> str: 3465 return f"RETURN {self.sql(expression, 'this')}" 3466 3467 def reference_sql(self, expression: exp.Reference) -> str: 3468 this = self.sql(expression, "this") 3469 expressions = self.expressions(expression, flat=True) 3470 expressions = f"({expressions})" if expressions else "" 3471 options = self.expressions(expression, key="options", flat=True, sep=" ") 3472 options = f" {options}" if options else "" 3473 return f"REFERENCES {this}{expressions}{options}" 3474 3475 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3476 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3477 parent = expression.parent 3478 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3479 return self.func( 3480 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3481 ) 3482 3483 def paren_sql(self, expression: exp.Paren) -> str: 3484 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3485 return f"({sql}{self.seg(')', sep='')}" 3486 3487 def neg_sql(self, expression: exp.Neg) -> str: 3488 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3489 this_sql = self.sql(expression, "this") 3490 sep = " " if this_sql[0] == "-" else "" 3491 return f"-{sep}{this_sql}" 3492 3493 def not_sql(self, expression: exp.Not) -> str: 3494 return f"NOT {self.sql(expression, 'this')}" 3495 3496 def alias_sql(self, expression: exp.Alias) -> str: 3497 alias = self.sql(expression, "alias") 3498 alias = f" AS {alias}" if alias else "" 3499 return f"{self.sql(expression, 'this')}{alias}" 3500 3501 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3502 alias = expression.args["alias"] 3503 3504 parent = expression.parent 3505 pivot = parent and parent.parent 3506 3507 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3508 identifier_alias = isinstance(alias, exp.Identifier) 3509 literal_alias = isinstance(alias, exp.Literal) 3510 3511 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3512 alias.replace(exp.Literal.string(alias.output_name)) 3513 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3514 alias.replace(exp.to_identifier(alias.output_name)) 3515 3516 return self.alias_sql(expression) 3517 3518 def aliases_sql(self, expression: exp.Aliases) -> str: 3519 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3520 3521 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3522 this = self.sql(expression, "this") 3523 index = self.sql(expression, "expression") 3524 return f"{this} AT {index}" 3525 3526 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3527 this = self.sql(expression, "this") 3528 zone = self.sql(expression, "zone") 3529 return f"{this} AT TIME ZONE {zone}" 3530 3531 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3532 this = self.sql(expression, "this") 3533 zone = self.sql(expression, "zone") 3534 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3535 3536 def add_sql(self, expression: exp.Add) -> str: 3537 return self.binary(expression, "+") 3538 3539 def and_sql( 3540 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3541 ) -> str: 3542 return self.connector_sql(expression, "AND", stack) 3543 3544 def or_sql( 3545 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3546 ) -> str: 3547 return self.connector_sql(expression, "OR", stack) 3548 3549 def xor_sql( 3550 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3551 ) -> str: 3552 return self.connector_sql(expression, "XOR", stack) 3553 3554 def connector_sql( 3555 self, 3556 expression: exp.Connector, 3557 op: str, 3558 stack: t.Optional[t.List[str | exp.Expression]] = None, 3559 ) -> str: 3560 if stack is not None: 3561 if expression.expressions: 3562 stack.append(self.expressions(expression, sep=f" {op} ")) 3563 else: 3564 stack.append(expression.right) 3565 if expression.comments and self.comments: 3566 for comment in expression.comments: 3567 if comment: 3568 op += f" /*{self.sanitize_comment(comment)}*/" 3569 stack.extend((op, expression.left)) 3570 return op 3571 3572 stack = [expression] 3573 sqls: t.List[str] = [] 3574 ops = set() 3575 3576 while stack: 3577 node = stack.pop() 3578 if isinstance(node, exp.Connector): 3579 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3580 else: 3581 sql = self.sql(node) 3582 if sqls and sqls[-1] in ops: 3583 sqls[-1] += f" {sql}" 3584 else: 3585 sqls.append(sql) 3586 3587 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3588 return sep.join(sqls) 3589 3590 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3591 return self.binary(expression, "&") 3592 3593 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3594 return self.binary(expression, "<<") 3595 3596 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3597 return f"~{self.sql(expression, 'this')}" 3598 3599 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3600 return self.binary(expression, "|") 3601 3602 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3603 return self.binary(expression, ">>") 3604 3605 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3606 return self.binary(expression, "^") 3607 3608 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3609 format_sql = self.sql(expression, "format") 3610 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3611 to_sql = self.sql(expression, "to") 3612 to_sql = f" {to_sql}" if to_sql else "" 3613 action = self.sql(expression, "action") 3614 action = f" {action}" if action else "" 3615 default = self.sql(expression, "default") 3616 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3617 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3618 3619 # Base implementation that excludes safe, zone, and target_type metadata args 3620 def strtotime_sql(self, expression: exp.StrToTime) -> str: 3621 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 3622 3623 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3624 zone = self.sql(expression, "this") 3625 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3626 3627 def collate_sql(self, expression: exp.Collate) -> str: 3628 if self.COLLATE_IS_FUNC: 3629 return self.function_fallback_sql(expression) 3630 return self.binary(expression, "COLLATE") 3631 3632 def command_sql(self, expression: exp.Command) -> str: 3633 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3634 3635 def comment_sql(self, expression: exp.Comment) -> str: 3636 this = self.sql(expression, "this") 3637 kind = expression.args["kind"] 3638 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3639 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3640 expression_sql = self.sql(expression, "expression") 3641 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3642 3643 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3644 this = self.sql(expression, "this") 3645 delete = " DELETE" if expression.args.get("delete") else "" 3646 recompress = self.sql(expression, "recompress") 3647 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3648 to_disk = self.sql(expression, "to_disk") 3649 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3650 to_volume = self.sql(expression, "to_volume") 3651 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3652 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3653 3654 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3655 where = self.sql(expression, "where") 3656 group = self.sql(expression, "group") 3657 aggregates = self.expressions(expression, key="aggregates") 3658 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3659 3660 if not (where or group or aggregates) and len(expression.expressions) == 1: 3661 return f"TTL {self.expressions(expression, flat=True)}" 3662 3663 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3664 3665 def transaction_sql(self, expression: exp.Transaction) -> str: 3666 modes = self.expressions(expression, key="modes") 3667 modes = f" {modes}" if modes else "" 3668 return f"BEGIN{modes}" 3669 3670 def commit_sql(self, expression: exp.Commit) -> str: 3671 chain = expression.args.get("chain") 3672 if chain is not None: 3673 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3674 3675 return f"COMMIT{chain or ''}" 3676 3677 def rollback_sql(self, expression: exp.Rollback) -> str: 3678 savepoint = expression.args.get("savepoint") 3679 savepoint = f" TO {savepoint}" if savepoint else "" 3680 return f"ROLLBACK{savepoint}" 3681 3682 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3683 this = self.sql(expression, "this") 3684 3685 dtype = self.sql(expression, "dtype") 3686 if dtype: 3687 collate = self.sql(expression, "collate") 3688 collate = f" COLLATE {collate}" if collate else "" 3689 using = self.sql(expression, "using") 3690 using = f" USING {using}" if using else "" 3691 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3692 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3693 3694 default = self.sql(expression, "default") 3695 if default: 3696 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3697 3698 comment = self.sql(expression, "comment") 3699 if comment: 3700 return f"ALTER COLUMN {this} COMMENT {comment}" 3701 3702 visible = expression.args.get("visible") 3703 if visible: 3704 return f"ALTER COLUMN {this} SET {visible}" 3705 3706 allow_null = expression.args.get("allow_null") 3707 drop = expression.args.get("drop") 3708 3709 if not drop and not allow_null: 3710 self.unsupported("Unsupported ALTER COLUMN syntax") 3711 3712 if allow_null is not None: 3713 keyword = "DROP" if drop else "SET" 3714 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3715 3716 return f"ALTER COLUMN {this} DROP DEFAULT" 3717 3718 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3719 this = self.sql(expression, "this") 3720 3721 visible = expression.args.get("visible") 3722 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3723 3724 return f"ALTER INDEX {this} {visible_sql}" 3725 3726 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3727 this = self.sql(expression, "this") 3728 if not isinstance(expression.this, exp.Var): 3729 this = f"KEY DISTKEY {this}" 3730 return f"ALTER DISTSTYLE {this}" 3731 3732 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3733 compound = " COMPOUND" if expression.args.get("compound") else "" 3734 this = self.sql(expression, "this") 3735 expressions = self.expressions(expression, flat=True) 3736 expressions = f"({expressions})" if expressions else "" 3737 return f"ALTER{compound} SORTKEY {this or expressions}" 3738 3739 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3740 if not self.RENAME_TABLE_WITH_DB: 3741 # Remove db from tables 3742 expression = expression.transform( 3743 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3744 ).assert_is(exp.AlterRename) 3745 this = self.sql(expression, "this") 3746 to_kw = " TO" if include_to else "" 3747 return f"RENAME{to_kw} {this}" 3748 3749 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3750 exists = " IF EXISTS" if expression.args.get("exists") else "" 3751 old_column = self.sql(expression, "this") 3752 new_column = self.sql(expression, "to") 3753 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3754 3755 def alterset_sql(self, expression: exp.AlterSet) -> str: 3756 exprs = self.expressions(expression, flat=True) 3757 if self.ALTER_SET_WRAPPED: 3758 exprs = f"({exprs})" 3759 3760 return f"SET {exprs}" 3761 3762 def alter_sql(self, expression: exp.Alter) -> str: 3763 actions = expression.args["actions"] 3764 3765 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3766 actions[0], exp.ColumnDef 3767 ): 3768 actions_sql = self.expressions(expression, key="actions", flat=True) 3769 actions_sql = f"ADD {actions_sql}" 3770 else: 3771 actions_list = [] 3772 for action in actions: 3773 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3774 action_sql = self.add_column_sql(action) 3775 else: 3776 action_sql = self.sql(action) 3777 if isinstance(action, exp.Query): 3778 action_sql = f"AS {action_sql}" 3779 3780 actions_list.append(action_sql) 3781 3782 actions_sql = self.format_args(*actions_list).lstrip("\n") 3783 3784 exists = " IF EXISTS" if expression.args.get("exists") else "" 3785 on_cluster = self.sql(expression, "cluster") 3786 on_cluster = f" {on_cluster}" if on_cluster else "" 3787 only = " ONLY" if expression.args.get("only") else "" 3788 options = self.expressions(expression, key="options") 3789 options = f", {options}" if options else "" 3790 kind = self.sql(expression, "kind") 3791 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3792 check = " WITH CHECK" if expression.args.get("check") else "" 3793 cascade = ( 3794 " CASCADE" 3795 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 3796 else "" 3797 ) 3798 this = self.sql(expression, "this") 3799 this = f" {this}" if this else "" 3800 3801 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 3802 3803 def altersession_sql(self, expression: exp.AlterSession) -> str: 3804 items_sql = self.expressions(expression, flat=True) 3805 keyword = "UNSET" if expression.args.get("unset") else "SET" 3806 return f"{keyword} {items_sql}" 3807 3808 def add_column_sql(self, expression: exp.Expression) -> str: 3809 sql = self.sql(expression) 3810 if isinstance(expression, exp.Schema): 3811 column_text = " COLUMNS" 3812 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3813 column_text = " COLUMN" 3814 else: 3815 column_text = "" 3816 3817 return f"ADD{column_text} {sql}" 3818 3819 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3820 expressions = self.expressions(expression) 3821 exists = " IF EXISTS " if expression.args.get("exists") else " " 3822 return f"DROP{exists}{expressions}" 3823 3824 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3825 return f"ADD {self.expressions(expression, indent=False)}" 3826 3827 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3828 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3829 location = self.sql(expression, "location") 3830 location = f" {location}" if location else "" 3831 return f"ADD {exists}{self.sql(expression.this)}{location}" 3832 3833 def distinct_sql(self, expression: exp.Distinct) -> str: 3834 this = self.expressions(expression, flat=True) 3835 3836 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3837 case = exp.case() 3838 for arg in expression.expressions: 3839 case = case.when(arg.is_(exp.null()), exp.null()) 3840 this = self.sql(case.else_(f"({this})")) 3841 3842 this = f" {this}" if this else "" 3843 3844 on = self.sql(expression, "on") 3845 on = f" ON {on}" if on else "" 3846 return f"DISTINCT{this}{on}" 3847 3848 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3849 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3850 3851 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3852 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3853 3854 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3855 this_sql = self.sql(expression, "this") 3856 expression_sql = self.sql(expression, "expression") 3857 kind = "MAX" if expression.args.get("max") else "MIN" 3858 return f"{this_sql} HAVING {kind} {expression_sql}" 3859 3860 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3861 return self.sql( 3862 exp.Cast( 3863 this=exp.Div(this=expression.this, expression=expression.expression), 3864 to=exp.DataType(this=exp.DataType.Type.INT), 3865 ) 3866 ) 3867 3868 def dpipe_sql(self, expression: exp.DPipe) -> str: 3869 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3870 return self.func( 3871 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3872 ) 3873 return self.binary(expression, "||") 3874 3875 def div_sql(self, expression: exp.Div) -> str: 3876 l, r = expression.left, expression.right 3877 3878 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3879 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3880 3881 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3882 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3883 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3884 3885 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3886 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3887 return self.sql( 3888 exp.cast( 3889 l / r, 3890 to=exp.DataType.Type.BIGINT, 3891 ) 3892 ) 3893 3894 return self.binary(expression, "/") 3895 3896 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3897 n = exp._wrap(expression.this, exp.Binary) 3898 d = exp._wrap(expression.expression, exp.Binary) 3899 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3900 3901 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3902 return self.binary(expression, "OVERLAPS") 3903 3904 def distance_sql(self, expression: exp.Distance) -> str: 3905 return self.binary(expression, "<->") 3906 3907 def dot_sql(self, expression: exp.Dot) -> str: 3908 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3909 3910 def eq_sql(self, expression: exp.EQ) -> str: 3911 return self.binary(expression, "=") 3912 3913 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3914 return self.binary(expression, ":=") 3915 3916 def escape_sql(self, expression: exp.Escape) -> str: 3917 return self.binary(expression, "ESCAPE") 3918 3919 def glob_sql(self, expression: exp.Glob) -> str: 3920 return self.binary(expression, "GLOB") 3921 3922 def gt_sql(self, expression: exp.GT) -> str: 3923 return self.binary(expression, ">") 3924 3925 def gte_sql(self, expression: exp.GTE) -> str: 3926 return self.binary(expression, ">=") 3927 3928 def is_sql(self, expression: exp.Is) -> str: 3929 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3930 return self.sql( 3931 expression.this if expression.expression.this else exp.not_(expression.this) 3932 ) 3933 return self.binary(expression, "IS") 3934 3935 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3936 this = expression.this 3937 rhs = expression.expression 3938 3939 if isinstance(expression, exp.Like): 3940 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3941 op = "LIKE" 3942 else: 3943 exp_class = exp.ILike 3944 op = "ILIKE" 3945 3946 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3947 exprs = rhs.this.unnest() 3948 3949 if isinstance(exprs, exp.Tuple): 3950 exprs = exprs.expressions 3951 3952 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3953 3954 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3955 for expr in exprs[1:]: 3956 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3957 3958 parent = expression.parent 3959 if not isinstance(parent, type(like_expr)) and isinstance(parent, exp.Condition): 3960 like_expr = exp.paren(like_expr, copy=False) 3961 3962 return self.sql(like_expr) 3963 3964 return self.binary(expression, op) 3965 3966 def like_sql(self, expression: exp.Like) -> str: 3967 return self._like_sql(expression) 3968 3969 def ilike_sql(self, expression: exp.ILike) -> str: 3970 return self._like_sql(expression) 3971 3972 def match_sql(self, expression: exp.Match) -> str: 3973 return self.binary(expression, "MATCH") 3974 3975 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3976 return self.binary(expression, "SIMILAR TO") 3977 3978 def lt_sql(self, expression: exp.LT) -> str: 3979 return self.binary(expression, "<") 3980 3981 def lte_sql(self, expression: exp.LTE) -> str: 3982 return self.binary(expression, "<=") 3983 3984 def mod_sql(self, expression: exp.Mod) -> str: 3985 return self.binary(expression, "%") 3986 3987 def mul_sql(self, expression: exp.Mul) -> str: 3988 return self.binary(expression, "*") 3989 3990 def neq_sql(self, expression: exp.NEQ) -> str: 3991 return self.binary(expression, "<>") 3992 3993 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3994 return self.binary(expression, "IS NOT DISTINCT FROM") 3995 3996 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3997 return self.binary(expression, "IS DISTINCT FROM") 3998 3999 def sub_sql(self, expression: exp.Sub) -> str: 4000 return self.binary(expression, "-") 4001 4002 def trycast_sql(self, expression: exp.TryCast) -> str: 4003 return self.cast_sql(expression, safe_prefix="TRY_") 4004 4005 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4006 return self.cast_sql(expression) 4007 4008 def try_sql(self, expression: exp.Try) -> str: 4009 if not self.TRY_SUPPORTED: 4010 self.unsupported("Unsupported TRY function") 4011 return self.sql(expression, "this") 4012 4013 return self.func("TRY", expression.this) 4014 4015 def log_sql(self, expression: exp.Log) -> str: 4016 this = expression.this 4017 expr = expression.expression 4018 4019 if self.dialect.LOG_BASE_FIRST is False: 4020 this, expr = expr, this 4021 elif self.dialect.LOG_BASE_FIRST is None and expr: 4022 if this.name in ("2", "10"): 4023 return self.func(f"LOG{this.name}", expr) 4024 4025 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4026 4027 return self.func("LOG", this, expr) 4028 4029 def use_sql(self, expression: exp.Use) -> str: 4030 kind = self.sql(expression, "kind") 4031 kind = f" {kind}" if kind else "" 4032 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4033 this = f" {this}" if this else "" 4034 return f"USE{kind}{this}" 4035 4036 def binary(self, expression: exp.Binary, op: str) -> str: 4037 sqls: t.List[str] = [] 4038 stack: t.List[t.Union[str, exp.Expression]] = [expression] 4039 binary_type = type(expression) 4040 4041 while stack: 4042 node = stack.pop() 4043 4044 if type(node) is binary_type: 4045 op_func = node.args.get("operator") 4046 if op_func: 4047 op = f"OPERATOR({self.sql(op_func)})" 4048 4049 stack.append(node.right) 4050 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4051 stack.append(node.left) 4052 else: 4053 sqls.append(self.sql(node)) 4054 4055 return "".join(sqls) 4056 4057 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4058 to_clause = self.sql(expression, "to") 4059 if to_clause: 4060 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4061 4062 return self.function_fallback_sql(expression) 4063 4064 def function_fallback_sql(self, expression: exp.Func) -> str: 4065 args = [] 4066 4067 for key in expression.arg_types: 4068 arg_value = expression.args.get(key) 4069 4070 if isinstance(arg_value, list): 4071 for value in arg_value: 4072 args.append(value) 4073 elif arg_value is not None: 4074 args.append(arg_value) 4075 4076 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4077 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4078 else: 4079 name = expression.sql_name() 4080 4081 return self.func(name, *args) 4082 4083 def func( 4084 self, 4085 name: str, 4086 *args: t.Optional[exp.Expression | str], 4087 prefix: str = "(", 4088 suffix: str = ")", 4089 normalize: bool = True, 4090 ) -> str: 4091 name = self.normalize_func(name) if normalize else name 4092 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4093 4094 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 4095 arg_sqls = tuple( 4096 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4097 ) 4098 if self.pretty and self.too_wide(arg_sqls): 4099 return self.indent( 4100 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4101 ) 4102 return sep.join(arg_sqls) 4103 4104 def too_wide(self, args: t.Iterable) -> bool: 4105 return sum(len(arg) for arg in args) > self.max_text_width 4106 4107 def format_time( 4108 self, 4109 expression: exp.Expression, 4110 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 4111 inverse_time_trie: t.Optional[t.Dict] = None, 4112 ) -> t.Optional[str]: 4113 return format_time( 4114 self.sql(expression, "format"), 4115 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4116 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4117 ) 4118 4119 def expressions( 4120 self, 4121 expression: t.Optional[exp.Expression] = None, 4122 key: t.Optional[str] = None, 4123 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 4124 flat: bool = False, 4125 indent: bool = True, 4126 skip_first: bool = False, 4127 skip_last: bool = False, 4128 sep: str = ", ", 4129 prefix: str = "", 4130 dynamic: bool = False, 4131 new_line: bool = False, 4132 ) -> str: 4133 expressions = expression.args.get(key or "expressions") if expression else sqls 4134 4135 if not expressions: 4136 return "" 4137 4138 if flat: 4139 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4140 4141 num_sqls = len(expressions) 4142 result_sqls = [] 4143 4144 for i, e in enumerate(expressions): 4145 sql = self.sql(e, comment=False) 4146 if not sql: 4147 continue 4148 4149 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 4150 4151 if self.pretty: 4152 if self.leading_comma: 4153 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4154 else: 4155 result_sqls.append( 4156 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4157 ) 4158 else: 4159 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4160 4161 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4162 if new_line: 4163 result_sqls.insert(0, "") 4164 result_sqls.append("") 4165 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4166 else: 4167 result_sql = "".join(result_sqls) 4168 4169 return ( 4170 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4171 if indent 4172 else result_sql 4173 ) 4174 4175 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 4176 flat = flat or isinstance(expression.parent, exp.Properties) 4177 expressions_sql = self.expressions(expression, flat=flat) 4178 if flat: 4179 return f"{op} {expressions_sql}" 4180 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4181 4182 def naked_property(self, expression: exp.Property) -> str: 4183 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4184 if not property_name: 4185 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4186 return f"{property_name} {self.sql(expression, 'this')}" 4187 4188 def tag_sql(self, expression: exp.Tag) -> str: 4189 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4190 4191 def token_sql(self, token_type: TokenType) -> str: 4192 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4193 4194 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4195 this = self.sql(expression, "this") 4196 expressions = self.no_identify(self.expressions, expression) 4197 expressions = ( 4198 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4199 ) 4200 return f"{this}{expressions}" if expressions.strip() != "" else this 4201 4202 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4203 this = self.sql(expression, "this") 4204 expressions = self.expressions(expression, flat=True) 4205 return f"{this}({expressions})" 4206 4207 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4208 return self.binary(expression, "=>") 4209 4210 def when_sql(self, expression: exp.When) -> str: 4211 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4212 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4213 condition = self.sql(expression, "condition") 4214 condition = f" AND {condition}" if condition else "" 4215 4216 then_expression = expression.args.get("then") 4217 if isinstance(then_expression, exp.Insert): 4218 this = self.sql(then_expression, "this") 4219 this = f"INSERT {this}" if this else "INSERT" 4220 then = self.sql(then_expression, "expression") 4221 then = f"{this} VALUES {then}" if then else this 4222 elif isinstance(then_expression, exp.Update): 4223 if isinstance(then_expression.args.get("expressions"), exp.Star): 4224 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4225 else: 4226 expressions_sql = self.expressions(then_expression) 4227 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4228 4229 else: 4230 then = self.sql(then_expression) 4231 return f"WHEN {matched}{source}{condition} THEN {then}" 4232 4233 def whens_sql(self, expression: exp.Whens) -> str: 4234 return self.expressions(expression, sep=" ", indent=False) 4235 4236 def merge_sql(self, expression: exp.Merge) -> str: 4237 table = expression.this 4238 table_alias = "" 4239 4240 hints = table.args.get("hints") 4241 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4242 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4243 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4244 4245 this = self.sql(table) 4246 using = f"USING {self.sql(expression, 'using')}" 4247 whens = self.sql(expression, "whens") 4248 4249 on = self.sql(expression, "on") 4250 on = f"ON {on}" if on else "" 4251 4252 if not on: 4253 on = self.expressions(expression, key="using_cond") 4254 on = f"USING ({on})" if on else "" 4255 4256 returning = self.sql(expression, "returning") 4257 if returning: 4258 whens = f"{whens}{returning}" 4259 4260 sep = self.sep() 4261 4262 return self.prepend_ctes( 4263 expression, 4264 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4265 ) 4266 4267 @unsupported_args("format") 4268 def tochar_sql(self, expression: exp.ToChar) -> str: 4269 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4270 4271 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4272 if not self.SUPPORTS_TO_NUMBER: 4273 self.unsupported("Unsupported TO_NUMBER function") 4274 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4275 4276 fmt = expression.args.get("format") 4277 if not fmt: 4278 self.unsupported("Conversion format is required for TO_NUMBER") 4279 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4280 4281 return self.func("TO_NUMBER", expression.this, fmt) 4282 4283 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4284 this = self.sql(expression, "this") 4285 kind = self.sql(expression, "kind") 4286 settings_sql = self.expressions(expression, key="settings", sep=" ") 4287 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4288 return f"{this}({kind}{args})" 4289 4290 def dictrange_sql(self, expression: exp.DictRange) -> str: 4291 this = self.sql(expression, "this") 4292 max = self.sql(expression, "max") 4293 min = self.sql(expression, "min") 4294 return f"{this}(MIN {min} MAX {max})" 4295 4296 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4297 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4298 4299 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4300 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4301 4302 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4303 def uniquekeyproperty_sql( 4304 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4305 ) -> str: 4306 return f"{prefix} ({self.expressions(expression, flat=True)})" 4307 4308 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4309 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4310 expressions = self.expressions(expression, flat=True) 4311 expressions = f" {self.wrap(expressions)}" if expressions else "" 4312 buckets = self.sql(expression, "buckets") 4313 kind = self.sql(expression, "kind") 4314 buckets = f" BUCKETS {buckets}" if buckets else "" 4315 order = self.sql(expression, "order") 4316 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4317 4318 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4319 return "" 4320 4321 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4322 expressions = self.expressions(expression, key="expressions", flat=True) 4323 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4324 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4325 buckets = self.sql(expression, "buckets") 4326 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4327 4328 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4329 this = self.sql(expression, "this") 4330 having = self.sql(expression, "having") 4331 4332 if having: 4333 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4334 4335 return self.func("ANY_VALUE", this) 4336 4337 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4338 transform = self.func("TRANSFORM", *expression.expressions) 4339 row_format_before = self.sql(expression, "row_format_before") 4340 row_format_before = f" {row_format_before}" if row_format_before else "" 4341 record_writer = self.sql(expression, "record_writer") 4342 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4343 using = f" USING {self.sql(expression, 'command_script')}" 4344 schema = self.sql(expression, "schema") 4345 schema = f" AS {schema}" if schema else "" 4346 row_format_after = self.sql(expression, "row_format_after") 4347 row_format_after = f" {row_format_after}" if row_format_after else "" 4348 record_reader = self.sql(expression, "record_reader") 4349 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4350 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4351 4352 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4353 key_block_size = self.sql(expression, "key_block_size") 4354 if key_block_size: 4355 return f"KEY_BLOCK_SIZE = {key_block_size}" 4356 4357 using = self.sql(expression, "using") 4358 if using: 4359 return f"USING {using}" 4360 4361 parser = self.sql(expression, "parser") 4362 if parser: 4363 return f"WITH PARSER {parser}" 4364 4365 comment = self.sql(expression, "comment") 4366 if comment: 4367 return f"COMMENT {comment}" 4368 4369 visible = expression.args.get("visible") 4370 if visible is not None: 4371 return "VISIBLE" if visible else "INVISIBLE" 4372 4373 engine_attr = self.sql(expression, "engine_attr") 4374 if engine_attr: 4375 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4376 4377 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4378 if secondary_engine_attr: 4379 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4380 4381 self.unsupported("Unsupported index constraint option.") 4382 return "" 4383 4384 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4385 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4386 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4387 4388 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4389 kind = self.sql(expression, "kind") 4390 kind = f"{kind} INDEX" if kind else "INDEX" 4391 this = self.sql(expression, "this") 4392 this = f" {this}" if this else "" 4393 index_type = self.sql(expression, "index_type") 4394 index_type = f" USING {index_type}" if index_type else "" 4395 expressions = self.expressions(expression, flat=True) 4396 expressions = f" ({expressions})" if expressions else "" 4397 options = self.expressions(expression, key="options", sep=" ") 4398 options = f" {options}" if options else "" 4399 return f"{kind}{this}{index_type}{expressions}{options}" 4400 4401 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4402 if self.NVL2_SUPPORTED: 4403 return self.function_fallback_sql(expression) 4404 4405 case = exp.Case().when( 4406 expression.this.is_(exp.null()).not_(copy=False), 4407 expression.args["true"], 4408 copy=False, 4409 ) 4410 else_cond = expression.args.get("false") 4411 if else_cond: 4412 case.else_(else_cond, copy=False) 4413 4414 return self.sql(case) 4415 4416 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4417 this = self.sql(expression, "this") 4418 expr = self.sql(expression, "expression") 4419 position = self.sql(expression, "position") 4420 position = f", {position}" if position else "" 4421 iterator = self.sql(expression, "iterator") 4422 condition = self.sql(expression, "condition") 4423 condition = f" IF {condition}" if condition else "" 4424 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4425 4426 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4427 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4428 4429 def opclass_sql(self, expression: exp.Opclass) -> str: 4430 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4431 4432 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4433 model = self.sql(expression, "this") 4434 model = f"MODEL {model}" 4435 expr = expression.expression 4436 if expr: 4437 expr_sql = self.sql(expression, "expression") 4438 expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql 4439 else: 4440 expr_sql = None 4441 4442 parameters = self.sql(expression, "params_struct") or None 4443 4444 return self.func(name, model, expr_sql, parameters) 4445 4446 def predict_sql(self, expression: exp.Predict) -> str: 4447 return self._ml_sql(expression, "PREDICT") 4448 4449 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4450 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4451 return self._ml_sql(expression, name) 4452 4453 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4454 return self._ml_sql(expression, "TRANSLATE") 4455 4456 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4457 return self._ml_sql(expression, "FORECAST") 4458 4459 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4460 this_sql = self.sql(expression, "this") 4461 if isinstance(expression.this, exp.Table): 4462 this_sql = f"TABLE {this_sql}" 4463 4464 return self.func( 4465 "FEATURES_AT_TIME", 4466 this_sql, 4467 expression.args.get("time"), 4468 expression.args.get("num_rows"), 4469 expression.args.get("ignore_feature_nulls"), 4470 ) 4471 4472 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4473 this_sql = self.sql(expression, "this") 4474 if isinstance(expression.this, exp.Table): 4475 this_sql = f"TABLE {this_sql}" 4476 4477 query_table = self.sql(expression, "query_table") 4478 if isinstance(expression.args["query_table"], exp.Table): 4479 query_table = f"TABLE {query_table}" 4480 4481 return self.func( 4482 "VECTOR_SEARCH", 4483 this_sql, 4484 expression.args.get("column_to_search"), 4485 query_table, 4486 expression.args.get("query_column_to_search"), 4487 expression.args.get("top_k"), 4488 expression.args.get("distance_type"), 4489 expression.args.get("options"), 4490 ) 4491 4492 def forin_sql(self, expression: exp.ForIn) -> str: 4493 this = self.sql(expression, "this") 4494 expression_sql = self.sql(expression, "expression") 4495 return f"FOR {this} DO {expression_sql}" 4496 4497 def refresh_sql(self, expression: exp.Refresh) -> str: 4498 this = self.sql(expression, "this") 4499 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 4500 return f"REFRESH {kind}{this}" 4501 4502 def toarray_sql(self, expression: exp.ToArray) -> str: 4503 arg = expression.this 4504 if not arg.type: 4505 from sqlglot.optimizer.annotate_types import annotate_types 4506 4507 arg = annotate_types(arg, dialect=self.dialect) 4508 4509 if arg.is_type(exp.DataType.Type.ARRAY): 4510 return self.sql(arg) 4511 4512 cond_for_null = arg.is_(exp.null()) 4513 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4514 4515 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4516 this = expression.this 4517 time_format = self.format_time(expression) 4518 4519 if time_format: 4520 return self.sql( 4521 exp.cast( 4522 exp.StrToTime(this=this, format=expression.args["format"]), 4523 exp.DataType.Type.TIME, 4524 ) 4525 ) 4526 4527 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4528 return self.sql(this) 4529 4530 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4531 4532 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4533 this = expression.this 4534 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4535 return self.sql(this) 4536 4537 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4538 4539 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4540 this = expression.this 4541 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4542 return self.sql(this) 4543 4544 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4545 4546 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4547 this = expression.this 4548 time_format = self.format_time(expression) 4549 safe = expression.args.get("safe") 4550 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4551 return self.sql( 4552 exp.cast( 4553 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4554 exp.DataType.Type.DATE, 4555 ) 4556 ) 4557 4558 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4559 return self.sql(this) 4560 4561 if safe: 4562 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DataType.Type.DATE))) 4563 4564 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4565 4566 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4567 return self.sql( 4568 exp.func( 4569 "DATEDIFF", 4570 expression.this, 4571 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4572 "day", 4573 ) 4574 ) 4575 4576 def lastday_sql(self, expression: exp.LastDay) -> str: 4577 if self.LAST_DAY_SUPPORTS_DATE_PART: 4578 return self.function_fallback_sql(expression) 4579 4580 unit = expression.text("unit") 4581 if unit and unit != "MONTH": 4582 self.unsupported("Date parts are not supported in LAST_DAY.") 4583 4584 return self.func("LAST_DAY", expression.this) 4585 4586 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4587 from sqlglot.dialects.dialect import unit_to_str 4588 4589 return self.func( 4590 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4591 ) 4592 4593 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4594 if self.CAN_IMPLEMENT_ARRAY_ANY: 4595 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4596 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4597 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4598 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4599 4600 from sqlglot.dialects import Dialect 4601 4602 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4603 if self.dialect.__class__ != Dialect: 4604 self.unsupported("ARRAY_ANY is unsupported") 4605 4606 return self.function_fallback_sql(expression) 4607 4608 def struct_sql(self, expression: exp.Struct) -> str: 4609 expression.set( 4610 "expressions", 4611 [ 4612 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4613 if isinstance(e, exp.PropertyEQ) 4614 else e 4615 for e in expression.expressions 4616 ], 4617 ) 4618 4619 return self.function_fallback_sql(expression) 4620 4621 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4622 low = self.sql(expression, "this") 4623 high = self.sql(expression, "expression") 4624 4625 return f"{low} TO {high}" 4626 4627 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4628 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4629 tables = f" {self.expressions(expression)}" 4630 4631 exists = " IF EXISTS" if expression.args.get("exists") else "" 4632 4633 on_cluster = self.sql(expression, "cluster") 4634 on_cluster = f" {on_cluster}" if on_cluster else "" 4635 4636 identity = self.sql(expression, "identity") 4637 identity = f" {identity} IDENTITY" if identity else "" 4638 4639 option = self.sql(expression, "option") 4640 option = f" {option}" if option else "" 4641 4642 partition = self.sql(expression, "partition") 4643 partition = f" {partition}" if partition else "" 4644 4645 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4646 4647 # This transpiles T-SQL's CONVERT function 4648 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4649 def convert_sql(self, expression: exp.Convert) -> str: 4650 to = expression.this 4651 value = expression.expression 4652 style = expression.args.get("style") 4653 safe = expression.args.get("safe") 4654 strict = expression.args.get("strict") 4655 4656 if not to or not value: 4657 return "" 4658 4659 # Retrieve length of datatype and override to default if not specified 4660 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4661 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4662 4663 transformed: t.Optional[exp.Expression] = None 4664 cast = exp.Cast if strict else exp.TryCast 4665 4666 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4667 if isinstance(style, exp.Literal) and style.is_int: 4668 from sqlglot.dialects.tsql import TSQL 4669 4670 style_value = style.name 4671 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4672 if not converted_style: 4673 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4674 4675 fmt = exp.Literal.string(converted_style) 4676 4677 if to.this == exp.DataType.Type.DATE: 4678 transformed = exp.StrToDate(this=value, format=fmt) 4679 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4680 transformed = exp.StrToTime(this=value, format=fmt) 4681 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4682 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4683 elif to.this == exp.DataType.Type.TEXT: 4684 transformed = exp.TimeToStr(this=value, format=fmt) 4685 4686 if not transformed: 4687 transformed = cast(this=value, to=to, safe=safe) 4688 4689 return self.sql(transformed) 4690 4691 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4692 this = expression.this 4693 if isinstance(this, exp.JSONPathWildcard): 4694 this = self.json_path_part(this) 4695 return f".{this}" if this else "" 4696 4697 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4698 return f".{this}" 4699 4700 this = self.json_path_part(this) 4701 return ( 4702 f"[{this}]" 4703 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4704 else f".{this}" 4705 ) 4706 4707 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4708 this = self.json_path_part(expression.this) 4709 return f"[{this}]" if this else "" 4710 4711 def _simplify_unless_literal(self, expression: E) -> E: 4712 if not isinstance(expression, exp.Literal): 4713 from sqlglot.optimizer.simplify import simplify 4714 4715 expression = simplify(expression, dialect=self.dialect) 4716 4717 return expression 4718 4719 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4720 this = expression.this 4721 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4722 self.unsupported( 4723 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4724 ) 4725 return self.sql(this) 4726 4727 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4728 # The first modifier here will be the one closest to the AggFunc's arg 4729 mods = sorted( 4730 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4731 key=lambda x: 0 4732 if isinstance(x, exp.HavingMax) 4733 else (1 if isinstance(x, exp.Order) else 2), 4734 ) 4735 4736 if mods: 4737 mod = mods[0] 4738 this = expression.__class__(this=mod.this.copy()) 4739 this.meta["inline"] = True 4740 mod.this.replace(this) 4741 return self.sql(expression.this) 4742 4743 agg_func = expression.find(exp.AggFunc) 4744 4745 if agg_func: 4746 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4747 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4748 4749 return f"{self.sql(expression, 'this')} {text}" 4750 4751 def _replace_line_breaks(self, string: str) -> str: 4752 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4753 if self.pretty: 4754 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4755 return string 4756 4757 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4758 option = self.sql(expression, "this") 4759 4760 if expression.expressions: 4761 upper = option.upper() 4762 4763 # Snowflake FILE_FORMAT options are separated by whitespace 4764 sep = " " if upper == "FILE_FORMAT" else ", " 4765 4766 # Databricks copy/format options do not set their list of values with EQ 4767 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4768 values = self.expressions(expression, flat=True, sep=sep) 4769 return f"{option}{op}({values})" 4770 4771 value = self.sql(expression, "expression") 4772 4773 if not value: 4774 return option 4775 4776 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4777 4778 return f"{option}{op}{value}" 4779 4780 def credentials_sql(self, expression: exp.Credentials) -> str: 4781 cred_expr = expression.args.get("credentials") 4782 if isinstance(cred_expr, exp.Literal): 4783 # Redshift case: CREDENTIALS <string> 4784 credentials = self.sql(expression, "credentials") 4785 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4786 else: 4787 # Snowflake case: CREDENTIALS = (...) 4788 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4789 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4790 4791 storage = self.sql(expression, "storage") 4792 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4793 4794 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4795 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4796 4797 iam_role = self.sql(expression, "iam_role") 4798 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4799 4800 region = self.sql(expression, "region") 4801 region = f" REGION {region}" if region else "" 4802 4803 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4804 4805 def copy_sql(self, expression: exp.Copy) -> str: 4806 this = self.sql(expression, "this") 4807 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4808 4809 credentials = self.sql(expression, "credentials") 4810 credentials = self.seg(credentials) if credentials else "" 4811 files = self.expressions(expression, key="files", flat=True) 4812 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4813 4814 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4815 params = self.expressions( 4816 expression, 4817 key="params", 4818 sep=sep, 4819 new_line=True, 4820 skip_last=True, 4821 skip_first=True, 4822 indent=self.COPY_PARAMS_ARE_WRAPPED, 4823 ) 4824 4825 if params: 4826 if self.COPY_PARAMS_ARE_WRAPPED: 4827 params = f" WITH ({params})" 4828 elif not self.pretty and (files or credentials): 4829 params = f" {params}" 4830 4831 return f"COPY{this}{kind} {files}{credentials}{params}" 4832 4833 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4834 return "" 4835 4836 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4837 on_sql = "ON" if expression.args.get("on") else "OFF" 4838 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4839 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4840 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4841 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4842 4843 if filter_col or retention_period: 4844 on_sql = self.func("ON", filter_col, retention_period) 4845 4846 return f"DATA_DELETION={on_sql}" 4847 4848 def maskingpolicycolumnconstraint_sql( 4849 self, expression: exp.MaskingPolicyColumnConstraint 4850 ) -> str: 4851 this = self.sql(expression, "this") 4852 expressions = self.expressions(expression, flat=True) 4853 expressions = f" USING ({expressions})" if expressions else "" 4854 return f"MASKING POLICY {this}{expressions}" 4855 4856 def gapfill_sql(self, expression: exp.GapFill) -> str: 4857 this = self.sql(expression, "this") 4858 this = f"TABLE {this}" 4859 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4860 4861 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4862 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4863 4864 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4865 this = self.sql(expression, "this") 4866 expr = expression.expression 4867 4868 if isinstance(expr, exp.Func): 4869 # T-SQL's CLR functions are case sensitive 4870 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4871 else: 4872 expr = self.sql(expression, "expression") 4873 4874 return self.scope_resolution(expr, this) 4875 4876 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4877 if self.PARSE_JSON_NAME is None: 4878 return self.sql(expression.this) 4879 4880 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4881 4882 def rand_sql(self, expression: exp.Rand) -> str: 4883 lower = self.sql(expression, "lower") 4884 upper = self.sql(expression, "upper") 4885 4886 if lower and upper: 4887 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4888 return self.func("RAND", expression.this) 4889 4890 def changes_sql(self, expression: exp.Changes) -> str: 4891 information = self.sql(expression, "information") 4892 information = f"INFORMATION => {information}" 4893 at_before = self.sql(expression, "at_before") 4894 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4895 end = self.sql(expression, "end") 4896 end = f"{self.seg('')}{end}" if end else "" 4897 4898 return f"CHANGES ({information}){at_before}{end}" 4899 4900 def pad_sql(self, expression: exp.Pad) -> str: 4901 prefix = "L" if expression.args.get("is_left") else "R" 4902 4903 fill_pattern = self.sql(expression, "fill_pattern") or None 4904 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4905 fill_pattern = "' '" 4906 4907 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4908 4909 def summarize_sql(self, expression: exp.Summarize) -> str: 4910 table = " TABLE" if expression.args.get("table") else "" 4911 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4912 4913 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4914 generate_series = exp.GenerateSeries(**expression.args) 4915 4916 parent = expression.parent 4917 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4918 parent = parent.parent 4919 4920 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4921 return self.sql(exp.Unnest(expressions=[generate_series])) 4922 4923 if isinstance(parent, exp.Select): 4924 self.unsupported("GenerateSeries projection unnesting is not supported.") 4925 4926 return self.sql(generate_series) 4927 4928 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4929 if self.SUPPORTS_CONVERT_TIMEZONE: 4930 return self.function_fallback_sql(expression) 4931 4932 source_tz = expression.args.get("source_tz") 4933 target_tz = expression.args.get("target_tz") 4934 timestamp = expression.args.get("timestamp") 4935 4936 if source_tz and timestamp: 4937 timestamp = exp.AtTimeZone( 4938 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4939 ) 4940 4941 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4942 4943 return self.sql(expr) 4944 4945 def json_sql(self, expression: exp.JSON) -> str: 4946 this = self.sql(expression, "this") 4947 this = f" {this}" if this else "" 4948 4949 _with = expression.args.get("with_") 4950 4951 if _with is None: 4952 with_sql = "" 4953 elif not _with: 4954 with_sql = " WITHOUT" 4955 else: 4956 with_sql = " WITH" 4957 4958 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4959 4960 return f"JSON{this}{with_sql}{unique_sql}" 4961 4962 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4963 def _generate_on_options(arg: t.Any) -> str: 4964 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4965 4966 path = self.sql(expression, "path") 4967 returning = self.sql(expression, "returning") 4968 returning = f" RETURNING {returning}" if returning else "" 4969 4970 on_condition = self.sql(expression, "on_condition") 4971 on_condition = f" {on_condition}" if on_condition else "" 4972 4973 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4974 4975 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4976 else_ = "ELSE " if expression.args.get("else_") else "" 4977 condition = self.sql(expression, "expression") 4978 condition = f"WHEN {condition} THEN " if condition else else_ 4979 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4980 return f"{condition}{insert}" 4981 4982 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4983 kind = self.sql(expression, "kind") 4984 expressions = self.seg(self.expressions(expression, sep=" ")) 4985 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4986 return res 4987 4988 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4989 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4990 empty = expression.args.get("empty") 4991 empty = ( 4992 f"DEFAULT {empty} ON EMPTY" 4993 if isinstance(empty, exp.Expression) 4994 else self.sql(expression, "empty") 4995 ) 4996 4997 error = expression.args.get("error") 4998 error = ( 4999 f"DEFAULT {error} ON ERROR" 5000 if isinstance(error, exp.Expression) 5001 else self.sql(expression, "error") 5002 ) 5003 5004 if error and empty: 5005 error = ( 5006 f"{empty} {error}" 5007 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5008 else f"{error} {empty}" 5009 ) 5010 empty = "" 5011 5012 null = self.sql(expression, "null") 5013 5014 return f"{empty}{error}{null}" 5015 5016 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5017 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5018 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5019 5020 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5021 this = self.sql(expression, "this") 5022 path = self.sql(expression, "path") 5023 5024 passing = self.expressions(expression, "passing") 5025 passing = f" PASSING {passing}" if passing else "" 5026 5027 on_condition = self.sql(expression, "on_condition") 5028 on_condition = f" {on_condition}" if on_condition else "" 5029 5030 path = f"{path}{passing}{on_condition}" 5031 5032 return self.func("JSON_EXISTS", this, path) 5033 5034 def _add_arrayagg_null_filter( 5035 self, 5036 array_agg_sql: str, 5037 array_agg_expr: exp.ArrayAgg, 5038 column_expr: exp.Expression, 5039 ) -> str: 5040 """ 5041 Add NULL filter to ARRAY_AGG if dialect requires it. 5042 5043 Args: 5044 array_agg_sql: The generated ARRAY_AGG SQL string 5045 array_agg_expr: The ArrayAgg expression node 5046 column_expr: The column/expression to filter (before ORDER BY wrapping) 5047 5048 Returns: 5049 SQL string with FILTER clause added if needed 5050 """ 5051 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5052 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5053 if not ( 5054 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5055 ): 5056 return array_agg_sql 5057 5058 parent = array_agg_expr.parent 5059 if isinstance(parent, exp.Filter): 5060 parent_cond = parent.expression.this 5061 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5062 elif column_expr.find(exp.Column): 5063 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5064 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5065 this_sql = ( 5066 self.expressions(column_expr) 5067 if isinstance(column_expr, exp.Distinct) 5068 else self.sql(column_expr) 5069 ) 5070 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5071 5072 return array_agg_sql 5073 5074 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5075 array_agg = self.function_fallback_sql(expression) 5076 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5077 5078 def slice_sql(self, expression: exp.Slice) -> str: 5079 step = self.sql(expression, "step") 5080 end = self.sql(expression.expression) 5081 begin = self.sql(expression.this) 5082 5083 sql = f"{end}:{step}" if step else end 5084 return f"{begin}:{sql}" if sql else f"{begin}:" 5085 5086 def apply_sql(self, expression: exp.Apply) -> str: 5087 this = self.sql(expression, "this") 5088 expr = self.sql(expression, "expression") 5089 5090 return f"{this} APPLY({expr})" 5091 5092 def _grant_or_revoke_sql( 5093 self, 5094 expression: exp.Grant | exp.Revoke, 5095 keyword: str, 5096 preposition: str, 5097 grant_option_prefix: str = "", 5098 grant_option_suffix: str = "", 5099 ) -> str: 5100 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5101 5102 kind = self.sql(expression, "kind") 5103 kind = f" {kind}" if kind else "" 5104 5105 securable = self.sql(expression, "securable") 5106 securable = f" {securable}" if securable else "" 5107 5108 principals = self.expressions(expression, key="principals", flat=True) 5109 5110 if not expression.args.get("grant_option"): 5111 grant_option_prefix = grant_option_suffix = "" 5112 5113 # cascade for revoke only 5114 cascade = self.sql(expression, "cascade") 5115 cascade = f" {cascade}" if cascade else "" 5116 5117 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5118 5119 def grant_sql(self, expression: exp.Grant) -> str: 5120 return self._grant_or_revoke_sql( 5121 expression, 5122 keyword="GRANT", 5123 preposition="TO", 5124 grant_option_suffix=" WITH GRANT OPTION", 5125 ) 5126 5127 def revoke_sql(self, expression: exp.Revoke) -> str: 5128 return self._grant_or_revoke_sql( 5129 expression, 5130 keyword="REVOKE", 5131 preposition="FROM", 5132 grant_option_prefix="GRANT OPTION FOR ", 5133 ) 5134 5135 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 5136 this = self.sql(expression, "this") 5137 columns = self.expressions(expression, flat=True) 5138 columns = f"({columns})" if columns else "" 5139 5140 return f"{this}{columns}" 5141 5142 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 5143 this = self.sql(expression, "this") 5144 5145 kind = self.sql(expression, "kind") 5146 kind = f"{kind} " if kind else "" 5147 5148 return f"{kind}{this}" 5149 5150 def columns_sql(self, expression: exp.Columns): 5151 func = self.function_fallback_sql(expression) 5152 if expression.args.get("unpack"): 5153 func = f"*{func}" 5154 5155 return func 5156 5157 def overlay_sql(self, expression: exp.Overlay): 5158 this = self.sql(expression, "this") 5159 expr = self.sql(expression, "expression") 5160 from_sql = self.sql(expression, "from_") 5161 for_sql = self.sql(expression, "for_") 5162 for_sql = f" FOR {for_sql}" if for_sql else "" 5163 5164 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5165 5166 @unsupported_args("format") 5167 def todouble_sql(self, expression: exp.ToDouble) -> str: 5168 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5169 return self.sql(cast(this=expression.this, to=exp.DataType.build(exp.DataType.Type.DOUBLE))) 5170 5171 def string_sql(self, expression: exp.String) -> str: 5172 this = expression.this 5173 zone = expression.args.get("zone") 5174 5175 if zone: 5176 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5177 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5178 # set for source_tz to transpile the time conversion before the STRING cast 5179 this = exp.ConvertTimezone( 5180 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5181 ) 5182 5183 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 5184 5185 def median_sql(self, expression: exp.Median): 5186 if not self.SUPPORTS_MEDIAN: 5187 return self.sql( 5188 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5189 ) 5190 5191 return self.function_fallback_sql(expression) 5192 5193 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5194 filler = self.sql(expression, "this") 5195 filler = f" {filler}" if filler else "" 5196 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5197 return f"TRUNCATE{filler} {with_count}" 5198 5199 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5200 if self.SUPPORTS_UNIX_SECONDS: 5201 return self.function_fallback_sql(expression) 5202 5203 start_ts = exp.cast( 5204 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 5205 ) 5206 5207 return self.sql( 5208 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5209 ) 5210 5211 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5212 dim = expression.expression 5213 5214 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5215 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5216 if not (dim.is_int and dim.name == "1"): 5217 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5218 dim = None 5219 5220 # If dimension is required but not specified, default initialize it 5221 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5222 dim = exp.Literal.number(1) 5223 5224 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5225 5226 def attach_sql(self, expression: exp.Attach) -> str: 5227 this = self.sql(expression, "this") 5228 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5229 expressions = self.expressions(expression) 5230 expressions = f" ({expressions})" if expressions else "" 5231 5232 return f"ATTACH{exists_sql} {this}{expressions}" 5233 5234 def detach_sql(self, expression: exp.Detach) -> str: 5235 this = self.sql(expression, "this") 5236 # the DATABASE keyword is required if IF EXISTS is set 5237 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5238 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5239 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5240 5241 return f"DETACH{exists_sql} {this}" 5242 5243 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5244 this = self.sql(expression, "this") 5245 value = self.sql(expression, "expression") 5246 value = f" {value}" if value else "" 5247 return f"{this}{value}" 5248 5249 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5250 return ( 5251 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5252 ) 5253 5254 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5255 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5256 encode = f"{encode} {self.sql(expression, 'this')}" 5257 5258 properties = expression.args.get("properties") 5259 if properties: 5260 encode = f"{encode} {self.properties(properties)}" 5261 5262 return encode 5263 5264 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5265 this = self.sql(expression, "this") 5266 include = f"INCLUDE {this}" 5267 5268 column_def = self.sql(expression, "column_def") 5269 if column_def: 5270 include = f"{include} {column_def}" 5271 5272 alias = self.sql(expression, "alias") 5273 if alias: 5274 include = f"{include} AS {alias}" 5275 5276 return include 5277 5278 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5279 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5280 name = f"{prefix} {self.sql(expression, 'this')}" 5281 return self.func("XMLELEMENT", name, *expression.expressions) 5282 5283 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5284 this = self.sql(expression, "this") 5285 expr = self.sql(expression, "expression") 5286 expr = f"({expr})" if expr else "" 5287 return f"{this}{expr}" 5288 5289 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5290 partitions = self.expressions(expression, "partition_expressions") 5291 create = self.expressions(expression, "create_expressions") 5292 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5293 5294 def partitionbyrangepropertydynamic_sql( 5295 self, expression: exp.PartitionByRangePropertyDynamic 5296 ) -> str: 5297 start = self.sql(expression, "start") 5298 end = self.sql(expression, "end") 5299 5300 every = expression.args["every"] 5301 if isinstance(every, exp.Interval) and every.this.is_string: 5302 every.this.replace(exp.Literal.number(every.name)) 5303 5304 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5305 5306 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5307 name = self.sql(expression, "this") 5308 values = self.expressions(expression, flat=True) 5309 5310 return f"NAME {name} VALUE {values}" 5311 5312 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5313 kind = self.sql(expression, "kind") 5314 sample = self.sql(expression, "sample") 5315 return f"SAMPLE {sample} {kind}" 5316 5317 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5318 kind = self.sql(expression, "kind") 5319 option = self.sql(expression, "option") 5320 option = f" {option}" if option else "" 5321 this = self.sql(expression, "this") 5322 this = f" {this}" if this else "" 5323 columns = self.expressions(expression) 5324 columns = f" {columns}" if columns else "" 5325 return f"{kind}{option} STATISTICS{this}{columns}" 5326 5327 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5328 this = self.sql(expression, "this") 5329 columns = self.expressions(expression) 5330 inner_expression = self.sql(expression, "expression") 5331 inner_expression = f" {inner_expression}" if inner_expression else "" 5332 update_options = self.sql(expression, "update_options") 5333 update_options = f" {update_options} UPDATE" if update_options else "" 5334 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5335 5336 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5337 kind = self.sql(expression, "kind") 5338 kind = f" {kind}" if kind else "" 5339 return f"DELETE{kind} STATISTICS" 5340 5341 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5342 inner_expression = self.sql(expression, "expression") 5343 return f"LIST CHAINED ROWS{inner_expression}" 5344 5345 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5346 kind = self.sql(expression, "kind") 5347 this = self.sql(expression, "this") 5348 this = f" {this}" if this else "" 5349 inner_expression = self.sql(expression, "expression") 5350 return f"VALIDATE {kind}{this}{inner_expression}" 5351 5352 def analyze_sql(self, expression: exp.Analyze) -> str: 5353 options = self.expressions(expression, key="options", sep=" ") 5354 options = f" {options}" if options else "" 5355 kind = self.sql(expression, "kind") 5356 kind = f" {kind}" if kind else "" 5357 this = self.sql(expression, "this") 5358 this = f" {this}" if this else "" 5359 mode = self.sql(expression, "mode") 5360 mode = f" {mode}" if mode else "" 5361 properties = self.sql(expression, "properties") 5362 properties = f" {properties}" if properties else "" 5363 partition = self.sql(expression, "partition") 5364 partition = f" {partition}" if partition else "" 5365 inner_expression = self.sql(expression, "expression") 5366 inner_expression = f" {inner_expression}" if inner_expression else "" 5367 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5368 5369 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5370 this = self.sql(expression, "this") 5371 namespaces = self.expressions(expression, key="namespaces") 5372 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5373 passing = self.expressions(expression, key="passing") 5374 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5375 columns = self.expressions(expression, key="columns") 5376 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5377 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5378 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5379 5380 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5381 this = self.sql(expression, "this") 5382 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5383 5384 def export_sql(self, expression: exp.Export) -> str: 5385 this = self.sql(expression, "this") 5386 connection = self.sql(expression, "connection") 5387 connection = f"WITH CONNECTION {connection} " if connection else "" 5388 options = self.sql(expression, "options") 5389 return f"EXPORT DATA {connection}{options} AS {this}" 5390 5391 def declare_sql(self, expression: exp.Declare) -> str: 5392 return f"DECLARE {self.expressions(expression, flat=True)}" 5393 5394 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5395 variable = self.sql(expression, "this") 5396 default = self.sql(expression, "default") 5397 default = f" = {default}" if default else "" 5398 5399 kind = self.sql(expression, "kind") 5400 if isinstance(expression.args.get("kind"), exp.Schema): 5401 kind = f"TABLE {kind}" 5402 5403 return f"{variable} AS {kind}{default}" 5404 5405 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5406 kind = self.sql(expression, "kind") 5407 this = self.sql(expression, "this") 5408 set = self.sql(expression, "expression") 5409 using = self.sql(expression, "using") 5410 using = f" USING {using}" if using else "" 5411 5412 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5413 5414 return f"{kind_sql} {this} SET {set}{using}" 5415 5416 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5417 params = self.expressions(expression, key="params", flat=True) 5418 return self.func(expression.name, *expression.expressions) + f"({params})" 5419 5420 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5421 return self.func(expression.name, *expression.expressions) 5422 5423 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5424 return self.anonymousaggfunc_sql(expression) 5425 5426 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5427 return self.parameterizedagg_sql(expression) 5428 5429 def show_sql(self, expression: exp.Show) -> str: 5430 self.unsupported("Unsupported SHOW statement") 5431 return "" 5432 5433 def install_sql(self, expression: exp.Install) -> str: 5434 self.unsupported("Unsupported INSTALL statement") 5435 return "" 5436 5437 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5438 # Snowflake GET/PUT statements: 5439 # PUT <file> <internalStage> <properties> 5440 # GET <internalStage> <file> <properties> 5441 props = expression.args.get("properties") 5442 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5443 this = self.sql(expression, "this") 5444 target = self.sql(expression, "target") 5445 5446 if isinstance(expression, exp.Put): 5447 return f"PUT {this} {target}{props_sql}" 5448 else: 5449 return f"GET {target} {this}{props_sql}" 5450 5451 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5452 this = self.sql(expression, "this") 5453 expr = self.sql(expression, "expression") 5454 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5455 return f"TRANSLATE({this} USING {expr}{with_error})" 5456 5457 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5458 if self.SUPPORTS_DECODE_CASE: 5459 return self.func("DECODE", *expression.expressions) 5460 5461 expression, *expressions = expression.expressions 5462 5463 ifs = [] 5464 for search, result in zip(expressions[::2], expressions[1::2]): 5465 if isinstance(search, exp.Literal): 5466 ifs.append(exp.If(this=expression.eq(search), true=result)) 5467 elif isinstance(search, exp.Null): 5468 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5469 else: 5470 if isinstance(search, exp.Binary): 5471 search = exp.paren(search) 5472 5473 cond = exp.or_( 5474 expression.eq(search), 5475 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5476 copy=False, 5477 ) 5478 ifs.append(exp.If(this=cond, true=result)) 5479 5480 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5481 return self.sql(case) 5482 5483 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5484 this = self.sql(expression, "this") 5485 this = self.seg(this, sep="") 5486 dimensions = self.expressions( 5487 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5488 ) 5489 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5490 metrics = self.expressions( 5491 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5492 ) 5493 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5494 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5495 facts = self.seg(f"FACTS {facts}") if facts else "" 5496 where = self.sql(expression, "where") 5497 where = self.seg(f"WHERE {where}") if where else "" 5498 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5499 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5500 5501 def getextract_sql(self, expression: exp.GetExtract) -> str: 5502 this = expression.this 5503 expr = expression.expression 5504 5505 if not this.type or not expression.type: 5506 from sqlglot.optimizer.annotate_types import annotate_types 5507 5508 this = annotate_types(this, dialect=self.dialect) 5509 5510 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5511 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5512 5513 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5514 5515 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5516 return self.sql( 5517 exp.DateAdd( 5518 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5519 expression=expression.this, 5520 unit=exp.var("DAY"), 5521 ) 5522 ) 5523 5524 def space_sql(self: Generator, expression: exp.Space) -> str: 5525 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5526 5527 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5528 return f"BUILD {self.sql(expression, 'this')}" 5529 5530 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5531 method = self.sql(expression, "method") 5532 kind = expression.args.get("kind") 5533 if not kind: 5534 return f"REFRESH {method}" 5535 5536 every = self.sql(expression, "every") 5537 unit = self.sql(expression, "unit") 5538 every = f" EVERY {every} {unit}" if every else "" 5539 starts = self.sql(expression, "starts") 5540 starts = f" STARTS {starts}" if starts else "" 5541 5542 return f"REFRESH {method} ON {kind}{every}{starts}" 5543 5544 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5545 self.unsupported("The model!attribute syntax is not supported") 5546 return "" 5547 5548 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5549 return self.func("DIRECTORY", expression.this) 5550 5551 def uuid_sql(self, expression: exp.Uuid) -> str: 5552 is_string = expression.args.get("is_string", False) 5553 uuid_func_sql = self.func("UUID") 5554 5555 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5556 return self.sql( 5557 exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect) 5558 ) 5559 5560 return uuid_func_sql 5561 5562 def initcap_sql(self, expression: exp.Initcap) -> str: 5563 delimiters = expression.expression 5564 5565 if delimiters: 5566 # do not generate delimiters arg if we are round-tripping from default delimiters 5567 if ( 5568 delimiters.is_string 5569 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5570 ): 5571 delimiters = None 5572 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5573 self.unsupported("INITCAP does not support custom delimiters") 5574 delimiters = None 5575 5576 return self.func("INITCAP", expression.this, delimiters) 5577 5578 def localtime_sql(self, expression: exp.Localtime) -> str: 5579 this = expression.this 5580 return self.func("LOCALTIME", this) if this else "LOCALTIME" 5581 5582 def localtimestamp_sql(self, expression: exp.Localtime) -> str: 5583 this = expression.this 5584 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 5585 5586 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5587 this = expression.this.name.upper() 5588 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5589 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5590 return "WEEK" 5591 5592 return self.func("WEEK", expression.this) 5593 5594 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 5595 this = self.expressions(expression) 5596 charset = self.sql(expression, "charset") 5597 using = f" USING {charset}" if charset else "" 5598 return self.func(name, this + using)
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: Always quote except for specials cases. 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.Adjacent: lambda self, e: self.binary(e, "-|-"), 117 exp.AllowedValuesProperty: lambda self, 118 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 119 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 120 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 121 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 122 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 123 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 124 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 125 exp.CaseSpecificColumnConstraint: lambda _, 126 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 127 exp.Ceil: lambda self, e: self.ceil_floor(e), 128 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 129 exp.CharacterSetProperty: lambda self, 130 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 131 exp.ClusteredColumnConstraint: lambda self, 132 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 133 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 134 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 135 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 136 exp.ConvertToCharset: lambda self, e: self.func( 137 "CONVERT", e.this, e.args["dest"], e.args.get("source") 138 ), 139 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 140 exp.CredentialsProperty: lambda self, 141 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 142 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 143 exp.SessionUser: lambda *_: "SESSION_USER", 144 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 145 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 146 exp.DynamicProperty: lambda *_: "DYNAMIC", 147 exp.EmptyProperty: lambda *_: "EMPTY", 148 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 149 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 150 exp.EphemeralColumnConstraint: lambda self, 151 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 152 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 153 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 154 exp.Except: lambda self, e: self.set_operations(e), 155 exp.ExternalProperty: lambda *_: "EXTERNAL", 156 exp.Floor: lambda self, e: self.ceil_floor(e), 157 exp.Get: lambda self, e: self.get_put_sql(e), 158 exp.GlobalProperty: lambda *_: "GLOBAL", 159 exp.HeapProperty: lambda *_: "HEAP", 160 exp.IcebergProperty: lambda *_: "ICEBERG", 161 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 162 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 163 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 164 exp.Intersect: lambda self, e: self.set_operations(e), 165 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 166 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 167 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 168 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 169 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 170 exp.LanguageProperty: lambda self, e: self.naked_property(e), 171 exp.LocationProperty: lambda self, e: self.naked_property(e), 172 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 173 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 174 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 175 exp.NonClusteredColumnConstraint: lambda self, 176 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 177 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 178 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 179 exp.OnCommitProperty: lambda _, 180 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 181 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 182 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 183 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 184 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 185 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 186 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 187 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 188 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 189 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 190 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 191 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 192 exp.ProjectionPolicyColumnConstraint: lambda self, 193 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 194 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 195 exp.Put: lambda self, e: self.get_put_sql(e), 196 exp.RemoteWithConnectionModelProperty: lambda self, 197 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 198 exp.ReturnsProperty: lambda self, e: ( 199 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 200 ), 201 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 202 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 203 exp.SecureProperty: lambda *_: "SECURE", 204 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 205 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 206 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 207 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 208 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 209 exp.SqlReadWriteProperty: lambda _, e: e.name, 210 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 211 exp.StabilityProperty: lambda _, e: e.name, 212 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 213 exp.StreamingTableProperty: lambda *_: "STREAMING", 214 exp.StrictProperty: lambda *_: "STRICT", 215 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 216 exp.TableColumn: lambda self, e: self.sql(e.this), 217 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 218 exp.TemporaryProperty: lambda *_: "TEMPORARY", 219 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 220 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 221 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 222 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 223 exp.TransientProperty: lambda *_: "TRANSIENT", 224 exp.Union: lambda self, e: self.set_operations(e), 225 exp.UnloggedProperty: lambda *_: "UNLOGGED", 226 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 227 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 228 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 229 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 230 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 231 exp.UtcTimestamp: lambda self, e: self.sql( 232 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 233 ), 234 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 235 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 236 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 237 exp.VolatileProperty: lambda *_: "VOLATILE", 238 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 239 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 240 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 241 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 242 exp.ForceProperty: lambda *_: "FORCE", 243 } 244 245 # Whether null ordering is supported in order by 246 # True: Full Support, None: No support, False: No support for certain cases 247 # such as window specifications, aggregate functions etc 248 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 249 250 # Whether ignore nulls is inside the agg or outside. 251 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 252 IGNORE_NULLS_IN_FUNC = False 253 254 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 255 LOCKING_READS_SUPPORTED = False 256 257 # Whether the EXCEPT and INTERSECT operations can return duplicates 258 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 259 260 # Wrap derived values in parens, usually standard but spark doesn't support it 261 WRAP_DERIVED_VALUES = True 262 263 # Whether create function uses an AS before the RETURN 264 CREATE_FUNCTION_RETURN_AS = True 265 266 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 267 MATCHED_BY_SOURCE = True 268 269 # Whether the INTERVAL expression works only with values like '1 day' 270 SINGLE_STRING_INTERVAL = False 271 272 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 273 INTERVAL_ALLOWS_PLURAL_FORM = True 274 275 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 276 LIMIT_FETCH = "ALL" 277 278 # Whether limit and fetch allows expresions or just limits 279 LIMIT_ONLY_LITERALS = False 280 281 # Whether a table is allowed to be renamed with a db 282 RENAME_TABLE_WITH_DB = True 283 284 # The separator for grouping sets and rollups 285 GROUPINGS_SEP = "," 286 287 # The string used for creating an index on a table 288 INDEX_ON = "ON" 289 290 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 291 INOUT_SEPARATOR = " " 292 293 # Whether join hints should be generated 294 JOIN_HINTS = True 295 296 # Whether directed joins are supported 297 DIRECTED_JOINS = False 298 299 # Whether table hints should be generated 300 TABLE_HINTS = True 301 302 # Whether query hints should be generated 303 QUERY_HINTS = True 304 305 # What kind of separator to use for query hints 306 QUERY_HINT_SEP = ", " 307 308 # Whether comparing against booleans (e.g. x IS TRUE) is supported 309 IS_BOOL_ALLOWED = True 310 311 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 312 DUPLICATE_KEY_UPDATE_WITH_SET = True 313 314 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 315 LIMIT_IS_TOP = False 316 317 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 318 RETURNING_END = True 319 320 # Whether to generate an unquoted value for EXTRACT's date part argument 321 EXTRACT_ALLOWS_QUOTES = True 322 323 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 324 TZ_TO_WITH_TIME_ZONE = False 325 326 # Whether the NVL2 function is supported 327 NVL2_SUPPORTED = True 328 329 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 330 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 331 332 # Whether VALUES statements can be used as derived tables. 333 # MySQL 5 and Redshift do not allow this, so when False, it will convert 334 # SELECT * VALUES into SELECT UNION 335 VALUES_AS_TABLE = True 336 337 # Whether the word COLUMN is included when adding a column with ALTER TABLE 338 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 339 340 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 341 UNNEST_WITH_ORDINALITY = True 342 343 # Whether FILTER (WHERE cond) can be used for conditional aggregation 344 AGGREGATE_FILTER_SUPPORTED = True 345 346 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 347 SEMI_ANTI_JOIN_WITH_SIDE = True 348 349 # Whether to include the type of a computed column in the CREATE DDL 350 COMPUTED_COLUMN_WITH_TYPE = True 351 352 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 353 SUPPORTS_TABLE_COPY = True 354 355 # Whether parentheses are required around the table sample's expression 356 TABLESAMPLE_REQUIRES_PARENS = True 357 358 # Whether a table sample clause's size needs to be followed by the ROWS keyword 359 TABLESAMPLE_SIZE_IS_ROWS = True 360 361 # The keyword(s) to use when generating a sample clause 362 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 363 364 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 365 TABLESAMPLE_WITH_METHOD = True 366 367 # The keyword to use when specifying the seed of a sample clause 368 TABLESAMPLE_SEED_KEYWORD = "SEED" 369 370 # Whether COLLATE is a function instead of a binary operator 371 COLLATE_IS_FUNC = False 372 373 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 374 DATA_TYPE_SPECIFIERS_ALLOWED = False 375 376 # Whether conditions require booleans WHERE x = 0 vs WHERE x 377 ENSURE_BOOLS = False 378 379 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 380 CTE_RECURSIVE_KEYWORD_REQUIRED = True 381 382 # Whether CONCAT requires >1 arguments 383 SUPPORTS_SINGLE_ARG_CONCAT = True 384 385 # Whether LAST_DAY function supports a date part argument 386 LAST_DAY_SUPPORTS_DATE_PART = True 387 388 # Whether named columns are allowed in table aliases 389 SUPPORTS_TABLE_ALIAS_COLUMNS = True 390 391 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 392 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 393 394 # What delimiter to use for separating JSON key/value pairs 395 JSON_KEY_VALUE_PAIR_SEP = ":" 396 397 # INSERT OVERWRITE TABLE x override 398 INSERT_OVERWRITE = " OVERWRITE TABLE" 399 400 # Whether the SELECT .. INTO syntax is used instead of CTAS 401 SUPPORTS_SELECT_INTO = False 402 403 # Whether UNLOGGED tables can be created 404 SUPPORTS_UNLOGGED_TABLES = False 405 406 # Whether the CREATE TABLE LIKE statement is supported 407 SUPPORTS_CREATE_TABLE_LIKE = True 408 409 # Whether the LikeProperty needs to be specified inside of the schema clause 410 LIKE_PROPERTY_INSIDE_SCHEMA = False 411 412 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 413 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 414 MULTI_ARG_DISTINCT = True 415 416 # Whether the JSON extraction operators expect a value of type JSON 417 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 418 419 # Whether bracketed keys like ["foo"] are supported in JSON paths 420 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 421 422 # Whether to escape keys using single quotes in JSON paths 423 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 424 425 # The JSONPathPart expressions supported by this dialect 426 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 427 428 # Whether any(f(x) for x in array) can be implemented by this dialect 429 CAN_IMPLEMENT_ARRAY_ANY = False 430 431 # Whether the function TO_NUMBER is supported 432 SUPPORTS_TO_NUMBER = True 433 434 # Whether EXCLUDE in window specification is supported 435 SUPPORTS_WINDOW_EXCLUDE = False 436 437 # Whether or not set op modifiers apply to the outer set op or select. 438 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 439 # True means limit 1 happens after the set op, False means it it happens on y. 440 SET_OP_MODIFIERS = True 441 442 # Whether parameters from COPY statement are wrapped in parentheses 443 COPY_PARAMS_ARE_WRAPPED = True 444 445 # Whether values of params are set with "=" token or empty space 446 COPY_PARAMS_EQ_REQUIRED = False 447 448 # Whether COPY statement has INTO keyword 449 COPY_HAS_INTO_KEYWORD = True 450 451 # Whether the conditional TRY(expression) function is supported 452 TRY_SUPPORTED = True 453 454 # Whether the UESCAPE syntax in unicode strings is supported 455 SUPPORTS_UESCAPE = True 456 457 # Function used to replace escaped unicode codes in unicode strings 458 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 459 460 # The keyword to use when generating a star projection with excluded columns 461 STAR_EXCEPT = "EXCEPT" 462 463 # The HEX function name 464 HEX_FUNC = "HEX" 465 466 # The keywords to use when prefixing & separating WITH based properties 467 WITH_PROPERTIES_PREFIX = "WITH" 468 469 # Whether to quote the generated expression of exp.JsonPath 470 QUOTE_JSON_PATH = True 471 472 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 473 PAD_FILL_PATTERN_IS_REQUIRED = False 474 475 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 476 SUPPORTS_EXPLODING_PROJECTIONS = True 477 478 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 479 ARRAY_CONCAT_IS_VAR_LEN = True 480 481 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 482 SUPPORTS_CONVERT_TIMEZONE = False 483 484 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 485 SUPPORTS_MEDIAN = True 486 487 # Whether UNIX_SECONDS(timestamp) is supported 488 SUPPORTS_UNIX_SECONDS = False 489 490 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 491 ALTER_SET_WRAPPED = False 492 493 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 494 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 495 # TODO: The normalization should be done by default once we've tested it across all dialects. 496 NORMALIZE_EXTRACT_DATE_PARTS = False 497 498 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 499 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 500 501 # The function name of the exp.ArraySize expression 502 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 503 504 # The syntax to use when altering the type of a column 505 ALTER_SET_TYPE = "SET DATA TYPE" 506 507 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 508 # None -> Doesn't support it at all 509 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 510 # True (Postgres) -> Explicitly requires it 511 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 512 513 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 514 SUPPORTS_DECODE_CASE = True 515 516 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 517 SUPPORTS_BETWEEN_FLAGS = False 518 519 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 520 SUPPORTS_LIKE_QUANTIFIERS = True 521 522 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 523 MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None 524 525 # Whether to include the VARIABLE keyword for SET assignments 526 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 527 528 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 529 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 530 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 531 UPDATE_STATEMENT_SUPPORTS_FROM = True 532 533 TYPE_MAPPING = { 534 exp.DataType.Type.DATETIME2: "TIMESTAMP", 535 exp.DataType.Type.NCHAR: "CHAR", 536 exp.DataType.Type.NVARCHAR: "VARCHAR", 537 exp.DataType.Type.MEDIUMTEXT: "TEXT", 538 exp.DataType.Type.LONGTEXT: "TEXT", 539 exp.DataType.Type.TINYTEXT: "TEXT", 540 exp.DataType.Type.BLOB: "VARBINARY", 541 exp.DataType.Type.MEDIUMBLOB: "BLOB", 542 exp.DataType.Type.LONGBLOB: "BLOB", 543 exp.DataType.Type.TINYBLOB: "BLOB", 544 exp.DataType.Type.INET: "INET", 545 exp.DataType.Type.ROWVERSION: "VARBINARY", 546 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 547 } 548 549 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 550 551 TIME_PART_SINGULARS = { 552 "MICROSECONDS": "MICROSECOND", 553 "SECONDS": "SECOND", 554 "MINUTES": "MINUTE", 555 "HOURS": "HOUR", 556 "DAYS": "DAY", 557 "WEEKS": "WEEK", 558 "MONTHS": "MONTH", 559 "QUARTERS": "QUARTER", 560 "YEARS": "YEAR", 561 } 562 563 AFTER_HAVING_MODIFIER_TRANSFORMS = { 564 "cluster": lambda self, e: self.sql(e, "cluster"), 565 "distribute": lambda self, e: self.sql(e, "distribute"), 566 "sort": lambda self, e: self.sql(e, "sort"), 567 "windows": lambda self, e: ( 568 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 569 if e.args.get("windows") 570 else "" 571 ), 572 "qualify": lambda self, e: self.sql(e, "qualify"), 573 } 574 575 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 576 577 STRUCT_DELIMITER = ("<", ">") 578 579 PARAMETER_TOKEN = "@" 580 NAMED_PLACEHOLDER_TOKEN = ":" 581 582 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 583 584 PROPERTIES_LOCATION = { 585 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 587 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 591 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 592 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 593 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 594 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 595 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 596 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 600 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 602 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 603 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 605 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 609 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 611 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 612 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 613 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 614 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 615 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 616 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 617 exp.HeapProperty: exp.Properties.Location.POST_WITH, 618 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 620 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 623 exp.JournalProperty: exp.Properties.Location.POST_NAME, 624 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 626 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 628 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 629 exp.LogProperty: exp.Properties.Location.POST_NAME, 630 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 631 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 632 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 633 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 634 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 635 exp.Order: exp.Properties.Location.POST_SCHEMA, 636 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 637 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 638 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 639 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 640 exp.Property: exp.Properties.Location.POST_WITH, 641 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 642 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 643 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 644 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 645 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 646 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 647 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 648 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 649 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 650 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 651 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 652 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 653 exp.Set: exp.Properties.Location.POST_SCHEMA, 654 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 655 exp.SetProperty: exp.Properties.Location.POST_CREATE, 656 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 657 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 658 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 659 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 660 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 661 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 662 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 663 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 664 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 665 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 666 exp.Tags: exp.Properties.Location.POST_WITH, 667 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 668 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 669 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 670 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 671 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 672 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 673 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 674 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 675 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 676 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 677 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 678 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 679 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 680 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 681 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 682 } 683 684 # Keywords that can't be used as unquoted identifier names 685 RESERVED_KEYWORDS: t.Set[str] = set() 686 687 # Expressions whose comments are separated from them for better formatting 688 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 689 exp.Command, 690 exp.Create, 691 exp.Describe, 692 exp.Delete, 693 exp.Drop, 694 exp.From, 695 exp.Insert, 696 exp.Join, 697 exp.MultitableInserts, 698 exp.Order, 699 exp.Group, 700 exp.Having, 701 exp.Select, 702 exp.SetOperation, 703 exp.Update, 704 exp.Where, 705 exp.With, 706 ) 707 708 # Expressions that should not have their comments generated in maybe_comment 709 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 710 exp.Binary, 711 exp.SetOperation, 712 ) 713 714 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 715 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 716 exp.Column, 717 exp.Literal, 718 exp.Neg, 719 exp.Paren, 720 ) 721 722 PARAMETERIZABLE_TEXT_TYPES = { 723 exp.DataType.Type.NVARCHAR, 724 exp.DataType.Type.VARCHAR, 725 exp.DataType.Type.CHAR, 726 exp.DataType.Type.NCHAR, 727 } 728 729 # Expressions that need to have all CTEs under them bubbled up to them 730 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 731 732 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 733 734 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 735 736 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 737 738 __slots__ = ( 739 "pretty", 740 "identify", 741 "normalize", 742 "pad", 743 "_indent", 744 "normalize_functions", 745 "unsupported_level", 746 "max_unsupported", 747 "leading_comma", 748 "max_text_width", 749 "comments", 750 "dialect", 751 "unsupported_messages", 752 "_escaped_quote_end", 753 "_escaped_byte_quote_end", 754 "_escaped_identifier_end", 755 "_next_name", 756 "_identifier_start", 757 "_identifier_end", 758 "_quote_json_path_key_using_brackets", 759 ) 760 761 def __init__( 762 self, 763 pretty: t.Optional[bool] = None, 764 identify: str | bool = False, 765 normalize: bool = False, 766 pad: int = 2, 767 indent: int = 2, 768 normalize_functions: t.Optional[str | bool] = None, 769 unsupported_level: ErrorLevel = ErrorLevel.WARN, 770 max_unsupported: int = 3, 771 leading_comma: bool = False, 772 max_text_width: int = 80, 773 comments: bool = True, 774 dialect: DialectType = None, 775 ): 776 import sqlglot 777 from sqlglot.dialects import Dialect 778 779 self.pretty = pretty if pretty is not None else sqlglot.pretty 780 self.identify = identify 781 self.normalize = normalize 782 self.pad = pad 783 self._indent = indent 784 self.unsupported_level = unsupported_level 785 self.max_unsupported = max_unsupported 786 self.leading_comma = leading_comma 787 self.max_text_width = max_text_width 788 self.comments = comments 789 self.dialect = Dialect.get_or_raise(dialect) 790 791 # This is both a Dialect property and a Generator argument, so we prioritize the latter 792 self.normalize_functions = ( 793 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 794 ) 795 796 self.unsupported_messages: t.List[str] = [] 797 self._escaped_quote_end: str = ( 798 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 799 ) 800 self._escaped_byte_quote_end: str = ( 801 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 802 if self.dialect.BYTE_END 803 else "" 804 ) 805 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 806 807 self._next_name = name_sequence("_t") 808 809 self._identifier_start = self.dialect.IDENTIFIER_START 810 self._identifier_end = self.dialect.IDENTIFIER_END 811 812 self._quote_json_path_key_using_brackets = True 813 814 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 815 """ 816 Generates the SQL string corresponding to the given syntax tree. 817 818 Args: 819 expression: The syntax tree. 820 copy: Whether to copy the expression. The generator performs mutations so 821 it is safer to copy. 822 823 Returns: 824 The SQL string corresponding to `expression`. 825 """ 826 if copy: 827 expression = expression.copy() 828 829 expression = self.preprocess(expression) 830 831 self.unsupported_messages = [] 832 sql = self.sql(expression).strip() 833 834 if self.pretty: 835 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 836 837 if self.unsupported_level == ErrorLevel.IGNORE: 838 return sql 839 840 if self.unsupported_level == ErrorLevel.WARN: 841 for msg in self.unsupported_messages: 842 logger.warning(msg) 843 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 844 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 845 846 return sql 847 848 def preprocess(self, expression: exp.Expression) -> exp.Expression: 849 """Apply generic preprocessing transformations to a given expression.""" 850 expression = self._move_ctes_to_top_level(expression) 851 852 if self.ENSURE_BOOLS: 853 from sqlglot.transforms import ensure_bools 854 855 expression = ensure_bools(expression) 856 857 return expression 858 859 def _move_ctes_to_top_level(self, expression: E) -> E: 860 if ( 861 not expression.parent 862 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 863 and any(node.parent is not expression for node in expression.find_all(exp.With)) 864 ): 865 from sqlglot.transforms import move_ctes_to_top_level 866 867 expression = move_ctes_to_top_level(expression) 868 return expression 869 870 def unsupported(self, message: str) -> None: 871 if self.unsupported_level == ErrorLevel.IMMEDIATE: 872 raise UnsupportedError(message) 873 self.unsupported_messages.append(message) 874 875 def sep(self, sep: str = " ") -> str: 876 return f"{sep.strip()}\n" if self.pretty else sep 877 878 def seg(self, sql: str, sep: str = " ") -> str: 879 return f"{self.sep(sep)}{sql}" 880 881 def sanitize_comment(self, comment: str) -> str: 882 comment = " " + comment if comment[0].strip() else comment 883 comment = comment + " " if comment[-1].strip() else comment 884 885 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 886 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 887 comment = comment.replace("*/", "* /") 888 889 return comment 890 891 def maybe_comment( 892 self, 893 sql: str, 894 expression: t.Optional[exp.Expression] = None, 895 comments: t.Optional[t.List[str]] = None, 896 separated: bool = False, 897 ) -> str: 898 comments = ( 899 ((expression and expression.comments) if comments is None else comments) # type: ignore 900 if self.comments 901 else None 902 ) 903 904 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 905 return sql 906 907 comments_sql = " ".join( 908 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 909 ) 910 911 if not comments_sql: 912 return sql 913 914 comments_sql = self._replace_line_breaks(comments_sql) 915 916 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 917 return ( 918 f"{self.sep()}{comments_sql}{sql}" 919 if not sql or sql[0].isspace() 920 else f"{comments_sql}{self.sep()}{sql}" 921 ) 922 923 return f"{sql} {comments_sql}" 924 925 def wrap(self, expression: exp.Expression | str) -> str: 926 this_sql = ( 927 self.sql(expression) 928 if isinstance(expression, exp.UNWRAPPED_QUERIES) 929 else self.sql(expression, "this") 930 ) 931 if not this_sql: 932 return "()" 933 934 this_sql = self.indent(this_sql, level=1, pad=0) 935 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 936 937 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 938 original = self.identify 939 self.identify = False 940 result = func(*args, **kwargs) 941 self.identify = original 942 return result 943 944 def normalize_func(self, name: str) -> str: 945 if self.normalize_functions == "upper" or self.normalize_functions is True: 946 return name.upper() 947 if self.normalize_functions == "lower": 948 return name.lower() 949 return name 950 951 def indent( 952 self, 953 sql: str, 954 level: int = 0, 955 pad: t.Optional[int] = None, 956 skip_first: bool = False, 957 skip_last: bool = False, 958 ) -> str: 959 if not self.pretty or not sql: 960 return sql 961 962 pad = self.pad if pad is None else pad 963 lines = sql.split("\n") 964 965 return "\n".join( 966 ( 967 line 968 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 969 else f"{' ' * (level * self._indent + pad)}{line}" 970 ) 971 for i, line in enumerate(lines) 972 ) 973 974 def sql( 975 self, 976 expression: t.Optional[str | exp.Expression], 977 key: t.Optional[str] = None, 978 comment: bool = True, 979 ) -> str: 980 if not expression: 981 return "" 982 983 if isinstance(expression, str): 984 return expression 985 986 if key: 987 value = expression.args.get(key) 988 if value: 989 return self.sql(value) 990 return "" 991 992 transform = self.TRANSFORMS.get(expression.__class__) 993 994 if callable(transform): 995 sql = transform(self, expression) 996 elif isinstance(expression, exp.Expression): 997 exp_handler_name = f"{expression.key}_sql" 998 999 if hasattr(self, exp_handler_name): 1000 sql = getattr(self, exp_handler_name)(expression) 1001 elif isinstance(expression, exp.Func): 1002 sql = self.function_fallback_sql(expression) 1003 elif isinstance(expression, exp.Property): 1004 sql = self.property_sql(expression) 1005 else: 1006 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1007 else: 1008 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 1009 1010 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1011 1012 def uncache_sql(self, expression: exp.Uncache) -> str: 1013 table = self.sql(expression, "this") 1014 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1015 return f"UNCACHE TABLE{exists_sql} {table}" 1016 1017 def cache_sql(self, expression: exp.Cache) -> str: 1018 lazy = " LAZY" if expression.args.get("lazy") else "" 1019 table = self.sql(expression, "this") 1020 options = expression.args.get("options") 1021 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1022 sql = self.sql(expression, "expression") 1023 sql = f" AS{self.sep()}{sql}" if sql else "" 1024 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1025 return self.prepend_ctes(expression, sql) 1026 1027 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1028 if isinstance(expression.parent, exp.Cast): 1029 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1030 default = "DEFAULT " if expression.args.get("default") else "" 1031 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1032 1033 def column_parts(self, expression: exp.Column) -> str: 1034 return ".".join( 1035 self.sql(part) 1036 for part in ( 1037 expression.args.get("catalog"), 1038 expression.args.get("db"), 1039 expression.args.get("table"), 1040 expression.args.get("this"), 1041 ) 1042 if part 1043 ) 1044 1045 def column_sql(self, expression: exp.Column) -> str: 1046 join_mark = " (+)" if expression.args.get("join_mark") else "" 1047 1048 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1049 join_mark = "" 1050 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1051 1052 return f"{self.column_parts(expression)}{join_mark}" 1053 1054 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1055 return self.column_sql(expression) 1056 1057 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1058 this = self.sql(expression, "this") 1059 this = f" {this}" if this else "" 1060 position = self.sql(expression, "position") 1061 return f"{position}{this}" 1062 1063 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1064 column = self.sql(expression, "this") 1065 kind = self.sql(expression, "kind") 1066 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1067 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1068 kind = f"{sep}{kind}" if kind else "" 1069 constraints = f" {constraints}" if constraints else "" 1070 position = self.sql(expression, "position") 1071 position = f" {position}" if position else "" 1072 1073 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1074 kind = "" 1075 1076 return f"{exists}{column}{kind}{constraints}{position}" 1077 1078 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1079 this = self.sql(expression, "this") 1080 kind_sql = self.sql(expression, "kind").strip() 1081 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1082 1083 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1084 this = self.sql(expression, "this") 1085 if expression.args.get("not_null"): 1086 persisted = " PERSISTED NOT NULL" 1087 elif expression.args.get("persisted"): 1088 persisted = " PERSISTED" 1089 else: 1090 persisted = "" 1091 1092 return f"AS {this}{persisted}" 1093 1094 def autoincrementcolumnconstraint_sql(self, _) -> str: 1095 return self.token_sql(TokenType.AUTO_INCREMENT) 1096 1097 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1098 if isinstance(expression.this, list): 1099 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1100 else: 1101 this = self.sql(expression, "this") 1102 1103 return f"COMPRESS {this}" 1104 1105 def generatedasidentitycolumnconstraint_sql( 1106 self, expression: exp.GeneratedAsIdentityColumnConstraint 1107 ) -> str: 1108 this = "" 1109 if expression.this is not None: 1110 on_null = " ON NULL" if expression.args.get("on_null") else "" 1111 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1112 1113 start = expression.args.get("start") 1114 start = f"START WITH {start}" if start else "" 1115 increment = expression.args.get("increment") 1116 increment = f" INCREMENT BY {increment}" if increment else "" 1117 minvalue = expression.args.get("minvalue") 1118 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1119 maxvalue = expression.args.get("maxvalue") 1120 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1121 cycle = expression.args.get("cycle") 1122 cycle_sql = "" 1123 1124 if cycle is not None: 1125 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1126 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1127 1128 sequence_opts = "" 1129 if start or increment or cycle_sql: 1130 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1131 sequence_opts = f" ({sequence_opts.strip()})" 1132 1133 expr = self.sql(expression, "expression") 1134 expr = f"({expr})" if expr else "IDENTITY" 1135 1136 return f"GENERATED{this} AS {expr}{sequence_opts}" 1137 1138 def generatedasrowcolumnconstraint_sql( 1139 self, expression: exp.GeneratedAsRowColumnConstraint 1140 ) -> str: 1141 start = "START" if expression.args.get("start") else "END" 1142 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1143 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1144 1145 def periodforsystemtimeconstraint_sql( 1146 self, expression: exp.PeriodForSystemTimeConstraint 1147 ) -> str: 1148 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1149 1150 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1151 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1152 1153 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1154 desc = expression.args.get("desc") 1155 if desc is not None: 1156 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1157 options = self.expressions(expression, key="options", flat=True, sep=" ") 1158 options = f" {options}" if options else "" 1159 return f"PRIMARY KEY{options}" 1160 1161 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1162 this = self.sql(expression, "this") 1163 this = f" {this}" if this else "" 1164 index_type = expression.args.get("index_type") 1165 index_type = f" USING {index_type}" if index_type else "" 1166 on_conflict = self.sql(expression, "on_conflict") 1167 on_conflict = f" {on_conflict}" if on_conflict else "" 1168 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1169 options = self.expressions(expression, key="options", flat=True, sep=" ") 1170 options = f" {options}" if options else "" 1171 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1172 1173 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1174 input_ = expression.args.get("input_") 1175 output = expression.args.get("output") 1176 variadic = expression.args.get("variadic") 1177 1178 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1179 if variadic: 1180 return "VARIADIC" 1181 1182 if input_ and output: 1183 return f"IN{self.INOUT_SEPARATOR}OUT" 1184 if input_: 1185 return "IN" 1186 if output: 1187 return "OUT" 1188 1189 return "" 1190 1191 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1192 return self.sql(expression, "this") 1193 1194 def create_sql(self, expression: exp.Create) -> str: 1195 kind = self.sql(expression, "kind") 1196 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1197 properties = expression.args.get("properties") 1198 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1199 1200 this = self.createable_sql(expression, properties_locs) 1201 1202 properties_sql = "" 1203 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1204 exp.Properties.Location.POST_WITH 1205 ): 1206 props_ast = exp.Properties( 1207 expressions=[ 1208 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1209 *properties_locs[exp.Properties.Location.POST_WITH], 1210 ] 1211 ) 1212 props_ast.parent = expression 1213 properties_sql = self.sql(props_ast) 1214 1215 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1216 properties_sql = self.sep() + properties_sql 1217 elif not self.pretty: 1218 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1219 properties_sql = f" {properties_sql}" 1220 1221 begin = " BEGIN" if expression.args.get("begin") else "" 1222 end = " END" if expression.args.get("end") else "" 1223 1224 expression_sql = self.sql(expression, "expression") 1225 if expression_sql: 1226 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1227 1228 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1229 postalias_props_sql = "" 1230 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1231 postalias_props_sql = self.properties( 1232 exp.Properties( 1233 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1234 ), 1235 wrapped=False, 1236 ) 1237 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1238 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1239 1240 postindex_props_sql = "" 1241 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1242 postindex_props_sql = self.properties( 1243 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1244 wrapped=False, 1245 prefix=" ", 1246 ) 1247 1248 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1249 indexes = f" {indexes}" if indexes else "" 1250 index_sql = indexes + postindex_props_sql 1251 1252 replace = " OR REPLACE" if expression.args.get("replace") else "" 1253 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1254 unique = " UNIQUE" if expression.args.get("unique") else "" 1255 1256 clustered = expression.args.get("clustered") 1257 if clustered is None: 1258 clustered_sql = "" 1259 elif clustered: 1260 clustered_sql = " CLUSTERED COLUMNSTORE" 1261 else: 1262 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1263 1264 postcreate_props_sql = "" 1265 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1266 postcreate_props_sql = self.properties( 1267 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1268 sep=" ", 1269 prefix=" ", 1270 wrapped=False, 1271 ) 1272 1273 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1274 1275 postexpression_props_sql = "" 1276 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1277 postexpression_props_sql = self.properties( 1278 exp.Properties( 1279 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1280 ), 1281 sep=" ", 1282 prefix=" ", 1283 wrapped=False, 1284 ) 1285 1286 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1287 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1288 no_schema_binding = ( 1289 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1290 ) 1291 1292 clone = self.sql(expression, "clone") 1293 clone = f" {clone}" if clone else "" 1294 1295 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1296 properties_expression = f"{expression_sql}{properties_sql}" 1297 else: 1298 properties_expression = f"{properties_sql}{expression_sql}" 1299 1300 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1301 return self.prepend_ctes(expression, expression_sql) 1302 1303 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1304 start = self.sql(expression, "start") 1305 start = f"START WITH {start}" if start else "" 1306 increment = self.sql(expression, "increment") 1307 increment = f" INCREMENT BY {increment}" if increment else "" 1308 minvalue = self.sql(expression, "minvalue") 1309 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1310 maxvalue = self.sql(expression, "maxvalue") 1311 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1312 owned = self.sql(expression, "owned") 1313 owned = f" OWNED BY {owned}" if owned else "" 1314 1315 cache = expression.args.get("cache") 1316 if cache is None: 1317 cache_str = "" 1318 elif cache is True: 1319 cache_str = " CACHE" 1320 else: 1321 cache_str = f" CACHE {cache}" 1322 1323 options = self.expressions(expression, key="options", flat=True, sep=" ") 1324 options = f" {options}" if options else "" 1325 1326 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1327 1328 def clone_sql(self, expression: exp.Clone) -> str: 1329 this = self.sql(expression, "this") 1330 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1331 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1332 return f"{shallow}{keyword} {this}" 1333 1334 def describe_sql(self, expression: exp.Describe) -> str: 1335 style = expression.args.get("style") 1336 style = f" {style}" if style else "" 1337 partition = self.sql(expression, "partition") 1338 partition = f" {partition}" if partition else "" 1339 format = self.sql(expression, "format") 1340 format = f" {format}" if format else "" 1341 as_json = " AS JSON" if expression.args.get("as_json") else "" 1342 1343 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1344 1345 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1346 tag = self.sql(expression, "tag") 1347 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1348 1349 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1350 with_ = self.sql(expression, "with_") 1351 if with_: 1352 sql = f"{with_}{self.sep()}{sql}" 1353 return sql 1354 1355 def with_sql(self, expression: exp.With) -> str: 1356 sql = self.expressions(expression, flat=True) 1357 recursive = ( 1358 "RECURSIVE " 1359 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1360 else "" 1361 ) 1362 search = self.sql(expression, "search") 1363 search = f" {search}" if search else "" 1364 1365 return f"WITH {recursive}{sql}{search}" 1366 1367 def cte_sql(self, expression: exp.CTE) -> str: 1368 alias = expression.args.get("alias") 1369 if alias: 1370 alias.add_comments(expression.pop_comments()) 1371 1372 alias_sql = self.sql(expression, "alias") 1373 1374 materialized = expression.args.get("materialized") 1375 if materialized is False: 1376 materialized = "NOT MATERIALIZED " 1377 elif materialized: 1378 materialized = "MATERIALIZED " 1379 1380 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1381 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1382 1383 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1384 1385 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1386 alias = self.sql(expression, "this") 1387 columns = self.expressions(expression, key="columns", flat=True) 1388 columns = f"({columns})" if columns else "" 1389 1390 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1391 columns = "" 1392 self.unsupported("Named columns are not supported in table alias.") 1393 1394 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1395 alias = self._next_name() 1396 1397 return f"{alias}{columns}" 1398 1399 def bitstring_sql(self, expression: exp.BitString) -> str: 1400 this = self.sql(expression, "this") 1401 if self.dialect.BIT_START: 1402 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1403 return f"{int(this, 2)}" 1404 1405 def hexstring_sql( 1406 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1407 ) -> str: 1408 this = self.sql(expression, "this") 1409 is_integer_type = expression.args.get("is_integer") 1410 1411 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1412 not self.dialect.HEX_START and not binary_function_repr 1413 ): 1414 # Integer representation will be returned if: 1415 # - The read dialect treats the hex value as integer literal but not the write 1416 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1417 return f"{int(this, 16)}" 1418 1419 if not is_integer_type: 1420 # Read dialect treats the hex value as BINARY/BLOB 1421 if binary_function_repr: 1422 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1423 return self.func(binary_function_repr, exp.Literal.string(this)) 1424 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1425 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1426 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1427 1428 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1429 1430 def bytestring_sql(self, expression: exp.ByteString) -> str: 1431 this = self.sql(expression, "this") 1432 if self.dialect.BYTE_START: 1433 escaped_byte_string = self.escape_str( 1434 this, 1435 escape_backslash=False, 1436 delimiter=self.dialect.BYTE_END, 1437 escaped_delimiter=self._escaped_byte_quote_end, 1438 is_byte_string=True, 1439 ) 1440 is_bytes = expression.args.get("is_bytes", False) 1441 delimited_byte_string = ( 1442 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1443 ) 1444 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1445 return self.sql( 1446 exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect) 1447 ) 1448 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1449 return self.sql( 1450 exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect) 1451 ) 1452 1453 return delimited_byte_string 1454 return this 1455 1456 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1457 this = self.sql(expression, "this") 1458 escape = expression.args.get("escape") 1459 1460 if self.dialect.UNICODE_START: 1461 escape_substitute = r"\\\1" 1462 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1463 else: 1464 escape_substitute = r"\\u\1" 1465 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1466 1467 if escape: 1468 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1469 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1470 else: 1471 escape_pattern = ESCAPED_UNICODE_RE 1472 escape_sql = "" 1473 1474 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1475 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1476 1477 return f"{left_quote}{this}{right_quote}{escape_sql}" 1478 1479 def rawstring_sql(self, expression: exp.RawString) -> str: 1480 string = expression.this 1481 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1482 string = string.replace("\\", "\\\\") 1483 1484 string = self.escape_str(string, escape_backslash=False) 1485 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1486 1487 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1488 this = self.sql(expression, "this") 1489 specifier = self.sql(expression, "expression") 1490 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1491 return f"{this}{specifier}" 1492 1493 def datatype_sql(self, expression: exp.DataType) -> str: 1494 nested = "" 1495 values = "" 1496 1497 expr_nested = expression.args.get("nested") 1498 interior = ( 1499 self.expressions( 1500 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1501 ) 1502 if expr_nested and self.pretty 1503 else self.expressions(expression, flat=True) 1504 ) 1505 1506 type_value = expression.this 1507 if type_value in self.UNSUPPORTED_TYPES: 1508 self.unsupported( 1509 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1510 ) 1511 1512 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1513 type_sql = self.sql(expression, "kind") 1514 else: 1515 type_sql = ( 1516 self.TYPE_MAPPING.get(type_value, type_value.value) 1517 if isinstance(type_value, exp.DataType.Type) 1518 else type_value 1519 ) 1520 1521 if interior: 1522 if expr_nested: 1523 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1524 if expression.args.get("values") is not None: 1525 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1526 values = self.expressions(expression, key="values", flat=True) 1527 values = f"{delimiters[0]}{values}{delimiters[1]}" 1528 elif type_value == exp.DataType.Type.INTERVAL: 1529 nested = f" {interior}" 1530 else: 1531 nested = f"({interior})" 1532 1533 type_sql = f"{type_sql}{nested}{values}" 1534 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1535 exp.DataType.Type.TIMETZ, 1536 exp.DataType.Type.TIMESTAMPTZ, 1537 ): 1538 type_sql = f"{type_sql} WITH TIME ZONE" 1539 1540 return type_sql 1541 1542 def directory_sql(self, expression: exp.Directory) -> str: 1543 local = "LOCAL " if expression.args.get("local") else "" 1544 row_format = self.sql(expression, "row_format") 1545 row_format = f" {row_format}" if row_format else "" 1546 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1547 1548 def delete_sql(self, expression: exp.Delete) -> str: 1549 this = self.sql(expression, "this") 1550 this = f" FROM {this}" if this else "" 1551 using = self.expressions(expression, key="using") 1552 using = f" USING {using}" if using else "" 1553 cluster = self.sql(expression, "cluster") 1554 cluster = f" {cluster}" if cluster else "" 1555 where = self.sql(expression, "where") 1556 returning = self.sql(expression, "returning") 1557 order = self.sql(expression, "order") 1558 limit = self.sql(expression, "limit") 1559 tables = self.expressions(expression, key="tables") 1560 tables = f" {tables}" if tables else "" 1561 if self.RETURNING_END: 1562 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1563 else: 1564 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1565 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1566 1567 def drop_sql(self, expression: exp.Drop) -> str: 1568 this = self.sql(expression, "this") 1569 expressions = self.expressions(expression, flat=True) 1570 expressions = f" ({expressions})" if expressions else "" 1571 kind = expression.args["kind"] 1572 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1573 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1574 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1575 on_cluster = self.sql(expression, "cluster") 1576 on_cluster = f" {on_cluster}" if on_cluster else "" 1577 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1578 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1579 cascade = " CASCADE" if expression.args.get("cascade") else "" 1580 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1581 purge = " PURGE" if expression.args.get("purge") else "" 1582 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1583 1584 def set_operation(self, expression: exp.SetOperation) -> str: 1585 op_type = type(expression) 1586 op_name = op_type.key.upper() 1587 1588 distinct = expression.args.get("distinct") 1589 if ( 1590 distinct is False 1591 and op_type in (exp.Except, exp.Intersect) 1592 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1593 ): 1594 self.unsupported(f"{op_name} ALL is not supported") 1595 1596 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1597 1598 if distinct is None: 1599 distinct = default_distinct 1600 if distinct is None: 1601 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1602 1603 if distinct is default_distinct: 1604 distinct_or_all = "" 1605 else: 1606 distinct_or_all = " DISTINCT" if distinct else " ALL" 1607 1608 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1609 side_kind = f"{side_kind} " if side_kind else "" 1610 1611 by_name = " BY NAME" if expression.args.get("by_name") else "" 1612 on = self.expressions(expression, key="on", flat=True) 1613 on = f" ON ({on})" if on else "" 1614 1615 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1616 1617 def set_operations(self, expression: exp.SetOperation) -> str: 1618 if not self.SET_OP_MODIFIERS: 1619 limit = expression.args.get("limit") 1620 order = expression.args.get("order") 1621 1622 if limit or order: 1623 select = self._move_ctes_to_top_level( 1624 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1625 ) 1626 1627 if limit: 1628 select = select.limit(limit.pop(), copy=False) 1629 if order: 1630 select = select.order_by(order.pop(), copy=False) 1631 return self.sql(select) 1632 1633 sqls: t.List[str] = [] 1634 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1635 1636 while stack: 1637 node = stack.pop() 1638 1639 if isinstance(node, exp.SetOperation): 1640 stack.append(node.expression) 1641 stack.append( 1642 self.maybe_comment( 1643 self.set_operation(node), comments=node.comments, separated=True 1644 ) 1645 ) 1646 stack.append(node.this) 1647 else: 1648 sqls.append(self.sql(node)) 1649 1650 this = self.sep().join(sqls) 1651 this = self.query_modifiers(expression, this) 1652 return self.prepend_ctes(expression, this) 1653 1654 def fetch_sql(self, expression: exp.Fetch) -> str: 1655 direction = expression.args.get("direction") 1656 direction = f" {direction}" if direction else "" 1657 count = self.sql(expression, "count") 1658 count = f" {count}" if count else "" 1659 limit_options = self.sql(expression, "limit_options") 1660 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1661 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1662 1663 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1664 percent = " PERCENT" if expression.args.get("percent") else "" 1665 rows = " ROWS" if expression.args.get("rows") else "" 1666 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1667 if not with_ties and rows: 1668 with_ties = " ONLY" 1669 return f"{percent}{rows}{with_ties}" 1670 1671 def filter_sql(self, expression: exp.Filter) -> str: 1672 if self.AGGREGATE_FILTER_SUPPORTED: 1673 this = self.sql(expression, "this") 1674 where = self.sql(expression, "expression").strip() 1675 return f"{this} FILTER({where})" 1676 1677 agg = expression.this 1678 agg_arg = agg.this 1679 cond = expression.expression.this 1680 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1681 return self.sql(agg) 1682 1683 def hint_sql(self, expression: exp.Hint) -> str: 1684 if not self.QUERY_HINTS: 1685 self.unsupported("Hints are not supported") 1686 return "" 1687 1688 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1689 1690 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1691 using = self.sql(expression, "using") 1692 using = f" USING {using}" if using else "" 1693 columns = self.expressions(expression, key="columns", flat=True) 1694 columns = f"({columns})" if columns else "" 1695 partition_by = self.expressions(expression, key="partition_by", flat=True) 1696 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1697 where = self.sql(expression, "where") 1698 include = self.expressions(expression, key="include", flat=True) 1699 if include: 1700 include = f" INCLUDE ({include})" 1701 with_storage = self.expressions(expression, key="with_storage", flat=True) 1702 with_storage = f" WITH ({with_storage})" if with_storage else "" 1703 tablespace = self.sql(expression, "tablespace") 1704 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1705 on = self.sql(expression, "on") 1706 on = f" ON {on}" if on else "" 1707 1708 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1709 1710 def index_sql(self, expression: exp.Index) -> str: 1711 unique = "UNIQUE " if expression.args.get("unique") else "" 1712 primary = "PRIMARY " if expression.args.get("primary") else "" 1713 amp = "AMP " if expression.args.get("amp") else "" 1714 name = self.sql(expression, "this") 1715 name = f"{name} " if name else "" 1716 table = self.sql(expression, "table") 1717 table = f"{self.INDEX_ON} {table}" if table else "" 1718 1719 index = "INDEX " if not table else "" 1720 1721 params = self.sql(expression, "params") 1722 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1723 1724 def identifier_sql(self, expression: exp.Identifier) -> str: 1725 text = expression.name 1726 lower = text.lower() 1727 text = lower if self.normalize and not expression.quoted else text 1728 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1729 if ( 1730 expression.quoted 1731 or self.dialect.can_quote(expression, self.identify) 1732 or lower in self.RESERVED_KEYWORDS 1733 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1734 ): 1735 text = f"{self._identifier_start}{text}{self._identifier_end}" 1736 return text 1737 1738 def hex_sql(self, expression: exp.Hex) -> str: 1739 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1740 if self.dialect.HEX_LOWERCASE: 1741 text = self.func("LOWER", text) 1742 1743 return text 1744 1745 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1746 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1747 if not self.dialect.HEX_LOWERCASE: 1748 text = self.func("LOWER", text) 1749 return text 1750 1751 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1752 input_format = self.sql(expression, "input_format") 1753 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1754 output_format = self.sql(expression, "output_format") 1755 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1756 return self.sep().join((input_format, output_format)) 1757 1758 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1759 string = self.sql(exp.Literal.string(expression.name)) 1760 return f"{prefix}{string}" 1761 1762 def partition_sql(self, expression: exp.Partition) -> str: 1763 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1764 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1765 1766 def properties_sql(self, expression: exp.Properties) -> str: 1767 root_properties = [] 1768 with_properties = [] 1769 1770 for p in expression.expressions: 1771 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1772 if p_loc == exp.Properties.Location.POST_WITH: 1773 with_properties.append(p) 1774 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1775 root_properties.append(p) 1776 1777 root_props_ast = exp.Properties(expressions=root_properties) 1778 root_props_ast.parent = expression.parent 1779 1780 with_props_ast = exp.Properties(expressions=with_properties) 1781 with_props_ast.parent = expression.parent 1782 1783 root_props = self.root_properties(root_props_ast) 1784 with_props = self.with_properties(with_props_ast) 1785 1786 if root_props and with_props and not self.pretty: 1787 with_props = " " + with_props 1788 1789 return root_props + with_props 1790 1791 def root_properties(self, properties: exp.Properties) -> str: 1792 if properties.expressions: 1793 return self.expressions(properties, indent=False, sep=" ") 1794 return "" 1795 1796 def properties( 1797 self, 1798 properties: exp.Properties, 1799 prefix: str = "", 1800 sep: str = ", ", 1801 suffix: str = "", 1802 wrapped: bool = True, 1803 ) -> str: 1804 if properties.expressions: 1805 expressions = self.expressions(properties, sep=sep, indent=False) 1806 if expressions: 1807 expressions = self.wrap(expressions) if wrapped else expressions 1808 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1809 return "" 1810 1811 def with_properties(self, properties: exp.Properties) -> str: 1812 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1813 1814 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1815 properties_locs = defaultdict(list) 1816 for p in properties.expressions: 1817 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1818 if p_loc != exp.Properties.Location.UNSUPPORTED: 1819 properties_locs[p_loc].append(p) 1820 else: 1821 self.unsupported(f"Unsupported property {p.key}") 1822 1823 return properties_locs 1824 1825 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1826 if isinstance(expression.this, exp.Dot): 1827 return self.sql(expression, "this") 1828 return f"'{expression.name}'" if string_key else expression.name 1829 1830 def property_sql(self, expression: exp.Property) -> str: 1831 property_cls = expression.__class__ 1832 if property_cls == exp.Property: 1833 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1834 1835 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1836 if not property_name: 1837 self.unsupported(f"Unsupported property {expression.key}") 1838 1839 return f"{property_name}={self.sql(expression, 'this')}" 1840 1841 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1842 if self.SUPPORTS_CREATE_TABLE_LIKE: 1843 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1844 options = f" {options}" if options else "" 1845 1846 like = f"LIKE {self.sql(expression, 'this')}{options}" 1847 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1848 like = f"({like})" 1849 1850 return like 1851 1852 if expression.expressions: 1853 self.unsupported("Transpilation of LIKE property options is unsupported") 1854 1855 select = exp.select("*").from_(expression.this).limit(0) 1856 return f"AS {self.sql(select)}" 1857 1858 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1859 no = "NO " if expression.args.get("no") else "" 1860 protection = " PROTECTION" if expression.args.get("protection") else "" 1861 return f"{no}FALLBACK{protection}" 1862 1863 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1864 no = "NO " if expression.args.get("no") else "" 1865 local = expression.args.get("local") 1866 local = f"{local} " if local else "" 1867 dual = "DUAL " if expression.args.get("dual") else "" 1868 before = "BEFORE " if expression.args.get("before") else "" 1869 after = "AFTER " if expression.args.get("after") else "" 1870 return f"{no}{local}{dual}{before}{after}JOURNAL" 1871 1872 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1873 freespace = self.sql(expression, "this") 1874 percent = " PERCENT" if expression.args.get("percent") else "" 1875 return f"FREESPACE={freespace}{percent}" 1876 1877 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1878 if expression.args.get("default"): 1879 property = "DEFAULT" 1880 elif expression.args.get("on"): 1881 property = "ON" 1882 else: 1883 property = "OFF" 1884 return f"CHECKSUM={property}" 1885 1886 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1887 if expression.args.get("no"): 1888 return "NO MERGEBLOCKRATIO" 1889 if expression.args.get("default"): 1890 return "DEFAULT MERGEBLOCKRATIO" 1891 1892 percent = " PERCENT" if expression.args.get("percent") else "" 1893 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1894 1895 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1896 default = expression.args.get("default") 1897 minimum = expression.args.get("minimum") 1898 maximum = expression.args.get("maximum") 1899 if default or minimum or maximum: 1900 if default: 1901 prop = "DEFAULT" 1902 elif minimum: 1903 prop = "MINIMUM" 1904 else: 1905 prop = "MAXIMUM" 1906 return f"{prop} DATABLOCKSIZE" 1907 units = expression.args.get("units") 1908 units = f" {units}" if units else "" 1909 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1910 1911 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1912 autotemp = expression.args.get("autotemp") 1913 always = expression.args.get("always") 1914 default = expression.args.get("default") 1915 manual = expression.args.get("manual") 1916 never = expression.args.get("never") 1917 1918 if autotemp is not None: 1919 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1920 elif always: 1921 prop = "ALWAYS" 1922 elif default: 1923 prop = "DEFAULT" 1924 elif manual: 1925 prop = "MANUAL" 1926 elif never: 1927 prop = "NEVER" 1928 return f"BLOCKCOMPRESSION={prop}" 1929 1930 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1931 no = expression.args.get("no") 1932 no = " NO" if no else "" 1933 concurrent = expression.args.get("concurrent") 1934 concurrent = " CONCURRENT" if concurrent else "" 1935 target = self.sql(expression, "target") 1936 target = f" {target}" if target else "" 1937 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1938 1939 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1940 if isinstance(expression.this, list): 1941 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1942 if expression.this: 1943 modulus = self.sql(expression, "this") 1944 remainder = self.sql(expression, "expression") 1945 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1946 1947 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1948 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1949 return f"FROM ({from_expressions}) TO ({to_expressions})" 1950 1951 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1952 this = self.sql(expression, "this") 1953 1954 for_values_or_default = expression.expression 1955 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1956 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1957 else: 1958 for_values_or_default = " DEFAULT" 1959 1960 return f"PARTITION OF {this}{for_values_or_default}" 1961 1962 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1963 kind = expression.args.get("kind") 1964 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1965 for_or_in = expression.args.get("for_or_in") 1966 for_or_in = f" {for_or_in}" if for_or_in else "" 1967 lock_type = expression.args.get("lock_type") 1968 override = " OVERRIDE" if expression.args.get("override") else "" 1969 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1970 1971 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1972 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1973 statistics = expression.args.get("statistics") 1974 statistics_sql = "" 1975 if statistics is not None: 1976 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1977 return f"{data_sql}{statistics_sql}" 1978 1979 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1980 this = self.sql(expression, "this") 1981 this = f"HISTORY_TABLE={this}" if this else "" 1982 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1983 data_consistency = ( 1984 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1985 ) 1986 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1987 retention_period = ( 1988 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1989 ) 1990 1991 if this: 1992 on_sql = self.func("ON", this, data_consistency, retention_period) 1993 else: 1994 on_sql = "ON" if expression.args.get("on") else "OFF" 1995 1996 sql = f"SYSTEM_VERSIONING={on_sql}" 1997 1998 return f"WITH({sql})" if expression.args.get("with_") else sql 1999 2000 def insert_sql(self, expression: exp.Insert) -> str: 2001 hint = self.sql(expression, "hint") 2002 overwrite = expression.args.get("overwrite") 2003 2004 if isinstance(expression.this, exp.Directory): 2005 this = " OVERWRITE" if overwrite else " INTO" 2006 else: 2007 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2008 2009 stored = self.sql(expression, "stored") 2010 stored = f" {stored}" if stored else "" 2011 alternative = expression.args.get("alternative") 2012 alternative = f" OR {alternative}" if alternative else "" 2013 ignore = " IGNORE" if expression.args.get("ignore") else "" 2014 is_function = expression.args.get("is_function") 2015 if is_function: 2016 this = f"{this} FUNCTION" 2017 this = f"{this} {self.sql(expression, 'this')}" 2018 2019 exists = " IF EXISTS" if expression.args.get("exists") else "" 2020 where = self.sql(expression, "where") 2021 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2022 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2023 on_conflict = self.sql(expression, "conflict") 2024 on_conflict = f" {on_conflict}" if on_conflict else "" 2025 by_name = " BY NAME" if expression.args.get("by_name") else "" 2026 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2027 returning = self.sql(expression, "returning") 2028 2029 if self.RETURNING_END: 2030 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2031 else: 2032 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2033 2034 partition_by = self.sql(expression, "partition") 2035 partition_by = f" {partition_by}" if partition_by else "" 2036 settings = self.sql(expression, "settings") 2037 settings = f" {settings}" if settings else "" 2038 2039 source = self.sql(expression, "source") 2040 source = f"TABLE {source}" if source else "" 2041 2042 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2043 return self.prepend_ctes(expression, sql) 2044 2045 def introducer_sql(self, expression: exp.Introducer) -> str: 2046 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2047 2048 def kill_sql(self, expression: exp.Kill) -> str: 2049 kind = self.sql(expression, "kind") 2050 kind = f" {kind}" if kind else "" 2051 this = self.sql(expression, "this") 2052 this = f" {this}" if this else "" 2053 return f"KILL{kind}{this}" 2054 2055 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2056 return expression.name 2057 2058 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2059 return expression.name 2060 2061 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2062 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2063 2064 constraint = self.sql(expression, "constraint") 2065 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2066 2067 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2068 if conflict_keys: 2069 conflict_keys = f"({conflict_keys})" 2070 2071 index_predicate = self.sql(expression, "index_predicate") 2072 conflict_keys = f"{conflict_keys}{index_predicate} " 2073 2074 action = self.sql(expression, "action") 2075 2076 expressions = self.expressions(expression, flat=True) 2077 if expressions: 2078 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2079 expressions = f" {set_keyword}{expressions}" 2080 2081 where = self.sql(expression, "where") 2082 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2083 2084 def returning_sql(self, expression: exp.Returning) -> str: 2085 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2086 2087 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2088 fields = self.sql(expression, "fields") 2089 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2090 escaped = self.sql(expression, "escaped") 2091 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2092 items = self.sql(expression, "collection_items") 2093 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2094 keys = self.sql(expression, "map_keys") 2095 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2096 lines = self.sql(expression, "lines") 2097 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2098 null = self.sql(expression, "null") 2099 null = f" NULL DEFINED AS {null}" if null else "" 2100 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2101 2102 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2103 return f"WITH ({self.expressions(expression, flat=True)})" 2104 2105 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2106 this = f"{self.sql(expression, 'this')} INDEX" 2107 target = self.sql(expression, "target") 2108 target = f" FOR {target}" if target else "" 2109 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2110 2111 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2112 this = self.sql(expression, "this") 2113 kind = self.sql(expression, "kind") 2114 expr = self.sql(expression, "expression") 2115 return f"{this} ({kind} => {expr})" 2116 2117 def table_parts(self, expression: exp.Table) -> str: 2118 return ".".join( 2119 self.sql(part) 2120 for part in ( 2121 expression.args.get("catalog"), 2122 expression.args.get("db"), 2123 expression.args.get("this"), 2124 ) 2125 if part is not None 2126 ) 2127 2128 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2129 table = self.table_parts(expression) 2130 only = "ONLY " if expression.args.get("only") else "" 2131 partition = self.sql(expression, "partition") 2132 partition = f" {partition}" if partition else "" 2133 version = self.sql(expression, "version") 2134 version = f" {version}" if version else "" 2135 alias = self.sql(expression, "alias") 2136 alias = f"{sep}{alias}" if alias else "" 2137 2138 sample = self.sql(expression, "sample") 2139 if self.dialect.ALIAS_POST_TABLESAMPLE: 2140 sample_pre_alias = sample 2141 sample_post_alias = "" 2142 else: 2143 sample_pre_alias = "" 2144 sample_post_alias = sample 2145 2146 hints = self.expressions(expression, key="hints", sep=" ") 2147 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2148 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2149 joins = self.indent( 2150 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2151 ) 2152 laterals = self.expressions(expression, key="laterals", sep="") 2153 2154 file_format = self.sql(expression, "format") 2155 if file_format: 2156 pattern = self.sql(expression, "pattern") 2157 pattern = f", PATTERN => {pattern}" if pattern else "" 2158 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2159 2160 ordinality = expression.args.get("ordinality") or "" 2161 if ordinality: 2162 ordinality = f" WITH ORDINALITY{alias}" 2163 alias = "" 2164 2165 when = self.sql(expression, "when") 2166 if when: 2167 table = f"{table} {when}" 2168 2169 changes = self.sql(expression, "changes") 2170 changes = f" {changes}" if changes else "" 2171 2172 rows_from = self.expressions(expression, key="rows_from") 2173 if rows_from: 2174 table = f"ROWS FROM {self.wrap(rows_from)}" 2175 2176 indexed = expression.args.get("indexed") 2177 if indexed is not None: 2178 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2179 else: 2180 indexed = "" 2181 2182 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{indexed}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2183 2184 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2185 table = self.func("TABLE", expression.this) 2186 alias = self.sql(expression, "alias") 2187 alias = f" AS {alias}" if alias else "" 2188 sample = self.sql(expression, "sample") 2189 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2190 joins = self.indent( 2191 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2192 ) 2193 return f"{table}{alias}{pivots}{sample}{joins}" 2194 2195 def tablesample_sql( 2196 self, 2197 expression: exp.TableSample, 2198 tablesample_keyword: t.Optional[str] = None, 2199 ) -> str: 2200 method = self.sql(expression, "method") 2201 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2202 numerator = self.sql(expression, "bucket_numerator") 2203 denominator = self.sql(expression, "bucket_denominator") 2204 field = self.sql(expression, "bucket_field") 2205 field = f" ON {field}" if field else "" 2206 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2207 seed = self.sql(expression, "seed") 2208 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2209 2210 size = self.sql(expression, "size") 2211 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2212 size = f"{size} ROWS" 2213 2214 percent = self.sql(expression, "percent") 2215 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2216 percent = f"{percent} PERCENT" 2217 2218 expr = f"{bucket}{percent}{size}" 2219 if self.TABLESAMPLE_REQUIRES_PARENS: 2220 expr = f"({expr})" 2221 2222 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2223 2224 def pivot_sql(self, expression: exp.Pivot) -> str: 2225 expressions = self.expressions(expression, flat=True) 2226 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2227 2228 group = self.sql(expression, "group") 2229 2230 if expression.this: 2231 this = self.sql(expression, "this") 2232 if not expressions: 2233 sql = f"UNPIVOT {this}" 2234 else: 2235 on = f"{self.seg('ON')} {expressions}" 2236 into = self.sql(expression, "into") 2237 into = f"{self.seg('INTO')} {into}" if into else "" 2238 using = self.expressions(expression, key="using", flat=True) 2239 using = f"{self.seg('USING')} {using}" if using else "" 2240 sql = f"{direction} {this}{on}{into}{using}{group}" 2241 return self.prepend_ctes(expression, sql) 2242 2243 alias = self.sql(expression, "alias") 2244 alias = f" AS {alias}" if alias else "" 2245 2246 fields = self.expressions( 2247 expression, 2248 "fields", 2249 sep=" ", 2250 dynamic=True, 2251 new_line=True, 2252 skip_first=True, 2253 skip_last=True, 2254 ) 2255 2256 include_nulls = expression.args.get("include_nulls") 2257 if include_nulls is not None: 2258 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2259 else: 2260 nulls = "" 2261 2262 default_on_null = self.sql(expression, "default_on_null") 2263 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2264 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2265 return self.prepend_ctes(expression, sql) 2266 2267 def version_sql(self, expression: exp.Version) -> str: 2268 this = f"FOR {expression.name}" 2269 kind = expression.text("kind") 2270 expr = self.sql(expression, "expression") 2271 return f"{this} {kind} {expr}" 2272 2273 def tuple_sql(self, expression: exp.Tuple) -> str: 2274 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2275 2276 def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]: 2277 """ 2278 Returns (join_sql, from_sql) for UPDATE statements. 2279 - join_sql: placed after UPDATE table, before SET 2280 - from_sql: placed after SET clause (standard position) 2281 Dialects like MySQL need to convert FROM to JOIN syntax. 2282 """ 2283 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2284 return ("", self.sql(expression, "from_")) 2285 2286 # Qualify unqualified columns in SET clause with the target table 2287 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2288 target_table = expression.this 2289 if isinstance(target_table, exp.Table): 2290 target_name = exp.to_identifier(target_table.alias_or_name) 2291 for eq in expression.expressions: 2292 col = eq.this 2293 if isinstance(col, exp.Column) and not col.table: 2294 col.set("table", target_name) 2295 2296 table = from_expr.this 2297 if nested_joins := table.args.get("joins", []): 2298 table.set("joins", None) 2299 2300 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2301 for nested in nested_joins: 2302 if not nested.args.get("on") and not nested.args.get("using"): 2303 nested.set("on", exp.true()) 2304 join_sql += self.sql(nested) 2305 2306 return (join_sql, "") 2307 2308 def update_sql(self, expression: exp.Update) -> str: 2309 this = self.sql(expression, "this") 2310 join_sql, from_sql = self._update_from_joins_sql(expression) 2311 set_sql = self.expressions(expression, flat=True) 2312 where_sql = self.sql(expression, "where") 2313 returning = self.sql(expression, "returning") 2314 order = self.sql(expression, "order") 2315 limit = self.sql(expression, "limit") 2316 if self.RETURNING_END: 2317 expression_sql = f"{from_sql}{where_sql}{returning}" 2318 else: 2319 expression_sql = f"{returning}{from_sql}{where_sql}" 2320 options = self.expressions(expression, key="options") 2321 options = f" OPTION({options})" if options else "" 2322 sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2323 return self.prepend_ctes(expression, sql) 2324 2325 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2326 values_as_table = values_as_table and self.VALUES_AS_TABLE 2327 2328 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2329 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2330 args = self.expressions(expression) 2331 alias = self.sql(expression, "alias") 2332 values = f"VALUES{self.seg('')}{args}" 2333 values = ( 2334 f"({values})" 2335 if self.WRAP_DERIVED_VALUES 2336 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2337 else values 2338 ) 2339 values = self.query_modifiers(expression, values) 2340 return f"{values} AS {alias}" if alias else values 2341 2342 # Converts `VALUES...` expression into a series of select unions. 2343 alias_node = expression.args.get("alias") 2344 column_names = alias_node and alias_node.columns 2345 2346 selects: t.List[exp.Query] = [] 2347 2348 for i, tup in enumerate(expression.expressions): 2349 row = tup.expressions 2350 2351 if i == 0 and column_names: 2352 row = [ 2353 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2354 ] 2355 2356 selects.append(exp.Select(expressions=row)) 2357 2358 if self.pretty: 2359 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2360 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2361 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2362 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2363 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2364 2365 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2366 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2367 return f"({unions}){alias}" 2368 2369 def var_sql(self, expression: exp.Var) -> str: 2370 return self.sql(expression, "this") 2371 2372 @unsupported_args("expressions") 2373 def into_sql(self, expression: exp.Into) -> str: 2374 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2375 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2376 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2377 2378 def from_sql(self, expression: exp.From) -> str: 2379 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2380 2381 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2382 grouping_sets = self.expressions(expression, indent=False) 2383 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2384 2385 def rollup_sql(self, expression: exp.Rollup) -> str: 2386 expressions = self.expressions(expression, indent=False) 2387 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2388 2389 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2390 this = self.sql(expression, "this") 2391 2392 columns = self.expressions(expression, flat=True) 2393 2394 from_sql = self.sql(expression, "from_index") 2395 from_sql = f" FROM {from_sql}" if from_sql else "" 2396 2397 properties = expression.args.get("properties") 2398 properties_sql = ( 2399 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2400 ) 2401 2402 return f"{this}({columns}){from_sql}{properties_sql}" 2403 2404 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2405 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2406 2407 def cube_sql(self, expression: exp.Cube) -> str: 2408 expressions = self.expressions(expression, indent=False) 2409 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2410 2411 def group_sql(self, expression: exp.Group) -> str: 2412 group_by_all = expression.args.get("all") 2413 if group_by_all is True: 2414 modifier = " ALL" 2415 elif group_by_all is False: 2416 modifier = " DISTINCT" 2417 else: 2418 modifier = "" 2419 2420 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2421 2422 grouping_sets = self.expressions(expression, key="grouping_sets") 2423 cube = self.expressions(expression, key="cube") 2424 rollup = self.expressions(expression, key="rollup") 2425 2426 groupings = csv( 2427 self.seg(grouping_sets) if grouping_sets else "", 2428 self.seg(cube) if cube else "", 2429 self.seg(rollup) if rollup else "", 2430 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2431 sep=self.GROUPINGS_SEP, 2432 ) 2433 2434 if ( 2435 expression.expressions 2436 and groupings 2437 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2438 ): 2439 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2440 2441 return f"{group_by}{groupings}" 2442 2443 def having_sql(self, expression: exp.Having) -> str: 2444 this = self.indent(self.sql(expression, "this")) 2445 return f"{self.seg('HAVING')}{self.sep()}{this}" 2446 2447 def connect_sql(self, expression: exp.Connect) -> str: 2448 start = self.sql(expression, "start") 2449 start = self.seg(f"START WITH {start}") if start else "" 2450 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2451 connect = self.sql(expression, "connect") 2452 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2453 return start + connect 2454 2455 def prior_sql(self, expression: exp.Prior) -> str: 2456 return f"PRIOR {self.sql(expression, 'this')}" 2457 2458 def join_sql(self, expression: exp.Join) -> str: 2459 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2460 side = None 2461 else: 2462 side = expression.side 2463 2464 op_sql = " ".join( 2465 op 2466 for op in ( 2467 expression.method, 2468 "GLOBAL" if expression.args.get("global_") else None, 2469 side, 2470 expression.kind, 2471 expression.hint if self.JOIN_HINTS else None, 2472 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2473 ) 2474 if op 2475 ) 2476 match_cond = self.sql(expression, "match_condition") 2477 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2478 on_sql = self.sql(expression, "on") 2479 using = expression.args.get("using") 2480 2481 if not on_sql and using: 2482 on_sql = csv(*(self.sql(column) for column in using)) 2483 2484 this = expression.this 2485 this_sql = self.sql(this) 2486 2487 exprs = self.expressions(expression) 2488 if exprs: 2489 this_sql = f"{this_sql},{self.seg(exprs)}" 2490 2491 if on_sql: 2492 on_sql = self.indent(on_sql, skip_first=True) 2493 space = self.seg(" " * self.pad) if self.pretty else " " 2494 if using: 2495 on_sql = f"{space}USING ({on_sql})" 2496 else: 2497 on_sql = f"{space}ON {on_sql}" 2498 elif not op_sql: 2499 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2500 return f" {this_sql}" 2501 2502 return f", {this_sql}" 2503 2504 if op_sql != "STRAIGHT_JOIN": 2505 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2506 2507 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2508 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2509 2510 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2511 args = self.expressions(expression, flat=True) 2512 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2513 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2514 2515 def lateral_op(self, expression: exp.Lateral) -> str: 2516 cross_apply = expression.args.get("cross_apply") 2517 2518 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2519 if cross_apply is True: 2520 op = "INNER JOIN " 2521 elif cross_apply is False: 2522 op = "LEFT JOIN " 2523 else: 2524 op = "" 2525 2526 return f"{op}LATERAL" 2527 2528 def lateral_sql(self, expression: exp.Lateral) -> str: 2529 this = self.sql(expression, "this") 2530 2531 if expression.args.get("view"): 2532 alias = expression.args["alias"] 2533 columns = self.expressions(alias, key="columns", flat=True) 2534 table = f" {alias.name}" if alias.name else "" 2535 columns = f" AS {columns}" if columns else "" 2536 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2537 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2538 2539 alias = self.sql(expression, "alias") 2540 alias = f" AS {alias}" if alias else "" 2541 2542 ordinality = expression.args.get("ordinality") or "" 2543 if ordinality: 2544 ordinality = f" WITH ORDINALITY{alias}" 2545 alias = "" 2546 2547 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2548 2549 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2550 this = self.sql(expression, "this") 2551 2552 args = [ 2553 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2554 for e in (expression.args.get(k) for k in ("offset", "expression")) 2555 if e 2556 ] 2557 2558 args_sql = ", ".join(self.sql(e) for e in args) 2559 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2560 expressions = self.expressions(expression, flat=True) 2561 limit_options = self.sql(expression, "limit_options") 2562 expressions = f" BY {expressions}" if expressions else "" 2563 2564 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2565 2566 def offset_sql(self, expression: exp.Offset) -> str: 2567 this = self.sql(expression, "this") 2568 value = expression.expression 2569 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2570 expressions = self.expressions(expression, flat=True) 2571 expressions = f" BY {expressions}" if expressions else "" 2572 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2573 2574 def setitem_sql(self, expression: exp.SetItem) -> str: 2575 kind = self.sql(expression, "kind") 2576 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2577 kind = "" 2578 else: 2579 kind = f"{kind} " if kind else "" 2580 this = self.sql(expression, "this") 2581 expressions = self.expressions(expression) 2582 collate = self.sql(expression, "collate") 2583 collate = f" COLLATE {collate}" if collate else "" 2584 global_ = "GLOBAL " if expression.args.get("global_") else "" 2585 return f"{global_}{kind}{this}{expressions}{collate}" 2586 2587 def set_sql(self, expression: exp.Set) -> str: 2588 expressions = f" {self.expressions(expression, flat=True)}" 2589 tag = " TAG" if expression.args.get("tag") else "" 2590 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2591 2592 def queryband_sql(self, expression: exp.QueryBand) -> str: 2593 this = self.sql(expression, "this") 2594 update = " UPDATE" if expression.args.get("update") else "" 2595 scope = self.sql(expression, "scope") 2596 scope = f" FOR {scope}" if scope else "" 2597 2598 return f"QUERY_BAND = {this}{update}{scope}" 2599 2600 def pragma_sql(self, expression: exp.Pragma) -> str: 2601 return f"PRAGMA {self.sql(expression, 'this')}" 2602 2603 def lock_sql(self, expression: exp.Lock) -> str: 2604 if not self.LOCKING_READS_SUPPORTED: 2605 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2606 return "" 2607 2608 update = expression.args["update"] 2609 key = expression.args.get("key") 2610 if update: 2611 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2612 else: 2613 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2614 expressions = self.expressions(expression, flat=True) 2615 expressions = f" OF {expressions}" if expressions else "" 2616 wait = expression.args.get("wait") 2617 2618 if wait is not None: 2619 if isinstance(wait, exp.Literal): 2620 wait = f" WAIT {self.sql(wait)}" 2621 else: 2622 wait = " NOWAIT" if wait else " SKIP LOCKED" 2623 2624 return f"{lock_type}{expressions}{wait or ''}" 2625 2626 def literal_sql(self, expression: exp.Literal) -> str: 2627 text = expression.this or "" 2628 if expression.is_string: 2629 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2630 return text 2631 2632 def escape_str( 2633 self, 2634 text: str, 2635 escape_backslash: bool = True, 2636 delimiter: t.Optional[str] = None, 2637 escaped_delimiter: t.Optional[str] = None, 2638 is_byte_string: bool = False, 2639 ) -> str: 2640 if is_byte_string: 2641 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2642 else: 2643 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2644 2645 if supports_escape_sequences: 2646 text = "".join( 2647 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2648 for ch in text 2649 ) 2650 2651 delimiter = delimiter or self.dialect.QUOTE_END 2652 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2653 2654 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2655 2656 def loaddata_sql(self, expression: exp.LoadData) -> str: 2657 local = " LOCAL" if expression.args.get("local") else "" 2658 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2659 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2660 this = f" INTO TABLE {self.sql(expression, 'this')}" 2661 partition = self.sql(expression, "partition") 2662 partition = f" {partition}" if partition else "" 2663 input_format = self.sql(expression, "input_format") 2664 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2665 serde = self.sql(expression, "serde") 2666 serde = f" SERDE {serde}" if serde else "" 2667 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2668 2669 def null_sql(self, *_) -> str: 2670 return "NULL" 2671 2672 def boolean_sql(self, expression: exp.Boolean) -> str: 2673 return "TRUE" if expression.this else "FALSE" 2674 2675 def booland_sql(self, expression: exp.Booland) -> str: 2676 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 2677 2678 def boolor_sql(self, expression: exp.Boolor) -> str: 2679 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 2680 2681 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2682 this = self.sql(expression, "this") 2683 this = f"{this} " if this else this 2684 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2685 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2686 2687 def withfill_sql(self, expression: exp.WithFill) -> str: 2688 from_sql = self.sql(expression, "from_") 2689 from_sql = f" FROM {from_sql}" if from_sql else "" 2690 to_sql = self.sql(expression, "to") 2691 to_sql = f" TO {to_sql}" if to_sql else "" 2692 step_sql = self.sql(expression, "step") 2693 step_sql = f" STEP {step_sql}" if step_sql else "" 2694 interpolated_values = [ 2695 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2696 if isinstance(e, exp.Alias) 2697 else self.sql(e, "this") 2698 for e in expression.args.get("interpolate") or [] 2699 ] 2700 interpolate = ( 2701 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2702 ) 2703 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2704 2705 def cluster_sql(self, expression: exp.Cluster) -> str: 2706 return self.op_expressions("CLUSTER BY", expression) 2707 2708 def distribute_sql(self, expression: exp.Distribute) -> str: 2709 return self.op_expressions("DISTRIBUTE BY", expression) 2710 2711 def sort_sql(self, expression: exp.Sort) -> str: 2712 return self.op_expressions("SORT BY", expression) 2713 2714 def ordered_sql(self, expression: exp.Ordered) -> str: 2715 desc = expression.args.get("desc") 2716 asc = not desc 2717 2718 nulls_first = expression.args.get("nulls_first") 2719 nulls_last = not nulls_first 2720 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2721 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2722 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2723 2724 this = self.sql(expression, "this") 2725 2726 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2727 nulls_sort_change = "" 2728 if nulls_first and ( 2729 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2730 ): 2731 nulls_sort_change = " NULLS FIRST" 2732 elif ( 2733 nulls_last 2734 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2735 and not nulls_are_last 2736 ): 2737 nulls_sort_change = " NULLS LAST" 2738 2739 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2740 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2741 window = expression.find_ancestor(exp.Window, exp.Select) 2742 if isinstance(window, exp.Window) and window.args.get("spec"): 2743 self.unsupported( 2744 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2745 ) 2746 nulls_sort_change = "" 2747 elif self.NULL_ORDERING_SUPPORTED is False and ( 2748 (asc and nulls_sort_change == " NULLS LAST") 2749 or (desc and nulls_sort_change == " NULLS FIRST") 2750 ): 2751 # BigQuery does not allow these ordering/nulls combinations when used under 2752 # an aggregation func or under a window containing one 2753 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2754 2755 if isinstance(ancestor, exp.Window): 2756 ancestor = ancestor.this 2757 if isinstance(ancestor, exp.AggFunc): 2758 self.unsupported( 2759 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2760 ) 2761 nulls_sort_change = "" 2762 elif self.NULL_ORDERING_SUPPORTED is None: 2763 if expression.this.is_int: 2764 self.unsupported( 2765 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2766 ) 2767 elif not isinstance(expression.this, exp.Rand): 2768 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2769 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2770 nulls_sort_change = "" 2771 2772 with_fill = self.sql(expression, "with_fill") 2773 with_fill = f" {with_fill}" if with_fill else "" 2774 2775 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2776 2777 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2778 window_frame = self.sql(expression, "window_frame") 2779 window_frame = f"{window_frame} " if window_frame else "" 2780 2781 this = self.sql(expression, "this") 2782 2783 return f"{window_frame}{this}" 2784 2785 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2786 partition = self.partition_by_sql(expression) 2787 order = self.sql(expression, "order") 2788 measures = self.expressions(expression, key="measures") 2789 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2790 rows = self.sql(expression, "rows") 2791 rows = self.seg(rows) if rows else "" 2792 after = self.sql(expression, "after") 2793 after = self.seg(after) if after else "" 2794 pattern = self.sql(expression, "pattern") 2795 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2796 definition_sqls = [ 2797 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2798 for definition in expression.args.get("define", []) 2799 ] 2800 definitions = self.expressions(sqls=definition_sqls) 2801 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2802 body = "".join( 2803 ( 2804 partition, 2805 order, 2806 measures, 2807 rows, 2808 after, 2809 pattern, 2810 define, 2811 ) 2812 ) 2813 alias = self.sql(expression, "alias") 2814 alias = f" {alias}" if alias else "" 2815 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2816 2817 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2818 limit = expression.args.get("limit") 2819 2820 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2821 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2822 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2823 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2824 2825 return csv( 2826 *sqls, 2827 *[self.sql(join) for join in expression.args.get("joins") or []], 2828 self.sql(expression, "match"), 2829 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2830 self.sql(expression, "prewhere"), 2831 self.sql(expression, "where"), 2832 self.sql(expression, "connect"), 2833 self.sql(expression, "group"), 2834 self.sql(expression, "having"), 2835 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2836 self.sql(expression, "order"), 2837 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2838 *self.after_limit_modifiers(expression), 2839 self.options_modifier(expression), 2840 self.for_modifiers(expression), 2841 sep="", 2842 ) 2843 2844 def options_modifier(self, expression: exp.Expression) -> str: 2845 options = self.expressions(expression, key="options") 2846 return f" {options}" if options else "" 2847 2848 def for_modifiers(self, expression: exp.Expression) -> str: 2849 for_modifiers = self.expressions(expression, key="for_") 2850 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2851 2852 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2853 self.unsupported("Unsupported query option.") 2854 return "" 2855 2856 def offset_limit_modifiers( 2857 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2858 ) -> t.List[str]: 2859 return [ 2860 self.sql(expression, "offset") if fetch else self.sql(limit), 2861 self.sql(limit) if fetch else self.sql(expression, "offset"), 2862 ] 2863 2864 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2865 locks = self.expressions(expression, key="locks", sep=" ") 2866 locks = f" {locks}" if locks else "" 2867 return [locks, self.sql(expression, "sample")] 2868 2869 def select_sql(self, expression: exp.Select) -> str: 2870 into = expression.args.get("into") 2871 if not self.SUPPORTS_SELECT_INTO and into: 2872 into.pop() 2873 2874 hint = self.sql(expression, "hint") 2875 distinct = self.sql(expression, "distinct") 2876 distinct = f" {distinct}" if distinct else "" 2877 kind = self.sql(expression, "kind") 2878 2879 limit = expression.args.get("limit") 2880 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2881 top = self.limit_sql(limit, top=True) 2882 limit.pop() 2883 else: 2884 top = "" 2885 2886 expressions = self.expressions(expression) 2887 2888 if kind: 2889 if kind in self.SELECT_KINDS: 2890 kind = f" AS {kind}" 2891 else: 2892 if kind == "STRUCT": 2893 expressions = self.expressions( 2894 sqls=[ 2895 self.sql( 2896 exp.Struct( 2897 expressions=[ 2898 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2899 if isinstance(e, exp.Alias) 2900 else e 2901 for e in expression.expressions 2902 ] 2903 ) 2904 ) 2905 ] 2906 ) 2907 kind = "" 2908 2909 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2910 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2911 2912 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2913 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2914 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2915 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2916 sql = self.query_modifiers( 2917 expression, 2918 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2919 self.sql(expression, "into", comment=False), 2920 self.sql(expression, "from_", comment=False), 2921 ) 2922 2923 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2924 if expression.args.get("with_"): 2925 sql = self.maybe_comment(sql, expression) 2926 expression.pop_comments() 2927 2928 sql = self.prepend_ctes(expression, sql) 2929 2930 if not self.SUPPORTS_SELECT_INTO and into: 2931 if into.args.get("temporary"): 2932 table_kind = " TEMPORARY" 2933 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2934 table_kind = " UNLOGGED" 2935 else: 2936 table_kind = "" 2937 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2938 2939 return sql 2940 2941 def schema_sql(self, expression: exp.Schema) -> str: 2942 this = self.sql(expression, "this") 2943 sql = self.schema_columns_sql(expression) 2944 return f"{this} {sql}" if this and sql else this or sql 2945 2946 def schema_columns_sql(self, expression: exp.Schema) -> str: 2947 if expression.expressions: 2948 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2949 return "" 2950 2951 def star_sql(self, expression: exp.Star) -> str: 2952 except_ = self.expressions(expression, key="except_", flat=True) 2953 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2954 replace = self.expressions(expression, key="replace", flat=True) 2955 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2956 rename = self.expressions(expression, key="rename", flat=True) 2957 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2958 return f"*{except_}{replace}{rename}" 2959 2960 def parameter_sql(self, expression: exp.Parameter) -> str: 2961 this = self.sql(expression, "this") 2962 return f"{self.PARAMETER_TOKEN}{this}" 2963 2964 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2965 this = self.sql(expression, "this") 2966 kind = expression.text("kind") 2967 if kind: 2968 kind = f"{kind}." 2969 return f"@@{kind}{this}" 2970 2971 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2972 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2973 2974 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2975 alias = self.sql(expression, "alias") 2976 alias = f"{sep}{alias}" if alias else "" 2977 sample = self.sql(expression, "sample") 2978 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2979 alias = f"{sample}{alias}" 2980 2981 # Set to None so it's not generated again by self.query_modifiers() 2982 expression.set("sample", None) 2983 2984 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2985 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2986 return self.prepend_ctes(expression, sql) 2987 2988 def qualify_sql(self, expression: exp.Qualify) -> str: 2989 this = self.indent(self.sql(expression, "this")) 2990 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2991 2992 def unnest_sql(self, expression: exp.Unnest) -> str: 2993 args = self.expressions(expression, flat=True) 2994 2995 alias = expression.args.get("alias") 2996 offset = expression.args.get("offset") 2997 2998 if self.UNNEST_WITH_ORDINALITY: 2999 if alias and isinstance(offset, exp.Expression): 3000 alias.append("columns", offset) 3001 3002 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3003 columns = alias.columns 3004 alias = self.sql(columns[0]) if columns else "" 3005 else: 3006 alias = self.sql(alias) 3007 3008 alias = f" AS {alias}" if alias else alias 3009 if self.UNNEST_WITH_ORDINALITY: 3010 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3011 else: 3012 if isinstance(offset, exp.Expression): 3013 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3014 elif offset: 3015 suffix = f"{alias} WITH OFFSET" 3016 else: 3017 suffix = alias 3018 3019 return f"UNNEST({args}){suffix}" 3020 3021 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3022 return "" 3023 3024 def where_sql(self, expression: exp.Where) -> str: 3025 this = self.indent(self.sql(expression, "this")) 3026 return f"{self.seg('WHERE')}{self.sep()}{this}" 3027 3028 def window_sql(self, expression: exp.Window) -> str: 3029 this = self.sql(expression, "this") 3030 partition = self.partition_by_sql(expression) 3031 order = expression.args.get("order") 3032 order = self.order_sql(order, flat=True) if order else "" 3033 spec = self.sql(expression, "spec") 3034 alias = self.sql(expression, "alias") 3035 over = self.sql(expression, "over") or "OVER" 3036 3037 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3038 3039 first = expression.args.get("first") 3040 if first is None: 3041 first = "" 3042 else: 3043 first = "FIRST" if first else "LAST" 3044 3045 if not partition and not order and not spec and alias: 3046 return f"{this} {alias}" 3047 3048 args = self.format_args( 3049 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3050 ) 3051 return f"{this} ({args})" 3052 3053 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3054 partition = self.expressions(expression, key="partition_by", flat=True) 3055 return f"PARTITION BY {partition}" if partition else "" 3056 3057 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3058 kind = self.sql(expression, "kind") 3059 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3060 end = ( 3061 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3062 or "CURRENT ROW" 3063 ) 3064 3065 window_spec = f"{kind} BETWEEN {start} AND {end}" 3066 3067 exclude = self.sql(expression, "exclude") 3068 if exclude: 3069 if self.SUPPORTS_WINDOW_EXCLUDE: 3070 window_spec += f" EXCLUDE {exclude}" 3071 else: 3072 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3073 3074 return window_spec 3075 3076 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3077 this = self.sql(expression, "this") 3078 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3079 return f"{this} WITHIN GROUP ({expression_sql})" 3080 3081 def between_sql(self, expression: exp.Between) -> str: 3082 this = self.sql(expression, "this") 3083 low = self.sql(expression, "low") 3084 high = self.sql(expression, "high") 3085 symmetric = expression.args.get("symmetric") 3086 3087 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3088 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3089 3090 flag = ( 3091 " SYMMETRIC" 3092 if symmetric 3093 else " ASYMMETRIC" 3094 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3095 else "" # silently drop ASYMMETRIC – semantics identical 3096 ) 3097 return f"{this} BETWEEN{flag} {low} AND {high}" 3098 3099 def bracket_offset_expressions( 3100 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 3101 ) -> t.List[exp.Expression]: 3102 return apply_index_offset( 3103 expression.this, 3104 expression.expressions, 3105 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3106 dialect=self.dialect, 3107 ) 3108 3109 def bracket_sql(self, expression: exp.Bracket) -> str: 3110 expressions = self.bracket_offset_expressions(expression) 3111 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3112 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3113 3114 def all_sql(self, expression: exp.All) -> str: 3115 this = self.sql(expression, "this") 3116 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3117 this = self.wrap(this) 3118 return f"ALL {this}" 3119 3120 def any_sql(self, expression: exp.Any) -> str: 3121 this = self.sql(expression, "this") 3122 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3123 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3124 this = self.wrap(this) 3125 return f"ANY{this}" 3126 return f"ANY {this}" 3127 3128 def exists_sql(self, expression: exp.Exists) -> str: 3129 return f"EXISTS{self.wrap(expression)}" 3130 3131 def case_sql(self, expression: exp.Case) -> str: 3132 this = self.sql(expression, "this") 3133 statements = [f"CASE {this}" if this else "CASE"] 3134 3135 for e in expression.args["ifs"]: 3136 statements.append(f"WHEN {self.sql(e, 'this')}") 3137 statements.append(f"THEN {self.sql(e, 'true')}") 3138 3139 default = self.sql(expression, "default") 3140 3141 if default: 3142 statements.append(f"ELSE {default}") 3143 3144 statements.append("END") 3145 3146 if self.pretty and self.too_wide(statements): 3147 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3148 3149 return " ".join(statements) 3150 3151 def constraint_sql(self, expression: exp.Constraint) -> str: 3152 this = self.sql(expression, "this") 3153 expressions = self.expressions(expression, flat=True) 3154 return f"CONSTRAINT {this} {expressions}" 3155 3156 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3157 order = expression.args.get("order") 3158 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3159 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3160 3161 def extract_sql(self, expression: exp.Extract) -> str: 3162 from sqlglot.dialects.dialect import map_date_part 3163 3164 this = ( 3165 map_date_part(expression.this, self.dialect) 3166 if self.NORMALIZE_EXTRACT_DATE_PARTS 3167 else expression.this 3168 ) 3169 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3170 expression_sql = self.sql(expression, "expression") 3171 3172 return f"EXTRACT({this_sql} FROM {expression_sql})" 3173 3174 def trim_sql(self, expression: exp.Trim) -> str: 3175 trim_type = self.sql(expression, "position") 3176 3177 if trim_type == "LEADING": 3178 func_name = "LTRIM" 3179 elif trim_type == "TRAILING": 3180 func_name = "RTRIM" 3181 else: 3182 func_name = "TRIM" 3183 3184 return self.func(func_name, expression.this, expression.expression) 3185 3186 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3187 args = expression.expressions 3188 if isinstance(expression, exp.ConcatWs): 3189 args = args[1:] # Skip the delimiter 3190 3191 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3192 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3193 3194 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3195 3196 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3197 if not e.type: 3198 from sqlglot.optimizer.annotate_types import annotate_types 3199 3200 e = annotate_types(e, dialect=self.dialect) 3201 3202 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3203 return e 3204 3205 return exp.func("coalesce", e, exp.Literal.string("")) 3206 3207 args = [_wrap_with_coalesce(e) for e in args] 3208 3209 return args 3210 3211 def concat_sql(self, expression: exp.Concat) -> str: 3212 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3213 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3214 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3215 # instead of coalescing them to empty string. 3216 from sqlglot.dialects.dialect import concat_to_dpipe_sql 3217 3218 return concat_to_dpipe_sql(self, expression) 3219 3220 expressions = self.convert_concat_args(expression) 3221 3222 # Some dialects don't allow a single-argument CONCAT call 3223 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3224 return self.sql(expressions[0]) 3225 3226 return self.func("CONCAT", *expressions) 3227 3228 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3229 return self.func( 3230 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3231 ) 3232 3233 def check_sql(self, expression: exp.Check) -> str: 3234 this = self.sql(expression, key="this") 3235 return f"CHECK ({this})" 3236 3237 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3238 expressions = self.expressions(expression, flat=True) 3239 expressions = f" ({expressions})" if expressions else "" 3240 reference = self.sql(expression, "reference") 3241 reference = f" {reference}" if reference else "" 3242 delete = self.sql(expression, "delete") 3243 delete = f" ON DELETE {delete}" if delete else "" 3244 update = self.sql(expression, "update") 3245 update = f" ON UPDATE {update}" if update else "" 3246 options = self.expressions(expression, key="options", flat=True, sep=" ") 3247 options = f" {options}" if options else "" 3248 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3249 3250 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3251 this = self.sql(expression, "this") 3252 this = f" {this}" if this else "" 3253 expressions = self.expressions(expression, flat=True) 3254 include = self.sql(expression, "include") 3255 options = self.expressions(expression, key="options", flat=True, sep=" ") 3256 options = f" {options}" if options else "" 3257 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3258 3259 def if_sql(self, expression: exp.If) -> str: 3260 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3261 3262 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3263 if self.MATCH_AGAINST_TABLE_PREFIX: 3264 expressions = [] 3265 for expr in expression.expressions: 3266 if isinstance(expr, exp.Table): 3267 expressions.append(f"TABLE {self.sql(expr)}") 3268 else: 3269 expressions.append(expr) 3270 else: 3271 expressions = expression.expressions 3272 3273 modifier = expression.args.get("modifier") 3274 modifier = f" {modifier}" if modifier else "" 3275 return ( 3276 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3277 ) 3278 3279 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3280 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3281 3282 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3283 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3284 3285 if expression.args.get("escape"): 3286 path = self.escape_str(path) 3287 3288 if self.QUOTE_JSON_PATH: 3289 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3290 3291 return path 3292 3293 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3294 if isinstance(expression, exp.JSONPathPart): 3295 transform = self.TRANSFORMS.get(expression.__class__) 3296 if not callable(transform): 3297 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3298 return "" 3299 3300 return transform(self, expression) 3301 3302 if isinstance(expression, int): 3303 return str(expression) 3304 3305 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3306 escaped = expression.replace("'", "\\'") 3307 escaped = f"\\'{expression}\\'" 3308 else: 3309 escaped = expression.replace('"', '\\"') 3310 escaped = f'"{escaped}"' 3311 3312 return escaped 3313 3314 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3315 return f"{self.sql(expression, 'this')} FORMAT JSON" 3316 3317 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3318 # Output the Teradata column FORMAT override. 3319 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3320 this = self.sql(expression, "this") 3321 fmt = self.sql(expression, "format") 3322 return f"{this} (FORMAT {fmt})" 3323 3324 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3325 null_handling = expression.args.get("null_handling") 3326 null_handling = f" {null_handling}" if null_handling else "" 3327 3328 unique_keys = expression.args.get("unique_keys") 3329 if unique_keys is not None: 3330 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3331 else: 3332 unique_keys = "" 3333 3334 return_type = self.sql(expression, "return_type") 3335 return_type = f" RETURNING {return_type}" if return_type else "" 3336 encoding = self.sql(expression, "encoding") 3337 encoding = f" ENCODING {encoding}" if encoding else "" 3338 3339 return self.func( 3340 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3341 *expression.expressions, 3342 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3343 ) 3344 3345 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3346 return self.jsonobject_sql(expression) 3347 3348 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3349 null_handling = expression.args.get("null_handling") 3350 null_handling = f" {null_handling}" if null_handling else "" 3351 return_type = self.sql(expression, "return_type") 3352 return_type = f" RETURNING {return_type}" if return_type else "" 3353 strict = " STRICT" if expression.args.get("strict") else "" 3354 return self.func( 3355 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3356 ) 3357 3358 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3359 this = self.sql(expression, "this") 3360 order = self.sql(expression, "order") 3361 null_handling = expression.args.get("null_handling") 3362 null_handling = f" {null_handling}" if null_handling else "" 3363 return_type = self.sql(expression, "return_type") 3364 return_type = f" RETURNING {return_type}" if return_type else "" 3365 strict = " STRICT" if expression.args.get("strict") else "" 3366 return self.func( 3367 "JSON_ARRAYAGG", 3368 this, 3369 suffix=f"{order}{null_handling}{return_type}{strict})", 3370 ) 3371 3372 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3373 path = self.sql(expression, "path") 3374 path = f" PATH {path}" if path else "" 3375 nested_schema = self.sql(expression, "nested_schema") 3376 3377 if nested_schema: 3378 return f"NESTED{path} {nested_schema}" 3379 3380 this = self.sql(expression, "this") 3381 kind = self.sql(expression, "kind") 3382 kind = f" {kind}" if kind else "" 3383 3384 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3385 return f"{this}{kind}{path}{ordinality}" 3386 3387 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3388 return self.func("COLUMNS", *expression.expressions) 3389 3390 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3391 this = self.sql(expression, "this") 3392 path = self.sql(expression, "path") 3393 path = f", {path}" if path else "" 3394 error_handling = expression.args.get("error_handling") 3395 error_handling = f" {error_handling}" if error_handling else "" 3396 empty_handling = expression.args.get("empty_handling") 3397 empty_handling = f" {empty_handling}" if empty_handling else "" 3398 schema = self.sql(expression, "schema") 3399 return self.func( 3400 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3401 ) 3402 3403 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3404 this = self.sql(expression, "this") 3405 kind = self.sql(expression, "kind") 3406 path = self.sql(expression, "path") 3407 path = f" {path}" if path else "" 3408 as_json = " AS JSON" if expression.args.get("as_json") else "" 3409 return f"{this} {kind}{path}{as_json}" 3410 3411 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3412 this = self.sql(expression, "this") 3413 path = self.sql(expression, "path") 3414 path = f", {path}" if path else "" 3415 expressions = self.expressions(expression) 3416 with_ = ( 3417 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3418 if expressions 3419 else "" 3420 ) 3421 return f"OPENJSON({this}{path}){with_}" 3422 3423 def in_sql(self, expression: exp.In) -> str: 3424 query = expression.args.get("query") 3425 unnest = expression.args.get("unnest") 3426 field = expression.args.get("field") 3427 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3428 3429 if query: 3430 in_sql = self.sql(query) 3431 elif unnest: 3432 in_sql = self.in_unnest_op(unnest) 3433 elif field: 3434 in_sql = self.sql(field) 3435 else: 3436 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3437 3438 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3439 3440 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3441 return f"(SELECT {self.sql(unnest)})" 3442 3443 def interval_sql(self, expression: exp.Interval) -> str: 3444 unit_expression = expression.args.get("unit") 3445 unit = self.sql(unit_expression) if unit_expression else "" 3446 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3447 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3448 unit = f" {unit}" if unit else "" 3449 3450 if self.SINGLE_STRING_INTERVAL: 3451 this = expression.this.name if expression.this else "" 3452 if this: 3453 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3454 return f"INTERVAL '{this}'{unit}" 3455 return f"INTERVAL '{this}{unit}'" 3456 return f"INTERVAL{unit}" 3457 3458 this = self.sql(expression, "this") 3459 if this: 3460 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3461 this = f" {this}" if unwrapped else f" ({this})" 3462 3463 return f"INTERVAL{this}{unit}" 3464 3465 def return_sql(self, expression: exp.Return) -> str: 3466 return f"RETURN {self.sql(expression, 'this')}" 3467 3468 def reference_sql(self, expression: exp.Reference) -> str: 3469 this = self.sql(expression, "this") 3470 expressions = self.expressions(expression, flat=True) 3471 expressions = f"({expressions})" if expressions else "" 3472 options = self.expressions(expression, key="options", flat=True, sep=" ") 3473 options = f" {options}" if options else "" 3474 return f"REFERENCES {this}{expressions}{options}" 3475 3476 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3477 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3478 parent = expression.parent 3479 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3480 return self.func( 3481 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3482 ) 3483 3484 def paren_sql(self, expression: exp.Paren) -> str: 3485 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3486 return f"({sql}{self.seg(')', sep='')}" 3487 3488 def neg_sql(self, expression: exp.Neg) -> str: 3489 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3490 this_sql = self.sql(expression, "this") 3491 sep = " " if this_sql[0] == "-" else "" 3492 return f"-{sep}{this_sql}" 3493 3494 def not_sql(self, expression: exp.Not) -> str: 3495 return f"NOT {self.sql(expression, 'this')}" 3496 3497 def alias_sql(self, expression: exp.Alias) -> str: 3498 alias = self.sql(expression, "alias") 3499 alias = f" AS {alias}" if alias else "" 3500 return f"{self.sql(expression, 'this')}{alias}" 3501 3502 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3503 alias = expression.args["alias"] 3504 3505 parent = expression.parent 3506 pivot = parent and parent.parent 3507 3508 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3509 identifier_alias = isinstance(alias, exp.Identifier) 3510 literal_alias = isinstance(alias, exp.Literal) 3511 3512 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3513 alias.replace(exp.Literal.string(alias.output_name)) 3514 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3515 alias.replace(exp.to_identifier(alias.output_name)) 3516 3517 return self.alias_sql(expression) 3518 3519 def aliases_sql(self, expression: exp.Aliases) -> str: 3520 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3521 3522 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3523 this = self.sql(expression, "this") 3524 index = self.sql(expression, "expression") 3525 return f"{this} AT {index}" 3526 3527 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3528 this = self.sql(expression, "this") 3529 zone = self.sql(expression, "zone") 3530 return f"{this} AT TIME ZONE {zone}" 3531 3532 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3533 this = self.sql(expression, "this") 3534 zone = self.sql(expression, "zone") 3535 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3536 3537 def add_sql(self, expression: exp.Add) -> str: 3538 return self.binary(expression, "+") 3539 3540 def and_sql( 3541 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3542 ) -> str: 3543 return self.connector_sql(expression, "AND", stack) 3544 3545 def or_sql( 3546 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3547 ) -> str: 3548 return self.connector_sql(expression, "OR", stack) 3549 3550 def xor_sql( 3551 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3552 ) -> str: 3553 return self.connector_sql(expression, "XOR", stack) 3554 3555 def connector_sql( 3556 self, 3557 expression: exp.Connector, 3558 op: str, 3559 stack: t.Optional[t.List[str | exp.Expression]] = None, 3560 ) -> str: 3561 if stack is not None: 3562 if expression.expressions: 3563 stack.append(self.expressions(expression, sep=f" {op} ")) 3564 else: 3565 stack.append(expression.right) 3566 if expression.comments and self.comments: 3567 for comment in expression.comments: 3568 if comment: 3569 op += f" /*{self.sanitize_comment(comment)}*/" 3570 stack.extend((op, expression.left)) 3571 return op 3572 3573 stack = [expression] 3574 sqls: t.List[str] = [] 3575 ops = set() 3576 3577 while stack: 3578 node = stack.pop() 3579 if isinstance(node, exp.Connector): 3580 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3581 else: 3582 sql = self.sql(node) 3583 if sqls and sqls[-1] in ops: 3584 sqls[-1] += f" {sql}" 3585 else: 3586 sqls.append(sql) 3587 3588 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3589 return sep.join(sqls) 3590 3591 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3592 return self.binary(expression, "&") 3593 3594 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3595 return self.binary(expression, "<<") 3596 3597 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3598 return f"~{self.sql(expression, 'this')}" 3599 3600 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3601 return self.binary(expression, "|") 3602 3603 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3604 return self.binary(expression, ">>") 3605 3606 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3607 return self.binary(expression, "^") 3608 3609 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3610 format_sql = self.sql(expression, "format") 3611 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3612 to_sql = self.sql(expression, "to") 3613 to_sql = f" {to_sql}" if to_sql else "" 3614 action = self.sql(expression, "action") 3615 action = f" {action}" if action else "" 3616 default = self.sql(expression, "default") 3617 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3618 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3619 3620 # Base implementation that excludes safe, zone, and target_type metadata args 3621 def strtotime_sql(self, expression: exp.StrToTime) -> str: 3622 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 3623 3624 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3625 zone = self.sql(expression, "this") 3626 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3627 3628 def collate_sql(self, expression: exp.Collate) -> str: 3629 if self.COLLATE_IS_FUNC: 3630 return self.function_fallback_sql(expression) 3631 return self.binary(expression, "COLLATE") 3632 3633 def command_sql(self, expression: exp.Command) -> str: 3634 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3635 3636 def comment_sql(self, expression: exp.Comment) -> str: 3637 this = self.sql(expression, "this") 3638 kind = expression.args["kind"] 3639 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3640 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3641 expression_sql = self.sql(expression, "expression") 3642 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3643 3644 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3645 this = self.sql(expression, "this") 3646 delete = " DELETE" if expression.args.get("delete") else "" 3647 recompress = self.sql(expression, "recompress") 3648 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3649 to_disk = self.sql(expression, "to_disk") 3650 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3651 to_volume = self.sql(expression, "to_volume") 3652 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3653 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3654 3655 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3656 where = self.sql(expression, "where") 3657 group = self.sql(expression, "group") 3658 aggregates = self.expressions(expression, key="aggregates") 3659 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3660 3661 if not (where or group or aggregates) and len(expression.expressions) == 1: 3662 return f"TTL {self.expressions(expression, flat=True)}" 3663 3664 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3665 3666 def transaction_sql(self, expression: exp.Transaction) -> str: 3667 modes = self.expressions(expression, key="modes") 3668 modes = f" {modes}" if modes else "" 3669 return f"BEGIN{modes}" 3670 3671 def commit_sql(self, expression: exp.Commit) -> str: 3672 chain = expression.args.get("chain") 3673 if chain is not None: 3674 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3675 3676 return f"COMMIT{chain or ''}" 3677 3678 def rollback_sql(self, expression: exp.Rollback) -> str: 3679 savepoint = expression.args.get("savepoint") 3680 savepoint = f" TO {savepoint}" if savepoint else "" 3681 return f"ROLLBACK{savepoint}" 3682 3683 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3684 this = self.sql(expression, "this") 3685 3686 dtype = self.sql(expression, "dtype") 3687 if dtype: 3688 collate = self.sql(expression, "collate") 3689 collate = f" COLLATE {collate}" if collate else "" 3690 using = self.sql(expression, "using") 3691 using = f" USING {using}" if using else "" 3692 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3693 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3694 3695 default = self.sql(expression, "default") 3696 if default: 3697 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3698 3699 comment = self.sql(expression, "comment") 3700 if comment: 3701 return f"ALTER COLUMN {this} COMMENT {comment}" 3702 3703 visible = expression.args.get("visible") 3704 if visible: 3705 return f"ALTER COLUMN {this} SET {visible}" 3706 3707 allow_null = expression.args.get("allow_null") 3708 drop = expression.args.get("drop") 3709 3710 if not drop and not allow_null: 3711 self.unsupported("Unsupported ALTER COLUMN syntax") 3712 3713 if allow_null is not None: 3714 keyword = "DROP" if drop else "SET" 3715 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3716 3717 return f"ALTER COLUMN {this} DROP DEFAULT" 3718 3719 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3720 this = self.sql(expression, "this") 3721 3722 visible = expression.args.get("visible") 3723 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3724 3725 return f"ALTER INDEX {this} {visible_sql}" 3726 3727 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3728 this = self.sql(expression, "this") 3729 if not isinstance(expression.this, exp.Var): 3730 this = f"KEY DISTKEY {this}" 3731 return f"ALTER DISTSTYLE {this}" 3732 3733 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3734 compound = " COMPOUND" if expression.args.get("compound") else "" 3735 this = self.sql(expression, "this") 3736 expressions = self.expressions(expression, flat=True) 3737 expressions = f"({expressions})" if expressions else "" 3738 return f"ALTER{compound} SORTKEY {this or expressions}" 3739 3740 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3741 if not self.RENAME_TABLE_WITH_DB: 3742 # Remove db from tables 3743 expression = expression.transform( 3744 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3745 ).assert_is(exp.AlterRename) 3746 this = self.sql(expression, "this") 3747 to_kw = " TO" if include_to else "" 3748 return f"RENAME{to_kw} {this}" 3749 3750 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3751 exists = " IF EXISTS" if expression.args.get("exists") else "" 3752 old_column = self.sql(expression, "this") 3753 new_column = self.sql(expression, "to") 3754 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3755 3756 def alterset_sql(self, expression: exp.AlterSet) -> str: 3757 exprs = self.expressions(expression, flat=True) 3758 if self.ALTER_SET_WRAPPED: 3759 exprs = f"({exprs})" 3760 3761 return f"SET {exprs}" 3762 3763 def alter_sql(self, expression: exp.Alter) -> str: 3764 actions = expression.args["actions"] 3765 3766 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3767 actions[0], exp.ColumnDef 3768 ): 3769 actions_sql = self.expressions(expression, key="actions", flat=True) 3770 actions_sql = f"ADD {actions_sql}" 3771 else: 3772 actions_list = [] 3773 for action in actions: 3774 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3775 action_sql = self.add_column_sql(action) 3776 else: 3777 action_sql = self.sql(action) 3778 if isinstance(action, exp.Query): 3779 action_sql = f"AS {action_sql}" 3780 3781 actions_list.append(action_sql) 3782 3783 actions_sql = self.format_args(*actions_list).lstrip("\n") 3784 3785 exists = " IF EXISTS" if expression.args.get("exists") else "" 3786 on_cluster = self.sql(expression, "cluster") 3787 on_cluster = f" {on_cluster}" if on_cluster else "" 3788 only = " ONLY" if expression.args.get("only") else "" 3789 options = self.expressions(expression, key="options") 3790 options = f", {options}" if options else "" 3791 kind = self.sql(expression, "kind") 3792 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3793 check = " WITH CHECK" if expression.args.get("check") else "" 3794 cascade = ( 3795 " CASCADE" 3796 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 3797 else "" 3798 ) 3799 this = self.sql(expression, "this") 3800 this = f" {this}" if this else "" 3801 3802 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 3803 3804 def altersession_sql(self, expression: exp.AlterSession) -> str: 3805 items_sql = self.expressions(expression, flat=True) 3806 keyword = "UNSET" if expression.args.get("unset") else "SET" 3807 return f"{keyword} {items_sql}" 3808 3809 def add_column_sql(self, expression: exp.Expression) -> str: 3810 sql = self.sql(expression) 3811 if isinstance(expression, exp.Schema): 3812 column_text = " COLUMNS" 3813 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3814 column_text = " COLUMN" 3815 else: 3816 column_text = "" 3817 3818 return f"ADD{column_text} {sql}" 3819 3820 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3821 expressions = self.expressions(expression) 3822 exists = " IF EXISTS " if expression.args.get("exists") else " " 3823 return f"DROP{exists}{expressions}" 3824 3825 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3826 return f"ADD {self.expressions(expression, indent=False)}" 3827 3828 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3829 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3830 location = self.sql(expression, "location") 3831 location = f" {location}" if location else "" 3832 return f"ADD {exists}{self.sql(expression.this)}{location}" 3833 3834 def distinct_sql(self, expression: exp.Distinct) -> str: 3835 this = self.expressions(expression, flat=True) 3836 3837 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3838 case = exp.case() 3839 for arg in expression.expressions: 3840 case = case.when(arg.is_(exp.null()), exp.null()) 3841 this = self.sql(case.else_(f"({this})")) 3842 3843 this = f" {this}" if this else "" 3844 3845 on = self.sql(expression, "on") 3846 on = f" ON {on}" if on else "" 3847 return f"DISTINCT{this}{on}" 3848 3849 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3850 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3851 3852 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3853 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3854 3855 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3856 this_sql = self.sql(expression, "this") 3857 expression_sql = self.sql(expression, "expression") 3858 kind = "MAX" if expression.args.get("max") else "MIN" 3859 return f"{this_sql} HAVING {kind} {expression_sql}" 3860 3861 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3862 return self.sql( 3863 exp.Cast( 3864 this=exp.Div(this=expression.this, expression=expression.expression), 3865 to=exp.DataType(this=exp.DataType.Type.INT), 3866 ) 3867 ) 3868 3869 def dpipe_sql(self, expression: exp.DPipe) -> str: 3870 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3871 return self.func( 3872 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3873 ) 3874 return self.binary(expression, "||") 3875 3876 def div_sql(self, expression: exp.Div) -> str: 3877 l, r = expression.left, expression.right 3878 3879 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3880 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3881 3882 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3883 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3884 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3885 3886 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3887 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3888 return self.sql( 3889 exp.cast( 3890 l / r, 3891 to=exp.DataType.Type.BIGINT, 3892 ) 3893 ) 3894 3895 return self.binary(expression, "/") 3896 3897 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3898 n = exp._wrap(expression.this, exp.Binary) 3899 d = exp._wrap(expression.expression, exp.Binary) 3900 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3901 3902 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3903 return self.binary(expression, "OVERLAPS") 3904 3905 def distance_sql(self, expression: exp.Distance) -> str: 3906 return self.binary(expression, "<->") 3907 3908 def dot_sql(self, expression: exp.Dot) -> str: 3909 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3910 3911 def eq_sql(self, expression: exp.EQ) -> str: 3912 return self.binary(expression, "=") 3913 3914 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3915 return self.binary(expression, ":=") 3916 3917 def escape_sql(self, expression: exp.Escape) -> str: 3918 return self.binary(expression, "ESCAPE") 3919 3920 def glob_sql(self, expression: exp.Glob) -> str: 3921 return self.binary(expression, "GLOB") 3922 3923 def gt_sql(self, expression: exp.GT) -> str: 3924 return self.binary(expression, ">") 3925 3926 def gte_sql(self, expression: exp.GTE) -> str: 3927 return self.binary(expression, ">=") 3928 3929 def is_sql(self, expression: exp.Is) -> str: 3930 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3931 return self.sql( 3932 expression.this if expression.expression.this else exp.not_(expression.this) 3933 ) 3934 return self.binary(expression, "IS") 3935 3936 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3937 this = expression.this 3938 rhs = expression.expression 3939 3940 if isinstance(expression, exp.Like): 3941 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3942 op = "LIKE" 3943 else: 3944 exp_class = exp.ILike 3945 op = "ILIKE" 3946 3947 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3948 exprs = rhs.this.unnest() 3949 3950 if isinstance(exprs, exp.Tuple): 3951 exprs = exprs.expressions 3952 3953 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3954 3955 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3956 for expr in exprs[1:]: 3957 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3958 3959 parent = expression.parent 3960 if not isinstance(parent, type(like_expr)) and isinstance(parent, exp.Condition): 3961 like_expr = exp.paren(like_expr, copy=False) 3962 3963 return self.sql(like_expr) 3964 3965 return self.binary(expression, op) 3966 3967 def like_sql(self, expression: exp.Like) -> str: 3968 return self._like_sql(expression) 3969 3970 def ilike_sql(self, expression: exp.ILike) -> str: 3971 return self._like_sql(expression) 3972 3973 def match_sql(self, expression: exp.Match) -> str: 3974 return self.binary(expression, "MATCH") 3975 3976 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3977 return self.binary(expression, "SIMILAR TO") 3978 3979 def lt_sql(self, expression: exp.LT) -> str: 3980 return self.binary(expression, "<") 3981 3982 def lte_sql(self, expression: exp.LTE) -> str: 3983 return self.binary(expression, "<=") 3984 3985 def mod_sql(self, expression: exp.Mod) -> str: 3986 return self.binary(expression, "%") 3987 3988 def mul_sql(self, expression: exp.Mul) -> str: 3989 return self.binary(expression, "*") 3990 3991 def neq_sql(self, expression: exp.NEQ) -> str: 3992 return self.binary(expression, "<>") 3993 3994 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3995 return self.binary(expression, "IS NOT DISTINCT FROM") 3996 3997 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3998 return self.binary(expression, "IS DISTINCT FROM") 3999 4000 def sub_sql(self, expression: exp.Sub) -> str: 4001 return self.binary(expression, "-") 4002 4003 def trycast_sql(self, expression: exp.TryCast) -> str: 4004 return self.cast_sql(expression, safe_prefix="TRY_") 4005 4006 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4007 return self.cast_sql(expression) 4008 4009 def try_sql(self, expression: exp.Try) -> str: 4010 if not self.TRY_SUPPORTED: 4011 self.unsupported("Unsupported TRY function") 4012 return self.sql(expression, "this") 4013 4014 return self.func("TRY", expression.this) 4015 4016 def log_sql(self, expression: exp.Log) -> str: 4017 this = expression.this 4018 expr = expression.expression 4019 4020 if self.dialect.LOG_BASE_FIRST is False: 4021 this, expr = expr, this 4022 elif self.dialect.LOG_BASE_FIRST is None and expr: 4023 if this.name in ("2", "10"): 4024 return self.func(f"LOG{this.name}", expr) 4025 4026 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4027 4028 return self.func("LOG", this, expr) 4029 4030 def use_sql(self, expression: exp.Use) -> str: 4031 kind = self.sql(expression, "kind") 4032 kind = f" {kind}" if kind else "" 4033 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4034 this = f" {this}" if this else "" 4035 return f"USE{kind}{this}" 4036 4037 def binary(self, expression: exp.Binary, op: str) -> str: 4038 sqls: t.List[str] = [] 4039 stack: t.List[t.Union[str, exp.Expression]] = [expression] 4040 binary_type = type(expression) 4041 4042 while stack: 4043 node = stack.pop() 4044 4045 if type(node) is binary_type: 4046 op_func = node.args.get("operator") 4047 if op_func: 4048 op = f"OPERATOR({self.sql(op_func)})" 4049 4050 stack.append(node.right) 4051 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4052 stack.append(node.left) 4053 else: 4054 sqls.append(self.sql(node)) 4055 4056 return "".join(sqls) 4057 4058 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4059 to_clause = self.sql(expression, "to") 4060 if to_clause: 4061 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4062 4063 return self.function_fallback_sql(expression) 4064 4065 def function_fallback_sql(self, expression: exp.Func) -> str: 4066 args = [] 4067 4068 for key in expression.arg_types: 4069 arg_value = expression.args.get(key) 4070 4071 if isinstance(arg_value, list): 4072 for value in arg_value: 4073 args.append(value) 4074 elif arg_value is not None: 4075 args.append(arg_value) 4076 4077 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4078 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4079 else: 4080 name = expression.sql_name() 4081 4082 return self.func(name, *args) 4083 4084 def func( 4085 self, 4086 name: str, 4087 *args: t.Optional[exp.Expression | str], 4088 prefix: str = "(", 4089 suffix: str = ")", 4090 normalize: bool = True, 4091 ) -> str: 4092 name = self.normalize_func(name) if normalize else name 4093 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4094 4095 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 4096 arg_sqls = tuple( 4097 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4098 ) 4099 if self.pretty and self.too_wide(arg_sqls): 4100 return self.indent( 4101 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4102 ) 4103 return sep.join(arg_sqls) 4104 4105 def too_wide(self, args: t.Iterable) -> bool: 4106 return sum(len(arg) for arg in args) > self.max_text_width 4107 4108 def format_time( 4109 self, 4110 expression: exp.Expression, 4111 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 4112 inverse_time_trie: t.Optional[t.Dict] = None, 4113 ) -> t.Optional[str]: 4114 return format_time( 4115 self.sql(expression, "format"), 4116 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4117 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4118 ) 4119 4120 def expressions( 4121 self, 4122 expression: t.Optional[exp.Expression] = None, 4123 key: t.Optional[str] = None, 4124 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 4125 flat: bool = False, 4126 indent: bool = True, 4127 skip_first: bool = False, 4128 skip_last: bool = False, 4129 sep: str = ", ", 4130 prefix: str = "", 4131 dynamic: bool = False, 4132 new_line: bool = False, 4133 ) -> str: 4134 expressions = expression.args.get(key or "expressions") if expression else sqls 4135 4136 if not expressions: 4137 return "" 4138 4139 if flat: 4140 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4141 4142 num_sqls = len(expressions) 4143 result_sqls = [] 4144 4145 for i, e in enumerate(expressions): 4146 sql = self.sql(e, comment=False) 4147 if not sql: 4148 continue 4149 4150 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 4151 4152 if self.pretty: 4153 if self.leading_comma: 4154 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4155 else: 4156 result_sqls.append( 4157 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4158 ) 4159 else: 4160 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4161 4162 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4163 if new_line: 4164 result_sqls.insert(0, "") 4165 result_sqls.append("") 4166 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4167 else: 4168 result_sql = "".join(result_sqls) 4169 4170 return ( 4171 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4172 if indent 4173 else result_sql 4174 ) 4175 4176 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 4177 flat = flat or isinstance(expression.parent, exp.Properties) 4178 expressions_sql = self.expressions(expression, flat=flat) 4179 if flat: 4180 return f"{op} {expressions_sql}" 4181 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4182 4183 def naked_property(self, expression: exp.Property) -> str: 4184 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4185 if not property_name: 4186 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4187 return f"{property_name} {self.sql(expression, 'this')}" 4188 4189 def tag_sql(self, expression: exp.Tag) -> str: 4190 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4191 4192 def token_sql(self, token_type: TokenType) -> str: 4193 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4194 4195 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4196 this = self.sql(expression, "this") 4197 expressions = self.no_identify(self.expressions, expression) 4198 expressions = ( 4199 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4200 ) 4201 return f"{this}{expressions}" if expressions.strip() != "" else this 4202 4203 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4204 this = self.sql(expression, "this") 4205 expressions = self.expressions(expression, flat=True) 4206 return f"{this}({expressions})" 4207 4208 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4209 return self.binary(expression, "=>") 4210 4211 def when_sql(self, expression: exp.When) -> str: 4212 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4213 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4214 condition = self.sql(expression, "condition") 4215 condition = f" AND {condition}" if condition else "" 4216 4217 then_expression = expression.args.get("then") 4218 if isinstance(then_expression, exp.Insert): 4219 this = self.sql(then_expression, "this") 4220 this = f"INSERT {this}" if this else "INSERT" 4221 then = self.sql(then_expression, "expression") 4222 then = f"{this} VALUES {then}" if then else this 4223 elif isinstance(then_expression, exp.Update): 4224 if isinstance(then_expression.args.get("expressions"), exp.Star): 4225 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4226 else: 4227 expressions_sql = self.expressions(then_expression) 4228 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4229 4230 else: 4231 then = self.sql(then_expression) 4232 return f"WHEN {matched}{source}{condition} THEN {then}" 4233 4234 def whens_sql(self, expression: exp.Whens) -> str: 4235 return self.expressions(expression, sep=" ", indent=False) 4236 4237 def merge_sql(self, expression: exp.Merge) -> str: 4238 table = expression.this 4239 table_alias = "" 4240 4241 hints = table.args.get("hints") 4242 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4243 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4244 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4245 4246 this = self.sql(table) 4247 using = f"USING {self.sql(expression, 'using')}" 4248 whens = self.sql(expression, "whens") 4249 4250 on = self.sql(expression, "on") 4251 on = f"ON {on}" if on else "" 4252 4253 if not on: 4254 on = self.expressions(expression, key="using_cond") 4255 on = f"USING ({on})" if on else "" 4256 4257 returning = self.sql(expression, "returning") 4258 if returning: 4259 whens = f"{whens}{returning}" 4260 4261 sep = self.sep() 4262 4263 return self.prepend_ctes( 4264 expression, 4265 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4266 ) 4267 4268 @unsupported_args("format") 4269 def tochar_sql(self, expression: exp.ToChar) -> str: 4270 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4271 4272 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4273 if not self.SUPPORTS_TO_NUMBER: 4274 self.unsupported("Unsupported TO_NUMBER function") 4275 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4276 4277 fmt = expression.args.get("format") 4278 if not fmt: 4279 self.unsupported("Conversion format is required for TO_NUMBER") 4280 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4281 4282 return self.func("TO_NUMBER", expression.this, fmt) 4283 4284 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4285 this = self.sql(expression, "this") 4286 kind = self.sql(expression, "kind") 4287 settings_sql = self.expressions(expression, key="settings", sep=" ") 4288 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4289 return f"{this}({kind}{args})" 4290 4291 def dictrange_sql(self, expression: exp.DictRange) -> str: 4292 this = self.sql(expression, "this") 4293 max = self.sql(expression, "max") 4294 min = self.sql(expression, "min") 4295 return f"{this}(MIN {min} MAX {max})" 4296 4297 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4298 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4299 4300 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4301 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4302 4303 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4304 def uniquekeyproperty_sql( 4305 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4306 ) -> str: 4307 return f"{prefix} ({self.expressions(expression, flat=True)})" 4308 4309 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4310 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4311 expressions = self.expressions(expression, flat=True) 4312 expressions = f" {self.wrap(expressions)}" if expressions else "" 4313 buckets = self.sql(expression, "buckets") 4314 kind = self.sql(expression, "kind") 4315 buckets = f" BUCKETS {buckets}" if buckets else "" 4316 order = self.sql(expression, "order") 4317 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4318 4319 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4320 return "" 4321 4322 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4323 expressions = self.expressions(expression, key="expressions", flat=True) 4324 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4325 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4326 buckets = self.sql(expression, "buckets") 4327 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4328 4329 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4330 this = self.sql(expression, "this") 4331 having = self.sql(expression, "having") 4332 4333 if having: 4334 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4335 4336 return self.func("ANY_VALUE", this) 4337 4338 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4339 transform = self.func("TRANSFORM", *expression.expressions) 4340 row_format_before = self.sql(expression, "row_format_before") 4341 row_format_before = f" {row_format_before}" if row_format_before else "" 4342 record_writer = self.sql(expression, "record_writer") 4343 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4344 using = f" USING {self.sql(expression, 'command_script')}" 4345 schema = self.sql(expression, "schema") 4346 schema = f" AS {schema}" if schema else "" 4347 row_format_after = self.sql(expression, "row_format_after") 4348 row_format_after = f" {row_format_after}" if row_format_after else "" 4349 record_reader = self.sql(expression, "record_reader") 4350 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4351 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4352 4353 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4354 key_block_size = self.sql(expression, "key_block_size") 4355 if key_block_size: 4356 return f"KEY_BLOCK_SIZE = {key_block_size}" 4357 4358 using = self.sql(expression, "using") 4359 if using: 4360 return f"USING {using}" 4361 4362 parser = self.sql(expression, "parser") 4363 if parser: 4364 return f"WITH PARSER {parser}" 4365 4366 comment = self.sql(expression, "comment") 4367 if comment: 4368 return f"COMMENT {comment}" 4369 4370 visible = expression.args.get("visible") 4371 if visible is not None: 4372 return "VISIBLE" if visible else "INVISIBLE" 4373 4374 engine_attr = self.sql(expression, "engine_attr") 4375 if engine_attr: 4376 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4377 4378 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4379 if secondary_engine_attr: 4380 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4381 4382 self.unsupported("Unsupported index constraint option.") 4383 return "" 4384 4385 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4386 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4387 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4388 4389 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4390 kind = self.sql(expression, "kind") 4391 kind = f"{kind} INDEX" if kind else "INDEX" 4392 this = self.sql(expression, "this") 4393 this = f" {this}" if this else "" 4394 index_type = self.sql(expression, "index_type") 4395 index_type = f" USING {index_type}" if index_type else "" 4396 expressions = self.expressions(expression, flat=True) 4397 expressions = f" ({expressions})" if expressions else "" 4398 options = self.expressions(expression, key="options", sep=" ") 4399 options = f" {options}" if options else "" 4400 return f"{kind}{this}{index_type}{expressions}{options}" 4401 4402 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4403 if self.NVL2_SUPPORTED: 4404 return self.function_fallback_sql(expression) 4405 4406 case = exp.Case().when( 4407 expression.this.is_(exp.null()).not_(copy=False), 4408 expression.args["true"], 4409 copy=False, 4410 ) 4411 else_cond = expression.args.get("false") 4412 if else_cond: 4413 case.else_(else_cond, copy=False) 4414 4415 return self.sql(case) 4416 4417 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4418 this = self.sql(expression, "this") 4419 expr = self.sql(expression, "expression") 4420 position = self.sql(expression, "position") 4421 position = f", {position}" if position else "" 4422 iterator = self.sql(expression, "iterator") 4423 condition = self.sql(expression, "condition") 4424 condition = f" IF {condition}" if condition else "" 4425 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4426 4427 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4428 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4429 4430 def opclass_sql(self, expression: exp.Opclass) -> str: 4431 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4432 4433 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4434 model = self.sql(expression, "this") 4435 model = f"MODEL {model}" 4436 expr = expression.expression 4437 if expr: 4438 expr_sql = self.sql(expression, "expression") 4439 expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql 4440 else: 4441 expr_sql = None 4442 4443 parameters = self.sql(expression, "params_struct") or None 4444 4445 return self.func(name, model, expr_sql, parameters) 4446 4447 def predict_sql(self, expression: exp.Predict) -> str: 4448 return self._ml_sql(expression, "PREDICT") 4449 4450 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4451 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4452 return self._ml_sql(expression, name) 4453 4454 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4455 return self._ml_sql(expression, "TRANSLATE") 4456 4457 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4458 return self._ml_sql(expression, "FORECAST") 4459 4460 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4461 this_sql = self.sql(expression, "this") 4462 if isinstance(expression.this, exp.Table): 4463 this_sql = f"TABLE {this_sql}" 4464 4465 return self.func( 4466 "FEATURES_AT_TIME", 4467 this_sql, 4468 expression.args.get("time"), 4469 expression.args.get("num_rows"), 4470 expression.args.get("ignore_feature_nulls"), 4471 ) 4472 4473 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4474 this_sql = self.sql(expression, "this") 4475 if isinstance(expression.this, exp.Table): 4476 this_sql = f"TABLE {this_sql}" 4477 4478 query_table = self.sql(expression, "query_table") 4479 if isinstance(expression.args["query_table"], exp.Table): 4480 query_table = f"TABLE {query_table}" 4481 4482 return self.func( 4483 "VECTOR_SEARCH", 4484 this_sql, 4485 expression.args.get("column_to_search"), 4486 query_table, 4487 expression.args.get("query_column_to_search"), 4488 expression.args.get("top_k"), 4489 expression.args.get("distance_type"), 4490 expression.args.get("options"), 4491 ) 4492 4493 def forin_sql(self, expression: exp.ForIn) -> str: 4494 this = self.sql(expression, "this") 4495 expression_sql = self.sql(expression, "expression") 4496 return f"FOR {this} DO {expression_sql}" 4497 4498 def refresh_sql(self, expression: exp.Refresh) -> str: 4499 this = self.sql(expression, "this") 4500 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 4501 return f"REFRESH {kind}{this}" 4502 4503 def toarray_sql(self, expression: exp.ToArray) -> str: 4504 arg = expression.this 4505 if not arg.type: 4506 from sqlglot.optimizer.annotate_types import annotate_types 4507 4508 arg = annotate_types(arg, dialect=self.dialect) 4509 4510 if arg.is_type(exp.DataType.Type.ARRAY): 4511 return self.sql(arg) 4512 4513 cond_for_null = arg.is_(exp.null()) 4514 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4515 4516 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4517 this = expression.this 4518 time_format = self.format_time(expression) 4519 4520 if time_format: 4521 return self.sql( 4522 exp.cast( 4523 exp.StrToTime(this=this, format=expression.args["format"]), 4524 exp.DataType.Type.TIME, 4525 ) 4526 ) 4527 4528 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4529 return self.sql(this) 4530 4531 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4532 4533 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4534 this = expression.this 4535 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4536 return self.sql(this) 4537 4538 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4539 4540 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4541 this = expression.this 4542 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4543 return self.sql(this) 4544 4545 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4546 4547 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4548 this = expression.this 4549 time_format = self.format_time(expression) 4550 safe = expression.args.get("safe") 4551 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4552 return self.sql( 4553 exp.cast( 4554 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4555 exp.DataType.Type.DATE, 4556 ) 4557 ) 4558 4559 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4560 return self.sql(this) 4561 4562 if safe: 4563 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DataType.Type.DATE))) 4564 4565 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4566 4567 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4568 return self.sql( 4569 exp.func( 4570 "DATEDIFF", 4571 expression.this, 4572 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4573 "day", 4574 ) 4575 ) 4576 4577 def lastday_sql(self, expression: exp.LastDay) -> str: 4578 if self.LAST_DAY_SUPPORTS_DATE_PART: 4579 return self.function_fallback_sql(expression) 4580 4581 unit = expression.text("unit") 4582 if unit and unit != "MONTH": 4583 self.unsupported("Date parts are not supported in LAST_DAY.") 4584 4585 return self.func("LAST_DAY", expression.this) 4586 4587 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4588 from sqlglot.dialects.dialect import unit_to_str 4589 4590 return self.func( 4591 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4592 ) 4593 4594 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4595 if self.CAN_IMPLEMENT_ARRAY_ANY: 4596 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4597 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4598 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4599 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4600 4601 from sqlglot.dialects import Dialect 4602 4603 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4604 if self.dialect.__class__ != Dialect: 4605 self.unsupported("ARRAY_ANY is unsupported") 4606 4607 return self.function_fallback_sql(expression) 4608 4609 def struct_sql(self, expression: exp.Struct) -> str: 4610 expression.set( 4611 "expressions", 4612 [ 4613 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4614 if isinstance(e, exp.PropertyEQ) 4615 else e 4616 for e in expression.expressions 4617 ], 4618 ) 4619 4620 return self.function_fallback_sql(expression) 4621 4622 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4623 low = self.sql(expression, "this") 4624 high = self.sql(expression, "expression") 4625 4626 return f"{low} TO {high}" 4627 4628 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4629 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4630 tables = f" {self.expressions(expression)}" 4631 4632 exists = " IF EXISTS" if expression.args.get("exists") else "" 4633 4634 on_cluster = self.sql(expression, "cluster") 4635 on_cluster = f" {on_cluster}" if on_cluster else "" 4636 4637 identity = self.sql(expression, "identity") 4638 identity = f" {identity} IDENTITY" if identity else "" 4639 4640 option = self.sql(expression, "option") 4641 option = f" {option}" if option else "" 4642 4643 partition = self.sql(expression, "partition") 4644 partition = f" {partition}" if partition else "" 4645 4646 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4647 4648 # This transpiles T-SQL's CONVERT function 4649 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4650 def convert_sql(self, expression: exp.Convert) -> str: 4651 to = expression.this 4652 value = expression.expression 4653 style = expression.args.get("style") 4654 safe = expression.args.get("safe") 4655 strict = expression.args.get("strict") 4656 4657 if not to or not value: 4658 return "" 4659 4660 # Retrieve length of datatype and override to default if not specified 4661 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4662 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4663 4664 transformed: t.Optional[exp.Expression] = None 4665 cast = exp.Cast if strict else exp.TryCast 4666 4667 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4668 if isinstance(style, exp.Literal) and style.is_int: 4669 from sqlglot.dialects.tsql import TSQL 4670 4671 style_value = style.name 4672 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4673 if not converted_style: 4674 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4675 4676 fmt = exp.Literal.string(converted_style) 4677 4678 if to.this == exp.DataType.Type.DATE: 4679 transformed = exp.StrToDate(this=value, format=fmt) 4680 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4681 transformed = exp.StrToTime(this=value, format=fmt) 4682 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4683 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4684 elif to.this == exp.DataType.Type.TEXT: 4685 transformed = exp.TimeToStr(this=value, format=fmt) 4686 4687 if not transformed: 4688 transformed = cast(this=value, to=to, safe=safe) 4689 4690 return self.sql(transformed) 4691 4692 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4693 this = expression.this 4694 if isinstance(this, exp.JSONPathWildcard): 4695 this = self.json_path_part(this) 4696 return f".{this}" if this else "" 4697 4698 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4699 return f".{this}" 4700 4701 this = self.json_path_part(this) 4702 return ( 4703 f"[{this}]" 4704 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4705 else f".{this}" 4706 ) 4707 4708 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4709 this = self.json_path_part(expression.this) 4710 return f"[{this}]" if this else "" 4711 4712 def _simplify_unless_literal(self, expression: E) -> E: 4713 if not isinstance(expression, exp.Literal): 4714 from sqlglot.optimizer.simplify import simplify 4715 4716 expression = simplify(expression, dialect=self.dialect) 4717 4718 return expression 4719 4720 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4721 this = expression.this 4722 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4723 self.unsupported( 4724 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4725 ) 4726 return self.sql(this) 4727 4728 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4729 # The first modifier here will be the one closest to the AggFunc's arg 4730 mods = sorted( 4731 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4732 key=lambda x: 0 4733 if isinstance(x, exp.HavingMax) 4734 else (1 if isinstance(x, exp.Order) else 2), 4735 ) 4736 4737 if mods: 4738 mod = mods[0] 4739 this = expression.__class__(this=mod.this.copy()) 4740 this.meta["inline"] = True 4741 mod.this.replace(this) 4742 return self.sql(expression.this) 4743 4744 agg_func = expression.find(exp.AggFunc) 4745 4746 if agg_func: 4747 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4748 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4749 4750 return f"{self.sql(expression, 'this')} {text}" 4751 4752 def _replace_line_breaks(self, string: str) -> str: 4753 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4754 if self.pretty: 4755 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4756 return string 4757 4758 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4759 option = self.sql(expression, "this") 4760 4761 if expression.expressions: 4762 upper = option.upper() 4763 4764 # Snowflake FILE_FORMAT options are separated by whitespace 4765 sep = " " if upper == "FILE_FORMAT" else ", " 4766 4767 # Databricks copy/format options do not set their list of values with EQ 4768 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4769 values = self.expressions(expression, flat=True, sep=sep) 4770 return f"{option}{op}({values})" 4771 4772 value = self.sql(expression, "expression") 4773 4774 if not value: 4775 return option 4776 4777 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4778 4779 return f"{option}{op}{value}" 4780 4781 def credentials_sql(self, expression: exp.Credentials) -> str: 4782 cred_expr = expression.args.get("credentials") 4783 if isinstance(cred_expr, exp.Literal): 4784 # Redshift case: CREDENTIALS <string> 4785 credentials = self.sql(expression, "credentials") 4786 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4787 else: 4788 # Snowflake case: CREDENTIALS = (...) 4789 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4790 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4791 4792 storage = self.sql(expression, "storage") 4793 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4794 4795 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4796 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4797 4798 iam_role = self.sql(expression, "iam_role") 4799 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4800 4801 region = self.sql(expression, "region") 4802 region = f" REGION {region}" if region else "" 4803 4804 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4805 4806 def copy_sql(self, expression: exp.Copy) -> str: 4807 this = self.sql(expression, "this") 4808 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4809 4810 credentials = self.sql(expression, "credentials") 4811 credentials = self.seg(credentials) if credentials else "" 4812 files = self.expressions(expression, key="files", flat=True) 4813 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4814 4815 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4816 params = self.expressions( 4817 expression, 4818 key="params", 4819 sep=sep, 4820 new_line=True, 4821 skip_last=True, 4822 skip_first=True, 4823 indent=self.COPY_PARAMS_ARE_WRAPPED, 4824 ) 4825 4826 if params: 4827 if self.COPY_PARAMS_ARE_WRAPPED: 4828 params = f" WITH ({params})" 4829 elif not self.pretty and (files or credentials): 4830 params = f" {params}" 4831 4832 return f"COPY{this}{kind} {files}{credentials}{params}" 4833 4834 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4835 return "" 4836 4837 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4838 on_sql = "ON" if expression.args.get("on") else "OFF" 4839 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4840 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4841 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4842 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4843 4844 if filter_col or retention_period: 4845 on_sql = self.func("ON", filter_col, retention_period) 4846 4847 return f"DATA_DELETION={on_sql}" 4848 4849 def maskingpolicycolumnconstraint_sql( 4850 self, expression: exp.MaskingPolicyColumnConstraint 4851 ) -> str: 4852 this = self.sql(expression, "this") 4853 expressions = self.expressions(expression, flat=True) 4854 expressions = f" USING ({expressions})" if expressions else "" 4855 return f"MASKING POLICY {this}{expressions}" 4856 4857 def gapfill_sql(self, expression: exp.GapFill) -> str: 4858 this = self.sql(expression, "this") 4859 this = f"TABLE {this}" 4860 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4861 4862 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4863 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4864 4865 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4866 this = self.sql(expression, "this") 4867 expr = expression.expression 4868 4869 if isinstance(expr, exp.Func): 4870 # T-SQL's CLR functions are case sensitive 4871 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4872 else: 4873 expr = self.sql(expression, "expression") 4874 4875 return self.scope_resolution(expr, this) 4876 4877 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4878 if self.PARSE_JSON_NAME is None: 4879 return self.sql(expression.this) 4880 4881 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4882 4883 def rand_sql(self, expression: exp.Rand) -> str: 4884 lower = self.sql(expression, "lower") 4885 upper = self.sql(expression, "upper") 4886 4887 if lower and upper: 4888 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4889 return self.func("RAND", expression.this) 4890 4891 def changes_sql(self, expression: exp.Changes) -> str: 4892 information = self.sql(expression, "information") 4893 information = f"INFORMATION => {information}" 4894 at_before = self.sql(expression, "at_before") 4895 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4896 end = self.sql(expression, "end") 4897 end = f"{self.seg('')}{end}" if end else "" 4898 4899 return f"CHANGES ({information}){at_before}{end}" 4900 4901 def pad_sql(self, expression: exp.Pad) -> str: 4902 prefix = "L" if expression.args.get("is_left") else "R" 4903 4904 fill_pattern = self.sql(expression, "fill_pattern") or None 4905 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4906 fill_pattern = "' '" 4907 4908 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4909 4910 def summarize_sql(self, expression: exp.Summarize) -> str: 4911 table = " TABLE" if expression.args.get("table") else "" 4912 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4913 4914 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4915 generate_series = exp.GenerateSeries(**expression.args) 4916 4917 parent = expression.parent 4918 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4919 parent = parent.parent 4920 4921 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4922 return self.sql(exp.Unnest(expressions=[generate_series])) 4923 4924 if isinstance(parent, exp.Select): 4925 self.unsupported("GenerateSeries projection unnesting is not supported.") 4926 4927 return self.sql(generate_series) 4928 4929 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4930 if self.SUPPORTS_CONVERT_TIMEZONE: 4931 return self.function_fallback_sql(expression) 4932 4933 source_tz = expression.args.get("source_tz") 4934 target_tz = expression.args.get("target_tz") 4935 timestamp = expression.args.get("timestamp") 4936 4937 if source_tz and timestamp: 4938 timestamp = exp.AtTimeZone( 4939 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4940 ) 4941 4942 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4943 4944 return self.sql(expr) 4945 4946 def json_sql(self, expression: exp.JSON) -> str: 4947 this = self.sql(expression, "this") 4948 this = f" {this}" if this else "" 4949 4950 _with = expression.args.get("with_") 4951 4952 if _with is None: 4953 with_sql = "" 4954 elif not _with: 4955 with_sql = " WITHOUT" 4956 else: 4957 with_sql = " WITH" 4958 4959 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4960 4961 return f"JSON{this}{with_sql}{unique_sql}" 4962 4963 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4964 def _generate_on_options(arg: t.Any) -> str: 4965 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4966 4967 path = self.sql(expression, "path") 4968 returning = self.sql(expression, "returning") 4969 returning = f" RETURNING {returning}" if returning else "" 4970 4971 on_condition = self.sql(expression, "on_condition") 4972 on_condition = f" {on_condition}" if on_condition else "" 4973 4974 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4975 4976 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4977 else_ = "ELSE " if expression.args.get("else_") else "" 4978 condition = self.sql(expression, "expression") 4979 condition = f"WHEN {condition} THEN " if condition else else_ 4980 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4981 return f"{condition}{insert}" 4982 4983 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4984 kind = self.sql(expression, "kind") 4985 expressions = self.seg(self.expressions(expression, sep=" ")) 4986 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4987 return res 4988 4989 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4990 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4991 empty = expression.args.get("empty") 4992 empty = ( 4993 f"DEFAULT {empty} ON EMPTY" 4994 if isinstance(empty, exp.Expression) 4995 else self.sql(expression, "empty") 4996 ) 4997 4998 error = expression.args.get("error") 4999 error = ( 5000 f"DEFAULT {error} ON ERROR" 5001 if isinstance(error, exp.Expression) 5002 else self.sql(expression, "error") 5003 ) 5004 5005 if error and empty: 5006 error = ( 5007 f"{empty} {error}" 5008 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5009 else f"{error} {empty}" 5010 ) 5011 empty = "" 5012 5013 null = self.sql(expression, "null") 5014 5015 return f"{empty}{error}{null}" 5016 5017 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5018 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5019 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5020 5021 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5022 this = self.sql(expression, "this") 5023 path = self.sql(expression, "path") 5024 5025 passing = self.expressions(expression, "passing") 5026 passing = f" PASSING {passing}" if passing else "" 5027 5028 on_condition = self.sql(expression, "on_condition") 5029 on_condition = f" {on_condition}" if on_condition else "" 5030 5031 path = f"{path}{passing}{on_condition}" 5032 5033 return self.func("JSON_EXISTS", this, path) 5034 5035 def _add_arrayagg_null_filter( 5036 self, 5037 array_agg_sql: str, 5038 array_agg_expr: exp.ArrayAgg, 5039 column_expr: exp.Expression, 5040 ) -> str: 5041 """ 5042 Add NULL filter to ARRAY_AGG if dialect requires it. 5043 5044 Args: 5045 array_agg_sql: The generated ARRAY_AGG SQL string 5046 array_agg_expr: The ArrayAgg expression node 5047 column_expr: The column/expression to filter (before ORDER BY wrapping) 5048 5049 Returns: 5050 SQL string with FILTER clause added if needed 5051 """ 5052 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5053 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5054 if not ( 5055 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5056 ): 5057 return array_agg_sql 5058 5059 parent = array_agg_expr.parent 5060 if isinstance(parent, exp.Filter): 5061 parent_cond = parent.expression.this 5062 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5063 elif column_expr.find(exp.Column): 5064 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5065 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5066 this_sql = ( 5067 self.expressions(column_expr) 5068 if isinstance(column_expr, exp.Distinct) 5069 else self.sql(column_expr) 5070 ) 5071 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5072 5073 return array_agg_sql 5074 5075 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5076 array_agg = self.function_fallback_sql(expression) 5077 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5078 5079 def slice_sql(self, expression: exp.Slice) -> str: 5080 step = self.sql(expression, "step") 5081 end = self.sql(expression.expression) 5082 begin = self.sql(expression.this) 5083 5084 sql = f"{end}:{step}" if step else end 5085 return f"{begin}:{sql}" if sql else f"{begin}:" 5086 5087 def apply_sql(self, expression: exp.Apply) -> str: 5088 this = self.sql(expression, "this") 5089 expr = self.sql(expression, "expression") 5090 5091 return f"{this} APPLY({expr})" 5092 5093 def _grant_or_revoke_sql( 5094 self, 5095 expression: exp.Grant | exp.Revoke, 5096 keyword: str, 5097 preposition: str, 5098 grant_option_prefix: str = "", 5099 grant_option_suffix: str = "", 5100 ) -> str: 5101 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5102 5103 kind = self.sql(expression, "kind") 5104 kind = f" {kind}" if kind else "" 5105 5106 securable = self.sql(expression, "securable") 5107 securable = f" {securable}" if securable else "" 5108 5109 principals = self.expressions(expression, key="principals", flat=True) 5110 5111 if not expression.args.get("grant_option"): 5112 grant_option_prefix = grant_option_suffix = "" 5113 5114 # cascade for revoke only 5115 cascade = self.sql(expression, "cascade") 5116 cascade = f" {cascade}" if cascade else "" 5117 5118 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5119 5120 def grant_sql(self, expression: exp.Grant) -> str: 5121 return self._grant_or_revoke_sql( 5122 expression, 5123 keyword="GRANT", 5124 preposition="TO", 5125 grant_option_suffix=" WITH GRANT OPTION", 5126 ) 5127 5128 def revoke_sql(self, expression: exp.Revoke) -> str: 5129 return self._grant_or_revoke_sql( 5130 expression, 5131 keyword="REVOKE", 5132 preposition="FROM", 5133 grant_option_prefix="GRANT OPTION FOR ", 5134 ) 5135 5136 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 5137 this = self.sql(expression, "this") 5138 columns = self.expressions(expression, flat=True) 5139 columns = f"({columns})" if columns else "" 5140 5141 return f"{this}{columns}" 5142 5143 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 5144 this = self.sql(expression, "this") 5145 5146 kind = self.sql(expression, "kind") 5147 kind = f"{kind} " if kind else "" 5148 5149 return f"{kind}{this}" 5150 5151 def columns_sql(self, expression: exp.Columns): 5152 func = self.function_fallback_sql(expression) 5153 if expression.args.get("unpack"): 5154 func = f"*{func}" 5155 5156 return func 5157 5158 def overlay_sql(self, expression: exp.Overlay): 5159 this = self.sql(expression, "this") 5160 expr = self.sql(expression, "expression") 5161 from_sql = self.sql(expression, "from_") 5162 for_sql = self.sql(expression, "for_") 5163 for_sql = f" FOR {for_sql}" if for_sql else "" 5164 5165 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5166 5167 @unsupported_args("format") 5168 def todouble_sql(self, expression: exp.ToDouble) -> str: 5169 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5170 return self.sql(cast(this=expression.this, to=exp.DataType.build(exp.DataType.Type.DOUBLE))) 5171 5172 def string_sql(self, expression: exp.String) -> str: 5173 this = expression.this 5174 zone = expression.args.get("zone") 5175 5176 if zone: 5177 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5178 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5179 # set for source_tz to transpile the time conversion before the STRING cast 5180 this = exp.ConvertTimezone( 5181 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5182 ) 5183 5184 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 5185 5186 def median_sql(self, expression: exp.Median): 5187 if not self.SUPPORTS_MEDIAN: 5188 return self.sql( 5189 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5190 ) 5191 5192 return self.function_fallback_sql(expression) 5193 5194 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5195 filler = self.sql(expression, "this") 5196 filler = f" {filler}" if filler else "" 5197 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5198 return f"TRUNCATE{filler} {with_count}" 5199 5200 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5201 if self.SUPPORTS_UNIX_SECONDS: 5202 return self.function_fallback_sql(expression) 5203 5204 start_ts = exp.cast( 5205 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 5206 ) 5207 5208 return self.sql( 5209 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5210 ) 5211 5212 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5213 dim = expression.expression 5214 5215 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5216 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5217 if not (dim.is_int and dim.name == "1"): 5218 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5219 dim = None 5220 5221 # If dimension is required but not specified, default initialize it 5222 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5223 dim = exp.Literal.number(1) 5224 5225 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5226 5227 def attach_sql(self, expression: exp.Attach) -> str: 5228 this = self.sql(expression, "this") 5229 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5230 expressions = self.expressions(expression) 5231 expressions = f" ({expressions})" if expressions else "" 5232 5233 return f"ATTACH{exists_sql} {this}{expressions}" 5234 5235 def detach_sql(self, expression: exp.Detach) -> str: 5236 this = self.sql(expression, "this") 5237 # the DATABASE keyword is required if IF EXISTS is set 5238 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5239 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5240 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5241 5242 return f"DETACH{exists_sql} {this}" 5243 5244 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5245 this = self.sql(expression, "this") 5246 value = self.sql(expression, "expression") 5247 value = f" {value}" if value else "" 5248 return f"{this}{value}" 5249 5250 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5251 return ( 5252 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5253 ) 5254 5255 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5256 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5257 encode = f"{encode} {self.sql(expression, 'this')}" 5258 5259 properties = expression.args.get("properties") 5260 if properties: 5261 encode = f"{encode} {self.properties(properties)}" 5262 5263 return encode 5264 5265 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5266 this = self.sql(expression, "this") 5267 include = f"INCLUDE {this}" 5268 5269 column_def = self.sql(expression, "column_def") 5270 if column_def: 5271 include = f"{include} {column_def}" 5272 5273 alias = self.sql(expression, "alias") 5274 if alias: 5275 include = f"{include} AS {alias}" 5276 5277 return include 5278 5279 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5280 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5281 name = f"{prefix} {self.sql(expression, 'this')}" 5282 return self.func("XMLELEMENT", name, *expression.expressions) 5283 5284 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5285 this = self.sql(expression, "this") 5286 expr = self.sql(expression, "expression") 5287 expr = f"({expr})" if expr else "" 5288 return f"{this}{expr}" 5289 5290 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5291 partitions = self.expressions(expression, "partition_expressions") 5292 create = self.expressions(expression, "create_expressions") 5293 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5294 5295 def partitionbyrangepropertydynamic_sql( 5296 self, expression: exp.PartitionByRangePropertyDynamic 5297 ) -> str: 5298 start = self.sql(expression, "start") 5299 end = self.sql(expression, "end") 5300 5301 every = expression.args["every"] 5302 if isinstance(every, exp.Interval) and every.this.is_string: 5303 every.this.replace(exp.Literal.number(every.name)) 5304 5305 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5306 5307 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5308 name = self.sql(expression, "this") 5309 values = self.expressions(expression, flat=True) 5310 5311 return f"NAME {name} VALUE {values}" 5312 5313 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5314 kind = self.sql(expression, "kind") 5315 sample = self.sql(expression, "sample") 5316 return f"SAMPLE {sample} {kind}" 5317 5318 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5319 kind = self.sql(expression, "kind") 5320 option = self.sql(expression, "option") 5321 option = f" {option}" if option else "" 5322 this = self.sql(expression, "this") 5323 this = f" {this}" if this else "" 5324 columns = self.expressions(expression) 5325 columns = f" {columns}" if columns else "" 5326 return f"{kind}{option} STATISTICS{this}{columns}" 5327 5328 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5329 this = self.sql(expression, "this") 5330 columns = self.expressions(expression) 5331 inner_expression = self.sql(expression, "expression") 5332 inner_expression = f" {inner_expression}" if inner_expression else "" 5333 update_options = self.sql(expression, "update_options") 5334 update_options = f" {update_options} UPDATE" if update_options else "" 5335 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5336 5337 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5338 kind = self.sql(expression, "kind") 5339 kind = f" {kind}" if kind else "" 5340 return f"DELETE{kind} STATISTICS" 5341 5342 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5343 inner_expression = self.sql(expression, "expression") 5344 return f"LIST CHAINED ROWS{inner_expression}" 5345 5346 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5347 kind = self.sql(expression, "kind") 5348 this = self.sql(expression, "this") 5349 this = f" {this}" if this else "" 5350 inner_expression = self.sql(expression, "expression") 5351 return f"VALIDATE {kind}{this}{inner_expression}" 5352 5353 def analyze_sql(self, expression: exp.Analyze) -> str: 5354 options = self.expressions(expression, key="options", sep=" ") 5355 options = f" {options}" if options else "" 5356 kind = self.sql(expression, "kind") 5357 kind = f" {kind}" if kind else "" 5358 this = self.sql(expression, "this") 5359 this = f" {this}" if this else "" 5360 mode = self.sql(expression, "mode") 5361 mode = f" {mode}" if mode else "" 5362 properties = self.sql(expression, "properties") 5363 properties = f" {properties}" if properties else "" 5364 partition = self.sql(expression, "partition") 5365 partition = f" {partition}" if partition else "" 5366 inner_expression = self.sql(expression, "expression") 5367 inner_expression = f" {inner_expression}" if inner_expression else "" 5368 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5369 5370 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5371 this = self.sql(expression, "this") 5372 namespaces = self.expressions(expression, key="namespaces") 5373 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5374 passing = self.expressions(expression, key="passing") 5375 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5376 columns = self.expressions(expression, key="columns") 5377 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5378 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5379 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5380 5381 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5382 this = self.sql(expression, "this") 5383 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5384 5385 def export_sql(self, expression: exp.Export) -> str: 5386 this = self.sql(expression, "this") 5387 connection = self.sql(expression, "connection") 5388 connection = f"WITH CONNECTION {connection} " if connection else "" 5389 options = self.sql(expression, "options") 5390 return f"EXPORT DATA {connection}{options} AS {this}" 5391 5392 def declare_sql(self, expression: exp.Declare) -> str: 5393 return f"DECLARE {self.expressions(expression, flat=True)}" 5394 5395 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5396 variable = self.sql(expression, "this") 5397 default = self.sql(expression, "default") 5398 default = f" = {default}" if default else "" 5399 5400 kind = self.sql(expression, "kind") 5401 if isinstance(expression.args.get("kind"), exp.Schema): 5402 kind = f"TABLE {kind}" 5403 5404 return f"{variable} AS {kind}{default}" 5405 5406 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5407 kind = self.sql(expression, "kind") 5408 this = self.sql(expression, "this") 5409 set = self.sql(expression, "expression") 5410 using = self.sql(expression, "using") 5411 using = f" USING {using}" if using else "" 5412 5413 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5414 5415 return f"{kind_sql} {this} SET {set}{using}" 5416 5417 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5418 params = self.expressions(expression, key="params", flat=True) 5419 return self.func(expression.name, *expression.expressions) + f"({params})" 5420 5421 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5422 return self.func(expression.name, *expression.expressions) 5423 5424 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5425 return self.anonymousaggfunc_sql(expression) 5426 5427 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5428 return self.parameterizedagg_sql(expression) 5429 5430 def show_sql(self, expression: exp.Show) -> str: 5431 self.unsupported("Unsupported SHOW statement") 5432 return "" 5433 5434 def install_sql(self, expression: exp.Install) -> str: 5435 self.unsupported("Unsupported INSTALL statement") 5436 return "" 5437 5438 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5439 # Snowflake GET/PUT statements: 5440 # PUT <file> <internalStage> <properties> 5441 # GET <internalStage> <file> <properties> 5442 props = expression.args.get("properties") 5443 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5444 this = self.sql(expression, "this") 5445 target = self.sql(expression, "target") 5446 5447 if isinstance(expression, exp.Put): 5448 return f"PUT {this} {target}{props_sql}" 5449 else: 5450 return f"GET {target} {this}{props_sql}" 5451 5452 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5453 this = self.sql(expression, "this") 5454 expr = self.sql(expression, "expression") 5455 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5456 return f"TRANSLATE({this} USING {expr}{with_error})" 5457 5458 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5459 if self.SUPPORTS_DECODE_CASE: 5460 return self.func("DECODE", *expression.expressions) 5461 5462 expression, *expressions = expression.expressions 5463 5464 ifs = [] 5465 for search, result in zip(expressions[::2], expressions[1::2]): 5466 if isinstance(search, exp.Literal): 5467 ifs.append(exp.If(this=expression.eq(search), true=result)) 5468 elif isinstance(search, exp.Null): 5469 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5470 else: 5471 if isinstance(search, exp.Binary): 5472 search = exp.paren(search) 5473 5474 cond = exp.or_( 5475 expression.eq(search), 5476 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5477 copy=False, 5478 ) 5479 ifs.append(exp.If(this=cond, true=result)) 5480 5481 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5482 return self.sql(case) 5483 5484 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5485 this = self.sql(expression, "this") 5486 this = self.seg(this, sep="") 5487 dimensions = self.expressions( 5488 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5489 ) 5490 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5491 metrics = self.expressions( 5492 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5493 ) 5494 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5495 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5496 facts = self.seg(f"FACTS {facts}") if facts else "" 5497 where = self.sql(expression, "where") 5498 where = self.seg(f"WHERE {where}") if where else "" 5499 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5500 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5501 5502 def getextract_sql(self, expression: exp.GetExtract) -> str: 5503 this = expression.this 5504 expr = expression.expression 5505 5506 if not this.type or not expression.type: 5507 from sqlglot.optimizer.annotate_types import annotate_types 5508 5509 this = annotate_types(this, dialect=self.dialect) 5510 5511 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5512 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5513 5514 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5515 5516 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5517 return self.sql( 5518 exp.DateAdd( 5519 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5520 expression=expression.this, 5521 unit=exp.var("DAY"), 5522 ) 5523 ) 5524 5525 def space_sql(self: Generator, expression: exp.Space) -> str: 5526 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5527 5528 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5529 return f"BUILD {self.sql(expression, 'this')}" 5530 5531 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5532 method = self.sql(expression, "method") 5533 kind = expression.args.get("kind") 5534 if not kind: 5535 return f"REFRESH {method}" 5536 5537 every = self.sql(expression, "every") 5538 unit = self.sql(expression, "unit") 5539 every = f" EVERY {every} {unit}" if every else "" 5540 starts = self.sql(expression, "starts") 5541 starts = f" STARTS {starts}" if starts else "" 5542 5543 return f"REFRESH {method} ON {kind}{every}{starts}" 5544 5545 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5546 self.unsupported("The model!attribute syntax is not supported") 5547 return "" 5548 5549 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5550 return self.func("DIRECTORY", expression.this) 5551 5552 def uuid_sql(self, expression: exp.Uuid) -> str: 5553 is_string = expression.args.get("is_string", False) 5554 uuid_func_sql = self.func("UUID") 5555 5556 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5557 return self.sql( 5558 exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect) 5559 ) 5560 5561 return uuid_func_sql 5562 5563 def initcap_sql(self, expression: exp.Initcap) -> str: 5564 delimiters = expression.expression 5565 5566 if delimiters: 5567 # do not generate delimiters arg if we are round-tripping from default delimiters 5568 if ( 5569 delimiters.is_string 5570 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5571 ): 5572 delimiters = None 5573 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5574 self.unsupported("INITCAP does not support custom delimiters") 5575 delimiters = None 5576 5577 return self.func("INITCAP", expression.this, delimiters) 5578 5579 def localtime_sql(self, expression: exp.Localtime) -> str: 5580 this = expression.this 5581 return self.func("LOCALTIME", this) if this else "LOCALTIME" 5582 5583 def localtimestamp_sql(self, expression: exp.Localtime) -> str: 5584 this = expression.this 5585 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 5586 5587 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5588 this = expression.this.name.upper() 5589 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5590 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5591 return "WEEK" 5592 5593 return self.func("WEEK", expression.this) 5594 5595 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 5596 this = self.expressions(expression) 5597 charset = self.sql(expression, "charset") 5598 using = f" USING {charset}" if charset else "" 5599 return self.func(name, this + using)
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: Always quote except for specials cases. '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)
761 def __init__( 762 self, 763 pretty: t.Optional[bool] = None, 764 identify: str | bool = False, 765 normalize: bool = False, 766 pad: int = 2, 767 indent: int = 2, 768 normalize_functions: t.Optional[str | bool] = None, 769 unsupported_level: ErrorLevel = ErrorLevel.WARN, 770 max_unsupported: int = 3, 771 leading_comma: bool = False, 772 max_text_width: int = 80, 773 comments: bool = True, 774 dialect: DialectType = None, 775 ): 776 import sqlglot 777 from sqlglot.dialects import Dialect 778 779 self.pretty = pretty if pretty is not None else sqlglot.pretty 780 self.identify = identify 781 self.normalize = normalize 782 self.pad = pad 783 self._indent = indent 784 self.unsupported_level = unsupported_level 785 self.max_unsupported = max_unsupported 786 self.leading_comma = leading_comma 787 self.max_text_width = max_text_width 788 self.comments = comments 789 self.dialect = Dialect.get_or_raise(dialect) 790 791 # This is both a Dialect property and a Generator argument, so we prioritize the latter 792 self.normalize_functions = ( 793 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 794 ) 795 796 self.unsupported_messages: t.List[str] = [] 797 self._escaped_quote_end: str = ( 798 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 799 ) 800 self._escaped_byte_quote_end: str = ( 801 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 802 if self.dialect.BYTE_END 803 else "" 804 ) 805 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 806 807 self._next_name = name_sequence("_t") 808 809 self._identifier_start = self.dialect.IDENTIFIER_START 810 self._identifier_end = self.dialect.IDENTIFIER_END 811 812 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.Adjacent'>: <function Generator.<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.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SessionUser'>: <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.NetFunc'>: <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.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExtendsRight'>: <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.ZeroFillColumnConstraint'>: <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.SafeFunc'>: <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.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.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.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'>, <class 'sqlglot.expressions.JSONPathRoot'>}
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.RefreshTriggerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RollupProperty'>: <Location.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Command'>, <class 'sqlglot.expressions.Create'>, <class 'sqlglot.expressions.Describe'>, <class 'sqlglot.expressions.Delete'>, <class 'sqlglot.expressions.Drop'>, <class 'sqlglot.expressions.From'>, <class 'sqlglot.expressions.Insert'>, <class 'sqlglot.expressions.Join'>, <class 'sqlglot.expressions.MultitableInserts'>, <class 'sqlglot.expressions.Order'>, <class 'sqlglot.expressions.Group'>, <class 'sqlglot.expressions.Having'>, <class 'sqlglot.expressions.Select'>, <class 'sqlglot.expressions.SetOperation'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES =
{<Type.NCHAR: 'NCHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.CHAR: 'CHAR'>}
814 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 815 """ 816 Generates the SQL string corresponding to the given syntax tree. 817 818 Args: 819 expression: The syntax tree. 820 copy: Whether to copy the expression. The generator performs mutations so 821 it is safer to copy. 822 823 Returns: 824 The SQL string corresponding to `expression`. 825 """ 826 if copy: 827 expression = expression.copy() 828 829 expression = self.preprocess(expression) 830 831 self.unsupported_messages = [] 832 sql = self.sql(expression).strip() 833 834 if self.pretty: 835 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 836 837 if self.unsupported_level == ErrorLevel.IGNORE: 838 return sql 839 840 if self.unsupported_level == ErrorLevel.WARN: 841 for msg in self.unsupported_messages: 842 logger.warning(msg) 843 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 844 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 845 846 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:
848 def preprocess(self, expression: exp.Expression) -> exp.Expression: 849 """Apply generic preprocessing transformations to a given expression.""" 850 expression = self._move_ctes_to_top_level(expression) 851 852 if self.ENSURE_BOOLS: 853 from sqlglot.transforms import ensure_bools 854 855 expression = ensure_bools(expression) 856 857 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
881 def sanitize_comment(self, comment: str) -> str: 882 comment = " " + comment if comment[0].strip() else comment 883 comment = comment + " " if comment[-1].strip() else comment 884 885 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 886 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 887 comment = comment.replace("*/", "* /") 888 889 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
891 def maybe_comment( 892 self, 893 sql: str, 894 expression: t.Optional[exp.Expression] = None, 895 comments: t.Optional[t.List[str]] = None, 896 separated: bool = False, 897 ) -> str: 898 comments = ( 899 ((expression and expression.comments) if comments is None else comments) # type: ignore 900 if self.comments 901 else None 902 ) 903 904 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 905 return sql 906 907 comments_sql = " ".join( 908 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 909 ) 910 911 if not comments_sql: 912 return sql 913 914 comments_sql = self._replace_line_breaks(comments_sql) 915 916 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 917 return ( 918 f"{self.sep()}{comments_sql}{sql}" 919 if not sql or sql[0].isspace() 920 else f"{comments_sql}{self.sep()}{sql}" 921 ) 922 923 return f"{sql} {comments_sql}"
925 def wrap(self, expression: exp.Expression | str) -> str: 926 this_sql = ( 927 self.sql(expression) 928 if isinstance(expression, exp.UNWRAPPED_QUERIES) 929 else self.sql(expression, "this") 930 ) 931 if not this_sql: 932 return "()" 933 934 this_sql = self.indent(this_sql, level=1, pad=0) 935 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:
951 def indent( 952 self, 953 sql: str, 954 level: int = 0, 955 pad: t.Optional[int] = None, 956 skip_first: bool = False, 957 skip_last: bool = False, 958 ) -> str: 959 if not self.pretty or not sql: 960 return sql 961 962 pad = self.pad if pad is None else pad 963 lines = sql.split("\n") 964 965 return "\n".join( 966 ( 967 line 968 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 969 else f"{' ' * (level * self._indent + pad)}{line}" 970 ) 971 for i, line in enumerate(lines) 972 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
974 def sql( 975 self, 976 expression: t.Optional[str | exp.Expression], 977 key: t.Optional[str] = None, 978 comment: bool = True, 979 ) -> str: 980 if not expression: 981 return "" 982 983 if isinstance(expression, str): 984 return expression 985 986 if key: 987 value = expression.args.get(key) 988 if value: 989 return self.sql(value) 990 return "" 991 992 transform = self.TRANSFORMS.get(expression.__class__) 993 994 if callable(transform): 995 sql = transform(self, expression) 996 elif isinstance(expression, exp.Expression): 997 exp_handler_name = f"{expression.key}_sql" 998 999 if hasattr(self, exp_handler_name): 1000 sql = getattr(self, exp_handler_name)(expression) 1001 elif isinstance(expression, exp.Func): 1002 sql = self.function_fallback_sql(expression) 1003 elif isinstance(expression, exp.Property): 1004 sql = self.property_sql(expression) 1005 else: 1006 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1007 else: 1008 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 1009 1010 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1017 def cache_sql(self, expression: exp.Cache) -> str: 1018 lazy = " LAZY" if expression.args.get("lazy") else "" 1019 table = self.sql(expression, "this") 1020 options = expression.args.get("options") 1021 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1022 sql = self.sql(expression, "expression") 1023 sql = f" AS{self.sep()}{sql}" if sql else "" 1024 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1025 return self.prepend_ctes(expression, sql)
1027 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1028 if isinstance(expression.parent, exp.Cast): 1029 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1030 default = "DEFAULT " if expression.args.get("default") else "" 1031 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1045 def column_sql(self, expression: exp.Column) -> str: 1046 join_mark = " (+)" if expression.args.get("join_mark") else "" 1047 1048 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1049 join_mark = "" 1050 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1051 1052 return f"{self.column_parts(expression)}{join_mark}"
1063 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1064 column = self.sql(expression, "this") 1065 kind = self.sql(expression, "kind") 1066 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1067 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1068 kind = f"{sep}{kind}" if kind else "" 1069 constraints = f" {constraints}" if constraints else "" 1070 position = self.sql(expression, "position") 1071 position = f" {position}" if position else "" 1072 1073 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1074 kind = "" 1075 1076 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1083 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1084 this = self.sql(expression, "this") 1085 if expression.args.get("not_null"): 1086 persisted = " PERSISTED NOT NULL" 1087 elif expression.args.get("persisted"): 1088 persisted = " PERSISTED" 1089 else: 1090 persisted = "" 1091 1092 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1105 def generatedasidentitycolumnconstraint_sql( 1106 self, expression: exp.GeneratedAsIdentityColumnConstraint 1107 ) -> str: 1108 this = "" 1109 if expression.this is not None: 1110 on_null = " ON NULL" if expression.args.get("on_null") else "" 1111 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1112 1113 start = expression.args.get("start") 1114 start = f"START WITH {start}" if start else "" 1115 increment = expression.args.get("increment") 1116 increment = f" INCREMENT BY {increment}" if increment else "" 1117 minvalue = expression.args.get("minvalue") 1118 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1119 maxvalue = expression.args.get("maxvalue") 1120 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1121 cycle = expression.args.get("cycle") 1122 cycle_sql = "" 1123 1124 if cycle is not None: 1125 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1126 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1127 1128 sequence_opts = "" 1129 if start or increment or cycle_sql: 1130 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1131 sequence_opts = f" ({sequence_opts.strip()})" 1132 1133 expr = self.sql(expression, "expression") 1134 expr = f"({expr})" if expr else "IDENTITY" 1135 1136 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1138 def generatedasrowcolumnconstraint_sql( 1139 self, expression: exp.GeneratedAsRowColumnConstraint 1140 ) -> str: 1141 start = "START" if expression.args.get("start") else "END" 1142 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1143 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:
1153 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1154 desc = expression.args.get("desc") 1155 if desc is not None: 1156 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1157 options = self.expressions(expression, key="options", flat=True, sep=" ") 1158 options = f" {options}" if options else "" 1159 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1161 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1162 this = self.sql(expression, "this") 1163 this = f" {this}" if this else "" 1164 index_type = expression.args.get("index_type") 1165 index_type = f" USING {index_type}" if index_type else "" 1166 on_conflict = self.sql(expression, "on_conflict") 1167 on_conflict = f" {on_conflict}" if on_conflict else "" 1168 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1169 options = self.expressions(expression, key="options", flat=True, sep=" ") 1170 options = f" {options}" if options else "" 1171 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1173 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1174 input_ = expression.args.get("input_") 1175 output = expression.args.get("output") 1176 variadic = expression.args.get("variadic") 1177 1178 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1179 if variadic: 1180 return "VARIADIC" 1181 1182 if input_ and output: 1183 return f"IN{self.INOUT_SEPARATOR}OUT" 1184 if input_: 1185 return "IN" 1186 if output: 1187 return "OUT" 1188 1189 return ""
1194 def create_sql(self, expression: exp.Create) -> str: 1195 kind = self.sql(expression, "kind") 1196 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1197 properties = expression.args.get("properties") 1198 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1199 1200 this = self.createable_sql(expression, properties_locs) 1201 1202 properties_sql = "" 1203 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1204 exp.Properties.Location.POST_WITH 1205 ): 1206 props_ast = exp.Properties( 1207 expressions=[ 1208 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1209 *properties_locs[exp.Properties.Location.POST_WITH], 1210 ] 1211 ) 1212 props_ast.parent = expression 1213 properties_sql = self.sql(props_ast) 1214 1215 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1216 properties_sql = self.sep() + properties_sql 1217 elif not self.pretty: 1218 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1219 properties_sql = f" {properties_sql}" 1220 1221 begin = " BEGIN" if expression.args.get("begin") else "" 1222 end = " END" if expression.args.get("end") else "" 1223 1224 expression_sql = self.sql(expression, "expression") 1225 if expression_sql: 1226 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1227 1228 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1229 postalias_props_sql = "" 1230 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1231 postalias_props_sql = self.properties( 1232 exp.Properties( 1233 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1234 ), 1235 wrapped=False, 1236 ) 1237 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1238 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1239 1240 postindex_props_sql = "" 1241 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1242 postindex_props_sql = self.properties( 1243 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1244 wrapped=False, 1245 prefix=" ", 1246 ) 1247 1248 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1249 indexes = f" {indexes}" if indexes else "" 1250 index_sql = indexes + postindex_props_sql 1251 1252 replace = " OR REPLACE" if expression.args.get("replace") else "" 1253 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1254 unique = " UNIQUE" if expression.args.get("unique") else "" 1255 1256 clustered = expression.args.get("clustered") 1257 if clustered is None: 1258 clustered_sql = "" 1259 elif clustered: 1260 clustered_sql = " CLUSTERED COLUMNSTORE" 1261 else: 1262 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1263 1264 postcreate_props_sql = "" 1265 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1266 postcreate_props_sql = self.properties( 1267 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1268 sep=" ", 1269 prefix=" ", 1270 wrapped=False, 1271 ) 1272 1273 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1274 1275 postexpression_props_sql = "" 1276 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1277 postexpression_props_sql = self.properties( 1278 exp.Properties( 1279 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1280 ), 1281 sep=" ", 1282 prefix=" ", 1283 wrapped=False, 1284 ) 1285 1286 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1287 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1288 no_schema_binding = ( 1289 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1290 ) 1291 1292 clone = self.sql(expression, "clone") 1293 clone = f" {clone}" if clone else "" 1294 1295 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1296 properties_expression = f"{expression_sql}{properties_sql}" 1297 else: 1298 properties_expression = f"{properties_sql}{expression_sql}" 1299 1300 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1301 return self.prepend_ctes(expression, expression_sql)
1303 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1304 start = self.sql(expression, "start") 1305 start = f"START WITH {start}" if start else "" 1306 increment = self.sql(expression, "increment") 1307 increment = f" INCREMENT BY {increment}" if increment else "" 1308 minvalue = self.sql(expression, "minvalue") 1309 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1310 maxvalue = self.sql(expression, "maxvalue") 1311 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1312 owned = self.sql(expression, "owned") 1313 owned = f" OWNED BY {owned}" if owned else "" 1314 1315 cache = expression.args.get("cache") 1316 if cache is None: 1317 cache_str = "" 1318 elif cache is True: 1319 cache_str = " CACHE" 1320 else: 1321 cache_str = f" CACHE {cache}" 1322 1323 options = self.expressions(expression, key="options", flat=True, sep=" ") 1324 options = f" {options}" if options else "" 1325 1326 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1328 def clone_sql(self, expression: exp.Clone) -> str: 1329 this = self.sql(expression, "this") 1330 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1331 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1332 return f"{shallow}{keyword} {this}"
1334 def describe_sql(self, expression: exp.Describe) -> str: 1335 style = expression.args.get("style") 1336 style = f" {style}" if style else "" 1337 partition = self.sql(expression, "partition") 1338 partition = f" {partition}" if partition else "" 1339 format = self.sql(expression, "format") 1340 format = f" {format}" if format else "" 1341 as_json = " AS JSON" if expression.args.get("as_json") else "" 1342 1343 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1355 def with_sql(self, expression: exp.With) -> str: 1356 sql = self.expressions(expression, flat=True) 1357 recursive = ( 1358 "RECURSIVE " 1359 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1360 else "" 1361 ) 1362 search = self.sql(expression, "search") 1363 search = f" {search}" if search else "" 1364 1365 return f"WITH {recursive}{sql}{search}"
1367 def cte_sql(self, expression: exp.CTE) -> str: 1368 alias = expression.args.get("alias") 1369 if alias: 1370 alias.add_comments(expression.pop_comments()) 1371 1372 alias_sql = self.sql(expression, "alias") 1373 1374 materialized = expression.args.get("materialized") 1375 if materialized is False: 1376 materialized = "NOT MATERIALIZED " 1377 elif materialized: 1378 materialized = "MATERIALIZED " 1379 1380 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1381 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1382 1383 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1385 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1386 alias = self.sql(expression, "this") 1387 columns = self.expressions(expression, key="columns", flat=True) 1388 columns = f"({columns})" if columns else "" 1389 1390 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1391 columns = "" 1392 self.unsupported("Named columns are not supported in table alias.") 1393 1394 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1395 alias = self._next_name() 1396 1397 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1405 def hexstring_sql( 1406 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1407 ) -> str: 1408 this = self.sql(expression, "this") 1409 is_integer_type = expression.args.get("is_integer") 1410 1411 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1412 not self.dialect.HEX_START and not binary_function_repr 1413 ): 1414 # Integer representation will be returned if: 1415 # - The read dialect treats the hex value as integer literal but not the write 1416 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1417 return f"{int(this, 16)}" 1418 1419 if not is_integer_type: 1420 # Read dialect treats the hex value as BINARY/BLOB 1421 if binary_function_repr: 1422 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1423 return self.func(binary_function_repr, exp.Literal.string(this)) 1424 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1425 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1426 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1427 1428 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1430 def bytestring_sql(self, expression: exp.ByteString) -> str: 1431 this = self.sql(expression, "this") 1432 if self.dialect.BYTE_START: 1433 escaped_byte_string = self.escape_str( 1434 this, 1435 escape_backslash=False, 1436 delimiter=self.dialect.BYTE_END, 1437 escaped_delimiter=self._escaped_byte_quote_end, 1438 is_byte_string=True, 1439 ) 1440 is_bytes = expression.args.get("is_bytes", False) 1441 delimited_byte_string = ( 1442 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1443 ) 1444 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1445 return self.sql( 1446 exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect) 1447 ) 1448 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1449 return self.sql( 1450 exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect) 1451 ) 1452 1453 return delimited_byte_string 1454 return this
1456 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1457 this = self.sql(expression, "this") 1458 escape = expression.args.get("escape") 1459 1460 if self.dialect.UNICODE_START: 1461 escape_substitute = r"\\\1" 1462 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1463 else: 1464 escape_substitute = r"\\u\1" 1465 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1466 1467 if escape: 1468 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1469 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1470 else: 1471 escape_pattern = ESCAPED_UNICODE_RE 1472 escape_sql = "" 1473 1474 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1475 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1476 1477 return f"{left_quote}{this}{right_quote}{escape_sql}"
1479 def rawstring_sql(self, expression: exp.RawString) -> str: 1480 string = expression.this 1481 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1482 string = string.replace("\\", "\\\\") 1483 1484 string = self.escape_str(string, escape_backslash=False) 1485 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1493 def datatype_sql(self, expression: exp.DataType) -> str: 1494 nested = "" 1495 values = "" 1496 1497 expr_nested = expression.args.get("nested") 1498 interior = ( 1499 self.expressions( 1500 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1501 ) 1502 if expr_nested and self.pretty 1503 else self.expressions(expression, flat=True) 1504 ) 1505 1506 type_value = expression.this 1507 if type_value in self.UNSUPPORTED_TYPES: 1508 self.unsupported( 1509 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1510 ) 1511 1512 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1513 type_sql = self.sql(expression, "kind") 1514 else: 1515 type_sql = ( 1516 self.TYPE_MAPPING.get(type_value, type_value.value) 1517 if isinstance(type_value, exp.DataType.Type) 1518 else type_value 1519 ) 1520 1521 if interior: 1522 if expr_nested: 1523 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1524 if expression.args.get("values") is not None: 1525 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1526 values = self.expressions(expression, key="values", flat=True) 1527 values = f"{delimiters[0]}{values}{delimiters[1]}" 1528 elif type_value == exp.DataType.Type.INTERVAL: 1529 nested = f" {interior}" 1530 else: 1531 nested = f"({interior})" 1532 1533 type_sql = f"{type_sql}{nested}{values}" 1534 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1535 exp.DataType.Type.TIMETZ, 1536 exp.DataType.Type.TIMESTAMPTZ, 1537 ): 1538 type_sql = f"{type_sql} WITH TIME ZONE" 1539 1540 return type_sql
1542 def directory_sql(self, expression: exp.Directory) -> str: 1543 local = "LOCAL " if expression.args.get("local") else "" 1544 row_format = self.sql(expression, "row_format") 1545 row_format = f" {row_format}" if row_format else "" 1546 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1548 def delete_sql(self, expression: exp.Delete) -> str: 1549 this = self.sql(expression, "this") 1550 this = f" FROM {this}" if this else "" 1551 using = self.expressions(expression, key="using") 1552 using = f" USING {using}" if using else "" 1553 cluster = self.sql(expression, "cluster") 1554 cluster = f" {cluster}" if cluster else "" 1555 where = self.sql(expression, "where") 1556 returning = self.sql(expression, "returning") 1557 order = self.sql(expression, "order") 1558 limit = self.sql(expression, "limit") 1559 tables = self.expressions(expression, key="tables") 1560 tables = f" {tables}" if tables else "" 1561 if self.RETURNING_END: 1562 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1563 else: 1564 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1565 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1567 def drop_sql(self, expression: exp.Drop) -> str: 1568 this = self.sql(expression, "this") 1569 expressions = self.expressions(expression, flat=True) 1570 expressions = f" ({expressions})" if expressions else "" 1571 kind = expression.args["kind"] 1572 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1573 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1574 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1575 on_cluster = self.sql(expression, "cluster") 1576 on_cluster = f" {on_cluster}" if on_cluster else "" 1577 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1578 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1579 cascade = " CASCADE" if expression.args.get("cascade") else "" 1580 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1581 purge = " PURGE" if expression.args.get("purge") else "" 1582 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1584 def set_operation(self, expression: exp.SetOperation) -> str: 1585 op_type = type(expression) 1586 op_name = op_type.key.upper() 1587 1588 distinct = expression.args.get("distinct") 1589 if ( 1590 distinct is False 1591 and op_type in (exp.Except, exp.Intersect) 1592 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1593 ): 1594 self.unsupported(f"{op_name} ALL is not supported") 1595 1596 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1597 1598 if distinct is None: 1599 distinct = default_distinct 1600 if distinct is None: 1601 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1602 1603 if distinct is default_distinct: 1604 distinct_or_all = "" 1605 else: 1606 distinct_or_all = " DISTINCT" if distinct else " ALL" 1607 1608 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1609 side_kind = f"{side_kind} " if side_kind else "" 1610 1611 by_name = " BY NAME" if expression.args.get("by_name") else "" 1612 on = self.expressions(expression, key="on", flat=True) 1613 on = f" ON ({on})" if on else "" 1614 1615 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1617 def set_operations(self, expression: exp.SetOperation) -> str: 1618 if not self.SET_OP_MODIFIERS: 1619 limit = expression.args.get("limit") 1620 order = expression.args.get("order") 1621 1622 if limit or order: 1623 select = self._move_ctes_to_top_level( 1624 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1625 ) 1626 1627 if limit: 1628 select = select.limit(limit.pop(), copy=False) 1629 if order: 1630 select = select.order_by(order.pop(), copy=False) 1631 return self.sql(select) 1632 1633 sqls: t.List[str] = [] 1634 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1635 1636 while stack: 1637 node = stack.pop() 1638 1639 if isinstance(node, exp.SetOperation): 1640 stack.append(node.expression) 1641 stack.append( 1642 self.maybe_comment( 1643 self.set_operation(node), comments=node.comments, separated=True 1644 ) 1645 ) 1646 stack.append(node.this) 1647 else: 1648 sqls.append(self.sql(node)) 1649 1650 this = self.sep().join(sqls) 1651 this = self.query_modifiers(expression, this) 1652 return self.prepend_ctes(expression, this)
1654 def fetch_sql(self, expression: exp.Fetch) -> str: 1655 direction = expression.args.get("direction") 1656 direction = f" {direction}" if direction else "" 1657 count = self.sql(expression, "count") 1658 count = f" {count}" if count else "" 1659 limit_options = self.sql(expression, "limit_options") 1660 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1661 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1663 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1664 percent = " PERCENT" if expression.args.get("percent") else "" 1665 rows = " ROWS" if expression.args.get("rows") else "" 1666 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1667 if not with_ties and rows: 1668 with_ties = " ONLY" 1669 return f"{percent}{rows}{with_ties}"
1671 def filter_sql(self, expression: exp.Filter) -> str: 1672 if self.AGGREGATE_FILTER_SUPPORTED: 1673 this = self.sql(expression, "this") 1674 where = self.sql(expression, "expression").strip() 1675 return f"{this} FILTER({where})" 1676 1677 agg = expression.this 1678 agg_arg = agg.this 1679 cond = expression.expression.this 1680 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1681 return self.sql(agg)
1690 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1691 using = self.sql(expression, "using") 1692 using = f" USING {using}" if using else "" 1693 columns = self.expressions(expression, key="columns", flat=True) 1694 columns = f"({columns})" if columns else "" 1695 partition_by = self.expressions(expression, key="partition_by", flat=True) 1696 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1697 where = self.sql(expression, "where") 1698 include = self.expressions(expression, key="include", flat=True) 1699 if include: 1700 include = f" INCLUDE ({include})" 1701 with_storage = self.expressions(expression, key="with_storage", flat=True) 1702 with_storage = f" WITH ({with_storage})" if with_storage else "" 1703 tablespace = self.sql(expression, "tablespace") 1704 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1705 on = self.sql(expression, "on") 1706 on = f" ON {on}" if on else "" 1707 1708 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1710 def index_sql(self, expression: exp.Index) -> str: 1711 unique = "UNIQUE " if expression.args.get("unique") else "" 1712 primary = "PRIMARY " if expression.args.get("primary") else "" 1713 amp = "AMP " if expression.args.get("amp") else "" 1714 name = self.sql(expression, "this") 1715 name = f"{name} " if name else "" 1716 table = self.sql(expression, "table") 1717 table = f"{self.INDEX_ON} {table}" if table else "" 1718 1719 index = "INDEX " if not table else "" 1720 1721 params = self.sql(expression, "params") 1722 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1724 def identifier_sql(self, expression: exp.Identifier) -> str: 1725 text = expression.name 1726 lower = text.lower() 1727 text = lower if self.normalize and not expression.quoted else text 1728 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1729 if ( 1730 expression.quoted 1731 or self.dialect.can_quote(expression, self.identify) 1732 or lower in self.RESERVED_KEYWORDS 1733 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1734 ): 1735 text = f"{self._identifier_start}{text}{self._identifier_end}" 1736 return text
1751 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1752 input_format = self.sql(expression, "input_format") 1753 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1754 output_format = self.sql(expression, "output_format") 1755 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1756 return self.sep().join((input_format, output_format))
1766 def properties_sql(self, expression: exp.Properties) -> str: 1767 root_properties = [] 1768 with_properties = [] 1769 1770 for p in expression.expressions: 1771 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1772 if p_loc == exp.Properties.Location.POST_WITH: 1773 with_properties.append(p) 1774 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1775 root_properties.append(p) 1776 1777 root_props_ast = exp.Properties(expressions=root_properties) 1778 root_props_ast.parent = expression.parent 1779 1780 with_props_ast = exp.Properties(expressions=with_properties) 1781 with_props_ast.parent = expression.parent 1782 1783 root_props = self.root_properties(root_props_ast) 1784 with_props = self.with_properties(with_props_ast) 1785 1786 if root_props and with_props and not self.pretty: 1787 with_props = " " + with_props 1788 1789 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1796 def properties( 1797 self, 1798 properties: exp.Properties, 1799 prefix: str = "", 1800 sep: str = ", ", 1801 suffix: str = "", 1802 wrapped: bool = True, 1803 ) -> str: 1804 if properties.expressions: 1805 expressions = self.expressions(properties, sep=sep, indent=False) 1806 if expressions: 1807 expressions = self.wrap(expressions) if wrapped else expressions 1808 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1809 return ""
1814 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1815 properties_locs = defaultdict(list) 1816 for p in properties.expressions: 1817 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1818 if p_loc != exp.Properties.Location.UNSUPPORTED: 1819 properties_locs[p_loc].append(p) 1820 else: 1821 self.unsupported(f"Unsupported property {p.key}") 1822 1823 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1830 def property_sql(self, expression: exp.Property) -> str: 1831 property_cls = expression.__class__ 1832 if property_cls == exp.Property: 1833 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1834 1835 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1836 if not property_name: 1837 self.unsupported(f"Unsupported property {expression.key}") 1838 1839 return f"{property_name}={self.sql(expression, 'this')}"
1841 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1842 if self.SUPPORTS_CREATE_TABLE_LIKE: 1843 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1844 options = f" {options}" if options else "" 1845 1846 like = f"LIKE {self.sql(expression, 'this')}{options}" 1847 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1848 like = f"({like})" 1849 1850 return like 1851 1852 if expression.expressions: 1853 self.unsupported("Transpilation of LIKE property options is unsupported") 1854 1855 select = exp.select("*").from_(expression.this).limit(0) 1856 return f"AS {self.sql(select)}"
1863 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1864 no = "NO " if expression.args.get("no") else "" 1865 local = expression.args.get("local") 1866 local = f"{local} " if local else "" 1867 dual = "DUAL " if expression.args.get("dual") else "" 1868 before = "BEFORE " if expression.args.get("before") else "" 1869 after = "AFTER " if expression.args.get("after") else "" 1870 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1886 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1887 if expression.args.get("no"): 1888 return "NO MERGEBLOCKRATIO" 1889 if expression.args.get("default"): 1890 return "DEFAULT MERGEBLOCKRATIO" 1891 1892 percent = " PERCENT" if expression.args.get("percent") else "" 1893 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1895 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1896 default = expression.args.get("default") 1897 minimum = expression.args.get("minimum") 1898 maximum = expression.args.get("maximum") 1899 if default or minimum or maximum: 1900 if default: 1901 prop = "DEFAULT" 1902 elif minimum: 1903 prop = "MINIMUM" 1904 else: 1905 prop = "MAXIMUM" 1906 return f"{prop} DATABLOCKSIZE" 1907 units = expression.args.get("units") 1908 units = f" {units}" if units else "" 1909 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1911 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1912 autotemp = expression.args.get("autotemp") 1913 always = expression.args.get("always") 1914 default = expression.args.get("default") 1915 manual = expression.args.get("manual") 1916 never = expression.args.get("never") 1917 1918 if autotemp is not None: 1919 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1920 elif always: 1921 prop = "ALWAYS" 1922 elif default: 1923 prop = "DEFAULT" 1924 elif manual: 1925 prop = "MANUAL" 1926 elif never: 1927 prop = "NEVER" 1928 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1930 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1931 no = expression.args.get("no") 1932 no = " NO" if no else "" 1933 concurrent = expression.args.get("concurrent") 1934 concurrent = " CONCURRENT" if concurrent else "" 1935 target = self.sql(expression, "target") 1936 target = f" {target}" if target else "" 1937 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1939 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1940 if isinstance(expression.this, list): 1941 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1942 if expression.this: 1943 modulus = self.sql(expression, "this") 1944 remainder = self.sql(expression, "expression") 1945 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1946 1947 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1948 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1949 return f"FROM ({from_expressions}) TO ({to_expressions})"
1951 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1952 this = self.sql(expression, "this") 1953 1954 for_values_or_default = expression.expression 1955 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1956 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1957 else: 1958 for_values_or_default = " DEFAULT" 1959 1960 return f"PARTITION OF {this}{for_values_or_default}"
1962 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1963 kind = expression.args.get("kind") 1964 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1965 for_or_in = expression.args.get("for_or_in") 1966 for_or_in = f" {for_or_in}" if for_or_in else "" 1967 lock_type = expression.args.get("lock_type") 1968 override = " OVERRIDE" if expression.args.get("override") else "" 1969 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1971 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1972 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1973 statistics = expression.args.get("statistics") 1974 statistics_sql = "" 1975 if statistics is not None: 1976 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1977 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1979 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1980 this = self.sql(expression, "this") 1981 this = f"HISTORY_TABLE={this}" if this else "" 1982 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1983 data_consistency = ( 1984 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1985 ) 1986 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1987 retention_period = ( 1988 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1989 ) 1990 1991 if this: 1992 on_sql = self.func("ON", this, data_consistency, retention_period) 1993 else: 1994 on_sql = "ON" if expression.args.get("on") else "OFF" 1995 1996 sql = f"SYSTEM_VERSIONING={on_sql}" 1997 1998 return f"WITH({sql})" if expression.args.get("with_") else sql
2000 def insert_sql(self, expression: exp.Insert) -> str: 2001 hint = self.sql(expression, "hint") 2002 overwrite = expression.args.get("overwrite") 2003 2004 if isinstance(expression.this, exp.Directory): 2005 this = " OVERWRITE" if overwrite else " INTO" 2006 else: 2007 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2008 2009 stored = self.sql(expression, "stored") 2010 stored = f" {stored}" if stored else "" 2011 alternative = expression.args.get("alternative") 2012 alternative = f" OR {alternative}" if alternative else "" 2013 ignore = " IGNORE" if expression.args.get("ignore") else "" 2014 is_function = expression.args.get("is_function") 2015 if is_function: 2016 this = f"{this} FUNCTION" 2017 this = f"{this} {self.sql(expression, 'this')}" 2018 2019 exists = " IF EXISTS" if expression.args.get("exists") else "" 2020 where = self.sql(expression, "where") 2021 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2022 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2023 on_conflict = self.sql(expression, "conflict") 2024 on_conflict = f" {on_conflict}" if on_conflict else "" 2025 by_name = " BY NAME" if expression.args.get("by_name") else "" 2026 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2027 returning = self.sql(expression, "returning") 2028 2029 if self.RETURNING_END: 2030 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2031 else: 2032 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2033 2034 partition_by = self.sql(expression, "partition") 2035 partition_by = f" {partition_by}" if partition_by else "" 2036 settings = self.sql(expression, "settings") 2037 settings = f" {settings}" if settings else "" 2038 2039 source = self.sql(expression, "source") 2040 source = f"TABLE {source}" if source else "" 2041 2042 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2043 return self.prepend_ctes(expression, sql)
2061 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2062 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2063 2064 constraint = self.sql(expression, "constraint") 2065 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2066 2067 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2068 if conflict_keys: 2069 conflict_keys = f"({conflict_keys})" 2070 2071 index_predicate = self.sql(expression, "index_predicate") 2072 conflict_keys = f"{conflict_keys}{index_predicate} " 2073 2074 action = self.sql(expression, "action") 2075 2076 expressions = self.expressions(expression, flat=True) 2077 if expressions: 2078 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2079 expressions = f" {set_keyword}{expressions}" 2080 2081 where = self.sql(expression, "where") 2082 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
2087 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2088 fields = self.sql(expression, "fields") 2089 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2090 escaped = self.sql(expression, "escaped") 2091 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2092 items = self.sql(expression, "collection_items") 2093 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2094 keys = self.sql(expression, "map_keys") 2095 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2096 lines = self.sql(expression, "lines") 2097 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2098 null = self.sql(expression, "null") 2099 null = f" NULL DEFINED AS {null}" if null else "" 2100 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2128 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2129 table = self.table_parts(expression) 2130 only = "ONLY " if expression.args.get("only") else "" 2131 partition = self.sql(expression, "partition") 2132 partition = f" {partition}" if partition else "" 2133 version = self.sql(expression, "version") 2134 version = f" {version}" if version else "" 2135 alias = self.sql(expression, "alias") 2136 alias = f"{sep}{alias}" if alias else "" 2137 2138 sample = self.sql(expression, "sample") 2139 if self.dialect.ALIAS_POST_TABLESAMPLE: 2140 sample_pre_alias = sample 2141 sample_post_alias = "" 2142 else: 2143 sample_pre_alias = "" 2144 sample_post_alias = sample 2145 2146 hints = self.expressions(expression, key="hints", sep=" ") 2147 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2148 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2149 joins = self.indent( 2150 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2151 ) 2152 laterals = self.expressions(expression, key="laterals", sep="") 2153 2154 file_format = self.sql(expression, "format") 2155 if file_format: 2156 pattern = self.sql(expression, "pattern") 2157 pattern = f", PATTERN => {pattern}" if pattern else "" 2158 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2159 2160 ordinality = expression.args.get("ordinality") or "" 2161 if ordinality: 2162 ordinality = f" WITH ORDINALITY{alias}" 2163 alias = "" 2164 2165 when = self.sql(expression, "when") 2166 if when: 2167 table = f"{table} {when}" 2168 2169 changes = self.sql(expression, "changes") 2170 changes = f" {changes}" if changes else "" 2171 2172 rows_from = self.expressions(expression, key="rows_from") 2173 if rows_from: 2174 table = f"ROWS FROM {self.wrap(rows_from)}" 2175 2176 indexed = expression.args.get("indexed") 2177 if indexed is not None: 2178 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2179 else: 2180 indexed = "" 2181 2182 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{indexed}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2184 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2185 table = self.func("TABLE", expression.this) 2186 alias = self.sql(expression, "alias") 2187 alias = f" AS {alias}" if alias else "" 2188 sample = self.sql(expression, "sample") 2189 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2190 joins = self.indent( 2191 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2192 ) 2193 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2195 def tablesample_sql( 2196 self, 2197 expression: exp.TableSample, 2198 tablesample_keyword: t.Optional[str] = None, 2199 ) -> str: 2200 method = self.sql(expression, "method") 2201 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2202 numerator = self.sql(expression, "bucket_numerator") 2203 denominator = self.sql(expression, "bucket_denominator") 2204 field = self.sql(expression, "bucket_field") 2205 field = f" ON {field}" if field else "" 2206 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2207 seed = self.sql(expression, "seed") 2208 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2209 2210 size = self.sql(expression, "size") 2211 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2212 size = f"{size} ROWS" 2213 2214 percent = self.sql(expression, "percent") 2215 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2216 percent = f"{percent} PERCENT" 2217 2218 expr = f"{bucket}{percent}{size}" 2219 if self.TABLESAMPLE_REQUIRES_PARENS: 2220 expr = f"({expr})" 2221 2222 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2224 def pivot_sql(self, expression: exp.Pivot) -> str: 2225 expressions = self.expressions(expression, flat=True) 2226 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2227 2228 group = self.sql(expression, "group") 2229 2230 if expression.this: 2231 this = self.sql(expression, "this") 2232 if not expressions: 2233 sql = f"UNPIVOT {this}" 2234 else: 2235 on = f"{self.seg('ON')} {expressions}" 2236 into = self.sql(expression, "into") 2237 into = f"{self.seg('INTO')} {into}" if into else "" 2238 using = self.expressions(expression, key="using", flat=True) 2239 using = f"{self.seg('USING')} {using}" if using else "" 2240 sql = f"{direction} {this}{on}{into}{using}{group}" 2241 return self.prepend_ctes(expression, sql) 2242 2243 alias = self.sql(expression, "alias") 2244 alias = f" AS {alias}" if alias else "" 2245 2246 fields = self.expressions( 2247 expression, 2248 "fields", 2249 sep=" ", 2250 dynamic=True, 2251 new_line=True, 2252 skip_first=True, 2253 skip_last=True, 2254 ) 2255 2256 include_nulls = expression.args.get("include_nulls") 2257 if include_nulls is not None: 2258 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2259 else: 2260 nulls = "" 2261 2262 default_on_null = self.sql(expression, "default_on_null") 2263 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2264 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2265 return self.prepend_ctes(expression, sql)
2308 def update_sql(self, expression: exp.Update) -> str: 2309 this = self.sql(expression, "this") 2310 join_sql, from_sql = self._update_from_joins_sql(expression) 2311 set_sql = self.expressions(expression, flat=True) 2312 where_sql = self.sql(expression, "where") 2313 returning = self.sql(expression, "returning") 2314 order = self.sql(expression, "order") 2315 limit = self.sql(expression, "limit") 2316 if self.RETURNING_END: 2317 expression_sql = f"{from_sql}{where_sql}{returning}" 2318 else: 2319 expression_sql = f"{returning}{from_sql}{where_sql}" 2320 options = self.expressions(expression, key="options") 2321 options = f" OPTION({options})" if options else "" 2322 sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2323 return self.prepend_ctes(expression, sql)
2325 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2326 values_as_table = values_as_table and self.VALUES_AS_TABLE 2327 2328 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2329 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2330 args = self.expressions(expression) 2331 alias = self.sql(expression, "alias") 2332 values = f"VALUES{self.seg('')}{args}" 2333 values = ( 2334 f"({values})" 2335 if self.WRAP_DERIVED_VALUES 2336 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2337 else values 2338 ) 2339 values = self.query_modifiers(expression, values) 2340 return f"{values} AS {alias}" if alias else values 2341 2342 # Converts `VALUES...` expression into a series of select unions. 2343 alias_node = expression.args.get("alias") 2344 column_names = alias_node and alias_node.columns 2345 2346 selects: t.List[exp.Query] = [] 2347 2348 for i, tup in enumerate(expression.expressions): 2349 row = tup.expressions 2350 2351 if i == 0 and column_names: 2352 row = [ 2353 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2354 ] 2355 2356 selects.append(exp.Select(expressions=row)) 2357 2358 if self.pretty: 2359 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2360 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2361 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2362 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2363 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2364 2365 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2366 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2367 return f"({unions}){alias}"
2372 @unsupported_args("expressions") 2373 def into_sql(self, expression: exp.Into) -> str: 2374 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2375 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2376 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2389 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2390 this = self.sql(expression, "this") 2391 2392 columns = self.expressions(expression, flat=True) 2393 2394 from_sql = self.sql(expression, "from_index") 2395 from_sql = f" FROM {from_sql}" if from_sql else "" 2396 2397 properties = expression.args.get("properties") 2398 properties_sql = ( 2399 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2400 ) 2401 2402 return f"{this}({columns}){from_sql}{properties_sql}"
2411 def group_sql(self, expression: exp.Group) -> str: 2412 group_by_all = expression.args.get("all") 2413 if group_by_all is True: 2414 modifier = " ALL" 2415 elif group_by_all is False: 2416 modifier = " DISTINCT" 2417 else: 2418 modifier = "" 2419 2420 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2421 2422 grouping_sets = self.expressions(expression, key="grouping_sets") 2423 cube = self.expressions(expression, key="cube") 2424 rollup = self.expressions(expression, key="rollup") 2425 2426 groupings = csv( 2427 self.seg(grouping_sets) if grouping_sets else "", 2428 self.seg(cube) if cube else "", 2429 self.seg(rollup) if rollup else "", 2430 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2431 sep=self.GROUPINGS_SEP, 2432 ) 2433 2434 if ( 2435 expression.expressions 2436 and groupings 2437 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2438 ): 2439 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2440 2441 return f"{group_by}{groupings}"
2447 def connect_sql(self, expression: exp.Connect) -> str: 2448 start = self.sql(expression, "start") 2449 start = self.seg(f"START WITH {start}") if start else "" 2450 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2451 connect = self.sql(expression, "connect") 2452 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2453 return start + connect
2458 def join_sql(self, expression: exp.Join) -> str: 2459 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2460 side = None 2461 else: 2462 side = expression.side 2463 2464 op_sql = " ".join( 2465 op 2466 for op in ( 2467 expression.method, 2468 "GLOBAL" if expression.args.get("global_") else None, 2469 side, 2470 expression.kind, 2471 expression.hint if self.JOIN_HINTS else None, 2472 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2473 ) 2474 if op 2475 ) 2476 match_cond = self.sql(expression, "match_condition") 2477 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2478 on_sql = self.sql(expression, "on") 2479 using = expression.args.get("using") 2480 2481 if not on_sql and using: 2482 on_sql = csv(*(self.sql(column) for column in using)) 2483 2484 this = expression.this 2485 this_sql = self.sql(this) 2486 2487 exprs = self.expressions(expression) 2488 if exprs: 2489 this_sql = f"{this_sql},{self.seg(exprs)}" 2490 2491 if on_sql: 2492 on_sql = self.indent(on_sql, skip_first=True) 2493 space = self.seg(" " * self.pad) if self.pretty else " " 2494 if using: 2495 on_sql = f"{space}USING ({on_sql})" 2496 else: 2497 on_sql = f"{space}ON {on_sql}" 2498 elif not op_sql: 2499 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2500 return f" {this_sql}" 2501 2502 return f", {this_sql}" 2503 2504 if op_sql != "STRAIGHT_JOIN": 2505 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2506 2507 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2508 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:
2515 def lateral_op(self, expression: exp.Lateral) -> str: 2516 cross_apply = expression.args.get("cross_apply") 2517 2518 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2519 if cross_apply is True: 2520 op = "INNER JOIN " 2521 elif cross_apply is False: 2522 op = "LEFT JOIN " 2523 else: 2524 op = "" 2525 2526 return f"{op}LATERAL"
2528 def lateral_sql(self, expression: exp.Lateral) -> str: 2529 this = self.sql(expression, "this") 2530 2531 if expression.args.get("view"): 2532 alias = expression.args["alias"] 2533 columns = self.expressions(alias, key="columns", flat=True) 2534 table = f" {alias.name}" if alias.name else "" 2535 columns = f" AS {columns}" if columns else "" 2536 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2537 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2538 2539 alias = self.sql(expression, "alias") 2540 alias = f" AS {alias}" if alias else "" 2541 2542 ordinality = expression.args.get("ordinality") or "" 2543 if ordinality: 2544 ordinality = f" WITH ORDINALITY{alias}" 2545 alias = "" 2546 2547 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2549 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2550 this = self.sql(expression, "this") 2551 2552 args = [ 2553 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2554 for e in (expression.args.get(k) for k in ("offset", "expression")) 2555 if e 2556 ] 2557 2558 args_sql = ", ".join(self.sql(e) for e in args) 2559 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2560 expressions = self.expressions(expression, flat=True) 2561 limit_options = self.sql(expression, "limit_options") 2562 expressions = f" BY {expressions}" if expressions else "" 2563 2564 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2566 def offset_sql(self, expression: exp.Offset) -> str: 2567 this = self.sql(expression, "this") 2568 value = expression.expression 2569 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2570 expressions = self.expressions(expression, flat=True) 2571 expressions = f" BY {expressions}" if expressions else "" 2572 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2574 def setitem_sql(self, expression: exp.SetItem) -> str: 2575 kind = self.sql(expression, "kind") 2576 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2577 kind = "" 2578 else: 2579 kind = f"{kind} " if kind else "" 2580 this = self.sql(expression, "this") 2581 expressions = self.expressions(expression) 2582 collate = self.sql(expression, "collate") 2583 collate = f" COLLATE {collate}" if collate else "" 2584 global_ = "GLOBAL " if expression.args.get("global_") else "" 2585 return f"{global_}{kind}{this}{expressions}{collate}"
2592 def queryband_sql(self, expression: exp.QueryBand) -> str: 2593 this = self.sql(expression, "this") 2594 update = " UPDATE" if expression.args.get("update") else "" 2595 scope = self.sql(expression, "scope") 2596 scope = f" FOR {scope}" if scope else "" 2597 2598 return f"QUERY_BAND = {this}{update}{scope}"
2603 def lock_sql(self, expression: exp.Lock) -> str: 2604 if not self.LOCKING_READS_SUPPORTED: 2605 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2606 return "" 2607 2608 update = expression.args["update"] 2609 key = expression.args.get("key") 2610 if update: 2611 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2612 else: 2613 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2614 expressions = self.expressions(expression, flat=True) 2615 expressions = f" OF {expressions}" if expressions else "" 2616 wait = expression.args.get("wait") 2617 2618 if wait is not None: 2619 if isinstance(wait, exp.Literal): 2620 wait = f" WAIT {self.sql(wait)}" 2621 else: 2622 wait = " NOWAIT" if wait else " SKIP LOCKED" 2623 2624 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, is_byte_string: bool = False) -> str:
2632 def escape_str( 2633 self, 2634 text: str, 2635 escape_backslash: bool = True, 2636 delimiter: t.Optional[str] = None, 2637 escaped_delimiter: t.Optional[str] = None, 2638 is_byte_string: bool = False, 2639 ) -> str: 2640 if is_byte_string: 2641 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2642 else: 2643 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2644 2645 if supports_escape_sequences: 2646 text = "".join( 2647 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2648 for ch in text 2649 ) 2650 2651 delimiter = delimiter or self.dialect.QUOTE_END 2652 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2653 2654 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2656 def loaddata_sql(self, expression: exp.LoadData) -> str: 2657 local = " LOCAL" if expression.args.get("local") else "" 2658 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2659 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2660 this = f" INTO TABLE {self.sql(expression, 'this')}" 2661 partition = self.sql(expression, "partition") 2662 partition = f" {partition}" if partition else "" 2663 input_format = self.sql(expression, "input_format") 2664 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2665 serde = self.sql(expression, "serde") 2666 serde = f" SERDE {serde}" if serde else "" 2667 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2681 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2682 this = self.sql(expression, "this") 2683 this = f"{this} " if this else this 2684 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2685 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2687 def withfill_sql(self, expression: exp.WithFill) -> str: 2688 from_sql = self.sql(expression, "from_") 2689 from_sql = f" FROM {from_sql}" if from_sql else "" 2690 to_sql = self.sql(expression, "to") 2691 to_sql = f" TO {to_sql}" if to_sql else "" 2692 step_sql = self.sql(expression, "step") 2693 step_sql = f" STEP {step_sql}" if step_sql else "" 2694 interpolated_values = [ 2695 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2696 if isinstance(e, exp.Alias) 2697 else self.sql(e, "this") 2698 for e in expression.args.get("interpolate") or [] 2699 ] 2700 interpolate = ( 2701 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2702 ) 2703 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2714 def ordered_sql(self, expression: exp.Ordered) -> str: 2715 desc = expression.args.get("desc") 2716 asc = not desc 2717 2718 nulls_first = expression.args.get("nulls_first") 2719 nulls_last = not nulls_first 2720 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2721 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2722 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2723 2724 this = self.sql(expression, "this") 2725 2726 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2727 nulls_sort_change = "" 2728 if nulls_first and ( 2729 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2730 ): 2731 nulls_sort_change = " NULLS FIRST" 2732 elif ( 2733 nulls_last 2734 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2735 and not nulls_are_last 2736 ): 2737 nulls_sort_change = " NULLS LAST" 2738 2739 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2740 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2741 window = expression.find_ancestor(exp.Window, exp.Select) 2742 if isinstance(window, exp.Window) and window.args.get("spec"): 2743 self.unsupported( 2744 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2745 ) 2746 nulls_sort_change = "" 2747 elif self.NULL_ORDERING_SUPPORTED is False and ( 2748 (asc and nulls_sort_change == " NULLS LAST") 2749 or (desc and nulls_sort_change == " NULLS FIRST") 2750 ): 2751 # BigQuery does not allow these ordering/nulls combinations when used under 2752 # an aggregation func or under a window containing one 2753 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2754 2755 if isinstance(ancestor, exp.Window): 2756 ancestor = ancestor.this 2757 if isinstance(ancestor, exp.AggFunc): 2758 self.unsupported( 2759 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2760 ) 2761 nulls_sort_change = "" 2762 elif self.NULL_ORDERING_SUPPORTED is None: 2763 if expression.this.is_int: 2764 self.unsupported( 2765 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2766 ) 2767 elif not isinstance(expression.this, exp.Rand): 2768 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2769 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2770 nulls_sort_change = "" 2771 2772 with_fill = self.sql(expression, "with_fill") 2773 with_fill = f" {with_fill}" if with_fill else "" 2774 2775 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2785 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2786 partition = self.partition_by_sql(expression) 2787 order = self.sql(expression, "order") 2788 measures = self.expressions(expression, key="measures") 2789 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2790 rows = self.sql(expression, "rows") 2791 rows = self.seg(rows) if rows else "" 2792 after = self.sql(expression, "after") 2793 after = self.seg(after) if after else "" 2794 pattern = self.sql(expression, "pattern") 2795 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2796 definition_sqls = [ 2797 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2798 for definition in expression.args.get("define", []) 2799 ] 2800 definitions = self.expressions(sqls=definition_sqls) 2801 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2802 body = "".join( 2803 ( 2804 partition, 2805 order, 2806 measures, 2807 rows, 2808 after, 2809 pattern, 2810 define, 2811 ) 2812 ) 2813 alias = self.sql(expression, "alias") 2814 alias = f" {alias}" if alias else "" 2815 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2817 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2818 limit = expression.args.get("limit") 2819 2820 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2821 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2822 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2823 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2824 2825 return csv( 2826 *sqls, 2827 *[self.sql(join) for join in expression.args.get("joins") or []], 2828 self.sql(expression, "match"), 2829 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2830 self.sql(expression, "prewhere"), 2831 self.sql(expression, "where"), 2832 self.sql(expression, "connect"), 2833 self.sql(expression, "group"), 2834 self.sql(expression, "having"), 2835 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2836 self.sql(expression, "order"), 2837 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2838 *self.after_limit_modifiers(expression), 2839 self.options_modifier(expression), 2840 self.for_modifiers(expression), 2841 sep="", 2842 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2856 def offset_limit_modifiers( 2857 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2858 ) -> t.List[str]: 2859 return [ 2860 self.sql(expression, "offset") if fetch else self.sql(limit), 2861 self.sql(limit) if fetch else self.sql(expression, "offset"), 2862 ]
2869 def select_sql(self, expression: exp.Select) -> str: 2870 into = expression.args.get("into") 2871 if not self.SUPPORTS_SELECT_INTO and into: 2872 into.pop() 2873 2874 hint = self.sql(expression, "hint") 2875 distinct = self.sql(expression, "distinct") 2876 distinct = f" {distinct}" if distinct else "" 2877 kind = self.sql(expression, "kind") 2878 2879 limit = expression.args.get("limit") 2880 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2881 top = self.limit_sql(limit, top=True) 2882 limit.pop() 2883 else: 2884 top = "" 2885 2886 expressions = self.expressions(expression) 2887 2888 if kind: 2889 if kind in self.SELECT_KINDS: 2890 kind = f" AS {kind}" 2891 else: 2892 if kind == "STRUCT": 2893 expressions = self.expressions( 2894 sqls=[ 2895 self.sql( 2896 exp.Struct( 2897 expressions=[ 2898 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2899 if isinstance(e, exp.Alias) 2900 else e 2901 for e in expression.expressions 2902 ] 2903 ) 2904 ) 2905 ] 2906 ) 2907 kind = "" 2908 2909 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2910 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2911 2912 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2913 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2914 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2915 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2916 sql = self.query_modifiers( 2917 expression, 2918 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2919 self.sql(expression, "into", comment=False), 2920 self.sql(expression, "from_", comment=False), 2921 ) 2922 2923 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2924 if expression.args.get("with_"): 2925 sql = self.maybe_comment(sql, expression) 2926 expression.pop_comments() 2927 2928 sql = self.prepend_ctes(expression, sql) 2929 2930 if not self.SUPPORTS_SELECT_INTO and into: 2931 if into.args.get("temporary"): 2932 table_kind = " TEMPORARY" 2933 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2934 table_kind = " UNLOGGED" 2935 else: 2936 table_kind = "" 2937 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2938 2939 return sql
2951 def star_sql(self, expression: exp.Star) -> str: 2952 except_ = self.expressions(expression, key="except_", flat=True) 2953 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2954 replace = self.expressions(expression, key="replace", flat=True) 2955 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2956 rename = self.expressions(expression, key="rename", flat=True) 2957 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2958 return f"*{except_}{replace}{rename}"
2974 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2975 alias = self.sql(expression, "alias") 2976 alias = f"{sep}{alias}" if alias else "" 2977 sample = self.sql(expression, "sample") 2978 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2979 alias = f"{sample}{alias}" 2980 2981 # Set to None so it's not generated again by self.query_modifiers() 2982 expression.set("sample", None) 2983 2984 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2985 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2986 return self.prepend_ctes(expression, sql)
2992 def unnest_sql(self, expression: exp.Unnest) -> str: 2993 args = self.expressions(expression, flat=True) 2994 2995 alias = expression.args.get("alias") 2996 offset = expression.args.get("offset") 2997 2998 if self.UNNEST_WITH_ORDINALITY: 2999 if alias and isinstance(offset, exp.Expression): 3000 alias.append("columns", offset) 3001 3002 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3003 columns = alias.columns 3004 alias = self.sql(columns[0]) if columns else "" 3005 else: 3006 alias = self.sql(alias) 3007 3008 alias = f" AS {alias}" if alias else alias 3009 if self.UNNEST_WITH_ORDINALITY: 3010 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3011 else: 3012 if isinstance(offset, exp.Expression): 3013 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3014 elif offset: 3015 suffix = f"{alias} WITH OFFSET" 3016 else: 3017 suffix = alias 3018 3019 return f"UNNEST({args}){suffix}"
3028 def window_sql(self, expression: exp.Window) -> str: 3029 this = self.sql(expression, "this") 3030 partition = self.partition_by_sql(expression) 3031 order = expression.args.get("order") 3032 order = self.order_sql(order, flat=True) if order else "" 3033 spec = self.sql(expression, "spec") 3034 alias = self.sql(expression, "alias") 3035 over = self.sql(expression, "over") or "OVER" 3036 3037 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3038 3039 first = expression.args.get("first") 3040 if first is None: 3041 first = "" 3042 else: 3043 first = "FIRST" if first else "LAST" 3044 3045 if not partition and not order and not spec and alias: 3046 return f"{this} {alias}" 3047 3048 args = self.format_args( 3049 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3050 ) 3051 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
3057 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3058 kind = self.sql(expression, "kind") 3059 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3060 end = ( 3061 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3062 or "CURRENT ROW" 3063 ) 3064 3065 window_spec = f"{kind} BETWEEN {start} AND {end}" 3066 3067 exclude = self.sql(expression, "exclude") 3068 if exclude: 3069 if self.SUPPORTS_WINDOW_EXCLUDE: 3070 window_spec += f" EXCLUDE {exclude}" 3071 else: 3072 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3073 3074 return window_spec
3081 def between_sql(self, expression: exp.Between) -> str: 3082 this = self.sql(expression, "this") 3083 low = self.sql(expression, "low") 3084 high = self.sql(expression, "high") 3085 symmetric = expression.args.get("symmetric") 3086 3087 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3088 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3089 3090 flag = ( 3091 " SYMMETRIC" 3092 if symmetric 3093 else " ASYMMETRIC" 3094 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3095 else "" # silently drop ASYMMETRIC – semantics identical 3096 ) 3097 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]:
3099 def bracket_offset_expressions( 3100 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 3101 ) -> t.List[exp.Expression]: 3102 return apply_index_offset( 3103 expression.this, 3104 expression.expressions, 3105 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3106 dialect=self.dialect, 3107 )
3120 def any_sql(self, expression: exp.Any) -> str: 3121 this = self.sql(expression, "this") 3122 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3123 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3124 this = self.wrap(this) 3125 return f"ANY{this}" 3126 return f"ANY {this}"
3131 def case_sql(self, expression: exp.Case) -> str: 3132 this = self.sql(expression, "this") 3133 statements = [f"CASE {this}" if this else "CASE"] 3134 3135 for e in expression.args["ifs"]: 3136 statements.append(f"WHEN {self.sql(e, 'this')}") 3137 statements.append(f"THEN {self.sql(e, 'true')}") 3138 3139 default = self.sql(expression, "default") 3140 3141 if default: 3142 statements.append(f"ELSE {default}") 3143 3144 statements.append("END") 3145 3146 if self.pretty and self.too_wide(statements): 3147 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3148 3149 return " ".join(statements)
3161 def extract_sql(self, expression: exp.Extract) -> str: 3162 from sqlglot.dialects.dialect import map_date_part 3163 3164 this = ( 3165 map_date_part(expression.this, self.dialect) 3166 if self.NORMALIZE_EXTRACT_DATE_PARTS 3167 else expression.this 3168 ) 3169 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3170 expression_sql = self.sql(expression, "expression") 3171 3172 return f"EXTRACT({this_sql} FROM {expression_sql})"
3174 def trim_sql(self, expression: exp.Trim) -> str: 3175 trim_type = self.sql(expression, "position") 3176 3177 if trim_type == "LEADING": 3178 func_name = "LTRIM" 3179 elif trim_type == "TRAILING": 3180 func_name = "RTRIM" 3181 else: 3182 func_name = "TRIM" 3183 3184 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]:
3186 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3187 args = expression.expressions 3188 if isinstance(expression, exp.ConcatWs): 3189 args = args[1:] # Skip the delimiter 3190 3191 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3192 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3193 3194 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3195 3196 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3197 if not e.type: 3198 from sqlglot.optimizer.annotate_types import annotate_types 3199 3200 e = annotate_types(e, dialect=self.dialect) 3201 3202 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3203 return e 3204 3205 return exp.func("coalesce", e, exp.Literal.string("")) 3206 3207 args = [_wrap_with_coalesce(e) for e in args] 3208 3209 return args
3211 def concat_sql(self, expression: exp.Concat) -> str: 3212 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3213 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3214 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3215 # instead of coalescing them to empty string. 3216 from sqlglot.dialects.dialect import concat_to_dpipe_sql 3217 3218 return concat_to_dpipe_sql(self, expression) 3219 3220 expressions = self.convert_concat_args(expression) 3221 3222 # Some dialects don't allow a single-argument CONCAT call 3223 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3224 return self.sql(expressions[0]) 3225 3226 return self.func("CONCAT", *expressions)
3237 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3238 expressions = self.expressions(expression, flat=True) 3239 expressions = f" ({expressions})" if expressions else "" 3240 reference = self.sql(expression, "reference") 3241 reference = f" {reference}" if reference else "" 3242 delete = self.sql(expression, "delete") 3243 delete = f" ON DELETE {delete}" if delete else "" 3244 update = self.sql(expression, "update") 3245 update = f" ON UPDATE {update}" if update else "" 3246 options = self.expressions(expression, key="options", flat=True, sep=" ") 3247 options = f" {options}" if options else "" 3248 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3250 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3251 this = self.sql(expression, "this") 3252 this = f" {this}" if this else "" 3253 expressions = self.expressions(expression, flat=True) 3254 include = self.sql(expression, "include") 3255 options = self.expressions(expression, key="options", flat=True, sep=" ") 3256 options = f" {options}" if options else "" 3257 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3262 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3263 if self.MATCH_AGAINST_TABLE_PREFIX: 3264 expressions = [] 3265 for expr in expression.expressions: 3266 if isinstance(expr, exp.Table): 3267 expressions.append(f"TABLE {self.sql(expr)}") 3268 else: 3269 expressions.append(expr) 3270 else: 3271 expressions = expression.expressions 3272 3273 modifier = expression.args.get("modifier") 3274 modifier = f" {modifier}" if modifier else "" 3275 return ( 3276 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3277 )
3282 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3283 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3284 3285 if expression.args.get("escape"): 3286 path = self.escape_str(path) 3287 3288 if self.QUOTE_JSON_PATH: 3289 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3290 3291 return path
3293 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3294 if isinstance(expression, exp.JSONPathPart): 3295 transform = self.TRANSFORMS.get(expression.__class__) 3296 if not callable(transform): 3297 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3298 return "" 3299 3300 return transform(self, expression) 3301 3302 if isinstance(expression, int): 3303 return str(expression) 3304 3305 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3306 escaped = expression.replace("'", "\\'") 3307 escaped = f"\\'{expression}\\'" 3308 else: 3309 escaped = expression.replace('"', '\\"') 3310 escaped = f'"{escaped}"' 3311 3312 return escaped
3317 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3318 # Output the Teradata column FORMAT override. 3319 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3320 this = self.sql(expression, "this") 3321 fmt = self.sql(expression, "format") 3322 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3324 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3325 null_handling = expression.args.get("null_handling") 3326 null_handling = f" {null_handling}" if null_handling else "" 3327 3328 unique_keys = expression.args.get("unique_keys") 3329 if unique_keys is not None: 3330 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3331 else: 3332 unique_keys = "" 3333 3334 return_type = self.sql(expression, "return_type") 3335 return_type = f" RETURNING {return_type}" if return_type else "" 3336 encoding = self.sql(expression, "encoding") 3337 encoding = f" ENCODING {encoding}" if encoding else "" 3338 3339 return self.func( 3340 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3341 *expression.expressions, 3342 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3343 )
3348 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3349 null_handling = expression.args.get("null_handling") 3350 null_handling = f" {null_handling}" if null_handling else "" 3351 return_type = self.sql(expression, "return_type") 3352 return_type = f" RETURNING {return_type}" if return_type else "" 3353 strict = " STRICT" if expression.args.get("strict") else "" 3354 return self.func( 3355 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3356 )
3358 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3359 this = self.sql(expression, "this") 3360 order = self.sql(expression, "order") 3361 null_handling = expression.args.get("null_handling") 3362 null_handling = f" {null_handling}" if null_handling else "" 3363 return_type = self.sql(expression, "return_type") 3364 return_type = f" RETURNING {return_type}" if return_type else "" 3365 strict = " STRICT" if expression.args.get("strict") else "" 3366 return self.func( 3367 "JSON_ARRAYAGG", 3368 this, 3369 suffix=f"{order}{null_handling}{return_type}{strict})", 3370 )
3372 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3373 path = self.sql(expression, "path") 3374 path = f" PATH {path}" if path else "" 3375 nested_schema = self.sql(expression, "nested_schema") 3376 3377 if nested_schema: 3378 return f"NESTED{path} {nested_schema}" 3379 3380 this = self.sql(expression, "this") 3381 kind = self.sql(expression, "kind") 3382 kind = f" {kind}" if kind else "" 3383 3384 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3385 return f"{this}{kind}{path}{ordinality}"
3390 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3391 this = self.sql(expression, "this") 3392 path = self.sql(expression, "path") 3393 path = f", {path}" if path else "" 3394 error_handling = expression.args.get("error_handling") 3395 error_handling = f" {error_handling}" if error_handling else "" 3396 empty_handling = expression.args.get("empty_handling") 3397 empty_handling = f" {empty_handling}" if empty_handling else "" 3398 schema = self.sql(expression, "schema") 3399 return self.func( 3400 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3401 )
3403 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3404 this = self.sql(expression, "this") 3405 kind = self.sql(expression, "kind") 3406 path = self.sql(expression, "path") 3407 path = f" {path}" if path else "" 3408 as_json = " AS JSON" if expression.args.get("as_json") else "" 3409 return f"{this} {kind}{path}{as_json}"
3411 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3412 this = self.sql(expression, "this") 3413 path = self.sql(expression, "path") 3414 path = f", {path}" if path else "" 3415 expressions = self.expressions(expression) 3416 with_ = ( 3417 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3418 if expressions 3419 else "" 3420 ) 3421 return f"OPENJSON({this}{path}){with_}"
3423 def in_sql(self, expression: exp.In) -> str: 3424 query = expression.args.get("query") 3425 unnest = expression.args.get("unnest") 3426 field = expression.args.get("field") 3427 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3428 3429 if query: 3430 in_sql = self.sql(query) 3431 elif unnest: 3432 in_sql = self.in_unnest_op(unnest) 3433 elif field: 3434 in_sql = self.sql(field) 3435 else: 3436 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3437 3438 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3443 def interval_sql(self, expression: exp.Interval) -> str: 3444 unit_expression = expression.args.get("unit") 3445 unit = self.sql(unit_expression) if unit_expression else "" 3446 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3447 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3448 unit = f" {unit}" if unit else "" 3449 3450 if self.SINGLE_STRING_INTERVAL: 3451 this = expression.this.name if expression.this else "" 3452 if this: 3453 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3454 return f"INTERVAL '{this}'{unit}" 3455 return f"INTERVAL '{this}{unit}'" 3456 return f"INTERVAL{unit}" 3457 3458 this = self.sql(expression, "this") 3459 if this: 3460 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3461 this = f" {this}" if unwrapped else f" ({this})" 3462 3463 return f"INTERVAL{this}{unit}"
3468 def reference_sql(self, expression: exp.Reference) -> str: 3469 this = self.sql(expression, "this") 3470 expressions = self.expressions(expression, flat=True) 3471 expressions = f"({expressions})" if expressions else "" 3472 options = self.expressions(expression, key="options", flat=True, sep=" ") 3473 options = f" {options}" if options else "" 3474 return f"REFERENCES {this}{expressions}{options}"
3476 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3477 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3478 parent = expression.parent 3479 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3480 return self.func( 3481 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3482 )
3502 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3503 alias = expression.args["alias"] 3504 3505 parent = expression.parent 3506 pivot = parent and parent.parent 3507 3508 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3509 identifier_alias = isinstance(alias, exp.Identifier) 3510 literal_alias = isinstance(alias, exp.Literal) 3511 3512 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3513 alias.replace(exp.Literal.string(alias.output_name)) 3514 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3515 alias.replace(exp.to_identifier(alias.output_name)) 3516 3517 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:
3555 def connector_sql( 3556 self, 3557 expression: exp.Connector, 3558 op: str, 3559 stack: t.Optional[t.List[str | exp.Expression]] = None, 3560 ) -> str: 3561 if stack is not None: 3562 if expression.expressions: 3563 stack.append(self.expressions(expression, sep=f" {op} ")) 3564 else: 3565 stack.append(expression.right) 3566 if expression.comments and self.comments: 3567 for comment in expression.comments: 3568 if comment: 3569 op += f" /*{self.sanitize_comment(comment)}*/" 3570 stack.extend((op, expression.left)) 3571 return op 3572 3573 stack = [expression] 3574 sqls: t.List[str] = [] 3575 ops = set() 3576 3577 while stack: 3578 node = stack.pop() 3579 if isinstance(node, exp.Connector): 3580 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3581 else: 3582 sql = self.sql(node) 3583 if sqls and sqls[-1] in ops: 3584 sqls[-1] += f" {sql}" 3585 else: 3586 sqls.append(sql) 3587 3588 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3589 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3609 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3610 format_sql = self.sql(expression, "format") 3611 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3612 to_sql = self.sql(expression, "to") 3613 to_sql = f" {to_sql}" if to_sql else "" 3614 action = self.sql(expression, "action") 3615 action = f" {action}" if action else "" 3616 default = self.sql(expression, "default") 3617 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3618 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3636 def comment_sql(self, expression: exp.Comment) -> str: 3637 this = self.sql(expression, "this") 3638 kind = expression.args["kind"] 3639 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3640 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3641 expression_sql = self.sql(expression, "expression") 3642 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3644 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3645 this = self.sql(expression, "this") 3646 delete = " DELETE" if expression.args.get("delete") else "" 3647 recompress = self.sql(expression, "recompress") 3648 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3649 to_disk = self.sql(expression, "to_disk") 3650 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3651 to_volume = self.sql(expression, "to_volume") 3652 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3653 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3655 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3656 where = self.sql(expression, "where") 3657 group = self.sql(expression, "group") 3658 aggregates = self.expressions(expression, key="aggregates") 3659 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3660 3661 if not (where or group or aggregates) and len(expression.expressions) == 1: 3662 return f"TTL {self.expressions(expression, flat=True)}" 3663 3664 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3683 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3684 this = self.sql(expression, "this") 3685 3686 dtype = self.sql(expression, "dtype") 3687 if dtype: 3688 collate = self.sql(expression, "collate") 3689 collate = f" COLLATE {collate}" if collate else "" 3690 using = self.sql(expression, "using") 3691 using = f" USING {using}" if using else "" 3692 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3693 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3694 3695 default = self.sql(expression, "default") 3696 if default: 3697 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3698 3699 comment = self.sql(expression, "comment") 3700 if comment: 3701 return f"ALTER COLUMN {this} COMMENT {comment}" 3702 3703 visible = expression.args.get("visible") 3704 if visible: 3705 return f"ALTER COLUMN {this} SET {visible}" 3706 3707 allow_null = expression.args.get("allow_null") 3708 drop = expression.args.get("drop") 3709 3710 if not drop and not allow_null: 3711 self.unsupported("Unsupported ALTER COLUMN syntax") 3712 3713 if allow_null is not None: 3714 keyword = "DROP" if drop else "SET" 3715 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3716 3717 return f"ALTER COLUMN {this} DROP DEFAULT"
3733 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3734 compound = " COMPOUND" if expression.args.get("compound") else "" 3735 this = self.sql(expression, "this") 3736 expressions = self.expressions(expression, flat=True) 3737 expressions = f"({expressions})" if expressions else "" 3738 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.AlterRename, include_to: bool = True) -> str:
3740 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3741 if not self.RENAME_TABLE_WITH_DB: 3742 # Remove db from tables 3743 expression = expression.transform( 3744 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3745 ).assert_is(exp.AlterRename) 3746 this = self.sql(expression, "this") 3747 to_kw = " TO" if include_to else "" 3748 return f"RENAME{to_kw} {this}"
3763 def alter_sql(self, expression: exp.Alter) -> str: 3764 actions = expression.args["actions"] 3765 3766 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3767 actions[0], exp.ColumnDef 3768 ): 3769 actions_sql = self.expressions(expression, key="actions", flat=True) 3770 actions_sql = f"ADD {actions_sql}" 3771 else: 3772 actions_list = [] 3773 for action in actions: 3774 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3775 action_sql = self.add_column_sql(action) 3776 else: 3777 action_sql = self.sql(action) 3778 if isinstance(action, exp.Query): 3779 action_sql = f"AS {action_sql}" 3780 3781 actions_list.append(action_sql) 3782 3783 actions_sql = self.format_args(*actions_list).lstrip("\n") 3784 3785 exists = " IF EXISTS" if expression.args.get("exists") else "" 3786 on_cluster = self.sql(expression, "cluster") 3787 on_cluster = f" {on_cluster}" if on_cluster else "" 3788 only = " ONLY" if expression.args.get("only") else "" 3789 options = self.expressions(expression, key="options") 3790 options = f", {options}" if options else "" 3791 kind = self.sql(expression, "kind") 3792 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3793 check = " WITH CHECK" if expression.args.get("check") else "" 3794 cascade = ( 3795 " CASCADE" 3796 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 3797 else "" 3798 ) 3799 this = self.sql(expression, "this") 3800 this = f" {this}" if this else "" 3801 3802 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
3809 def add_column_sql(self, expression: exp.Expression) -> str: 3810 sql = self.sql(expression) 3811 if isinstance(expression, exp.Schema): 3812 column_text = " COLUMNS" 3813 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3814 column_text = " COLUMN" 3815 else: 3816 column_text = "" 3817 3818 return f"ADD{column_text} {sql}"
3828 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3829 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3830 location = self.sql(expression, "location") 3831 location = f" {location}" if location else "" 3832 return f"ADD {exists}{self.sql(expression.this)}{location}"
3834 def distinct_sql(self, expression: exp.Distinct) -> str: 3835 this = self.expressions(expression, flat=True) 3836 3837 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3838 case = exp.case() 3839 for arg in expression.expressions: 3840 case = case.when(arg.is_(exp.null()), exp.null()) 3841 this = self.sql(case.else_(f"({this})")) 3842 3843 this = f" {this}" if this else "" 3844 3845 on = self.sql(expression, "on") 3846 on = f" ON {on}" if on else "" 3847 return f"DISTINCT{this}{on}"
3876 def div_sql(self, expression: exp.Div) -> str: 3877 l, r = expression.left, expression.right 3878 3879 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3880 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3881 3882 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3883 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3884 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3885 3886 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3887 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3888 return self.sql( 3889 exp.cast( 3890 l / r, 3891 to=exp.DataType.Type.BIGINT, 3892 ) 3893 ) 3894 3895 return self.binary(expression, "/")
4016 def log_sql(self, expression: exp.Log) -> str: 4017 this = expression.this 4018 expr = expression.expression 4019 4020 if self.dialect.LOG_BASE_FIRST is False: 4021 this, expr = expr, this 4022 elif self.dialect.LOG_BASE_FIRST is None and expr: 4023 if this.name in ("2", "10"): 4024 return self.func(f"LOG{this.name}", expr) 4025 4026 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4027 4028 return self.func("LOG", this, expr)
4037 def binary(self, expression: exp.Binary, op: str) -> str: 4038 sqls: t.List[str] = [] 4039 stack: t.List[t.Union[str, exp.Expression]] = [expression] 4040 binary_type = type(expression) 4041 4042 while stack: 4043 node = stack.pop() 4044 4045 if type(node) is binary_type: 4046 op_func = node.args.get("operator") 4047 if op_func: 4048 op = f"OPERATOR({self.sql(op_func)})" 4049 4050 stack.append(node.right) 4051 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4052 stack.append(node.left) 4053 else: 4054 sqls.append(self.sql(node)) 4055 4056 return "".join(sqls)
4065 def function_fallback_sql(self, expression: exp.Func) -> str: 4066 args = [] 4067 4068 for key in expression.arg_types: 4069 arg_value = expression.args.get(key) 4070 4071 if isinstance(arg_value, list): 4072 for value in arg_value: 4073 args.append(value) 4074 elif arg_value is not None: 4075 args.append(arg_value) 4076 4077 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4078 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4079 else: 4080 name = expression.sql_name() 4081 4082 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:
4084 def func( 4085 self, 4086 name: str, 4087 *args: t.Optional[exp.Expression | str], 4088 prefix: str = "(", 4089 suffix: str = ")", 4090 normalize: bool = True, 4091 ) -> str: 4092 name = self.normalize_func(name) if normalize else name 4093 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
4095 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 4096 arg_sqls = tuple( 4097 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4098 ) 4099 if self.pretty and self.too_wide(arg_sqls): 4100 return self.indent( 4101 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4102 ) 4103 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]:
4108 def format_time( 4109 self, 4110 expression: exp.Expression, 4111 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 4112 inverse_time_trie: t.Optional[t.Dict] = None, 4113 ) -> t.Optional[str]: 4114 return format_time( 4115 self.sql(expression, "format"), 4116 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4117 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4118 )
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:
4120 def expressions( 4121 self, 4122 expression: t.Optional[exp.Expression] = None, 4123 key: t.Optional[str] = None, 4124 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 4125 flat: bool = False, 4126 indent: bool = True, 4127 skip_first: bool = False, 4128 skip_last: bool = False, 4129 sep: str = ", ", 4130 prefix: str = "", 4131 dynamic: bool = False, 4132 new_line: bool = False, 4133 ) -> str: 4134 expressions = expression.args.get(key or "expressions") if expression else sqls 4135 4136 if not expressions: 4137 return "" 4138 4139 if flat: 4140 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4141 4142 num_sqls = len(expressions) 4143 result_sqls = [] 4144 4145 for i, e in enumerate(expressions): 4146 sql = self.sql(e, comment=False) 4147 if not sql: 4148 continue 4149 4150 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 4151 4152 if self.pretty: 4153 if self.leading_comma: 4154 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4155 else: 4156 result_sqls.append( 4157 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4158 ) 4159 else: 4160 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4161 4162 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4163 if new_line: 4164 result_sqls.insert(0, "") 4165 result_sqls.append("") 4166 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4167 else: 4168 result_sql = "".join(result_sqls) 4169 4170 return ( 4171 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4172 if indent 4173 else result_sql 4174 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
4176 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 4177 flat = flat or isinstance(expression.parent, exp.Properties) 4178 expressions_sql = self.expressions(expression, flat=flat) 4179 if flat: 4180 return f"{op} {expressions_sql}" 4181 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4183 def naked_property(self, expression: exp.Property) -> str: 4184 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4185 if not property_name: 4186 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4187 return f"{property_name} {self.sql(expression, 'this')}"
4195 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4196 this = self.sql(expression, "this") 4197 expressions = self.no_identify(self.expressions, expression) 4198 expressions = ( 4199 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4200 ) 4201 return f"{this}{expressions}" if expressions.strip() != "" else this
4211 def when_sql(self, expression: exp.When) -> str: 4212 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4213 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4214 condition = self.sql(expression, "condition") 4215 condition = f" AND {condition}" if condition else "" 4216 4217 then_expression = expression.args.get("then") 4218 if isinstance(then_expression, exp.Insert): 4219 this = self.sql(then_expression, "this") 4220 this = f"INSERT {this}" if this else "INSERT" 4221 then = self.sql(then_expression, "expression") 4222 then = f"{this} VALUES {then}" if then else this 4223 elif isinstance(then_expression, exp.Update): 4224 if isinstance(then_expression.args.get("expressions"), exp.Star): 4225 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4226 else: 4227 expressions_sql = self.expressions(then_expression) 4228 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4229 4230 else: 4231 then = self.sql(then_expression) 4232 return f"WHEN {matched}{source}{condition} THEN {then}"
4237 def merge_sql(self, expression: exp.Merge) -> str: 4238 table = expression.this 4239 table_alias = "" 4240 4241 hints = table.args.get("hints") 4242 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4243 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4244 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4245 4246 this = self.sql(table) 4247 using = f"USING {self.sql(expression, 'using')}" 4248 whens = self.sql(expression, "whens") 4249 4250 on = self.sql(expression, "on") 4251 on = f"ON {on}" if on else "" 4252 4253 if not on: 4254 on = self.expressions(expression, key="using_cond") 4255 on = f"USING ({on})" if on else "" 4256 4257 returning = self.sql(expression, "returning") 4258 if returning: 4259 whens = f"{whens}{returning}" 4260 4261 sep = self.sep() 4262 4263 return self.prepend_ctes( 4264 expression, 4265 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4266 )
4272 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4273 if not self.SUPPORTS_TO_NUMBER: 4274 self.unsupported("Unsupported TO_NUMBER function") 4275 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4276 4277 fmt = expression.args.get("format") 4278 if not fmt: 4279 self.unsupported("Conversion format is required for TO_NUMBER") 4280 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4281 4282 return self.func("TO_NUMBER", expression.this, fmt)
4284 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4285 this = self.sql(expression, "this") 4286 kind = self.sql(expression, "kind") 4287 settings_sql = self.expressions(expression, key="settings", sep=" ") 4288 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4289 return f"{this}({kind}{args})"
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
4310 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4311 expressions = self.expressions(expression, flat=True) 4312 expressions = f" {self.wrap(expressions)}" if expressions else "" 4313 buckets = self.sql(expression, "buckets") 4314 kind = self.sql(expression, "kind") 4315 buckets = f" BUCKETS {buckets}" if buckets else "" 4316 order = self.sql(expression, "order") 4317 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4322 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4323 expressions = self.expressions(expression, key="expressions", flat=True) 4324 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4325 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4326 buckets = self.sql(expression, "buckets") 4327 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4329 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4330 this = self.sql(expression, "this") 4331 having = self.sql(expression, "having") 4332 4333 if having: 4334 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4335 4336 return self.func("ANY_VALUE", this)
4338 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4339 transform = self.func("TRANSFORM", *expression.expressions) 4340 row_format_before = self.sql(expression, "row_format_before") 4341 row_format_before = f" {row_format_before}" if row_format_before else "" 4342 record_writer = self.sql(expression, "record_writer") 4343 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4344 using = f" USING {self.sql(expression, 'command_script')}" 4345 schema = self.sql(expression, "schema") 4346 schema = f" AS {schema}" if schema else "" 4347 row_format_after = self.sql(expression, "row_format_after") 4348 row_format_after = f" {row_format_after}" if row_format_after else "" 4349 record_reader = self.sql(expression, "record_reader") 4350 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4351 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4353 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4354 key_block_size = self.sql(expression, "key_block_size") 4355 if key_block_size: 4356 return f"KEY_BLOCK_SIZE = {key_block_size}" 4357 4358 using = self.sql(expression, "using") 4359 if using: 4360 return f"USING {using}" 4361 4362 parser = self.sql(expression, "parser") 4363 if parser: 4364 return f"WITH PARSER {parser}" 4365 4366 comment = self.sql(expression, "comment") 4367 if comment: 4368 return f"COMMENT {comment}" 4369 4370 visible = expression.args.get("visible") 4371 if visible is not None: 4372 return "VISIBLE" if visible else "INVISIBLE" 4373 4374 engine_attr = self.sql(expression, "engine_attr") 4375 if engine_attr: 4376 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4377 4378 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4379 if secondary_engine_attr: 4380 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4381 4382 self.unsupported("Unsupported index constraint option.") 4383 return ""
4389 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4390 kind = self.sql(expression, "kind") 4391 kind = f"{kind} INDEX" if kind else "INDEX" 4392 this = self.sql(expression, "this") 4393 this = f" {this}" if this else "" 4394 index_type = self.sql(expression, "index_type") 4395 index_type = f" USING {index_type}" if index_type else "" 4396 expressions = self.expressions(expression, flat=True) 4397 expressions = f" ({expressions})" if expressions else "" 4398 options = self.expressions(expression, key="options", sep=" ") 4399 options = f" {options}" if options else "" 4400 return f"{kind}{this}{index_type}{expressions}{options}"
4402 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4403 if self.NVL2_SUPPORTED: 4404 return self.function_fallback_sql(expression) 4405 4406 case = exp.Case().when( 4407 expression.this.is_(exp.null()).not_(copy=False), 4408 expression.args["true"], 4409 copy=False, 4410 ) 4411 else_cond = expression.args.get("false") 4412 if else_cond: 4413 case.else_(else_cond, copy=False) 4414 4415 return self.sql(case)
4417 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4418 this = self.sql(expression, "this") 4419 expr = self.sql(expression, "expression") 4420 position = self.sql(expression, "position") 4421 position = f", {position}" if position else "" 4422 iterator = self.sql(expression, "iterator") 4423 condition = self.sql(expression, "condition") 4424 condition = f" IF {condition}" if condition else "" 4425 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4460 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4461 this_sql = self.sql(expression, "this") 4462 if isinstance(expression.this, exp.Table): 4463 this_sql = f"TABLE {this_sql}" 4464 4465 return self.func( 4466 "FEATURES_AT_TIME", 4467 this_sql, 4468 expression.args.get("time"), 4469 expression.args.get("num_rows"), 4470 expression.args.get("ignore_feature_nulls"), 4471 )
4473 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4474 this_sql = self.sql(expression, "this") 4475 if isinstance(expression.this, exp.Table): 4476 this_sql = f"TABLE {this_sql}" 4477 4478 query_table = self.sql(expression, "query_table") 4479 if isinstance(expression.args["query_table"], exp.Table): 4480 query_table = f"TABLE {query_table}" 4481 4482 return self.func( 4483 "VECTOR_SEARCH", 4484 this_sql, 4485 expression.args.get("column_to_search"), 4486 query_table, 4487 expression.args.get("query_column_to_search"), 4488 expression.args.get("top_k"), 4489 expression.args.get("distance_type"), 4490 expression.args.get("options"), 4491 )
4503 def toarray_sql(self, expression: exp.ToArray) -> str: 4504 arg = expression.this 4505 if not arg.type: 4506 from sqlglot.optimizer.annotate_types import annotate_types 4507 4508 arg = annotate_types(arg, dialect=self.dialect) 4509 4510 if arg.is_type(exp.DataType.Type.ARRAY): 4511 return self.sql(arg) 4512 4513 cond_for_null = arg.is_(exp.null()) 4514 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4516 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4517 this = expression.this 4518 time_format = self.format_time(expression) 4519 4520 if time_format: 4521 return self.sql( 4522 exp.cast( 4523 exp.StrToTime(this=this, format=expression.args["format"]), 4524 exp.DataType.Type.TIME, 4525 ) 4526 ) 4527 4528 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4529 return self.sql(this) 4530 4531 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4533 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4534 this = expression.this 4535 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4536 return self.sql(this) 4537 4538 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4540 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4541 this = expression.this 4542 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4543 return self.sql(this) 4544 4545 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4547 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4548 this = expression.this 4549 time_format = self.format_time(expression) 4550 safe = expression.args.get("safe") 4551 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4552 return self.sql( 4553 exp.cast( 4554 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4555 exp.DataType.Type.DATE, 4556 ) 4557 ) 4558 4559 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4560 return self.sql(this) 4561 4562 if safe: 4563 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DataType.Type.DATE))) 4564 4565 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4577 def lastday_sql(self, expression: exp.LastDay) -> str: 4578 if self.LAST_DAY_SUPPORTS_DATE_PART: 4579 return self.function_fallback_sql(expression) 4580 4581 unit = expression.text("unit") 4582 if unit and unit != "MONTH": 4583 self.unsupported("Date parts are not supported in LAST_DAY.") 4584 4585 return self.func("LAST_DAY", expression.this)
4594 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4595 if self.CAN_IMPLEMENT_ARRAY_ANY: 4596 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4597 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4598 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4599 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4600 4601 from sqlglot.dialects import Dialect 4602 4603 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4604 if self.dialect.__class__ != Dialect: 4605 self.unsupported("ARRAY_ANY is unsupported") 4606 4607 return self.function_fallback_sql(expression)
4609 def struct_sql(self, expression: exp.Struct) -> str: 4610 expression.set( 4611 "expressions", 4612 [ 4613 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4614 if isinstance(e, exp.PropertyEQ) 4615 else e 4616 for e in expression.expressions 4617 ], 4618 ) 4619 4620 return self.function_fallback_sql(expression)
4628 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4629 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4630 tables = f" {self.expressions(expression)}" 4631 4632 exists = " IF EXISTS" if expression.args.get("exists") else "" 4633 4634 on_cluster = self.sql(expression, "cluster") 4635 on_cluster = f" {on_cluster}" if on_cluster else "" 4636 4637 identity = self.sql(expression, "identity") 4638 identity = f" {identity} IDENTITY" if identity else "" 4639 4640 option = self.sql(expression, "option") 4641 option = f" {option}" if option else "" 4642 4643 partition = self.sql(expression, "partition") 4644 partition = f" {partition}" if partition else "" 4645 4646 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4650 def convert_sql(self, expression: exp.Convert) -> str: 4651 to = expression.this 4652 value = expression.expression 4653 style = expression.args.get("style") 4654 safe = expression.args.get("safe") 4655 strict = expression.args.get("strict") 4656 4657 if not to or not value: 4658 return "" 4659 4660 # Retrieve length of datatype and override to default if not specified 4661 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4662 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4663 4664 transformed: t.Optional[exp.Expression] = None 4665 cast = exp.Cast if strict else exp.TryCast 4666 4667 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4668 if isinstance(style, exp.Literal) and style.is_int: 4669 from sqlglot.dialects.tsql import TSQL 4670 4671 style_value = style.name 4672 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4673 if not converted_style: 4674 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4675 4676 fmt = exp.Literal.string(converted_style) 4677 4678 if to.this == exp.DataType.Type.DATE: 4679 transformed = exp.StrToDate(this=value, format=fmt) 4680 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4681 transformed = exp.StrToTime(this=value, format=fmt) 4682 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4683 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4684 elif to.this == exp.DataType.Type.TEXT: 4685 transformed = exp.TimeToStr(this=value, format=fmt) 4686 4687 if not transformed: 4688 transformed = cast(this=value, to=to, safe=safe) 4689 4690 return self.sql(transformed)
4758 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4759 option = self.sql(expression, "this") 4760 4761 if expression.expressions: 4762 upper = option.upper() 4763 4764 # Snowflake FILE_FORMAT options are separated by whitespace 4765 sep = " " if upper == "FILE_FORMAT" else ", " 4766 4767 # Databricks copy/format options do not set their list of values with EQ 4768 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4769 values = self.expressions(expression, flat=True, sep=sep) 4770 return f"{option}{op}({values})" 4771 4772 value = self.sql(expression, "expression") 4773 4774 if not value: 4775 return option 4776 4777 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4778 4779 return f"{option}{op}{value}"
4781 def credentials_sql(self, expression: exp.Credentials) -> str: 4782 cred_expr = expression.args.get("credentials") 4783 if isinstance(cred_expr, exp.Literal): 4784 # Redshift case: CREDENTIALS <string> 4785 credentials = self.sql(expression, "credentials") 4786 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4787 else: 4788 # Snowflake case: CREDENTIALS = (...) 4789 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4790 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4791 4792 storage = self.sql(expression, "storage") 4793 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4794 4795 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4796 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4797 4798 iam_role = self.sql(expression, "iam_role") 4799 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4800 4801 region = self.sql(expression, "region") 4802 region = f" REGION {region}" if region else "" 4803 4804 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4806 def copy_sql(self, expression: exp.Copy) -> str: 4807 this = self.sql(expression, "this") 4808 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4809 4810 credentials = self.sql(expression, "credentials") 4811 credentials = self.seg(credentials) if credentials else "" 4812 files = self.expressions(expression, key="files", flat=True) 4813 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4814 4815 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4816 params = self.expressions( 4817 expression, 4818 key="params", 4819 sep=sep, 4820 new_line=True, 4821 skip_last=True, 4822 skip_first=True, 4823 indent=self.COPY_PARAMS_ARE_WRAPPED, 4824 ) 4825 4826 if params: 4827 if self.COPY_PARAMS_ARE_WRAPPED: 4828 params = f" WITH ({params})" 4829 elif not self.pretty and (files or credentials): 4830 params = f" {params}" 4831 4832 return f"COPY{this}{kind} {files}{credentials}{params}"
4837 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4838 on_sql = "ON" if expression.args.get("on") else "OFF" 4839 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4840 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4841 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4842 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4843 4844 if filter_col or retention_period: 4845 on_sql = self.func("ON", filter_col, retention_period) 4846 4847 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4849 def maskingpolicycolumnconstraint_sql( 4850 self, expression: exp.MaskingPolicyColumnConstraint 4851 ) -> str: 4852 this = self.sql(expression, "this") 4853 expressions = self.expressions(expression, flat=True) 4854 expressions = f" USING ({expressions})" if expressions else "" 4855 return f"MASKING POLICY {this}{expressions}"
4865 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4866 this = self.sql(expression, "this") 4867 expr = expression.expression 4868 4869 if isinstance(expr, exp.Func): 4870 # T-SQL's CLR functions are case sensitive 4871 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4872 else: 4873 expr = self.sql(expression, "expression") 4874 4875 return self.scope_resolution(expr, this)
4883 def rand_sql(self, expression: exp.Rand) -> str: 4884 lower = self.sql(expression, "lower") 4885 upper = self.sql(expression, "upper") 4886 4887 if lower and upper: 4888 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4889 return self.func("RAND", expression.this)
4891 def changes_sql(self, expression: exp.Changes) -> str: 4892 information = self.sql(expression, "information") 4893 information = f"INFORMATION => {information}" 4894 at_before = self.sql(expression, "at_before") 4895 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4896 end = self.sql(expression, "end") 4897 end = f"{self.seg('')}{end}" if end else "" 4898 4899 return f"CHANGES ({information}){at_before}{end}"
4901 def pad_sql(self, expression: exp.Pad) -> str: 4902 prefix = "L" if expression.args.get("is_left") else "R" 4903 4904 fill_pattern = self.sql(expression, "fill_pattern") or None 4905 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4906 fill_pattern = "' '" 4907 4908 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4914 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4915 generate_series = exp.GenerateSeries(**expression.args) 4916 4917 parent = expression.parent 4918 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4919 parent = parent.parent 4920 4921 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4922 return self.sql(exp.Unnest(expressions=[generate_series])) 4923 4924 if isinstance(parent, exp.Select): 4925 self.unsupported("GenerateSeries projection unnesting is not supported.") 4926 4927 return self.sql(generate_series)
4929 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4930 if self.SUPPORTS_CONVERT_TIMEZONE: 4931 return self.function_fallback_sql(expression) 4932 4933 source_tz = expression.args.get("source_tz") 4934 target_tz = expression.args.get("target_tz") 4935 timestamp = expression.args.get("timestamp") 4936 4937 if source_tz and timestamp: 4938 timestamp = exp.AtTimeZone( 4939 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4940 ) 4941 4942 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4943 4944 return self.sql(expr)
4946 def json_sql(self, expression: exp.JSON) -> str: 4947 this = self.sql(expression, "this") 4948 this = f" {this}" if this else "" 4949 4950 _with = expression.args.get("with_") 4951 4952 if _with is None: 4953 with_sql = "" 4954 elif not _with: 4955 with_sql = " WITHOUT" 4956 else: 4957 with_sql = " WITH" 4958 4959 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4960 4961 return f"JSON{this}{with_sql}{unique_sql}"
4963 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4964 def _generate_on_options(arg: t.Any) -> str: 4965 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4966 4967 path = self.sql(expression, "path") 4968 returning = self.sql(expression, "returning") 4969 returning = f" RETURNING {returning}" if returning else "" 4970 4971 on_condition = self.sql(expression, "on_condition") 4972 on_condition = f" {on_condition}" if on_condition else "" 4973 4974 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4976 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4977 else_ = "ELSE " if expression.args.get("else_") else "" 4978 condition = self.sql(expression, "expression") 4979 condition = f"WHEN {condition} THEN " if condition else else_ 4980 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4981 return f"{condition}{insert}"
4989 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4990 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4991 empty = expression.args.get("empty") 4992 empty = ( 4993 f"DEFAULT {empty} ON EMPTY" 4994 if isinstance(empty, exp.Expression) 4995 else self.sql(expression, "empty") 4996 ) 4997 4998 error = expression.args.get("error") 4999 error = ( 5000 f"DEFAULT {error} ON ERROR" 5001 if isinstance(error, exp.Expression) 5002 else self.sql(expression, "error") 5003 ) 5004 5005 if error and empty: 5006 error = ( 5007 f"{empty} {error}" 5008 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5009 else f"{error} {empty}" 5010 ) 5011 empty = "" 5012 5013 null = self.sql(expression, "null") 5014 5015 return f"{empty}{error}{null}"
5021 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5022 this = self.sql(expression, "this") 5023 path = self.sql(expression, "path") 5024 5025 passing = self.expressions(expression, "passing") 5026 passing = f" PASSING {passing}" if passing else "" 5027 5028 on_condition = self.sql(expression, "on_condition") 5029 on_condition = f" {on_condition}" if on_condition else "" 5030 5031 path = f"{path}{passing}{on_condition}" 5032 5033 return self.func("JSON_EXISTS", this, path)
5158 def overlay_sql(self, expression: exp.Overlay): 5159 this = self.sql(expression, "this") 5160 expr = self.sql(expression, "expression") 5161 from_sql = self.sql(expression, "from_") 5162 for_sql = self.sql(expression, "for_") 5163 for_sql = f" FOR {for_sql}" if for_sql else "" 5164 5165 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
5172 def string_sql(self, expression: exp.String) -> str: 5173 this = expression.this 5174 zone = expression.args.get("zone") 5175 5176 if zone: 5177 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5178 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5179 # set for source_tz to transpile the time conversion before the STRING cast 5180 this = exp.ConvertTimezone( 5181 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5182 ) 5183 5184 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
5194 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5195 filler = self.sql(expression, "this") 5196 filler = f" {filler}" if filler else "" 5197 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5198 return f"TRUNCATE{filler} {with_count}"
5200 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5201 if self.SUPPORTS_UNIX_SECONDS: 5202 return self.function_fallback_sql(expression) 5203 5204 start_ts = exp.cast( 5205 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 5206 ) 5207 5208 return self.sql( 5209 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5210 )
5212 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5213 dim = expression.expression 5214 5215 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5216 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5217 if not (dim.is_int and dim.name == "1"): 5218 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5219 dim = None 5220 5221 # If dimension is required but not specified, default initialize it 5222 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5223 dim = exp.Literal.number(1) 5224 5225 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5227 def attach_sql(self, expression: exp.Attach) -> str: 5228 this = self.sql(expression, "this") 5229 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5230 expressions = self.expressions(expression) 5231 expressions = f" ({expressions})" if expressions else "" 5232 5233 return f"ATTACH{exists_sql} {this}{expressions}"
5235 def detach_sql(self, expression: exp.Detach) -> str: 5236 this = self.sql(expression, "this") 5237 # the DATABASE keyword is required if IF EXISTS is set 5238 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5239 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5240 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5241 5242 return f"DETACH{exists_sql} {this}"
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
5255 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5256 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5257 encode = f"{encode} {self.sql(expression, 'this')}" 5258 5259 properties = expression.args.get("properties") 5260 if properties: 5261 encode = f"{encode} {self.properties(properties)}" 5262 5263 return encode
5265 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5266 this = self.sql(expression, "this") 5267 include = f"INCLUDE {this}" 5268 5269 column_def = self.sql(expression, "column_def") 5270 if column_def: 5271 include = f"{include} {column_def}" 5272 5273 alias = self.sql(expression, "alias") 5274 if alias: 5275 include = f"{include} AS {alias}" 5276 5277 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
5290 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5291 partitions = self.expressions(expression, "partition_expressions") 5292 create = self.expressions(expression, "create_expressions") 5293 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
5295 def partitionbyrangepropertydynamic_sql( 5296 self, expression: exp.PartitionByRangePropertyDynamic 5297 ) -> str: 5298 start = self.sql(expression, "start") 5299 end = self.sql(expression, "end") 5300 5301 every = expression.args["every"] 5302 if isinstance(every, exp.Interval) and every.this.is_string: 5303 every.this.replace(exp.Literal.number(every.name)) 5304 5305 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5318 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5319 kind = self.sql(expression, "kind") 5320 option = self.sql(expression, "option") 5321 option = f" {option}" if option else "" 5322 this = self.sql(expression, "this") 5323 this = f" {this}" if this else "" 5324 columns = self.expressions(expression) 5325 columns = f" {columns}" if columns else "" 5326 return f"{kind}{option} STATISTICS{this}{columns}"
5328 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5329 this = self.sql(expression, "this") 5330 columns = self.expressions(expression) 5331 inner_expression = self.sql(expression, "expression") 5332 inner_expression = f" {inner_expression}" if inner_expression else "" 5333 update_options = self.sql(expression, "update_options") 5334 update_options = f" {update_options} UPDATE" if update_options else "" 5335 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
5346 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5347 kind = self.sql(expression, "kind") 5348 this = self.sql(expression, "this") 5349 this = f" {this}" if this else "" 5350 inner_expression = self.sql(expression, "expression") 5351 return f"VALIDATE {kind}{this}{inner_expression}"
5353 def analyze_sql(self, expression: exp.Analyze) -> str: 5354 options = self.expressions(expression, key="options", sep=" ") 5355 options = f" {options}" if options else "" 5356 kind = self.sql(expression, "kind") 5357 kind = f" {kind}" if kind else "" 5358 this = self.sql(expression, "this") 5359 this = f" {this}" if this else "" 5360 mode = self.sql(expression, "mode") 5361 mode = f" {mode}" if mode else "" 5362 properties = self.sql(expression, "properties") 5363 properties = f" {properties}" if properties else "" 5364 partition = self.sql(expression, "partition") 5365 partition = f" {partition}" if partition else "" 5366 inner_expression = self.sql(expression, "expression") 5367 inner_expression = f" {inner_expression}" if inner_expression else "" 5368 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5370 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5371 this = self.sql(expression, "this") 5372 namespaces = self.expressions(expression, key="namespaces") 5373 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5374 passing = self.expressions(expression, key="passing") 5375 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5376 columns = self.expressions(expression, key="columns") 5377 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5378 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5379 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5385 def export_sql(self, expression: exp.Export) -> str: 5386 this = self.sql(expression, "this") 5387 connection = self.sql(expression, "connection") 5388 connection = f"WITH CONNECTION {connection} " if connection else "" 5389 options = self.sql(expression, "options") 5390 return f"EXPORT DATA {connection}{options} AS {this}"
5395 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5396 variable = self.sql(expression, "this") 5397 default = self.sql(expression, "default") 5398 default = f" = {default}" if default else "" 5399 5400 kind = self.sql(expression, "kind") 5401 if isinstance(expression.args.get("kind"), exp.Schema): 5402 kind = f"TABLE {kind}" 5403 5404 return f"{variable} AS {kind}{default}"
5406 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5407 kind = self.sql(expression, "kind") 5408 this = self.sql(expression, "this") 5409 set = self.sql(expression, "expression") 5410 using = self.sql(expression, "using") 5411 using = f" USING {using}" if using else "" 5412 5413 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5414 5415 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5438 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5439 # Snowflake GET/PUT statements: 5440 # PUT <file> <internalStage> <properties> 5441 # GET <internalStage> <file> <properties> 5442 props = expression.args.get("properties") 5443 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5444 this = self.sql(expression, "this") 5445 target = self.sql(expression, "target") 5446 5447 if isinstance(expression, exp.Put): 5448 return f"PUT {this} {target}{props_sql}" 5449 else: 5450 return f"GET {target} {this}{props_sql}"
5458 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5459 if self.SUPPORTS_DECODE_CASE: 5460 return self.func("DECODE", *expression.expressions) 5461 5462 expression, *expressions = expression.expressions 5463 5464 ifs = [] 5465 for search, result in zip(expressions[::2], expressions[1::2]): 5466 if isinstance(search, exp.Literal): 5467 ifs.append(exp.If(this=expression.eq(search), true=result)) 5468 elif isinstance(search, exp.Null): 5469 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5470 else: 5471 if isinstance(search, exp.Binary): 5472 search = exp.paren(search) 5473 5474 cond = exp.or_( 5475 expression.eq(search), 5476 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5477 copy=False, 5478 ) 5479 ifs.append(exp.If(this=cond, true=result)) 5480 5481 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5482 return self.sql(case)
5484 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5485 this = self.sql(expression, "this") 5486 this = self.seg(this, sep="") 5487 dimensions = self.expressions( 5488 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5489 ) 5490 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5491 metrics = self.expressions( 5492 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5493 ) 5494 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5495 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5496 facts = self.seg(f"FACTS {facts}") if facts else "" 5497 where = self.sql(expression, "where") 5498 where = self.seg(f"WHERE {where}") if where else "" 5499 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5500 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5502 def getextract_sql(self, expression: exp.GetExtract) -> str: 5503 this = expression.this 5504 expr = expression.expression 5505 5506 if not this.type or not expression.type: 5507 from sqlglot.optimizer.annotate_types import annotate_types 5508 5509 this = annotate_types(this, dialect=self.dialect) 5510 5511 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5512 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5513 5514 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql(self, expression: sqlglot.expressions.RefreshTriggerProperty) -> str:
5531 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5532 method = self.sql(expression, "method") 5533 kind = expression.args.get("kind") 5534 if not kind: 5535 return f"REFRESH {method}" 5536 5537 every = self.sql(expression, "every") 5538 unit = self.sql(expression, "unit") 5539 every = f" EVERY {every} {unit}" if every else "" 5540 starts = self.sql(expression, "starts") 5541 starts = f" STARTS {starts}" if starts else "" 5542 5543 return f"REFRESH {method} ON {kind}{every}{starts}"
5552 def uuid_sql(self, expression: exp.Uuid) -> str: 5553 is_string = expression.args.get("is_string", False) 5554 uuid_func_sql = self.func("UUID") 5555 5556 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5557 return self.sql( 5558 exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect) 5559 ) 5560 5561 return uuid_func_sql
5563 def initcap_sql(self, expression: exp.Initcap) -> str: 5564 delimiters = expression.expression 5565 5566 if delimiters: 5567 # do not generate delimiters arg if we are round-tripping from default delimiters 5568 if ( 5569 delimiters.is_string 5570 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5571 ): 5572 delimiters = None 5573 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5574 self.unsupported("INITCAP does not support custom delimiters") 5575 delimiters = None 5576 5577 return self.func("INITCAP", expression.this, delimiters)
5587 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5588 this = expression.this.name.upper() 5589 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5590 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5591 return "WEEK" 5592 5593 return self.func("WEEK", expression.this)