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.expressions import apply_index_offset 12from sqlglot.helper import csv, name_sequence, seq_get 13from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 14from sqlglot.time import format_time 15from sqlglot.tokens import TokenType 16 17if t.TYPE_CHECKING: 18 from sqlglot._typing import E 19 from sqlglot.dialects.dialect import DialectType 20 21 G = t.TypeVar("G", bound="Generator") 22 GeneratorMethod = t.Callable[[G, E], str] 23 24logger = logging.getLogger("sqlglot") 25 26ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 27UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 28 29 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 62 63 64class _Generator(type): 65 def __new__(cls, clsname, bases, attrs): 66 klass = super().__new__(cls, clsname, bases, attrs) 67 68 # Remove transforms that correspond to unsupported JSONPathPart expressions 69 for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS: 70 klass.TRANSFORMS.pop(part, None) 71 72 return klass 73 74 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.EndStatement: lambda *_: "END", 150 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 151 exp.EphemeralColumnConstraint: lambda self, 152 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 153 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 154 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 155 exp.Except: lambda self, e: self.set_operations(e), 156 exp.ExternalProperty: lambda *_: "EXTERNAL", 157 exp.Floor: lambda self, e: self.ceil_floor(e), 158 exp.Get: lambda self, e: self.get_put_sql(e), 159 exp.GlobalProperty: lambda *_: "GLOBAL", 160 exp.HeapProperty: lambda *_: "HEAP", 161 exp.IcebergProperty: lambda *_: "ICEBERG", 162 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 163 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 164 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 165 exp.Intersect: lambda self, e: self.set_operations(e), 166 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 167 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 168 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 169 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 170 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 171 exp.LanguageProperty: lambda self, e: self.naked_property(e), 172 exp.LocationProperty: lambda self, e: self.naked_property(e), 173 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 174 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 175 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 176 exp.NonClusteredColumnConstraint: lambda self, 177 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 178 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 179 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 180 exp.OnCommitProperty: lambda _, 181 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 182 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 183 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 184 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 185 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 186 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 187 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 188 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 189 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 190 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 191 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 192 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 193 exp.ProjectionPolicyColumnConstraint: lambda self, 194 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 195 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 196 exp.Put: lambda self, e: self.get_put_sql(e), 197 exp.RemoteWithConnectionModelProperty: lambda self, 198 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 199 exp.ReturnsProperty: lambda self, e: ( 200 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 201 ), 202 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 203 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 204 exp.SecureProperty: lambda *_: "SECURE", 205 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 206 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 207 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 208 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 209 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 210 exp.SqlReadWriteProperty: lambda _, e: e.name, 211 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 212 exp.StabilityProperty: lambda _, e: e.name, 213 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 214 exp.StreamingTableProperty: lambda *_: "STREAMING", 215 exp.StrictProperty: lambda *_: "STRICT", 216 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 217 exp.TableColumn: lambda self, e: self.sql(e.this), 218 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 219 exp.TemporaryProperty: lambda *_: "TEMPORARY", 220 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 221 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 222 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 223 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 224 exp.TransientProperty: lambda *_: "TRANSIENT", 225 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 226 exp.Union: lambda self, e: self.set_operations(e), 227 exp.UnloggedProperty: lambda *_: "UNLOGGED", 228 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 229 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 230 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 231 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 232 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 233 exp.UtcTimestamp: lambda self, e: self.sql( 234 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 235 ), 236 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 237 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 238 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 239 exp.VolatileProperty: lambda *_: "VOLATILE", 240 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 241 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 242 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 243 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 244 exp.ForceProperty: lambda *_: "FORCE", 245 } 246 247 # Whether null ordering is supported in order by 248 # True: Full Support, None: No support, False: No support for certain cases 249 # such as window specifications, aggregate functions etc 250 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 251 252 # Whether ignore nulls is inside the agg or outside. 253 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 254 IGNORE_NULLS_IN_FUNC = False 255 256 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 257 LOCKING_READS_SUPPORTED = False 258 259 # Whether the EXCEPT and INTERSECT operations can return duplicates 260 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 261 262 # Wrap derived values in parens, usually standard but spark doesn't support it 263 WRAP_DERIVED_VALUES = True 264 265 # Whether create function uses an AS before the RETURN 266 CREATE_FUNCTION_RETURN_AS = True 267 268 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 269 MATCHED_BY_SOURCE = True 270 271 # Whether the INTERVAL expression works only with values like '1 day' 272 SINGLE_STRING_INTERVAL = False 273 274 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 275 INTERVAL_ALLOWS_PLURAL_FORM = True 276 277 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 278 LIMIT_FETCH = "ALL" 279 280 # Whether limit and fetch allows expresions or just limits 281 LIMIT_ONLY_LITERALS = False 282 283 # Whether a table is allowed to be renamed with a db 284 RENAME_TABLE_WITH_DB = True 285 286 # The separator for grouping sets and rollups 287 GROUPINGS_SEP = "," 288 289 # The string used for creating an index on a table 290 INDEX_ON = "ON" 291 292 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 293 INOUT_SEPARATOR = " " 294 295 # Whether join hints should be generated 296 JOIN_HINTS = True 297 298 # Whether directed joins are supported 299 DIRECTED_JOINS = False 300 301 # Whether table hints should be generated 302 TABLE_HINTS = True 303 304 # Whether query hints should be generated 305 QUERY_HINTS = True 306 307 # What kind of separator to use for query hints 308 QUERY_HINT_SEP = ", " 309 310 # Whether comparing against booleans (e.g. x IS TRUE) is supported 311 IS_BOOL_ALLOWED = True 312 313 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 314 DUPLICATE_KEY_UPDATE_WITH_SET = True 315 316 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 317 LIMIT_IS_TOP = False 318 319 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 320 RETURNING_END = True 321 322 # Whether to generate an unquoted value for EXTRACT's date part argument 323 EXTRACT_ALLOWS_QUOTES = True 324 325 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 326 TZ_TO_WITH_TIME_ZONE = False 327 328 # Whether the NVL2 function is supported 329 NVL2_SUPPORTED = True 330 331 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 332 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 333 334 # Whether VALUES statements can be used as derived tables. 335 # MySQL 5 and Redshift do not allow this, so when False, it will convert 336 # SELECT * VALUES into SELECT UNION 337 VALUES_AS_TABLE = True 338 339 # Whether the word COLUMN is included when adding a column with ALTER TABLE 340 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 341 342 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 343 UNNEST_WITH_ORDINALITY = True 344 345 # Whether FILTER (WHERE cond) can be used for conditional aggregation 346 AGGREGATE_FILTER_SUPPORTED = True 347 348 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 349 SEMI_ANTI_JOIN_WITH_SIDE = True 350 351 # Whether to include the type of a computed column in the CREATE DDL 352 COMPUTED_COLUMN_WITH_TYPE = True 353 354 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 355 SUPPORTS_TABLE_COPY = True 356 357 # Whether parentheses are required around the table sample's expression 358 TABLESAMPLE_REQUIRES_PARENS = True 359 360 # Whether a table sample clause's size needs to be followed by the ROWS keyword 361 TABLESAMPLE_SIZE_IS_ROWS = True 362 363 # The keyword(s) to use when generating a sample clause 364 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 365 366 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 367 TABLESAMPLE_WITH_METHOD = True 368 369 # The keyword to use when specifying the seed of a sample clause 370 TABLESAMPLE_SEED_KEYWORD = "SEED" 371 372 # Whether COLLATE is a function instead of a binary operator 373 COLLATE_IS_FUNC = False 374 375 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 376 DATA_TYPE_SPECIFIERS_ALLOWED = False 377 378 # Whether conditions require booleans WHERE x = 0 vs WHERE x 379 ENSURE_BOOLS = False 380 381 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 382 CTE_RECURSIVE_KEYWORD_REQUIRED = True 383 384 # Whether CONCAT requires >1 arguments 385 SUPPORTS_SINGLE_ARG_CONCAT = True 386 387 # Whether LAST_DAY function supports a date part argument 388 LAST_DAY_SUPPORTS_DATE_PART = True 389 390 # Whether named columns are allowed in table aliases 391 SUPPORTS_TABLE_ALIAS_COLUMNS = True 392 393 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 394 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 395 396 # What delimiter to use for separating JSON key/value pairs 397 JSON_KEY_VALUE_PAIR_SEP = ":" 398 399 # INSERT OVERWRITE TABLE x override 400 INSERT_OVERWRITE = " OVERWRITE TABLE" 401 402 # Whether the SELECT .. INTO syntax is used instead of CTAS 403 SUPPORTS_SELECT_INTO = False 404 405 # Whether UNLOGGED tables can be created 406 SUPPORTS_UNLOGGED_TABLES = False 407 408 # Whether the CREATE TABLE LIKE statement is supported 409 SUPPORTS_CREATE_TABLE_LIKE = True 410 411 # Whether the LikeProperty needs to be specified inside of the schema clause 412 LIKE_PROPERTY_INSIDE_SCHEMA = False 413 414 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 415 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 416 MULTI_ARG_DISTINCT = True 417 418 # Whether the JSON extraction operators expect a value of type JSON 419 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 420 421 # Whether bracketed keys like ["foo"] are supported in JSON paths 422 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 423 424 # Whether to escape keys using single quotes in JSON paths 425 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 426 427 # The JSONPathPart expressions supported by this dialect 428 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 429 430 # Whether any(f(x) for x in array) can be implemented by this dialect 431 CAN_IMPLEMENT_ARRAY_ANY = False 432 433 # Whether the function TO_NUMBER is supported 434 SUPPORTS_TO_NUMBER = True 435 436 # Whether EXCLUDE in window specification is supported 437 SUPPORTS_WINDOW_EXCLUDE = False 438 439 # Whether or not set op modifiers apply to the outer set op or select. 440 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 441 # True means limit 1 happens after the set op, False means it it happens on y. 442 SET_OP_MODIFIERS = True 443 444 # Whether parameters from COPY statement are wrapped in parentheses 445 COPY_PARAMS_ARE_WRAPPED = True 446 447 # Whether values of params are set with "=" token or empty space 448 COPY_PARAMS_EQ_REQUIRED = False 449 450 # Whether COPY statement has INTO keyword 451 COPY_HAS_INTO_KEYWORD = True 452 453 # Whether the conditional TRY(expression) function is supported 454 TRY_SUPPORTED = True 455 456 # Whether the UESCAPE syntax in unicode strings is supported 457 SUPPORTS_UESCAPE = True 458 459 # Function used to replace escaped unicode codes in unicode strings 460 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 461 462 # The keyword to use when generating a star projection with excluded columns 463 STAR_EXCEPT = "EXCEPT" 464 465 # The HEX function name 466 HEX_FUNC = "HEX" 467 468 # The keywords to use when prefixing & separating WITH based properties 469 WITH_PROPERTIES_PREFIX = "WITH" 470 471 # Whether to quote the generated expression of exp.JsonPath 472 QUOTE_JSON_PATH = True 473 474 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 475 PAD_FILL_PATTERN_IS_REQUIRED = False 476 477 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 478 SUPPORTS_EXPLODING_PROJECTIONS = True 479 480 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 481 ARRAY_CONCAT_IS_VAR_LEN = True 482 483 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 484 SUPPORTS_CONVERT_TIMEZONE = False 485 486 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 487 SUPPORTS_MEDIAN = True 488 489 # Whether UNIX_SECONDS(timestamp) is supported 490 SUPPORTS_UNIX_SECONDS = False 491 492 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 493 ALTER_SET_WRAPPED = False 494 495 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 496 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 497 # TODO: The normalization should be done by default once we've tested it across all dialects. 498 NORMALIZE_EXTRACT_DATE_PARTS = False 499 500 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 501 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 502 503 # The function name of the exp.ArraySize expression 504 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 505 506 # The syntax to use when altering the type of a column 507 ALTER_SET_TYPE = "SET DATA TYPE" 508 509 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 510 # None -> Doesn't support it at all 511 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 512 # True (Postgres) -> Explicitly requires it 513 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 514 515 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 516 SUPPORTS_DECODE_CASE = True 517 518 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 519 SUPPORTS_BETWEEN_FLAGS = False 520 521 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 522 SUPPORTS_LIKE_QUANTIFIERS = True 523 524 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 525 MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None 526 527 # Whether to include the VARIABLE keyword for SET assignments 528 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 529 530 # The keyword to use for default value assignment in DECLARE statements 531 DECLARE_DEFAULT_ASSIGNMENT = "=" 532 533 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 534 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 535 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 536 UPDATE_STATEMENT_SUPPORTS_FROM = True 537 538 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 539 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 540 541 TYPE_MAPPING = { 542 exp.DataType.Type.DATETIME2: "TIMESTAMP", 543 exp.DataType.Type.NCHAR: "CHAR", 544 exp.DataType.Type.NVARCHAR: "VARCHAR", 545 exp.DataType.Type.MEDIUMTEXT: "TEXT", 546 exp.DataType.Type.LONGTEXT: "TEXT", 547 exp.DataType.Type.TINYTEXT: "TEXT", 548 exp.DataType.Type.BLOB: "VARBINARY", 549 exp.DataType.Type.MEDIUMBLOB: "BLOB", 550 exp.DataType.Type.LONGBLOB: "BLOB", 551 exp.DataType.Type.TINYBLOB: "BLOB", 552 exp.DataType.Type.INET: "INET", 553 exp.DataType.Type.ROWVERSION: "VARBINARY", 554 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 555 } 556 557 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 558 559 TIME_PART_SINGULARS = { 560 "MICROSECONDS": "MICROSECOND", 561 "SECONDS": "SECOND", 562 "MINUTES": "MINUTE", 563 "HOURS": "HOUR", 564 "DAYS": "DAY", 565 "WEEKS": "WEEK", 566 "MONTHS": "MONTH", 567 "QUARTERS": "QUARTER", 568 "YEARS": "YEAR", 569 } 570 571 AFTER_HAVING_MODIFIER_TRANSFORMS = { 572 "cluster": lambda self, e: self.sql(e, "cluster"), 573 "distribute": lambda self, e: self.sql(e, "distribute"), 574 "sort": lambda self, e: self.sql(e, "sort"), 575 "windows": lambda self, e: ( 576 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 577 if e.args.get("windows") 578 else "" 579 ), 580 "qualify": lambda self, e: self.sql(e, "qualify"), 581 } 582 583 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 584 585 STRUCT_DELIMITER = ("<", ">") 586 587 PARAMETER_TOKEN = "@" 588 NAMED_PLACEHOLDER_TOKEN = ":" 589 590 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 591 592 PROPERTIES_LOCATION = { 593 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 594 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 595 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 596 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 599 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 600 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 601 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 604 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 608 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 609 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 610 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 611 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 612 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 613 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 614 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 615 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 617 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 620 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 621 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 622 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 623 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 624 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 625 exp.HeapProperty: exp.Properties.Location.POST_WITH, 626 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 628 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 629 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 630 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 631 exp.JournalProperty: exp.Properties.Location.POST_NAME, 632 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 633 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 634 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 635 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 636 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 637 exp.LogProperty: exp.Properties.Location.POST_NAME, 638 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 639 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 640 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 641 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 642 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 643 exp.Order: exp.Properties.Location.POST_SCHEMA, 644 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 645 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 646 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 647 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 648 exp.Property: exp.Properties.Location.POST_WITH, 649 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 650 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 651 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 652 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 653 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 654 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 655 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 656 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 657 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 658 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 659 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 660 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 661 exp.Set: exp.Properties.Location.POST_SCHEMA, 662 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 663 exp.SetProperty: exp.Properties.Location.POST_CREATE, 664 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 665 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 666 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 667 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 668 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 669 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 670 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 671 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 672 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 673 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 674 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 675 exp.Tags: exp.Properties.Location.POST_WITH, 676 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 677 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 678 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 679 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 680 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 681 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 682 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 683 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 684 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 685 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 686 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 687 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 688 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 689 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 690 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 691 } 692 693 # Keywords that can't be used as unquoted identifier names 694 RESERVED_KEYWORDS: t.Set[str] = set() 695 696 # Expressions whose comments are separated from them for better formatting 697 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 698 exp.Command, 699 exp.Create, 700 exp.Describe, 701 exp.Delete, 702 exp.Drop, 703 exp.From, 704 exp.Insert, 705 exp.Join, 706 exp.MultitableInserts, 707 exp.Order, 708 exp.Group, 709 exp.Having, 710 exp.Select, 711 exp.SetOperation, 712 exp.Update, 713 exp.Where, 714 exp.With, 715 ) 716 717 # Expressions that should not have their comments generated in maybe_comment 718 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 719 exp.Binary, 720 exp.SetOperation, 721 ) 722 723 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 724 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 725 exp.Column, 726 exp.Literal, 727 exp.Neg, 728 exp.Paren, 729 ) 730 731 PARAMETERIZABLE_TEXT_TYPES = { 732 exp.DataType.Type.NVARCHAR, 733 exp.DataType.Type.VARCHAR, 734 exp.DataType.Type.CHAR, 735 exp.DataType.Type.NCHAR, 736 } 737 738 # Expressions that need to have all CTEs under them bubbled up to them 739 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 740 741 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 742 743 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 744 745 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 746 747 __slots__ = ( 748 "pretty", 749 "identify", 750 "normalize", 751 "pad", 752 "_indent", 753 "normalize_functions", 754 "unsupported_level", 755 "max_unsupported", 756 "leading_comma", 757 "max_text_width", 758 "comments", 759 "dialect", 760 "unsupported_messages", 761 "_escaped_quote_end", 762 "_escaped_byte_quote_end", 763 "_escaped_identifier_end", 764 "_next_name", 765 "_identifier_start", 766 "_identifier_end", 767 "_quote_json_path_key_using_brackets", 768 ) 769 770 def __init__( 771 self, 772 pretty: t.Optional[bool] = None, 773 identify: str | bool = False, 774 normalize: bool = False, 775 pad: int = 2, 776 indent: int = 2, 777 normalize_functions: t.Optional[str | bool] = None, 778 unsupported_level: ErrorLevel = ErrorLevel.WARN, 779 max_unsupported: int = 3, 780 leading_comma: bool = False, 781 max_text_width: int = 80, 782 comments: bool = True, 783 dialect: DialectType = None, 784 ): 785 import sqlglot 786 from sqlglot.dialects import Dialect 787 788 self.pretty = pretty if pretty is not None else sqlglot.pretty 789 self.identify = identify 790 self.normalize = normalize 791 self.pad = pad 792 self._indent = indent 793 self.unsupported_level = unsupported_level 794 self.max_unsupported = max_unsupported 795 self.leading_comma = leading_comma 796 self.max_text_width = max_text_width 797 self.comments = comments 798 self.dialect = Dialect.get_or_raise(dialect) 799 800 # This is both a Dialect property and a Generator argument, so we prioritize the latter 801 self.normalize_functions = ( 802 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 803 ) 804 805 self.unsupported_messages: t.List[str] = [] 806 self._escaped_quote_end: str = ( 807 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 808 ) 809 self._escaped_byte_quote_end: str = ( 810 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 811 if self.dialect.BYTE_END 812 else "" 813 ) 814 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 815 816 self._next_name = name_sequence("_t") 817 818 self._identifier_start = self.dialect.IDENTIFIER_START 819 self._identifier_end = self.dialect.IDENTIFIER_END 820 821 self._quote_json_path_key_using_brackets = True 822 823 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 824 """ 825 Generates the SQL string corresponding to the given syntax tree. 826 827 Args: 828 expression: The syntax tree. 829 copy: Whether to copy the expression. The generator performs mutations so 830 it is safer to copy. 831 832 Returns: 833 The SQL string corresponding to `expression`. 834 """ 835 if copy: 836 expression = expression.copy() 837 838 expression = self.preprocess(expression) 839 840 self.unsupported_messages = [] 841 sql = self.sql(expression).strip() 842 843 if self.pretty: 844 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 845 846 if self.unsupported_level == ErrorLevel.IGNORE: 847 return sql 848 849 if self.unsupported_level == ErrorLevel.WARN: 850 for msg in self.unsupported_messages: 851 logger.warning(msg) 852 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 853 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 854 855 return sql 856 857 def preprocess(self, expression: exp.Expression) -> exp.Expression: 858 """Apply generic preprocessing transformations to a given expression.""" 859 expression = self._move_ctes_to_top_level(expression) 860 861 if self.ENSURE_BOOLS: 862 from sqlglot.transforms import ensure_bools 863 864 expression = ensure_bools(expression) 865 866 return expression 867 868 def _move_ctes_to_top_level(self, expression: E) -> E: 869 if ( 870 not expression.parent 871 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 872 and any(node.parent is not expression for node in expression.find_all(exp.With)) 873 ): 874 from sqlglot.transforms import move_ctes_to_top_level 875 876 expression = move_ctes_to_top_level(expression) 877 return expression 878 879 def unsupported(self, message: str) -> None: 880 if self.unsupported_level == ErrorLevel.IMMEDIATE: 881 raise UnsupportedError(message) 882 self.unsupported_messages.append(message) 883 884 def sep(self, sep: str = " ") -> str: 885 return f"{sep.strip()}\n" if self.pretty else sep 886 887 def seg(self, sql: str, sep: str = " ") -> str: 888 return f"{self.sep(sep)}{sql}" 889 890 def sanitize_comment(self, comment: str) -> str: 891 comment = " " + comment if comment[0].strip() else comment 892 comment = comment + " " if comment[-1].strip() else comment 893 894 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 895 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 896 comment = comment.replace("*/", "* /") 897 898 return comment 899 900 def maybe_comment( 901 self, 902 sql: str, 903 expression: t.Optional[exp.Expression] = None, 904 comments: t.Optional[t.List[str]] = None, 905 separated: bool = False, 906 ) -> str: 907 comments = ( 908 ((expression and expression.comments) if comments is None else comments) # type: ignore 909 if self.comments 910 else None 911 ) 912 913 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 914 return sql 915 916 comments_sql = " ".join( 917 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 918 ) 919 920 if not comments_sql: 921 return sql 922 923 comments_sql = self._replace_line_breaks(comments_sql) 924 925 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 926 return ( 927 f"{self.sep()}{comments_sql}{sql}" 928 if not sql or sql[0].isspace() 929 else f"{comments_sql}{self.sep()}{sql}" 930 ) 931 932 return f"{sql} {comments_sql}" 933 934 def wrap(self, expression: exp.Expression | str) -> str: 935 this_sql = ( 936 self.sql(expression) 937 if isinstance(expression, exp.UNWRAPPED_QUERIES) 938 else self.sql(expression, "this") 939 ) 940 if not this_sql: 941 return "()" 942 943 this_sql = self.indent(this_sql, level=1, pad=0) 944 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 945 946 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 947 original = self.identify 948 self.identify = False 949 result = func(*args, **kwargs) 950 self.identify = original 951 return result 952 953 def normalize_func(self, name: str) -> str: 954 if self.normalize_functions == "upper" or self.normalize_functions is True: 955 return name.upper() 956 if self.normalize_functions == "lower": 957 return name.lower() 958 return name 959 960 def indent( 961 self, 962 sql: str, 963 level: int = 0, 964 pad: t.Optional[int] = None, 965 skip_first: bool = False, 966 skip_last: bool = False, 967 ) -> str: 968 if not self.pretty or not sql: 969 return sql 970 971 pad = self.pad if pad is None else pad 972 lines = sql.split("\n") 973 974 return "\n".join( 975 ( 976 line 977 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 978 else f"{' ' * (level * self._indent + pad)}{line}" 979 ) 980 for i, line in enumerate(lines) 981 ) 982 983 def sql( 984 self, 985 expression: t.Optional[str | exp.Expression], 986 key: t.Optional[str] = None, 987 comment: bool = True, 988 ) -> str: 989 if not expression: 990 return "" 991 992 if isinstance(expression, str): 993 return expression 994 995 if key: 996 value = expression.args.get(key) 997 if value: 998 return self.sql(value) 999 return "" 1000 1001 transform = self.TRANSFORMS.get(expression.__class__) 1002 1003 if transform: 1004 sql = transform(self, expression) 1005 else: 1006 exp_handler_name = expression.key + "_sql" 1007 1008 if handler := getattr(self, exp_handler_name, None): 1009 sql = handler(expression) 1010 elif isinstance(expression, exp.Func): 1011 sql = self.function_fallback_sql(expression) 1012 elif isinstance(expression, exp.Property): 1013 sql = self.property_sql(expression) 1014 else: 1015 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1016 1017 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1018 1019 def uncache_sql(self, expression: exp.Uncache) -> str: 1020 table = self.sql(expression, "this") 1021 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1022 return f"UNCACHE TABLE{exists_sql} {table}" 1023 1024 def cache_sql(self, expression: exp.Cache) -> str: 1025 lazy = " LAZY" if expression.args.get("lazy") else "" 1026 table = self.sql(expression, "this") 1027 options = expression.args.get("options") 1028 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1029 sql = self.sql(expression, "expression") 1030 sql = f" AS{self.sep()}{sql}" if sql else "" 1031 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1032 return self.prepend_ctes(expression, sql) 1033 1034 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1035 if isinstance(expression.parent, exp.Cast): 1036 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1037 default = "DEFAULT " if expression.args.get("default") else "" 1038 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1039 1040 def column_parts(self, expression: exp.Column) -> str: 1041 return ".".join( 1042 self.sql(part) 1043 for part in ( 1044 expression.args.get("catalog"), 1045 expression.args.get("db"), 1046 expression.args.get("table"), 1047 expression.args.get("this"), 1048 ) 1049 if part 1050 ) 1051 1052 def column_sql(self, expression: exp.Column) -> str: 1053 join_mark = " (+)" if expression.args.get("join_mark") else "" 1054 1055 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1056 join_mark = "" 1057 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1058 1059 return f"{self.column_parts(expression)}{join_mark}" 1060 1061 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1062 return self.column_sql(expression) 1063 1064 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1065 this = self.sql(expression, "this") 1066 this = f" {this}" if this else "" 1067 position = self.sql(expression, "position") 1068 return f"{position}{this}" 1069 1070 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1071 column = self.sql(expression, "this") 1072 kind = self.sql(expression, "kind") 1073 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1074 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1075 kind = f"{sep}{kind}" if kind else "" 1076 constraints = f" {constraints}" if constraints else "" 1077 position = self.sql(expression, "position") 1078 position = f" {position}" if position else "" 1079 1080 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1081 kind = "" 1082 1083 return f"{exists}{column}{kind}{constraints}{position}" 1084 1085 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1086 this = self.sql(expression, "this") 1087 kind_sql = self.sql(expression, "kind").strip() 1088 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1089 1090 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1091 this = self.sql(expression, "this") 1092 if expression.args.get("not_null"): 1093 persisted = " PERSISTED NOT NULL" 1094 elif expression.args.get("persisted"): 1095 persisted = " PERSISTED" 1096 else: 1097 persisted = "" 1098 1099 return f"AS {this}{persisted}" 1100 1101 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1102 return self.token_sql(TokenType.AUTO_INCREMENT) 1103 1104 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1105 if isinstance(expression.this, list): 1106 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1107 else: 1108 this = self.sql(expression, "this") 1109 1110 return f"COMPRESS {this}" 1111 1112 def generatedasidentitycolumnconstraint_sql( 1113 self, expression: exp.GeneratedAsIdentityColumnConstraint 1114 ) -> str: 1115 this = "" 1116 if expression.this is not None: 1117 on_null = " ON NULL" if expression.args.get("on_null") else "" 1118 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1119 1120 start = expression.args.get("start") 1121 start = f"START WITH {start}" if start else "" 1122 increment = expression.args.get("increment") 1123 increment = f" INCREMENT BY {increment}" if increment else "" 1124 minvalue = expression.args.get("minvalue") 1125 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1126 maxvalue = expression.args.get("maxvalue") 1127 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1128 cycle = expression.args.get("cycle") 1129 cycle_sql = "" 1130 1131 if cycle is not None: 1132 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1133 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1134 1135 sequence_opts = "" 1136 if start or increment or cycle_sql: 1137 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1138 sequence_opts = f" ({sequence_opts.strip()})" 1139 1140 expr = self.sql(expression, "expression") 1141 expr = f"({expr})" if expr else "IDENTITY" 1142 1143 return f"GENERATED{this} AS {expr}{sequence_opts}" 1144 1145 def generatedasrowcolumnconstraint_sql( 1146 self, expression: exp.GeneratedAsRowColumnConstraint 1147 ) -> str: 1148 start = "START" if expression.args.get("start") else "END" 1149 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1150 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1151 1152 def periodforsystemtimeconstraint_sql( 1153 self, expression: exp.PeriodForSystemTimeConstraint 1154 ) -> str: 1155 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1156 1157 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1158 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1159 1160 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1161 desc = expression.args.get("desc") 1162 if desc is not None: 1163 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1164 options = self.expressions(expression, key="options", flat=True, sep=" ") 1165 options = f" {options}" if options else "" 1166 return f"PRIMARY KEY{options}" 1167 1168 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1169 this = self.sql(expression, "this") 1170 this = f" {this}" if this else "" 1171 index_type = expression.args.get("index_type") 1172 index_type = f" USING {index_type}" if index_type else "" 1173 on_conflict = self.sql(expression, "on_conflict") 1174 on_conflict = f" {on_conflict}" if on_conflict else "" 1175 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1176 options = self.expressions(expression, key="options", flat=True, sep=" ") 1177 options = f" {options}" if options else "" 1178 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1179 1180 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1181 input_ = expression.args.get("input_") 1182 output = expression.args.get("output") 1183 variadic = expression.args.get("variadic") 1184 1185 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1186 if variadic: 1187 return "VARIADIC" 1188 1189 if input_ and output: 1190 return f"IN{self.INOUT_SEPARATOR}OUT" 1191 if input_: 1192 return "IN" 1193 if output: 1194 return "OUT" 1195 1196 return "" 1197 1198 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1199 return self.sql(expression, "this") 1200 1201 def create_sql(self, expression: exp.Create) -> str: 1202 kind = self.sql(expression, "kind") 1203 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1204 1205 properties = expression.args.get("properties") 1206 1207 if ( 1208 kind == "TRIGGER" 1209 and properties 1210 and properties.expressions 1211 and isinstance(properties.expressions[0], exp.TriggerProperties) 1212 and properties.expressions[0].args.get("constraint") 1213 ): 1214 kind = f"CONSTRAINT {kind}" 1215 1216 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1217 1218 this = self.createable_sql(expression, properties_locs) 1219 1220 properties_sql = "" 1221 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1222 exp.Properties.Location.POST_WITH 1223 ): 1224 props_ast = exp.Properties( 1225 expressions=[ 1226 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1227 *properties_locs[exp.Properties.Location.POST_WITH], 1228 ] 1229 ) 1230 props_ast.parent = expression 1231 properties_sql = self.sql(props_ast) 1232 1233 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1234 properties_sql = self.sep() + properties_sql 1235 elif not self.pretty: 1236 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1237 properties_sql = f" {properties_sql}" 1238 1239 begin = " BEGIN" if expression.args.get("begin") else "" 1240 1241 expression_sql = self.sql(expression, "expression") 1242 if expression_sql: 1243 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1244 1245 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1246 postalias_props_sql = "" 1247 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1248 postalias_props_sql = self.properties( 1249 exp.Properties( 1250 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1251 ), 1252 wrapped=False, 1253 ) 1254 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1255 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1256 1257 postindex_props_sql = "" 1258 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1259 postindex_props_sql = self.properties( 1260 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1261 wrapped=False, 1262 prefix=" ", 1263 ) 1264 1265 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1266 indexes = f" {indexes}" if indexes else "" 1267 index_sql = indexes + postindex_props_sql 1268 1269 replace = " OR REPLACE" if expression.args.get("replace") else "" 1270 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1271 unique = " UNIQUE" if expression.args.get("unique") else "" 1272 1273 clustered = expression.args.get("clustered") 1274 if clustered is None: 1275 clustered_sql = "" 1276 elif clustered: 1277 clustered_sql = " CLUSTERED COLUMNSTORE" 1278 else: 1279 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1280 1281 postcreate_props_sql = "" 1282 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1283 postcreate_props_sql = self.properties( 1284 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1285 sep=" ", 1286 prefix=" ", 1287 wrapped=False, 1288 ) 1289 1290 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1291 1292 postexpression_props_sql = "" 1293 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1294 postexpression_props_sql = self.properties( 1295 exp.Properties( 1296 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1297 ), 1298 sep=" ", 1299 prefix=" ", 1300 wrapped=False, 1301 ) 1302 1303 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1304 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1305 no_schema_binding = ( 1306 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1307 ) 1308 1309 clone = self.sql(expression, "clone") 1310 clone = f" {clone}" if clone else "" 1311 1312 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1313 properties_expression = f"{expression_sql}{properties_sql}" 1314 else: 1315 properties_expression = f"{properties_sql}{expression_sql}" 1316 1317 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1318 return self.prepend_ctes(expression, expression_sql) 1319 1320 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1321 start = self.sql(expression, "start") 1322 start = f"START WITH {start}" if start else "" 1323 increment = self.sql(expression, "increment") 1324 increment = f" INCREMENT BY {increment}" if increment else "" 1325 minvalue = self.sql(expression, "minvalue") 1326 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1327 maxvalue = self.sql(expression, "maxvalue") 1328 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1329 owned = self.sql(expression, "owned") 1330 owned = f" OWNED BY {owned}" if owned else "" 1331 1332 cache = expression.args.get("cache") 1333 if cache is None: 1334 cache_str = "" 1335 elif cache is True: 1336 cache_str = " CACHE" 1337 else: 1338 cache_str = f" CACHE {cache}" 1339 1340 options = self.expressions(expression, key="options", flat=True, sep=" ") 1341 options = f" {options}" if options else "" 1342 1343 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1344 1345 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1346 timing = expression.args.get("timing", "") 1347 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1348 timing_events = f"{timing} {events}".strip() if timing or events else "" 1349 1350 parts = [timing_events, "ON", self.sql(expression, "table")] 1351 1352 if referenced_table := expression.args.get("referenced_table"): 1353 parts.extend(["FROM", self.sql(referenced_table)]) 1354 1355 if deferrable := expression.args.get("deferrable"): 1356 parts.append(deferrable) 1357 1358 if initially := expression.args.get("initially"): 1359 parts.append(f"INITIALLY {initially}") 1360 1361 if referencing := expression.args.get("referencing"): 1362 parts.append(self.sql(referencing)) 1363 1364 if for_each := expression.args.get("for_each"): 1365 parts.append(f"FOR EACH {for_each}") 1366 1367 if when := expression.args.get("when"): 1368 parts.append(f"WHEN ({self.sql(when)})") 1369 1370 parts.append(self.sql(expression, "execute")) 1371 1372 return self.sep().join(parts) 1373 1374 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1375 parts = [] 1376 1377 if old_alias := expression.args.get("old"): 1378 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1379 1380 if new_alias := expression.args.get("new"): 1381 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1382 1383 return f"REFERENCING {' '.join(parts)}" 1384 1385 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1386 columns = expression.args.get("columns") 1387 if columns: 1388 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1389 1390 return self.sql(expression, "this") 1391 1392 def clone_sql(self, expression: exp.Clone) -> str: 1393 this = self.sql(expression, "this") 1394 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1395 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1396 return f"{shallow}{keyword} {this}" 1397 1398 def describe_sql(self, expression: exp.Describe) -> str: 1399 style = expression.args.get("style") 1400 style = f" {style}" if style else "" 1401 partition = self.sql(expression, "partition") 1402 partition = f" {partition}" if partition else "" 1403 format = self.sql(expression, "format") 1404 format = f" {format}" if format else "" 1405 as_json = " AS JSON" if expression.args.get("as_json") else "" 1406 1407 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1408 1409 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1410 tag = self.sql(expression, "tag") 1411 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1412 1413 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1414 with_ = self.sql(expression, "with_") 1415 if with_: 1416 sql = f"{with_}{self.sep()}{sql}" 1417 return sql 1418 1419 def with_sql(self, expression: exp.With) -> str: 1420 sql = self.expressions(expression, flat=True) 1421 recursive = ( 1422 "RECURSIVE " 1423 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1424 else "" 1425 ) 1426 search = self.sql(expression, "search") 1427 search = f" {search}" if search else "" 1428 1429 return f"WITH {recursive}{sql}{search}" 1430 1431 def cte_sql(self, expression: exp.CTE) -> str: 1432 alias = expression.args.get("alias") 1433 if alias: 1434 alias.add_comments(expression.pop_comments()) 1435 1436 alias_sql = self.sql(expression, "alias") 1437 1438 materialized = expression.args.get("materialized") 1439 if materialized is False: 1440 materialized = "NOT MATERIALIZED " 1441 elif materialized: 1442 materialized = "MATERIALIZED " 1443 1444 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1445 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1446 1447 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1448 1449 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1450 alias = self.sql(expression, "this") 1451 columns = self.expressions(expression, key="columns", flat=True) 1452 columns = f"({columns})" if columns else "" 1453 1454 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1455 columns = "" 1456 self.unsupported("Named columns are not supported in table alias.") 1457 1458 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1459 alias = self._next_name() 1460 1461 return f"{alias}{columns}" 1462 1463 def bitstring_sql(self, expression: exp.BitString) -> str: 1464 this = self.sql(expression, "this") 1465 if self.dialect.BIT_START: 1466 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1467 return f"{int(this, 2)}" 1468 1469 def hexstring_sql( 1470 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1471 ) -> str: 1472 this = self.sql(expression, "this") 1473 is_integer_type = expression.args.get("is_integer") 1474 1475 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1476 not self.dialect.HEX_START and not binary_function_repr 1477 ): 1478 # Integer representation will be returned if: 1479 # - The read dialect treats the hex value as integer literal but not the write 1480 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1481 return f"{int(this, 16)}" 1482 1483 if not is_integer_type: 1484 # Read dialect treats the hex value as BINARY/BLOB 1485 if binary_function_repr: 1486 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1487 return self.func(binary_function_repr, exp.Literal.string(this)) 1488 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1489 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1490 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1491 1492 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1493 1494 def bytestring_sql(self, expression: exp.ByteString) -> str: 1495 this = self.sql(expression, "this") 1496 if self.dialect.BYTE_START: 1497 escaped_byte_string = self.escape_str( 1498 this, 1499 escape_backslash=False, 1500 delimiter=self.dialect.BYTE_END, 1501 escaped_delimiter=self._escaped_byte_quote_end, 1502 is_byte_string=True, 1503 ) 1504 is_bytes = expression.args.get("is_bytes", False) 1505 delimited_byte_string = ( 1506 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1507 ) 1508 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1509 return self.sql( 1510 exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect) 1511 ) 1512 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1513 return self.sql( 1514 exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect) 1515 ) 1516 1517 return delimited_byte_string 1518 return this 1519 1520 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1521 this = self.sql(expression, "this") 1522 escape = expression.args.get("escape") 1523 1524 if self.dialect.UNICODE_START: 1525 escape_substitute = r"\\\1" 1526 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1527 else: 1528 escape_substitute = r"\\u\1" 1529 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1530 1531 if escape: 1532 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1533 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1534 else: 1535 escape_pattern = ESCAPED_UNICODE_RE 1536 escape_sql = "" 1537 1538 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1539 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1540 1541 return f"{left_quote}{this}{right_quote}{escape_sql}" 1542 1543 def rawstring_sql(self, expression: exp.RawString) -> str: 1544 string = expression.this 1545 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1546 string = string.replace("\\", "\\\\") 1547 1548 string = self.escape_str(string, escape_backslash=False) 1549 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1550 1551 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1552 this = self.sql(expression, "this") 1553 specifier = self.sql(expression, "expression") 1554 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1555 return f"{this}{specifier}" 1556 1557 def datatype_sql(self, expression: exp.DataType) -> str: 1558 nested = "" 1559 values = "" 1560 1561 expr_nested = expression.args.get("nested") 1562 interior = ( 1563 self.expressions( 1564 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1565 ) 1566 if expr_nested and self.pretty 1567 else self.expressions(expression, flat=True) 1568 ) 1569 1570 type_value = expression.this 1571 if type_value in self.UNSUPPORTED_TYPES: 1572 self.unsupported( 1573 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1574 ) 1575 1576 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1577 type_sql = self.sql(expression, "kind") 1578 else: 1579 type_sql = ( 1580 self.TYPE_MAPPING.get(type_value, type_value.value) 1581 if isinstance(type_value, exp.DataType.Type) 1582 else type_value 1583 ) 1584 1585 if interior: 1586 if expr_nested: 1587 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1588 if expression.args.get("values") is not None: 1589 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1590 values = self.expressions(expression, key="values", flat=True) 1591 values = f"{delimiters[0]}{values}{delimiters[1]}" 1592 elif type_value == exp.DataType.Type.INTERVAL: 1593 nested = f" {interior}" 1594 else: 1595 nested = f"({interior})" 1596 1597 type_sql = f"{type_sql}{nested}{values}" 1598 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1599 exp.DataType.Type.TIMETZ, 1600 exp.DataType.Type.TIMESTAMPTZ, 1601 ): 1602 type_sql = f"{type_sql} WITH TIME ZONE" 1603 1604 return type_sql 1605 1606 def directory_sql(self, expression: exp.Directory) -> str: 1607 local = "LOCAL " if expression.args.get("local") else "" 1608 row_format = self.sql(expression, "row_format") 1609 row_format = f" {row_format}" if row_format else "" 1610 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1611 1612 def delete_sql(self, expression: exp.Delete) -> str: 1613 this = self.sql(expression, "this") 1614 this = f" FROM {this}" if this else "" 1615 using = self.expressions(expression, key="using") 1616 using = f" USING {using}" if using else "" 1617 cluster = self.sql(expression, "cluster") 1618 cluster = f" {cluster}" if cluster else "" 1619 where = self.sql(expression, "where") 1620 returning = self.sql(expression, "returning") 1621 order = self.sql(expression, "order") 1622 limit = self.sql(expression, "limit") 1623 tables = self.expressions(expression, key="tables") 1624 tables = f" {tables}" if tables else "" 1625 if self.RETURNING_END: 1626 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1627 else: 1628 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1629 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1630 1631 def drop_sql(self, expression: exp.Drop) -> str: 1632 this = self.sql(expression, "this") 1633 expressions = self.expressions(expression, flat=True) 1634 expressions = f" ({expressions})" if expressions else "" 1635 kind = expression.args["kind"] 1636 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1637 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1638 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1639 on_cluster = self.sql(expression, "cluster") 1640 on_cluster = f" {on_cluster}" if on_cluster else "" 1641 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1642 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1643 cascade = " CASCADE" if expression.args.get("cascade") else "" 1644 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1645 purge = " PURGE" if expression.args.get("purge") else "" 1646 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1647 1648 def set_operation(self, expression: exp.SetOperation) -> str: 1649 op_type = type(expression) 1650 op_name = op_type.key.upper() 1651 1652 distinct = expression.args.get("distinct") 1653 if ( 1654 distinct is False 1655 and op_type in (exp.Except, exp.Intersect) 1656 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1657 ): 1658 self.unsupported(f"{op_name} ALL is not supported") 1659 1660 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1661 1662 if distinct is None: 1663 distinct = default_distinct 1664 if distinct is None: 1665 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1666 1667 if distinct is default_distinct: 1668 distinct_or_all = "" 1669 else: 1670 distinct_or_all = " DISTINCT" if distinct else " ALL" 1671 1672 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1673 side_kind = f"{side_kind} " if side_kind else "" 1674 1675 by_name = " BY NAME" if expression.args.get("by_name") else "" 1676 on = self.expressions(expression, key="on", flat=True) 1677 on = f" ON ({on})" if on else "" 1678 1679 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1680 1681 def set_operations(self, expression: exp.SetOperation) -> str: 1682 if not self.SET_OP_MODIFIERS: 1683 limit = expression.args.get("limit") 1684 order = expression.args.get("order") 1685 1686 if limit or order: 1687 select = self._move_ctes_to_top_level( 1688 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1689 ) 1690 1691 if limit: 1692 select = select.limit(limit.pop(), copy=False) 1693 if order: 1694 select = select.order_by(order.pop(), copy=False) 1695 return self.sql(select) 1696 1697 sqls: t.List[str] = [] 1698 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1699 1700 while stack: 1701 node = stack.pop() 1702 1703 if isinstance(node, exp.SetOperation): 1704 stack.append(node.expression) 1705 stack.append( 1706 self.maybe_comment( 1707 self.set_operation(node), comments=node.comments, separated=True 1708 ) 1709 ) 1710 stack.append(node.this) 1711 else: 1712 sqls.append(self.sql(node)) 1713 1714 this = self.sep().join(sqls) 1715 this = self.query_modifiers(expression, this) 1716 return self.prepend_ctes(expression, this) 1717 1718 def fetch_sql(self, expression: exp.Fetch) -> str: 1719 direction = expression.args.get("direction") 1720 direction = f" {direction}" if direction else "" 1721 count = self.sql(expression, "count") 1722 count = f" {count}" if count else "" 1723 limit_options = self.sql(expression, "limit_options") 1724 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1725 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1726 1727 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1728 percent = " PERCENT" if expression.args.get("percent") else "" 1729 rows = " ROWS" if expression.args.get("rows") else "" 1730 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1731 if not with_ties and rows: 1732 with_ties = " ONLY" 1733 return f"{percent}{rows}{with_ties}" 1734 1735 def filter_sql(self, expression: exp.Filter) -> str: 1736 if self.AGGREGATE_FILTER_SUPPORTED: 1737 this = self.sql(expression, "this") 1738 where = self.sql(expression, "expression").strip() 1739 return f"{this} FILTER({where})" 1740 1741 agg = expression.this 1742 agg_arg = agg.this 1743 cond = expression.expression.this 1744 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1745 return self.sql(agg) 1746 1747 def hint_sql(self, expression: exp.Hint) -> str: 1748 if not self.QUERY_HINTS: 1749 self.unsupported("Hints are not supported") 1750 return "" 1751 1752 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1753 1754 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1755 using = self.sql(expression, "using") 1756 using = f" USING {using}" if using else "" 1757 columns = self.expressions(expression, key="columns", flat=True) 1758 columns = f"({columns})" if columns else "" 1759 partition_by = self.expressions(expression, key="partition_by", flat=True) 1760 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1761 where = self.sql(expression, "where") 1762 include = self.expressions(expression, key="include", flat=True) 1763 if include: 1764 include = f" INCLUDE ({include})" 1765 with_storage = self.expressions(expression, key="with_storage", flat=True) 1766 with_storage = f" WITH ({with_storage})" if with_storage else "" 1767 tablespace = self.sql(expression, "tablespace") 1768 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1769 on = self.sql(expression, "on") 1770 on = f" ON {on}" if on else "" 1771 1772 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1773 1774 def index_sql(self, expression: exp.Index) -> str: 1775 unique = "UNIQUE " if expression.args.get("unique") else "" 1776 primary = "PRIMARY " if expression.args.get("primary") else "" 1777 amp = "AMP " if expression.args.get("amp") else "" 1778 name = self.sql(expression, "this") 1779 name = f"{name} " if name else "" 1780 table = self.sql(expression, "table") 1781 table = f"{self.INDEX_ON} {table}" if table else "" 1782 1783 index = "INDEX " if not table else "" 1784 1785 params = self.sql(expression, "params") 1786 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1787 1788 def identifier_sql(self, expression: exp.Identifier) -> str: 1789 text = expression.name 1790 lower = text.lower() 1791 quoted = expression.quoted 1792 text = lower if self.normalize and not quoted else text 1793 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1794 if ( 1795 quoted 1796 or self.dialect.can_quote(expression, self.identify) 1797 or lower in self.RESERVED_KEYWORDS 1798 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1799 ): 1800 text = f"{self._identifier_start}{text}{self._identifier_end}" 1801 return text 1802 1803 def hex_sql(self, expression: exp.Hex) -> str: 1804 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1805 if self.dialect.HEX_LOWERCASE: 1806 text = self.func("LOWER", text) 1807 1808 return text 1809 1810 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1811 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1812 if not self.dialect.HEX_LOWERCASE: 1813 text = self.func("LOWER", text) 1814 return text 1815 1816 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1817 input_format = self.sql(expression, "input_format") 1818 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1819 output_format = self.sql(expression, "output_format") 1820 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1821 return self.sep().join((input_format, output_format)) 1822 1823 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1824 string = self.sql(exp.Literal.string(expression.name)) 1825 return f"{prefix}{string}" 1826 1827 def partition_sql(self, expression: exp.Partition) -> str: 1828 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1829 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1830 1831 def properties_sql(self, expression: exp.Properties) -> str: 1832 root_properties = [] 1833 with_properties = [] 1834 1835 for p in expression.expressions: 1836 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1837 if p_loc == exp.Properties.Location.POST_WITH: 1838 with_properties.append(p) 1839 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1840 root_properties.append(p) 1841 1842 root_props_ast = exp.Properties(expressions=root_properties) 1843 root_props_ast.parent = expression.parent 1844 1845 with_props_ast = exp.Properties(expressions=with_properties) 1846 with_props_ast.parent = expression.parent 1847 1848 root_props = self.root_properties(root_props_ast) 1849 with_props = self.with_properties(with_props_ast) 1850 1851 if root_props and with_props and not self.pretty: 1852 with_props = " " + with_props 1853 1854 return root_props + with_props 1855 1856 def root_properties(self, properties: exp.Properties) -> str: 1857 if properties.expressions: 1858 return self.expressions(properties, indent=False, sep=" ") 1859 return "" 1860 1861 def properties( 1862 self, 1863 properties: exp.Properties, 1864 prefix: str = "", 1865 sep: str = ", ", 1866 suffix: str = "", 1867 wrapped: bool = True, 1868 ) -> str: 1869 if properties.expressions: 1870 expressions = self.expressions(properties, sep=sep, indent=False) 1871 if expressions: 1872 expressions = self.wrap(expressions) if wrapped else expressions 1873 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1874 return "" 1875 1876 def with_properties(self, properties: exp.Properties) -> str: 1877 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1878 1879 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1880 properties_locs = defaultdict(list) 1881 for p in properties.expressions: 1882 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1883 if p_loc != exp.Properties.Location.UNSUPPORTED: 1884 properties_locs[p_loc].append(p) 1885 else: 1886 self.unsupported(f"Unsupported property {p.key}") 1887 1888 return properties_locs 1889 1890 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1891 if isinstance(expression.this, exp.Dot): 1892 return self.sql(expression, "this") 1893 return f"'{expression.name}'" if string_key else expression.name 1894 1895 def property_sql(self, expression: exp.Property) -> str: 1896 property_cls = expression.__class__ 1897 if property_cls == exp.Property: 1898 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1899 1900 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1901 if not property_name: 1902 self.unsupported(f"Unsupported property {expression.key}") 1903 1904 return f"{property_name}={self.sql(expression, 'this')}" 1905 1906 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1907 if self.SUPPORTS_CREATE_TABLE_LIKE: 1908 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1909 options = f" {options}" if options else "" 1910 1911 like = f"LIKE {self.sql(expression, 'this')}{options}" 1912 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1913 like = f"({like})" 1914 1915 return like 1916 1917 if expression.expressions: 1918 self.unsupported("Transpilation of LIKE property options is unsupported") 1919 1920 select = exp.select("*").from_(expression.this).limit(0) 1921 return f"AS {self.sql(select)}" 1922 1923 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1924 no = "NO " if expression.args.get("no") else "" 1925 protection = " PROTECTION" if expression.args.get("protection") else "" 1926 return f"{no}FALLBACK{protection}" 1927 1928 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1929 no = "NO " if expression.args.get("no") else "" 1930 local = expression.args.get("local") 1931 local = f"{local} " if local else "" 1932 dual = "DUAL " if expression.args.get("dual") else "" 1933 before = "BEFORE " if expression.args.get("before") else "" 1934 after = "AFTER " if expression.args.get("after") else "" 1935 return f"{no}{local}{dual}{before}{after}JOURNAL" 1936 1937 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1938 freespace = self.sql(expression, "this") 1939 percent = " PERCENT" if expression.args.get("percent") else "" 1940 return f"FREESPACE={freespace}{percent}" 1941 1942 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1943 if expression.args.get("default"): 1944 property = "DEFAULT" 1945 elif expression.args.get("on"): 1946 property = "ON" 1947 else: 1948 property = "OFF" 1949 return f"CHECKSUM={property}" 1950 1951 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1952 if expression.args.get("no"): 1953 return "NO MERGEBLOCKRATIO" 1954 if expression.args.get("default"): 1955 return "DEFAULT MERGEBLOCKRATIO" 1956 1957 percent = " PERCENT" if expression.args.get("percent") else "" 1958 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1959 1960 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1961 default = expression.args.get("default") 1962 minimum = expression.args.get("minimum") 1963 maximum = expression.args.get("maximum") 1964 if default or minimum or maximum: 1965 if default: 1966 prop = "DEFAULT" 1967 elif minimum: 1968 prop = "MINIMUM" 1969 else: 1970 prop = "MAXIMUM" 1971 return f"{prop} DATABLOCKSIZE" 1972 units = expression.args.get("units") 1973 units = f" {units}" if units else "" 1974 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1975 1976 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1977 autotemp = expression.args.get("autotemp") 1978 always = expression.args.get("always") 1979 default = expression.args.get("default") 1980 manual = expression.args.get("manual") 1981 never = expression.args.get("never") 1982 1983 if autotemp is not None: 1984 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1985 elif always: 1986 prop = "ALWAYS" 1987 elif default: 1988 prop = "DEFAULT" 1989 elif manual: 1990 prop = "MANUAL" 1991 elif never: 1992 prop = "NEVER" 1993 return f"BLOCKCOMPRESSION={prop}" 1994 1995 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1996 no = expression.args.get("no") 1997 no = " NO" if no else "" 1998 concurrent = expression.args.get("concurrent") 1999 concurrent = " CONCURRENT" if concurrent else "" 2000 target = self.sql(expression, "target") 2001 target = f" {target}" if target else "" 2002 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2003 2004 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2005 if isinstance(expression.this, list): 2006 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2007 if expression.this: 2008 modulus = self.sql(expression, "this") 2009 remainder = self.sql(expression, "expression") 2010 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2011 2012 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2013 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2014 return f"FROM ({from_expressions}) TO ({to_expressions})" 2015 2016 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2017 this = self.sql(expression, "this") 2018 2019 for_values_or_default = expression.expression 2020 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2021 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2022 else: 2023 for_values_or_default = " DEFAULT" 2024 2025 return f"PARTITION OF {this}{for_values_or_default}" 2026 2027 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2028 kind = expression.args.get("kind") 2029 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2030 for_or_in = expression.args.get("for_or_in") 2031 for_or_in = f" {for_or_in}" if for_or_in else "" 2032 lock_type = expression.args.get("lock_type") 2033 override = " OVERRIDE" if expression.args.get("override") else "" 2034 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2035 2036 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2037 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2038 statistics = expression.args.get("statistics") 2039 statistics_sql = "" 2040 if statistics is not None: 2041 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2042 return f"{data_sql}{statistics_sql}" 2043 2044 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2045 this = self.sql(expression, "this") 2046 this = f"HISTORY_TABLE={this}" if this else "" 2047 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 2048 data_consistency = ( 2049 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2050 ) 2051 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 2052 retention_period = ( 2053 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2054 ) 2055 2056 if this: 2057 on_sql = self.func("ON", this, data_consistency, retention_period) 2058 else: 2059 on_sql = "ON" if expression.args.get("on") else "OFF" 2060 2061 sql = f"SYSTEM_VERSIONING={on_sql}" 2062 2063 return f"WITH({sql})" if expression.args.get("with_") else sql 2064 2065 def insert_sql(self, expression: exp.Insert) -> str: 2066 hint = self.sql(expression, "hint") 2067 overwrite = expression.args.get("overwrite") 2068 2069 if isinstance(expression.this, exp.Directory): 2070 this = " OVERWRITE" if overwrite else " INTO" 2071 else: 2072 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2073 2074 stored = self.sql(expression, "stored") 2075 stored = f" {stored}" if stored else "" 2076 alternative = expression.args.get("alternative") 2077 alternative = f" OR {alternative}" if alternative else "" 2078 ignore = " IGNORE" if expression.args.get("ignore") else "" 2079 is_function = expression.args.get("is_function") 2080 if is_function: 2081 this = f"{this} FUNCTION" 2082 this = f"{this} {self.sql(expression, 'this')}" 2083 2084 exists = " IF EXISTS" if expression.args.get("exists") else "" 2085 where = self.sql(expression, "where") 2086 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2087 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2088 on_conflict = self.sql(expression, "conflict") 2089 on_conflict = f" {on_conflict}" if on_conflict else "" 2090 by_name = " BY NAME" if expression.args.get("by_name") else "" 2091 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2092 returning = self.sql(expression, "returning") 2093 2094 if self.RETURNING_END: 2095 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2096 else: 2097 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2098 2099 partition_by = self.sql(expression, "partition") 2100 partition_by = f" {partition_by}" if partition_by else "" 2101 settings = self.sql(expression, "settings") 2102 settings = f" {settings}" if settings else "" 2103 2104 source = self.sql(expression, "source") 2105 source = f"TABLE {source}" if source else "" 2106 2107 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2108 return self.prepend_ctes(expression, sql) 2109 2110 def introducer_sql(self, expression: exp.Introducer) -> str: 2111 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2112 2113 def kill_sql(self, expression: exp.Kill) -> str: 2114 kind = self.sql(expression, "kind") 2115 kind = f" {kind}" if kind else "" 2116 this = self.sql(expression, "this") 2117 this = f" {this}" if this else "" 2118 return f"KILL{kind}{this}" 2119 2120 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2121 return expression.name 2122 2123 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2124 return expression.name 2125 2126 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2127 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2128 2129 constraint = self.sql(expression, "constraint") 2130 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2131 2132 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2133 if conflict_keys: 2134 conflict_keys = f"({conflict_keys})" 2135 2136 index_predicate = self.sql(expression, "index_predicate") 2137 conflict_keys = f"{conflict_keys}{index_predicate} " 2138 2139 action = self.sql(expression, "action") 2140 2141 expressions = self.expressions(expression, flat=True) 2142 if expressions: 2143 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2144 expressions = f" {set_keyword}{expressions}" 2145 2146 where = self.sql(expression, "where") 2147 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2148 2149 def returning_sql(self, expression: exp.Returning) -> str: 2150 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2151 2152 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2153 fields = self.sql(expression, "fields") 2154 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2155 escaped = self.sql(expression, "escaped") 2156 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2157 items = self.sql(expression, "collection_items") 2158 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2159 keys = self.sql(expression, "map_keys") 2160 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2161 lines = self.sql(expression, "lines") 2162 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2163 null = self.sql(expression, "null") 2164 null = f" NULL DEFINED AS {null}" if null else "" 2165 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2166 2167 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2168 return f"WITH ({self.expressions(expression, flat=True)})" 2169 2170 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2171 this = f"{self.sql(expression, 'this')} INDEX" 2172 target = self.sql(expression, "target") 2173 target = f" FOR {target}" if target else "" 2174 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2175 2176 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2177 this = self.sql(expression, "this") 2178 kind = self.sql(expression, "kind") 2179 expr = self.sql(expression, "expression") 2180 return f"{this} ({kind} => {expr})" 2181 2182 def table_parts(self, expression: exp.Table) -> str: 2183 return ".".join( 2184 self.sql(part) 2185 for part in ( 2186 expression.args.get("catalog"), 2187 expression.args.get("db"), 2188 expression.args.get("this"), 2189 ) 2190 if part is not None 2191 ) 2192 2193 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2194 table = self.table_parts(expression) 2195 only = "ONLY " if expression.args.get("only") else "" 2196 partition = self.sql(expression, "partition") 2197 partition = f" {partition}" if partition else "" 2198 version = self.sql(expression, "version") 2199 version = f" {version}" if version else "" 2200 alias = self.sql(expression, "alias") 2201 alias = f"{sep}{alias}" if alias else "" 2202 2203 sample = self.sql(expression, "sample") 2204 post_alias = "" 2205 pre_alias = "" 2206 2207 if self.dialect.ALIAS_POST_TABLESAMPLE: 2208 pre_alias = sample 2209 else: 2210 post_alias = sample 2211 2212 if self.dialect.ALIAS_POST_VERSION: 2213 pre_alias = f"{pre_alias}{version}" 2214 else: 2215 post_alias = f"{post_alias}{version}" 2216 2217 hints = self.expressions(expression, key="hints", sep=" ") 2218 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2219 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2220 joins = self.indent( 2221 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2222 ) 2223 laterals = self.expressions(expression, key="laterals", sep="") 2224 2225 file_format = self.sql(expression, "format") 2226 if file_format: 2227 pattern = self.sql(expression, "pattern") 2228 pattern = f", PATTERN => {pattern}" if pattern else "" 2229 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2230 2231 ordinality = expression.args.get("ordinality") or "" 2232 if ordinality: 2233 ordinality = f" WITH ORDINALITY{alias}" 2234 alias = "" 2235 2236 when = self.sql(expression, "when") 2237 if when: 2238 table = f"{table} {when}" 2239 2240 changes = self.sql(expression, "changes") 2241 changes = f" {changes}" if changes else "" 2242 2243 rows_from = self.expressions(expression, key="rows_from") 2244 if rows_from: 2245 table = f"ROWS FROM {self.wrap(rows_from)}" 2246 2247 indexed = expression.args.get("indexed") 2248 if indexed is not None: 2249 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2250 else: 2251 indexed = "" 2252 2253 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2254 2255 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2256 table = self.func("TABLE", expression.this) 2257 alias = self.sql(expression, "alias") 2258 alias = f" AS {alias}" if alias else "" 2259 sample = self.sql(expression, "sample") 2260 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2261 joins = self.indent( 2262 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2263 ) 2264 return f"{table}{alias}{pivots}{sample}{joins}" 2265 2266 def tablesample_sql( 2267 self, 2268 expression: exp.TableSample, 2269 tablesample_keyword: t.Optional[str] = None, 2270 ) -> str: 2271 method = self.sql(expression, "method") 2272 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2273 numerator = self.sql(expression, "bucket_numerator") 2274 denominator = self.sql(expression, "bucket_denominator") 2275 field = self.sql(expression, "bucket_field") 2276 field = f" ON {field}" if field else "" 2277 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2278 seed = self.sql(expression, "seed") 2279 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2280 2281 size = self.sql(expression, "size") 2282 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2283 size = f"{size} ROWS" 2284 2285 percent = self.sql(expression, "percent") 2286 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2287 percent = f"{percent} PERCENT" 2288 2289 expr = f"{bucket}{percent}{size}" 2290 if self.TABLESAMPLE_REQUIRES_PARENS: 2291 expr = f"({expr})" 2292 2293 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2294 2295 def pivot_sql(self, expression: exp.Pivot) -> str: 2296 expressions = self.expressions(expression, flat=True) 2297 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2298 2299 group = self.sql(expression, "group") 2300 2301 if expression.this: 2302 this = self.sql(expression, "this") 2303 if not expressions: 2304 sql = f"UNPIVOT {this}" 2305 else: 2306 on = f"{self.seg('ON')} {expressions}" 2307 into = self.sql(expression, "into") 2308 into = f"{self.seg('INTO')} {into}" if into else "" 2309 using = self.expressions(expression, key="using", flat=True) 2310 using = f"{self.seg('USING')} {using}" if using else "" 2311 sql = f"{direction} {this}{on}{into}{using}{group}" 2312 return self.prepend_ctes(expression, sql) 2313 2314 alias = self.sql(expression, "alias") 2315 alias = f" AS {alias}" if alias else "" 2316 2317 fields = self.expressions( 2318 expression, 2319 "fields", 2320 sep=" ", 2321 dynamic=True, 2322 new_line=True, 2323 skip_first=True, 2324 skip_last=True, 2325 ) 2326 2327 include_nulls = expression.args.get("include_nulls") 2328 if include_nulls is not None: 2329 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2330 else: 2331 nulls = "" 2332 2333 default_on_null = self.sql(expression, "default_on_null") 2334 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2335 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2336 return self.prepend_ctes(expression, sql) 2337 2338 def version_sql(self, expression: exp.Version) -> str: 2339 this = f"FOR {expression.name}" 2340 kind = expression.text("kind") 2341 expr = self.sql(expression, "expression") 2342 return f"{this} {kind} {expr}" 2343 2344 def tuple_sql(self, expression: exp.Tuple) -> str: 2345 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2346 2347 def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]: 2348 """ 2349 Returns (join_sql, from_sql) for UPDATE statements. 2350 - join_sql: placed after UPDATE table, before SET 2351 - from_sql: placed after SET clause (standard position) 2352 Dialects like MySQL need to convert FROM to JOIN syntax. 2353 """ 2354 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2355 return ("", self.sql(expression, "from_")) 2356 2357 # Qualify unqualified columns in SET clause with the target table 2358 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2359 target_table = expression.this 2360 if isinstance(target_table, exp.Table): 2361 target_name = exp.to_identifier(target_table.alias_or_name) 2362 for eq in expression.expressions: 2363 col = eq.this 2364 if isinstance(col, exp.Column) and not col.table: 2365 col.set("table", target_name) 2366 2367 table = from_expr.this 2368 if nested_joins := table.args.get("joins", []): 2369 table.set("joins", None) 2370 2371 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2372 for nested in nested_joins: 2373 if not nested.args.get("on") and not nested.args.get("using"): 2374 nested.set("on", exp.true()) 2375 join_sql += self.sql(nested) 2376 2377 return (join_sql, "") 2378 2379 def update_sql(self, expression: exp.Update) -> str: 2380 this = self.sql(expression, "this") 2381 join_sql, from_sql = self._update_from_joins_sql(expression) 2382 set_sql = self.expressions(expression, flat=True) 2383 where_sql = self.sql(expression, "where") 2384 returning = self.sql(expression, "returning") 2385 order = self.sql(expression, "order") 2386 limit = self.sql(expression, "limit") 2387 if self.RETURNING_END: 2388 expression_sql = f"{from_sql}{where_sql}{returning}" 2389 else: 2390 expression_sql = f"{returning}{from_sql}{where_sql}" 2391 options = self.expressions(expression, key="options") 2392 options = f" OPTION({options})" if options else "" 2393 sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2394 return self.prepend_ctes(expression, sql) 2395 2396 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2397 values_as_table = values_as_table and self.VALUES_AS_TABLE 2398 2399 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2400 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2401 args = self.expressions(expression) 2402 alias = self.sql(expression, "alias") 2403 values = f"VALUES{self.seg('')}{args}" 2404 values = ( 2405 f"({values})" 2406 if self.WRAP_DERIVED_VALUES 2407 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2408 else values 2409 ) 2410 values = self.query_modifiers(expression, values) 2411 return f"{values} AS {alias}" if alias else values 2412 2413 # Converts `VALUES...` expression into a series of select unions. 2414 alias_node = expression.args.get("alias") 2415 column_names = alias_node and alias_node.columns 2416 2417 selects: t.List[exp.Query] = [] 2418 2419 for i, tup in enumerate(expression.expressions): 2420 row = tup.expressions 2421 2422 if i == 0 and column_names: 2423 row = [ 2424 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2425 ] 2426 2427 selects.append(exp.Select(expressions=row)) 2428 2429 if self.pretty: 2430 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2431 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2432 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2433 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2434 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2435 2436 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2437 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2438 return f"({unions}){alias}" 2439 2440 def var_sql(self, expression: exp.Var) -> str: 2441 return self.sql(expression, "this") 2442 2443 @unsupported_args("expressions") 2444 def into_sql(self, expression: exp.Into) -> str: 2445 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2446 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2447 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2448 2449 def from_sql(self, expression: exp.From) -> str: 2450 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2451 2452 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2453 grouping_sets = self.expressions(expression, indent=False) 2454 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2455 2456 def rollup_sql(self, expression: exp.Rollup) -> str: 2457 expressions = self.expressions(expression, indent=False) 2458 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2459 2460 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2461 this = self.sql(expression, "this") 2462 2463 columns = self.expressions(expression, flat=True) 2464 2465 from_sql = self.sql(expression, "from_index") 2466 from_sql = f" FROM {from_sql}" if from_sql else "" 2467 2468 properties = expression.args.get("properties") 2469 properties_sql = ( 2470 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2471 ) 2472 2473 return f"{this}({columns}){from_sql}{properties_sql}" 2474 2475 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2476 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2477 2478 def cube_sql(self, expression: exp.Cube) -> str: 2479 expressions = self.expressions(expression, indent=False) 2480 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2481 2482 def group_sql(self, expression: exp.Group) -> str: 2483 group_by_all = expression.args.get("all") 2484 if group_by_all is True: 2485 modifier = " ALL" 2486 elif group_by_all is False: 2487 modifier = " DISTINCT" 2488 else: 2489 modifier = "" 2490 2491 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2492 2493 grouping_sets = self.expressions(expression, key="grouping_sets") 2494 cube = self.expressions(expression, key="cube") 2495 rollup = self.expressions(expression, key="rollup") 2496 2497 groupings = csv( 2498 self.seg(grouping_sets) if grouping_sets else "", 2499 self.seg(cube) if cube else "", 2500 self.seg(rollup) if rollup else "", 2501 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2502 sep=self.GROUPINGS_SEP, 2503 ) 2504 2505 if ( 2506 expression.expressions 2507 and groupings 2508 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2509 ): 2510 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2511 2512 return f"{group_by}{groupings}" 2513 2514 def having_sql(self, expression: exp.Having) -> str: 2515 this = self.indent(self.sql(expression, "this")) 2516 return f"{self.seg('HAVING')}{self.sep()}{this}" 2517 2518 def connect_sql(self, expression: exp.Connect) -> str: 2519 start = self.sql(expression, "start") 2520 start = self.seg(f"START WITH {start}") if start else "" 2521 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2522 connect = self.sql(expression, "connect") 2523 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2524 return start + connect 2525 2526 def prior_sql(self, expression: exp.Prior) -> str: 2527 return f"PRIOR {self.sql(expression, 'this')}" 2528 2529 def join_sql(self, expression: exp.Join) -> str: 2530 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2531 side = None 2532 else: 2533 side = expression.side 2534 2535 op_sql = " ".join( 2536 op 2537 for op in ( 2538 expression.method, 2539 "GLOBAL" if expression.args.get("global_") else None, 2540 side, 2541 expression.kind, 2542 expression.hint if self.JOIN_HINTS else None, 2543 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2544 ) 2545 if op 2546 ) 2547 match_cond = self.sql(expression, "match_condition") 2548 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2549 on_sql = self.sql(expression, "on") 2550 using = expression.args.get("using") 2551 2552 if not on_sql and using: 2553 on_sql = csv(*(self.sql(column) for column in using)) 2554 2555 this = expression.this 2556 this_sql = self.sql(this) 2557 2558 exprs = self.expressions(expression) 2559 if exprs: 2560 this_sql = f"{this_sql},{self.seg(exprs)}" 2561 2562 if on_sql: 2563 on_sql = self.indent(on_sql, skip_first=True) 2564 space = self.seg(" " * self.pad) if self.pretty else " " 2565 if using: 2566 on_sql = f"{space}USING ({on_sql})" 2567 else: 2568 on_sql = f"{space}ON {on_sql}" 2569 elif not op_sql: 2570 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2571 return f" {this_sql}" 2572 2573 return f", {this_sql}" 2574 2575 if op_sql != "STRAIGHT_JOIN": 2576 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2577 2578 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2579 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2580 2581 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2582 args = self.expressions(expression, flat=True) 2583 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2584 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2585 2586 def lateral_op(self, expression: exp.Lateral) -> str: 2587 cross_apply = expression.args.get("cross_apply") 2588 2589 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2590 if cross_apply is True: 2591 op = "INNER JOIN " 2592 elif cross_apply is False: 2593 op = "LEFT JOIN " 2594 else: 2595 op = "" 2596 2597 return f"{op}LATERAL" 2598 2599 def lateral_sql(self, expression: exp.Lateral) -> str: 2600 this = self.sql(expression, "this") 2601 2602 if expression.args.get("view"): 2603 alias = expression.args["alias"] 2604 columns = self.expressions(alias, key="columns", flat=True) 2605 table = f" {alias.name}" if alias.name else "" 2606 columns = f" AS {columns}" if columns else "" 2607 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2608 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2609 2610 alias = self.sql(expression, "alias") 2611 alias = f" AS {alias}" if alias else "" 2612 2613 ordinality = expression.args.get("ordinality") or "" 2614 if ordinality: 2615 ordinality = f" WITH ORDINALITY{alias}" 2616 alias = "" 2617 2618 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2619 2620 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2621 this = self.sql(expression, "this") 2622 2623 args = [ 2624 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2625 for e in (expression.args.get(k) for k in ("offset", "expression")) 2626 if e 2627 ] 2628 2629 args_sql = ", ".join(self.sql(e) for e in args) 2630 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2631 expressions = self.expressions(expression, flat=True) 2632 limit_options = self.sql(expression, "limit_options") 2633 expressions = f" BY {expressions}" if expressions else "" 2634 2635 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2636 2637 def offset_sql(self, expression: exp.Offset) -> str: 2638 this = self.sql(expression, "this") 2639 value = expression.expression 2640 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2641 expressions = self.expressions(expression, flat=True) 2642 expressions = f" BY {expressions}" if expressions else "" 2643 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2644 2645 def setitem_sql(self, expression: exp.SetItem) -> str: 2646 kind = self.sql(expression, "kind") 2647 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2648 kind = "" 2649 else: 2650 kind = f"{kind} " if kind else "" 2651 this = self.sql(expression, "this") 2652 expressions = self.expressions(expression) 2653 collate = self.sql(expression, "collate") 2654 collate = f" COLLATE {collate}" if collate else "" 2655 global_ = "GLOBAL " if expression.args.get("global_") else "" 2656 return f"{global_}{kind}{this}{expressions}{collate}" 2657 2658 def set_sql(self, expression: exp.Set) -> str: 2659 expressions = f" {self.expressions(expression, flat=True)}" 2660 tag = " TAG" if expression.args.get("tag") else "" 2661 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2662 2663 def queryband_sql(self, expression: exp.QueryBand) -> str: 2664 this = self.sql(expression, "this") 2665 update = " UPDATE" if expression.args.get("update") else "" 2666 scope = self.sql(expression, "scope") 2667 scope = f" FOR {scope}" if scope else "" 2668 2669 return f"QUERY_BAND = {this}{update}{scope}" 2670 2671 def pragma_sql(self, expression: exp.Pragma) -> str: 2672 return f"PRAGMA {self.sql(expression, 'this')}" 2673 2674 def lock_sql(self, expression: exp.Lock) -> str: 2675 if not self.LOCKING_READS_SUPPORTED: 2676 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2677 return "" 2678 2679 update = expression.args["update"] 2680 key = expression.args.get("key") 2681 if update: 2682 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2683 else: 2684 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2685 expressions = self.expressions(expression, flat=True) 2686 expressions = f" OF {expressions}" if expressions else "" 2687 wait = expression.args.get("wait") 2688 2689 if wait is not None: 2690 if isinstance(wait, exp.Literal): 2691 wait = f" WAIT {self.sql(wait)}" 2692 else: 2693 wait = " NOWAIT" if wait else " SKIP LOCKED" 2694 2695 return f"{lock_type}{expressions}{wait or ''}" 2696 2697 def literal_sql(self, expression: exp.Literal) -> str: 2698 text = expression.this or "" 2699 if expression.is_string: 2700 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2701 return text 2702 2703 def escape_str( 2704 self, 2705 text: str, 2706 escape_backslash: bool = True, 2707 delimiter: t.Optional[str] = None, 2708 escaped_delimiter: t.Optional[str] = None, 2709 is_byte_string: bool = False, 2710 ) -> str: 2711 if is_byte_string: 2712 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2713 else: 2714 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2715 2716 if supports_escape_sequences: 2717 text = "".join( 2718 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2719 for ch in text 2720 ) 2721 2722 delimiter = delimiter or self.dialect.QUOTE_END 2723 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2724 2725 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2726 2727 def loaddata_sql(self, expression: exp.LoadData) -> str: 2728 local = " LOCAL" if expression.args.get("local") else "" 2729 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2730 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2731 this = f" INTO TABLE {self.sql(expression, 'this')}" 2732 partition = self.sql(expression, "partition") 2733 partition = f" {partition}" if partition else "" 2734 input_format = self.sql(expression, "input_format") 2735 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2736 serde = self.sql(expression, "serde") 2737 serde = f" SERDE {serde}" if serde else "" 2738 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2739 2740 def null_sql(self, *_) -> str: 2741 return "NULL" 2742 2743 def boolean_sql(self, expression: exp.Boolean) -> str: 2744 return "TRUE" if expression.this else "FALSE" 2745 2746 def booland_sql(self, expression: exp.Booland) -> str: 2747 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 2748 2749 def boolor_sql(self, expression: exp.Boolor) -> str: 2750 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 2751 2752 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2753 this = self.sql(expression, "this") 2754 this = f"{this} " if this else this 2755 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2756 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2757 2758 def withfill_sql(self, expression: exp.WithFill) -> str: 2759 from_sql = self.sql(expression, "from_") 2760 from_sql = f" FROM {from_sql}" if from_sql else "" 2761 to_sql = self.sql(expression, "to") 2762 to_sql = f" TO {to_sql}" if to_sql else "" 2763 step_sql = self.sql(expression, "step") 2764 step_sql = f" STEP {step_sql}" if step_sql else "" 2765 interpolated_values = [ 2766 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2767 if isinstance(e, exp.Alias) 2768 else self.sql(e, "this") 2769 for e in expression.args.get("interpolate") or [] 2770 ] 2771 interpolate = ( 2772 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2773 ) 2774 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2775 2776 def cluster_sql(self, expression: exp.Cluster) -> str: 2777 return self.op_expressions("CLUSTER BY", expression) 2778 2779 def distribute_sql(self, expression: exp.Distribute) -> str: 2780 return self.op_expressions("DISTRIBUTE BY", expression) 2781 2782 def sort_sql(self, expression: exp.Sort) -> str: 2783 return self.op_expressions("SORT BY", expression) 2784 2785 def ordered_sql(self, expression: exp.Ordered) -> str: 2786 desc = expression.args.get("desc") 2787 asc = not desc 2788 2789 nulls_first = expression.args.get("nulls_first") 2790 nulls_last = not nulls_first 2791 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2792 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2793 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2794 2795 this = self.sql(expression, "this") 2796 2797 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2798 nulls_sort_change = "" 2799 if nulls_first and ( 2800 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2801 ): 2802 nulls_sort_change = " NULLS FIRST" 2803 elif ( 2804 nulls_last 2805 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2806 and not nulls_are_last 2807 ): 2808 nulls_sort_change = " NULLS LAST" 2809 2810 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2811 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2812 window = expression.find_ancestor(exp.Window, exp.Select) 2813 if isinstance(window, exp.Window) and window.args.get("spec"): 2814 self.unsupported( 2815 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2816 ) 2817 nulls_sort_change = "" 2818 elif self.NULL_ORDERING_SUPPORTED is False and ( 2819 (asc and nulls_sort_change == " NULLS LAST") 2820 or (desc and nulls_sort_change == " NULLS FIRST") 2821 ): 2822 # BigQuery does not allow these ordering/nulls combinations when used under 2823 # an aggregation func or under a window containing one 2824 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2825 2826 if isinstance(ancestor, exp.Window): 2827 ancestor = ancestor.this 2828 if isinstance(ancestor, exp.AggFunc): 2829 self.unsupported( 2830 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2831 ) 2832 nulls_sort_change = "" 2833 elif self.NULL_ORDERING_SUPPORTED is None: 2834 if expression.this.is_int: 2835 self.unsupported( 2836 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2837 ) 2838 elif not isinstance(expression.this, exp.Rand): 2839 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2840 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2841 nulls_sort_change = "" 2842 2843 with_fill = self.sql(expression, "with_fill") 2844 with_fill = f" {with_fill}" if with_fill else "" 2845 2846 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2847 2848 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2849 window_frame = self.sql(expression, "window_frame") 2850 window_frame = f"{window_frame} " if window_frame else "" 2851 2852 this = self.sql(expression, "this") 2853 2854 return f"{window_frame}{this}" 2855 2856 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2857 partition = self.partition_by_sql(expression) 2858 order = self.sql(expression, "order") 2859 measures = self.expressions(expression, key="measures") 2860 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2861 rows = self.sql(expression, "rows") 2862 rows = self.seg(rows) if rows else "" 2863 after = self.sql(expression, "after") 2864 after = self.seg(after) if after else "" 2865 pattern = self.sql(expression, "pattern") 2866 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2867 definition_sqls = [ 2868 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2869 for definition in expression.args.get("define", []) 2870 ] 2871 definitions = self.expressions(sqls=definition_sqls) 2872 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2873 body = "".join( 2874 ( 2875 partition, 2876 order, 2877 measures, 2878 rows, 2879 after, 2880 pattern, 2881 define, 2882 ) 2883 ) 2884 alias = self.sql(expression, "alias") 2885 alias = f" {alias}" if alias else "" 2886 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2887 2888 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2889 limit = expression.args.get("limit") 2890 2891 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2892 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2893 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2894 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2895 2896 return csv( 2897 *sqls, 2898 *[self.sql(join) for join in expression.args.get("joins") or []], 2899 self.sql(expression, "match"), 2900 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2901 self.sql(expression, "prewhere"), 2902 self.sql(expression, "where"), 2903 self.sql(expression, "connect"), 2904 self.sql(expression, "group"), 2905 self.sql(expression, "having"), 2906 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2907 self.sql(expression, "order"), 2908 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2909 *self.after_limit_modifiers(expression), 2910 self.options_modifier(expression), 2911 self.for_modifiers(expression), 2912 sep="", 2913 ) 2914 2915 def options_modifier(self, expression: exp.Expression) -> str: 2916 options = self.expressions(expression, key="options") 2917 return f" {options}" if options else "" 2918 2919 def for_modifiers(self, expression: exp.Expression) -> str: 2920 for_modifiers = self.expressions(expression, key="for_") 2921 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2922 2923 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2924 self.unsupported("Unsupported query option.") 2925 return "" 2926 2927 def offset_limit_modifiers( 2928 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2929 ) -> t.List[str]: 2930 return [ 2931 self.sql(expression, "offset") if fetch else self.sql(limit), 2932 self.sql(limit) if fetch else self.sql(expression, "offset"), 2933 ] 2934 2935 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2936 locks = self.expressions(expression, key="locks", sep=" ") 2937 locks = f" {locks}" if locks else "" 2938 return [locks, self.sql(expression, "sample")] 2939 2940 def select_sql(self, expression: exp.Select) -> str: 2941 into = expression.args.get("into") 2942 if not self.SUPPORTS_SELECT_INTO and into: 2943 into.pop() 2944 2945 hint = self.sql(expression, "hint") 2946 distinct = self.sql(expression, "distinct") 2947 distinct = f" {distinct}" if distinct else "" 2948 kind = self.sql(expression, "kind") 2949 2950 limit = expression.args.get("limit") 2951 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2952 top = self.limit_sql(limit, top=True) 2953 limit.pop() 2954 else: 2955 top = "" 2956 2957 expressions = self.expressions(expression) 2958 2959 if kind: 2960 if kind in self.SELECT_KINDS: 2961 kind = f" AS {kind}" 2962 else: 2963 if kind == "STRUCT": 2964 expressions = self.expressions( 2965 sqls=[ 2966 self.sql( 2967 exp.Struct( 2968 expressions=[ 2969 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2970 if isinstance(e, exp.Alias) 2971 else e 2972 for e in expression.expressions 2973 ] 2974 ) 2975 ) 2976 ] 2977 ) 2978 kind = "" 2979 2980 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2981 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2982 2983 exclude = expression.args.get("exclude") 2984 2985 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 2986 exclude_sql = self.expressions(sqls=exclude, flat=True) 2987 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 2988 2989 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2990 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2991 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2992 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2993 sql = self.query_modifiers( 2994 expression, 2995 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2996 self.sql(expression, "into", comment=False), 2997 self.sql(expression, "from_", comment=False), 2998 ) 2999 3000 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3001 if expression.args.get("with_"): 3002 sql = self.maybe_comment(sql, expression) 3003 expression.pop_comments() 3004 3005 sql = self.prepend_ctes(expression, sql) 3006 3007 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3008 expression.set("exclude", None) 3009 subquery = expression.subquery(copy=False) 3010 star = exp.Star(except_=exclude) 3011 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3012 3013 if not self.SUPPORTS_SELECT_INTO and into: 3014 if into.args.get("temporary"): 3015 table_kind = " TEMPORARY" 3016 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3017 table_kind = " UNLOGGED" 3018 else: 3019 table_kind = "" 3020 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3021 3022 return sql 3023 3024 def schema_sql(self, expression: exp.Schema) -> str: 3025 this = self.sql(expression, "this") 3026 sql = self.schema_columns_sql(expression) 3027 return f"{this} {sql}" if this and sql else this or sql 3028 3029 def schema_columns_sql(self, expression: exp.Schema) -> str: 3030 if expression.expressions: 3031 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3032 return "" 3033 3034 def star_sql(self, expression: exp.Star) -> str: 3035 except_ = self.expressions(expression, key="except_", flat=True) 3036 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3037 replace = self.expressions(expression, key="replace", flat=True) 3038 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3039 rename = self.expressions(expression, key="rename", flat=True) 3040 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3041 return f"*{except_}{replace}{rename}" 3042 3043 def parameter_sql(self, expression: exp.Parameter) -> str: 3044 this = self.sql(expression, "this") 3045 return f"{self.PARAMETER_TOKEN}{this}" 3046 3047 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3048 this = self.sql(expression, "this") 3049 kind = expression.text("kind") 3050 if kind: 3051 kind = f"{kind}." 3052 return f"@@{kind}{this}" 3053 3054 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3055 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3056 3057 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3058 alias = self.sql(expression, "alias") 3059 alias = f"{sep}{alias}" if alias else "" 3060 sample = self.sql(expression, "sample") 3061 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3062 alias = f"{sample}{alias}" 3063 3064 # Set to None so it's not generated again by self.query_modifiers() 3065 expression.set("sample", None) 3066 3067 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3068 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3069 return self.prepend_ctes(expression, sql) 3070 3071 def qualify_sql(self, expression: exp.Qualify) -> str: 3072 this = self.indent(self.sql(expression, "this")) 3073 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3074 3075 def unnest_sql(self, expression: exp.Unnest) -> str: 3076 args = self.expressions(expression, flat=True) 3077 3078 alias = expression.args.get("alias") 3079 offset = expression.args.get("offset") 3080 3081 if self.UNNEST_WITH_ORDINALITY: 3082 if alias and isinstance(offset, exp.Expression): 3083 alias.append("columns", offset) 3084 3085 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3086 columns = alias.columns 3087 alias = self.sql(columns[0]) if columns else "" 3088 else: 3089 alias = self.sql(alias) 3090 3091 alias = f" AS {alias}" if alias else alias 3092 if self.UNNEST_WITH_ORDINALITY: 3093 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3094 else: 3095 if isinstance(offset, exp.Expression): 3096 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3097 elif offset: 3098 suffix = f"{alias} WITH OFFSET" 3099 else: 3100 suffix = alias 3101 3102 return f"UNNEST({args}){suffix}" 3103 3104 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3105 return "" 3106 3107 def where_sql(self, expression: exp.Where) -> str: 3108 this = self.indent(self.sql(expression, "this")) 3109 return f"{self.seg('WHERE')}{self.sep()}{this}" 3110 3111 def window_sql(self, expression: exp.Window) -> str: 3112 this = self.sql(expression, "this") 3113 partition = self.partition_by_sql(expression) 3114 order = expression.args.get("order") 3115 order = self.order_sql(order, flat=True) if order else "" 3116 spec = self.sql(expression, "spec") 3117 alias = self.sql(expression, "alias") 3118 over = self.sql(expression, "over") or "OVER" 3119 3120 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3121 3122 first = expression.args.get("first") 3123 if first is None: 3124 first = "" 3125 else: 3126 first = "FIRST" if first else "LAST" 3127 3128 if not partition and not order and not spec and alias: 3129 return f"{this} {alias}" 3130 3131 args = self.format_args( 3132 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3133 ) 3134 return f"{this} ({args})" 3135 3136 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3137 partition = self.expressions(expression, key="partition_by", flat=True) 3138 return f"PARTITION BY {partition}" if partition else "" 3139 3140 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3141 kind = self.sql(expression, "kind") 3142 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3143 end = ( 3144 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3145 or "CURRENT ROW" 3146 ) 3147 3148 window_spec = f"{kind} BETWEEN {start} AND {end}" 3149 3150 exclude = self.sql(expression, "exclude") 3151 if exclude: 3152 if self.SUPPORTS_WINDOW_EXCLUDE: 3153 window_spec += f" EXCLUDE {exclude}" 3154 else: 3155 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3156 3157 return window_spec 3158 3159 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3160 this = self.sql(expression, "this") 3161 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3162 return f"{this} WITHIN GROUP ({expression_sql})" 3163 3164 def between_sql(self, expression: exp.Between) -> str: 3165 this = self.sql(expression, "this") 3166 low = self.sql(expression, "low") 3167 high = self.sql(expression, "high") 3168 symmetric = expression.args.get("symmetric") 3169 3170 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3171 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3172 3173 flag = ( 3174 " SYMMETRIC" 3175 if symmetric 3176 else " ASYMMETRIC" 3177 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3178 else "" # silently drop ASYMMETRIC – semantics identical 3179 ) 3180 return f"{this} BETWEEN{flag} {low} AND {high}" 3181 3182 def bracket_offset_expressions( 3183 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 3184 ) -> t.List[exp.Expression]: 3185 return apply_index_offset( 3186 expression.this, 3187 expression.expressions, 3188 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3189 dialect=self.dialect, 3190 ) 3191 3192 def bracket_sql(self, expression: exp.Bracket) -> str: 3193 expressions = self.bracket_offset_expressions(expression) 3194 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3195 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3196 3197 def all_sql(self, expression: exp.All) -> str: 3198 this = self.sql(expression, "this") 3199 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3200 this = self.wrap(this) 3201 return f"ALL {this}" 3202 3203 def any_sql(self, expression: exp.Any) -> str: 3204 this = self.sql(expression, "this") 3205 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3206 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3207 this = self.wrap(this) 3208 return f"ANY{this}" 3209 return f"ANY {this}" 3210 3211 def exists_sql(self, expression: exp.Exists) -> str: 3212 return f"EXISTS{self.wrap(expression)}" 3213 3214 def case_sql(self, expression: exp.Case) -> str: 3215 this = self.sql(expression, "this") 3216 statements = [f"CASE {this}" if this else "CASE"] 3217 3218 for e in expression.args["ifs"]: 3219 statements.append(f"WHEN {self.sql(e, 'this')}") 3220 statements.append(f"THEN {self.sql(e, 'true')}") 3221 3222 default = self.sql(expression, "default") 3223 3224 if default: 3225 statements.append(f"ELSE {default}") 3226 3227 statements.append("END") 3228 3229 if self.pretty and self.too_wide(statements): 3230 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3231 3232 return " ".join(statements) 3233 3234 def constraint_sql(self, expression: exp.Constraint) -> str: 3235 this = self.sql(expression, "this") 3236 expressions = self.expressions(expression, flat=True) 3237 return f"CONSTRAINT {this} {expressions}" 3238 3239 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3240 order = expression.args.get("order") 3241 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3242 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3243 3244 def extract_sql(self, expression: exp.Extract) -> str: 3245 from sqlglot.dialects.dialect import map_date_part 3246 3247 this = ( 3248 map_date_part(expression.this, self.dialect) 3249 if self.NORMALIZE_EXTRACT_DATE_PARTS 3250 else expression.this 3251 ) 3252 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3253 expression_sql = self.sql(expression, "expression") 3254 3255 return f"EXTRACT({this_sql} FROM {expression_sql})" 3256 3257 def trim_sql(self, expression: exp.Trim) -> str: 3258 trim_type = self.sql(expression, "position") 3259 3260 if trim_type == "LEADING": 3261 func_name = "LTRIM" 3262 elif trim_type == "TRAILING": 3263 func_name = "RTRIM" 3264 else: 3265 func_name = "TRIM" 3266 3267 return self.func(func_name, expression.this, expression.expression) 3268 3269 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3270 args = expression.expressions 3271 if isinstance(expression, exp.ConcatWs): 3272 args = args[1:] # Skip the delimiter 3273 3274 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3275 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3276 3277 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3278 3279 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3280 if not e.type: 3281 from sqlglot.optimizer.annotate_types import annotate_types 3282 3283 e = annotate_types(e, dialect=self.dialect) 3284 3285 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3286 return e 3287 3288 return exp.func("coalesce", e, exp.Literal.string("")) 3289 3290 args = [_wrap_with_coalesce(e) for e in args] 3291 3292 return args 3293 3294 def concat_sql(self, expression: exp.Concat) -> str: 3295 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3296 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3297 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3298 # instead of coalescing them to empty string. 3299 from sqlglot.dialects.dialect import concat_to_dpipe_sql 3300 3301 return concat_to_dpipe_sql(self, expression) 3302 3303 expressions = self.convert_concat_args(expression) 3304 3305 # Some dialects don't allow a single-argument CONCAT call 3306 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3307 return self.sql(expressions[0]) 3308 3309 return self.func("CONCAT", *expressions) 3310 3311 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3312 return self.func( 3313 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3314 ) 3315 3316 def check_sql(self, expression: exp.Check) -> str: 3317 this = self.sql(expression, key="this") 3318 return f"CHECK ({this})" 3319 3320 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3321 expressions = self.expressions(expression, flat=True) 3322 expressions = f" ({expressions})" if expressions else "" 3323 reference = self.sql(expression, "reference") 3324 reference = f" {reference}" if reference else "" 3325 delete = self.sql(expression, "delete") 3326 delete = f" ON DELETE {delete}" if delete else "" 3327 update = self.sql(expression, "update") 3328 update = f" ON UPDATE {update}" if update else "" 3329 options = self.expressions(expression, key="options", flat=True, sep=" ") 3330 options = f" {options}" if options else "" 3331 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3332 3333 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3334 this = self.sql(expression, "this") 3335 this = f" {this}" if this else "" 3336 expressions = self.expressions(expression, flat=True) 3337 include = self.sql(expression, "include") 3338 options = self.expressions(expression, key="options", flat=True, sep=" ") 3339 options = f" {options}" if options else "" 3340 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3341 3342 def if_sql(self, expression: exp.If) -> str: 3343 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3344 3345 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3346 if self.MATCH_AGAINST_TABLE_PREFIX: 3347 expressions = [] 3348 for expr in expression.expressions: 3349 if isinstance(expr, exp.Table): 3350 expressions.append(f"TABLE {self.sql(expr)}") 3351 else: 3352 expressions.append(expr) 3353 else: 3354 expressions = expression.expressions 3355 3356 modifier = expression.args.get("modifier") 3357 modifier = f" {modifier}" if modifier else "" 3358 return ( 3359 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3360 ) 3361 3362 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3363 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3364 3365 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3366 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3367 3368 if expression.args.get("escape"): 3369 path = self.escape_str(path) 3370 3371 if self.QUOTE_JSON_PATH: 3372 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3373 3374 return path 3375 3376 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3377 if isinstance(expression, exp.JSONPathPart): 3378 transform = self.TRANSFORMS.get(expression.__class__) 3379 if not callable(transform): 3380 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3381 return "" 3382 3383 return transform(self, expression) 3384 3385 if isinstance(expression, int): 3386 return str(expression) 3387 3388 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3389 escaped = expression.replace("'", "\\'") 3390 escaped = f"\\'{expression}\\'" 3391 else: 3392 escaped = expression.replace('"', '\\"') 3393 escaped = f'"{escaped}"' 3394 3395 return escaped 3396 3397 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3398 return f"{self.sql(expression, 'this')} FORMAT JSON" 3399 3400 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3401 # Output the Teradata column FORMAT override. 3402 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3403 this = self.sql(expression, "this") 3404 fmt = self.sql(expression, "format") 3405 return f"{this} (FORMAT {fmt})" 3406 3407 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3408 null_handling = expression.args.get("null_handling") 3409 null_handling = f" {null_handling}" if null_handling else "" 3410 3411 unique_keys = expression.args.get("unique_keys") 3412 if unique_keys is not None: 3413 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3414 else: 3415 unique_keys = "" 3416 3417 return_type = self.sql(expression, "return_type") 3418 return_type = f" RETURNING {return_type}" if return_type else "" 3419 encoding = self.sql(expression, "encoding") 3420 encoding = f" ENCODING {encoding}" if encoding else "" 3421 3422 return self.func( 3423 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3424 *expression.expressions, 3425 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3426 ) 3427 3428 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3429 return self.jsonobject_sql(expression) 3430 3431 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3432 null_handling = expression.args.get("null_handling") 3433 null_handling = f" {null_handling}" if null_handling else "" 3434 return_type = self.sql(expression, "return_type") 3435 return_type = f" RETURNING {return_type}" if return_type else "" 3436 strict = " STRICT" if expression.args.get("strict") else "" 3437 return self.func( 3438 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3439 ) 3440 3441 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3442 this = self.sql(expression, "this") 3443 order = self.sql(expression, "order") 3444 null_handling = expression.args.get("null_handling") 3445 null_handling = f" {null_handling}" if null_handling else "" 3446 return_type = self.sql(expression, "return_type") 3447 return_type = f" RETURNING {return_type}" if return_type else "" 3448 strict = " STRICT" if expression.args.get("strict") else "" 3449 return self.func( 3450 "JSON_ARRAYAGG", 3451 this, 3452 suffix=f"{order}{null_handling}{return_type}{strict})", 3453 ) 3454 3455 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3456 path = self.sql(expression, "path") 3457 path = f" PATH {path}" if path else "" 3458 nested_schema = self.sql(expression, "nested_schema") 3459 3460 if nested_schema: 3461 return f"NESTED{path} {nested_schema}" 3462 3463 this = self.sql(expression, "this") 3464 kind = self.sql(expression, "kind") 3465 kind = f" {kind}" if kind else "" 3466 3467 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3468 return f"{this}{kind}{path}{ordinality}" 3469 3470 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3471 return self.func("COLUMNS", *expression.expressions) 3472 3473 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3474 this = self.sql(expression, "this") 3475 path = self.sql(expression, "path") 3476 path = f", {path}" if path else "" 3477 error_handling = expression.args.get("error_handling") 3478 error_handling = f" {error_handling}" if error_handling else "" 3479 empty_handling = expression.args.get("empty_handling") 3480 empty_handling = f" {empty_handling}" if empty_handling else "" 3481 schema = self.sql(expression, "schema") 3482 return self.func( 3483 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3484 ) 3485 3486 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3487 this = self.sql(expression, "this") 3488 kind = self.sql(expression, "kind") 3489 path = self.sql(expression, "path") 3490 path = f" {path}" if path else "" 3491 as_json = " AS JSON" if expression.args.get("as_json") else "" 3492 return f"{this} {kind}{path}{as_json}" 3493 3494 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3495 this = self.sql(expression, "this") 3496 path = self.sql(expression, "path") 3497 path = f", {path}" if path else "" 3498 expressions = self.expressions(expression) 3499 with_ = ( 3500 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3501 if expressions 3502 else "" 3503 ) 3504 return f"OPENJSON({this}{path}){with_}" 3505 3506 def in_sql(self, expression: exp.In) -> str: 3507 query = expression.args.get("query") 3508 unnest = expression.args.get("unnest") 3509 field = expression.args.get("field") 3510 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3511 3512 if query: 3513 in_sql = self.sql(query) 3514 elif unnest: 3515 in_sql = self.in_unnest_op(unnest) 3516 elif field: 3517 in_sql = self.sql(field) 3518 else: 3519 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3520 3521 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3522 3523 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3524 return f"(SELECT {self.sql(unnest)})" 3525 3526 def interval_sql(self, expression: exp.Interval) -> str: 3527 unit_expression = expression.args.get("unit") 3528 unit = self.sql(unit_expression) if unit_expression else "" 3529 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3530 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3531 unit = f" {unit}" if unit else "" 3532 3533 if self.SINGLE_STRING_INTERVAL: 3534 this = expression.this.name if expression.this else "" 3535 if this: 3536 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3537 return f"INTERVAL '{this}'{unit}" 3538 return f"INTERVAL '{this}{unit}'" 3539 return f"INTERVAL{unit}" 3540 3541 this = self.sql(expression, "this") 3542 if this: 3543 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3544 this = f" {this}" if unwrapped else f" ({this})" 3545 3546 return f"INTERVAL{this}{unit}" 3547 3548 def return_sql(self, expression: exp.Return) -> str: 3549 return f"RETURN {self.sql(expression, 'this')}" 3550 3551 def reference_sql(self, expression: exp.Reference) -> str: 3552 this = self.sql(expression, "this") 3553 expressions = self.expressions(expression, flat=True) 3554 expressions = f"({expressions})" if expressions else "" 3555 options = self.expressions(expression, key="options", flat=True, sep=" ") 3556 options = f" {options}" if options else "" 3557 return f"REFERENCES {this}{expressions}{options}" 3558 3559 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3560 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3561 parent = expression.parent 3562 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3563 3564 return self.func( 3565 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3566 ) 3567 3568 def paren_sql(self, expression: exp.Paren) -> str: 3569 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3570 return f"({sql}{self.seg(')', sep='')}" 3571 3572 def neg_sql(self, expression: exp.Neg) -> str: 3573 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3574 this_sql = self.sql(expression, "this") 3575 sep = " " if this_sql[0] == "-" else "" 3576 return f"-{sep}{this_sql}" 3577 3578 def not_sql(self, expression: exp.Not) -> str: 3579 return f"NOT {self.sql(expression, 'this')}" 3580 3581 def alias_sql(self, expression: exp.Alias) -> str: 3582 alias = self.sql(expression, "alias") 3583 alias = f" AS {alias}" if alias else "" 3584 return f"{self.sql(expression, 'this')}{alias}" 3585 3586 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3587 alias = expression.args["alias"] 3588 3589 parent = expression.parent 3590 pivot = parent and parent.parent 3591 3592 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3593 identifier_alias = isinstance(alias, exp.Identifier) 3594 literal_alias = isinstance(alias, exp.Literal) 3595 3596 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3597 alias.replace(exp.Literal.string(alias.output_name)) 3598 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3599 alias.replace(exp.to_identifier(alias.output_name)) 3600 3601 return self.alias_sql(expression) 3602 3603 def aliases_sql(self, expression: exp.Aliases) -> str: 3604 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3605 3606 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3607 this = self.sql(expression, "this") 3608 index = self.sql(expression, "expression") 3609 return f"{this} AT {index}" 3610 3611 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3612 this = self.sql(expression, "this") 3613 zone = self.sql(expression, "zone") 3614 return f"{this} AT TIME ZONE {zone}" 3615 3616 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3617 this = self.sql(expression, "this") 3618 zone = self.sql(expression, "zone") 3619 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3620 3621 def add_sql(self, expression: exp.Add) -> str: 3622 return self.binary(expression, "+") 3623 3624 def and_sql( 3625 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3626 ) -> str: 3627 return self.connector_sql(expression, "AND", stack) 3628 3629 def or_sql( 3630 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3631 ) -> str: 3632 return self.connector_sql(expression, "OR", stack) 3633 3634 def xor_sql( 3635 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3636 ) -> str: 3637 return self.connector_sql(expression, "XOR", stack) 3638 3639 def connector_sql( 3640 self, 3641 expression: exp.Connector, 3642 op: str, 3643 stack: t.Optional[t.List[str | exp.Expression]] = None, 3644 ) -> str: 3645 if stack is not None: 3646 if expression.expressions: 3647 stack.append(self.expressions(expression, sep=f" {op} ")) 3648 else: 3649 stack.append(expression.right) 3650 if expression.comments and self.comments: 3651 for comment in expression.comments: 3652 if comment: 3653 op += f" /*{self.sanitize_comment(comment)}*/" 3654 stack.extend((op, expression.left)) 3655 return op 3656 3657 stack = [expression] 3658 sqls: t.List[str] = [] 3659 ops = set() 3660 3661 while stack: 3662 node = stack.pop() 3663 if isinstance(node, exp.Connector): 3664 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3665 else: 3666 sql = self.sql(node) 3667 if sqls and sqls[-1] in ops: 3668 sqls[-1] += f" {sql}" 3669 else: 3670 sqls.append(sql) 3671 3672 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3673 return sep.join(sqls) 3674 3675 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3676 return self.binary(expression, "&") 3677 3678 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3679 return self.binary(expression, "<<") 3680 3681 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3682 return f"~{self.sql(expression, 'this')}" 3683 3684 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3685 return self.binary(expression, "|") 3686 3687 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3688 return self.binary(expression, ">>") 3689 3690 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3691 return self.binary(expression, "^") 3692 3693 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3694 format_sql = self.sql(expression, "format") 3695 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3696 to_sql = self.sql(expression, "to") 3697 to_sql = f" {to_sql}" if to_sql else "" 3698 action = self.sql(expression, "action") 3699 action = f" {action}" if action else "" 3700 default = self.sql(expression, "default") 3701 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3702 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3703 3704 # Base implementation that excludes safe, zone, and target_type metadata args 3705 def strtotime_sql(self, expression: exp.StrToTime) -> str: 3706 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 3707 3708 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3709 zone = self.sql(expression, "this") 3710 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3711 3712 def collate_sql(self, expression: exp.Collate) -> str: 3713 if self.COLLATE_IS_FUNC: 3714 return self.function_fallback_sql(expression) 3715 return self.binary(expression, "COLLATE") 3716 3717 def command_sql(self, expression: exp.Command) -> str: 3718 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3719 3720 def comment_sql(self, expression: exp.Comment) -> str: 3721 this = self.sql(expression, "this") 3722 kind = expression.args["kind"] 3723 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3724 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3725 expression_sql = self.sql(expression, "expression") 3726 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3727 3728 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3729 this = self.sql(expression, "this") 3730 delete = " DELETE" if expression.args.get("delete") else "" 3731 recompress = self.sql(expression, "recompress") 3732 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3733 to_disk = self.sql(expression, "to_disk") 3734 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3735 to_volume = self.sql(expression, "to_volume") 3736 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3737 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3738 3739 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3740 where = self.sql(expression, "where") 3741 group = self.sql(expression, "group") 3742 aggregates = self.expressions(expression, key="aggregates") 3743 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3744 3745 if not (where or group or aggregates) and len(expression.expressions) == 1: 3746 return f"TTL {self.expressions(expression, flat=True)}" 3747 3748 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3749 3750 def transaction_sql(self, expression: exp.Transaction) -> str: 3751 modes = self.expressions(expression, key="modes") 3752 modes = f" {modes}" if modes else "" 3753 return f"BEGIN{modes}" 3754 3755 def commit_sql(self, expression: exp.Commit) -> str: 3756 chain = expression.args.get("chain") 3757 if chain is not None: 3758 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3759 3760 return f"COMMIT{chain or ''}" 3761 3762 def rollback_sql(self, expression: exp.Rollback) -> str: 3763 savepoint = expression.args.get("savepoint") 3764 savepoint = f" TO {savepoint}" if savepoint else "" 3765 return f"ROLLBACK{savepoint}" 3766 3767 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3768 this = self.sql(expression, "this") 3769 3770 dtype = self.sql(expression, "dtype") 3771 if dtype: 3772 collate = self.sql(expression, "collate") 3773 collate = f" COLLATE {collate}" if collate else "" 3774 using = self.sql(expression, "using") 3775 using = f" USING {using}" if using else "" 3776 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3777 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3778 3779 default = self.sql(expression, "default") 3780 if default: 3781 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3782 3783 comment = self.sql(expression, "comment") 3784 if comment: 3785 return f"ALTER COLUMN {this} COMMENT {comment}" 3786 3787 visible = expression.args.get("visible") 3788 if visible: 3789 return f"ALTER COLUMN {this} SET {visible}" 3790 3791 allow_null = expression.args.get("allow_null") 3792 drop = expression.args.get("drop") 3793 3794 if not drop and not allow_null: 3795 self.unsupported("Unsupported ALTER COLUMN syntax") 3796 3797 if allow_null is not None: 3798 keyword = "DROP" if drop else "SET" 3799 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3800 3801 return f"ALTER COLUMN {this} DROP DEFAULT" 3802 3803 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3804 this = self.sql(expression, "this") 3805 3806 visible = expression.args.get("visible") 3807 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3808 3809 return f"ALTER INDEX {this} {visible_sql}" 3810 3811 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3812 this = self.sql(expression, "this") 3813 if not isinstance(expression.this, exp.Var): 3814 this = f"KEY DISTKEY {this}" 3815 return f"ALTER DISTSTYLE {this}" 3816 3817 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3818 compound = " COMPOUND" if expression.args.get("compound") else "" 3819 this = self.sql(expression, "this") 3820 expressions = self.expressions(expression, flat=True) 3821 expressions = f"({expressions})" if expressions else "" 3822 return f"ALTER{compound} SORTKEY {this or expressions}" 3823 3824 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3825 if not self.RENAME_TABLE_WITH_DB: 3826 # Remove db from tables 3827 expression = expression.transform( 3828 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3829 ).assert_is(exp.AlterRename) 3830 this = self.sql(expression, "this") 3831 to_kw = " TO" if include_to else "" 3832 return f"RENAME{to_kw} {this}" 3833 3834 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3835 exists = " IF EXISTS" if expression.args.get("exists") else "" 3836 old_column = self.sql(expression, "this") 3837 new_column = self.sql(expression, "to") 3838 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3839 3840 def alterset_sql(self, expression: exp.AlterSet) -> str: 3841 exprs = self.expressions(expression, flat=True) 3842 if self.ALTER_SET_WRAPPED: 3843 exprs = f"({exprs})" 3844 3845 return f"SET {exprs}" 3846 3847 def alter_sql(self, expression: exp.Alter) -> str: 3848 actions = expression.args["actions"] 3849 3850 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3851 actions[0], exp.ColumnDef 3852 ): 3853 actions_sql = self.expressions(expression, key="actions", flat=True) 3854 actions_sql = f"ADD {actions_sql}" 3855 else: 3856 actions_list = [] 3857 for action in actions: 3858 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3859 action_sql = self.add_column_sql(action) 3860 else: 3861 action_sql = self.sql(action) 3862 if isinstance(action, exp.Query): 3863 action_sql = f"AS {action_sql}" 3864 3865 actions_list.append(action_sql) 3866 3867 actions_sql = self.format_args(*actions_list).lstrip("\n") 3868 3869 exists = " IF EXISTS" if expression.args.get("exists") else "" 3870 on_cluster = self.sql(expression, "cluster") 3871 on_cluster = f" {on_cluster}" if on_cluster else "" 3872 only = " ONLY" if expression.args.get("only") else "" 3873 options = self.expressions(expression, key="options") 3874 options = f", {options}" if options else "" 3875 kind = self.sql(expression, "kind") 3876 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3877 check = " WITH CHECK" if expression.args.get("check") else "" 3878 cascade = ( 3879 " CASCADE" 3880 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 3881 else "" 3882 ) 3883 this = self.sql(expression, "this") 3884 this = f" {this}" if this else "" 3885 3886 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 3887 3888 def altersession_sql(self, expression: exp.AlterSession) -> str: 3889 items_sql = self.expressions(expression, flat=True) 3890 keyword = "UNSET" if expression.args.get("unset") else "SET" 3891 return f"{keyword} {items_sql}" 3892 3893 def add_column_sql(self, expression: exp.Expression) -> str: 3894 sql = self.sql(expression) 3895 if isinstance(expression, exp.Schema): 3896 column_text = " COLUMNS" 3897 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3898 column_text = " COLUMN" 3899 else: 3900 column_text = "" 3901 3902 return f"ADD{column_text} {sql}" 3903 3904 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3905 expressions = self.expressions(expression) 3906 exists = " IF EXISTS " if expression.args.get("exists") else " " 3907 return f"DROP{exists}{expressions}" 3908 3909 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3910 return f"ADD {self.expressions(expression, indent=False)}" 3911 3912 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3913 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3914 location = self.sql(expression, "location") 3915 location = f" {location}" if location else "" 3916 return f"ADD {exists}{self.sql(expression.this)}{location}" 3917 3918 def distinct_sql(self, expression: exp.Distinct) -> str: 3919 this = self.expressions(expression, flat=True) 3920 3921 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3922 case = exp.case() 3923 for arg in expression.expressions: 3924 case = case.when(arg.is_(exp.null()), exp.null()) 3925 this = self.sql(case.else_(f"({this})")) 3926 3927 this = f" {this}" if this else "" 3928 3929 on = self.sql(expression, "on") 3930 on = f" ON {on}" if on else "" 3931 return f"DISTINCT{this}{on}" 3932 3933 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3934 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3935 3936 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3937 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3938 3939 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3940 this_sql = self.sql(expression, "this") 3941 expression_sql = self.sql(expression, "expression") 3942 kind = "MAX" if expression.args.get("max") else "MIN" 3943 return f"{this_sql} HAVING {kind} {expression_sql}" 3944 3945 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3946 return self.sql( 3947 exp.Cast( 3948 this=exp.Div(this=expression.this, expression=expression.expression), 3949 to=exp.DataType(this=exp.DataType.Type.INT), 3950 ) 3951 ) 3952 3953 def dpipe_sql(self, expression: exp.DPipe) -> str: 3954 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3955 return self.func( 3956 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3957 ) 3958 return self.binary(expression, "||") 3959 3960 def div_sql(self, expression: exp.Div) -> str: 3961 l, r = expression.left, expression.right 3962 3963 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3964 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3965 3966 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3967 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3968 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3969 3970 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3971 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3972 return self.sql( 3973 exp.cast( 3974 l / r, 3975 to=exp.DataType.Type.BIGINT, 3976 ) 3977 ) 3978 3979 return self.binary(expression, "/") 3980 3981 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3982 n = exp._wrap(expression.this, exp.Binary) 3983 d = exp._wrap(expression.expression, exp.Binary) 3984 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3985 3986 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3987 return self.binary(expression, "OVERLAPS") 3988 3989 def distance_sql(self, expression: exp.Distance) -> str: 3990 return self.binary(expression, "<->") 3991 3992 def dot_sql(self, expression: exp.Dot) -> str: 3993 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3994 3995 def eq_sql(self, expression: exp.EQ) -> str: 3996 return self.binary(expression, "=") 3997 3998 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3999 return self.binary(expression, ":=") 4000 4001 def escape_sql(self, expression: exp.Escape) -> str: 4002 return self.binary(expression, "ESCAPE") 4003 4004 def glob_sql(self, expression: exp.Glob) -> str: 4005 return self.binary(expression, "GLOB") 4006 4007 def gt_sql(self, expression: exp.GT) -> str: 4008 return self.binary(expression, ">") 4009 4010 def gte_sql(self, expression: exp.GTE) -> str: 4011 return self.binary(expression, ">=") 4012 4013 def is_sql(self, expression: exp.Is) -> str: 4014 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4015 return self.sql( 4016 expression.this if expression.expression.this else exp.not_(expression.this) 4017 ) 4018 return self.binary(expression, "IS") 4019 4020 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 4021 this = expression.this 4022 rhs = expression.expression 4023 4024 if isinstance(expression, exp.Like): 4025 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 4026 op = "LIKE" 4027 else: 4028 exp_class = exp.ILike 4029 op = "ILIKE" 4030 4031 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4032 exprs = rhs.this.unnest() 4033 4034 if isinstance(exprs, exp.Tuple): 4035 exprs = exprs.expressions 4036 4037 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4038 4039 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 4040 for expr in exprs[1:]: 4041 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 4042 4043 parent = expression.parent 4044 if not isinstance(parent, type(like_expr)) and isinstance(parent, exp.Condition): 4045 like_expr = exp.paren(like_expr, copy=False) 4046 4047 return self.sql(like_expr) 4048 4049 return self.binary(expression, op) 4050 4051 def like_sql(self, expression: exp.Like) -> str: 4052 return self._like_sql(expression) 4053 4054 def ilike_sql(self, expression: exp.ILike) -> str: 4055 return self._like_sql(expression) 4056 4057 def match_sql(self, expression: exp.Match) -> str: 4058 return self.binary(expression, "MATCH") 4059 4060 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4061 return self.binary(expression, "SIMILAR TO") 4062 4063 def lt_sql(self, expression: exp.LT) -> str: 4064 return self.binary(expression, "<") 4065 4066 def lte_sql(self, expression: exp.LTE) -> str: 4067 return self.binary(expression, "<=") 4068 4069 def mod_sql(self, expression: exp.Mod) -> str: 4070 return self.binary(expression, "%") 4071 4072 def mul_sql(self, expression: exp.Mul) -> str: 4073 return self.binary(expression, "*") 4074 4075 def neq_sql(self, expression: exp.NEQ) -> str: 4076 return self.binary(expression, "<>") 4077 4078 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4079 return self.binary(expression, "IS NOT DISTINCT FROM") 4080 4081 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4082 return self.binary(expression, "IS DISTINCT FROM") 4083 4084 def sub_sql(self, expression: exp.Sub) -> str: 4085 return self.binary(expression, "-") 4086 4087 def trycast_sql(self, expression: exp.TryCast) -> str: 4088 return self.cast_sql(expression, safe_prefix="TRY_") 4089 4090 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4091 return self.cast_sql(expression) 4092 4093 def try_sql(self, expression: exp.Try) -> str: 4094 if not self.TRY_SUPPORTED: 4095 self.unsupported("Unsupported TRY function") 4096 return self.sql(expression, "this") 4097 4098 return self.func("TRY", expression.this) 4099 4100 def log_sql(self, expression: exp.Log) -> str: 4101 this = expression.this 4102 expr = expression.expression 4103 4104 if self.dialect.LOG_BASE_FIRST is False: 4105 this, expr = expr, this 4106 elif self.dialect.LOG_BASE_FIRST is None and expr: 4107 if this.name in ("2", "10"): 4108 return self.func(f"LOG{this.name}", expr) 4109 4110 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4111 4112 return self.func("LOG", this, expr) 4113 4114 def use_sql(self, expression: exp.Use) -> str: 4115 kind = self.sql(expression, "kind") 4116 kind = f" {kind}" if kind else "" 4117 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4118 this = f" {this}" if this else "" 4119 return f"USE{kind}{this}" 4120 4121 def binary(self, expression: exp.Binary, op: str) -> str: 4122 sqls: t.List[str] = [] 4123 stack: t.List[t.Union[str, exp.Expression]] = [expression] 4124 binary_type = type(expression) 4125 4126 while stack: 4127 node = stack.pop() 4128 4129 if type(node) is binary_type: 4130 op_func = node.args.get("operator") 4131 if op_func: 4132 op = f"OPERATOR({self.sql(op_func)})" 4133 4134 stack.append(node.right) 4135 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4136 stack.append(node.left) 4137 else: 4138 sqls.append(self.sql(node)) 4139 4140 return "".join(sqls) 4141 4142 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4143 to_clause = self.sql(expression, "to") 4144 if to_clause: 4145 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4146 4147 return self.function_fallback_sql(expression) 4148 4149 def function_fallback_sql(self, expression: exp.Func) -> str: 4150 args = [] 4151 4152 for key in expression.arg_types: 4153 arg_value = expression.args.get(key) 4154 4155 if isinstance(arg_value, list): 4156 for value in arg_value: 4157 args.append(value) 4158 elif arg_value is not None: 4159 args.append(arg_value) 4160 4161 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4162 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4163 else: 4164 name = expression.sql_name() 4165 4166 return self.func(name, *args) 4167 4168 def func( 4169 self, 4170 name: str, 4171 *args: t.Optional[exp.Expression | str], 4172 prefix: str = "(", 4173 suffix: str = ")", 4174 normalize: bool = True, 4175 ) -> str: 4176 name = self.normalize_func(name) if normalize else name 4177 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4178 4179 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 4180 arg_sqls = tuple( 4181 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4182 ) 4183 if self.pretty and self.too_wide(arg_sqls): 4184 return self.indent( 4185 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4186 ) 4187 return sep.join(arg_sqls) 4188 4189 def too_wide(self, args: t.Iterable) -> bool: 4190 return sum(len(arg) for arg in args) > self.max_text_width 4191 4192 def format_time( 4193 self, 4194 expression: exp.Expression, 4195 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 4196 inverse_time_trie: t.Optional[t.Dict] = None, 4197 ) -> t.Optional[str]: 4198 return format_time( 4199 self.sql(expression, "format"), 4200 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4201 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4202 ) 4203 4204 def expressions( 4205 self, 4206 expression: t.Optional[exp.Expression] = None, 4207 key: t.Optional[str] = None, 4208 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 4209 flat: bool = False, 4210 indent: bool = True, 4211 skip_first: bool = False, 4212 skip_last: bool = False, 4213 sep: str = ", ", 4214 prefix: str = "", 4215 dynamic: bool = False, 4216 new_line: bool = False, 4217 ) -> str: 4218 expressions = expression.args.get(key or "expressions") if expression else sqls 4219 4220 if not expressions: 4221 return "" 4222 4223 if flat: 4224 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4225 4226 num_sqls = len(expressions) 4227 result_sqls = [] 4228 4229 for i, e in enumerate(expressions): 4230 sql = self.sql(e, comment=False) 4231 if not sql: 4232 continue 4233 4234 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 4235 4236 if self.pretty: 4237 if self.leading_comma: 4238 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4239 else: 4240 result_sqls.append( 4241 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4242 ) 4243 else: 4244 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4245 4246 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4247 if new_line: 4248 result_sqls.insert(0, "") 4249 result_sqls.append("") 4250 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4251 else: 4252 result_sql = "".join(result_sqls) 4253 4254 return ( 4255 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4256 if indent 4257 else result_sql 4258 ) 4259 4260 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 4261 flat = flat or isinstance(expression.parent, exp.Properties) 4262 expressions_sql = self.expressions(expression, flat=flat) 4263 if flat: 4264 return f"{op} {expressions_sql}" 4265 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4266 4267 def naked_property(self, expression: exp.Property) -> str: 4268 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4269 if not property_name: 4270 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4271 return f"{property_name} {self.sql(expression, 'this')}" 4272 4273 def tag_sql(self, expression: exp.Tag) -> str: 4274 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4275 4276 def token_sql(self, token_type: TokenType) -> str: 4277 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4278 4279 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4280 this = self.sql(expression, "this") 4281 expressions = self.no_identify(self.expressions, expression) 4282 expressions = ( 4283 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4284 ) 4285 return f"{this}{expressions}" if expressions.strip() != "" else this 4286 4287 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4288 this = self.sql(expression, "this") 4289 expressions = self.expressions(expression, flat=True) 4290 return f"{this}({expressions})" 4291 4292 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4293 return self.binary(expression, "=>") 4294 4295 def when_sql(self, expression: exp.When) -> str: 4296 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4297 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4298 condition = self.sql(expression, "condition") 4299 condition = f" AND {condition}" if condition else "" 4300 4301 then_expression = expression.args.get("then") 4302 if isinstance(then_expression, exp.Insert): 4303 this = self.sql(then_expression, "this") 4304 this = f"INSERT {this}" if this else "INSERT" 4305 then = self.sql(then_expression, "expression") 4306 then = f"{this} VALUES {then}" if then else this 4307 elif isinstance(then_expression, exp.Update): 4308 if isinstance(then_expression.args.get("expressions"), exp.Star): 4309 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4310 else: 4311 expressions_sql = self.expressions(then_expression) 4312 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4313 4314 else: 4315 then = self.sql(then_expression) 4316 return f"WHEN {matched}{source}{condition} THEN {then}" 4317 4318 def whens_sql(self, expression: exp.Whens) -> str: 4319 return self.expressions(expression, sep=" ", indent=False) 4320 4321 def merge_sql(self, expression: exp.Merge) -> str: 4322 table = expression.this 4323 table_alias = "" 4324 4325 hints = table.args.get("hints") 4326 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4327 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4328 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4329 4330 this = self.sql(table) 4331 using = f"USING {self.sql(expression, 'using')}" 4332 whens = self.sql(expression, "whens") 4333 4334 on = self.sql(expression, "on") 4335 on = f"ON {on}" if on else "" 4336 4337 if not on: 4338 on = self.expressions(expression, key="using_cond") 4339 on = f"USING ({on})" if on else "" 4340 4341 returning = self.sql(expression, "returning") 4342 if returning: 4343 whens = f"{whens}{returning}" 4344 4345 sep = self.sep() 4346 4347 return self.prepend_ctes( 4348 expression, 4349 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4350 ) 4351 4352 @unsupported_args("format") 4353 def tochar_sql(self, expression: exp.ToChar) -> str: 4354 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4355 4356 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4357 if not self.SUPPORTS_TO_NUMBER: 4358 self.unsupported("Unsupported TO_NUMBER function") 4359 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4360 4361 fmt = expression.args.get("format") 4362 if not fmt: 4363 self.unsupported("Conversion format is required for TO_NUMBER") 4364 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4365 4366 return self.func("TO_NUMBER", expression.this, fmt) 4367 4368 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4369 this = self.sql(expression, "this") 4370 kind = self.sql(expression, "kind") 4371 settings_sql = self.expressions(expression, key="settings", sep=" ") 4372 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4373 return f"{this}({kind}{args})" 4374 4375 def dictrange_sql(self, expression: exp.DictRange) -> str: 4376 this = self.sql(expression, "this") 4377 max = self.sql(expression, "max") 4378 min = self.sql(expression, "min") 4379 return f"{this}(MIN {min} MAX {max})" 4380 4381 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4382 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4383 4384 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4385 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4386 4387 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4388 def uniquekeyproperty_sql( 4389 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4390 ) -> str: 4391 return f"{prefix} ({self.expressions(expression, flat=True)})" 4392 4393 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4394 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4395 expressions = self.expressions(expression, flat=True) 4396 expressions = f" {self.wrap(expressions)}" if expressions else "" 4397 buckets = self.sql(expression, "buckets") 4398 kind = self.sql(expression, "kind") 4399 buckets = f" BUCKETS {buckets}" if buckets else "" 4400 order = self.sql(expression, "order") 4401 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4402 4403 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4404 return "" 4405 4406 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4407 expressions = self.expressions(expression, key="expressions", flat=True) 4408 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4409 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4410 buckets = self.sql(expression, "buckets") 4411 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4412 4413 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4414 this = self.sql(expression, "this") 4415 having = self.sql(expression, "having") 4416 4417 if having: 4418 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4419 4420 return self.func("ANY_VALUE", this) 4421 4422 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4423 transform = self.func("TRANSFORM", *expression.expressions) 4424 row_format_before = self.sql(expression, "row_format_before") 4425 row_format_before = f" {row_format_before}" if row_format_before else "" 4426 record_writer = self.sql(expression, "record_writer") 4427 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4428 using = f" USING {self.sql(expression, 'command_script')}" 4429 schema = self.sql(expression, "schema") 4430 schema = f" AS {schema}" if schema else "" 4431 row_format_after = self.sql(expression, "row_format_after") 4432 row_format_after = f" {row_format_after}" if row_format_after else "" 4433 record_reader = self.sql(expression, "record_reader") 4434 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4435 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4436 4437 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4438 key_block_size = self.sql(expression, "key_block_size") 4439 if key_block_size: 4440 return f"KEY_BLOCK_SIZE = {key_block_size}" 4441 4442 using = self.sql(expression, "using") 4443 if using: 4444 return f"USING {using}" 4445 4446 parser = self.sql(expression, "parser") 4447 if parser: 4448 return f"WITH PARSER {parser}" 4449 4450 comment = self.sql(expression, "comment") 4451 if comment: 4452 return f"COMMENT {comment}" 4453 4454 visible = expression.args.get("visible") 4455 if visible is not None: 4456 return "VISIBLE" if visible else "INVISIBLE" 4457 4458 engine_attr = self.sql(expression, "engine_attr") 4459 if engine_attr: 4460 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4461 4462 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4463 if secondary_engine_attr: 4464 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4465 4466 self.unsupported("Unsupported index constraint option.") 4467 return "" 4468 4469 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4470 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4471 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4472 4473 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4474 kind = self.sql(expression, "kind") 4475 kind = f"{kind} INDEX" if kind else "INDEX" 4476 this = self.sql(expression, "this") 4477 this = f" {this}" if this else "" 4478 index_type = self.sql(expression, "index_type") 4479 index_type = f" USING {index_type}" if index_type else "" 4480 expressions = self.expressions(expression, flat=True) 4481 expressions = f" ({expressions})" if expressions else "" 4482 options = self.expressions(expression, key="options", sep=" ") 4483 options = f" {options}" if options else "" 4484 return f"{kind}{this}{index_type}{expressions}{options}" 4485 4486 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4487 if self.NVL2_SUPPORTED: 4488 return self.function_fallback_sql(expression) 4489 4490 case = exp.Case().when( 4491 expression.this.is_(exp.null()).not_(copy=False), 4492 expression.args["true"], 4493 copy=False, 4494 ) 4495 else_cond = expression.args.get("false") 4496 if else_cond: 4497 case.else_(else_cond, copy=False) 4498 4499 return self.sql(case) 4500 4501 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4502 this = self.sql(expression, "this") 4503 expr = self.sql(expression, "expression") 4504 position = self.sql(expression, "position") 4505 position = f", {position}" if position else "" 4506 iterator = self.sql(expression, "iterator") 4507 condition = self.sql(expression, "condition") 4508 condition = f" IF {condition}" if condition else "" 4509 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4510 4511 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4512 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4513 4514 def opclass_sql(self, expression: exp.Opclass) -> str: 4515 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4516 4517 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4518 model = self.sql(expression, "this") 4519 model = f"MODEL {model}" 4520 expr = expression.expression 4521 if expr: 4522 expr_sql = self.sql(expression, "expression") 4523 expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql 4524 else: 4525 expr_sql = None 4526 4527 parameters = self.sql(expression, "params_struct") or None 4528 4529 return self.func(name, model, expr_sql, parameters) 4530 4531 def predict_sql(self, expression: exp.Predict) -> str: 4532 return self._ml_sql(expression, "PREDICT") 4533 4534 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4535 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4536 return self._ml_sql(expression, name) 4537 4538 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4539 return self._ml_sql(expression, "TRANSLATE") 4540 4541 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4542 return self._ml_sql(expression, "FORECAST") 4543 4544 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4545 this_sql = self.sql(expression, "this") 4546 if isinstance(expression.this, exp.Table): 4547 this_sql = f"TABLE {this_sql}" 4548 4549 return self.func( 4550 "FEATURES_AT_TIME", 4551 this_sql, 4552 expression.args.get("time"), 4553 expression.args.get("num_rows"), 4554 expression.args.get("ignore_feature_nulls"), 4555 ) 4556 4557 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4558 this_sql = self.sql(expression, "this") 4559 if isinstance(expression.this, exp.Table): 4560 this_sql = f"TABLE {this_sql}" 4561 4562 query_table = self.sql(expression, "query_table") 4563 if isinstance(expression.args["query_table"], exp.Table): 4564 query_table = f"TABLE {query_table}" 4565 4566 return self.func( 4567 "VECTOR_SEARCH", 4568 this_sql, 4569 expression.args.get("column_to_search"), 4570 query_table, 4571 expression.args.get("query_column_to_search"), 4572 expression.args.get("top_k"), 4573 expression.args.get("distance_type"), 4574 expression.args.get("options"), 4575 ) 4576 4577 def forin_sql(self, expression: exp.ForIn) -> str: 4578 this = self.sql(expression, "this") 4579 expression_sql = self.sql(expression, "expression") 4580 return f"FOR {this} DO {expression_sql}" 4581 4582 def refresh_sql(self, expression: exp.Refresh) -> str: 4583 this = self.sql(expression, "this") 4584 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 4585 return f"REFRESH {kind}{this}" 4586 4587 def toarray_sql(self, expression: exp.ToArray) -> str: 4588 arg = expression.this 4589 if not arg.type: 4590 from sqlglot.optimizer.annotate_types import annotate_types 4591 4592 arg = annotate_types(arg, dialect=self.dialect) 4593 4594 if arg.is_type(exp.DataType.Type.ARRAY): 4595 return self.sql(arg) 4596 4597 cond_for_null = arg.is_(exp.null()) 4598 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4599 4600 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4601 this = expression.this 4602 time_format = self.format_time(expression) 4603 4604 if time_format: 4605 return self.sql( 4606 exp.cast( 4607 exp.StrToTime(this=this, format=expression.args["format"]), 4608 exp.DataType.Type.TIME, 4609 ) 4610 ) 4611 4612 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4613 return self.sql(this) 4614 4615 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4616 4617 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4618 this = expression.this 4619 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4620 return self.sql(this) 4621 4622 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4623 4624 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4625 this = expression.this 4626 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4627 return self.sql(this) 4628 4629 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4630 4631 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4632 this = expression.this 4633 time_format = self.format_time(expression) 4634 safe = expression.args.get("safe") 4635 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4636 return self.sql( 4637 exp.cast( 4638 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4639 exp.DataType.Type.DATE, 4640 ) 4641 ) 4642 4643 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4644 return self.sql(this) 4645 4646 if safe: 4647 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DataType.Type.DATE))) 4648 4649 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4650 4651 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4652 return self.sql( 4653 exp.func( 4654 "DATEDIFF", 4655 expression.this, 4656 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4657 "day", 4658 ) 4659 ) 4660 4661 def lastday_sql(self, expression: exp.LastDay) -> str: 4662 if self.LAST_DAY_SUPPORTS_DATE_PART: 4663 return self.function_fallback_sql(expression) 4664 4665 unit = expression.text("unit") 4666 if unit and unit != "MONTH": 4667 self.unsupported("Date parts are not supported in LAST_DAY.") 4668 4669 return self.func("LAST_DAY", expression.this) 4670 4671 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4672 from sqlglot.dialects.dialect import unit_to_str 4673 4674 return self.func( 4675 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4676 ) 4677 4678 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4679 if self.CAN_IMPLEMENT_ARRAY_ANY: 4680 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4681 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4682 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4683 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4684 4685 from sqlglot.dialects import Dialect 4686 4687 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4688 if self.dialect.__class__ != Dialect: 4689 self.unsupported("ARRAY_ANY is unsupported") 4690 4691 return self.function_fallback_sql(expression) 4692 4693 def struct_sql(self, expression: exp.Struct) -> str: 4694 expression.set( 4695 "expressions", 4696 [ 4697 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4698 if isinstance(e, exp.PropertyEQ) 4699 else e 4700 for e in expression.expressions 4701 ], 4702 ) 4703 4704 return self.function_fallback_sql(expression) 4705 4706 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4707 low = self.sql(expression, "this") 4708 high = self.sql(expression, "expression") 4709 4710 return f"{low} TO {high}" 4711 4712 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4713 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4714 tables = f" {self.expressions(expression)}" 4715 4716 exists = " IF EXISTS" if expression.args.get("exists") else "" 4717 4718 on_cluster = self.sql(expression, "cluster") 4719 on_cluster = f" {on_cluster}" if on_cluster else "" 4720 4721 identity = self.sql(expression, "identity") 4722 identity = f" {identity} IDENTITY" if identity else "" 4723 4724 option = self.sql(expression, "option") 4725 option = f" {option}" if option else "" 4726 4727 partition = self.sql(expression, "partition") 4728 partition = f" {partition}" if partition else "" 4729 4730 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4731 4732 # This transpiles T-SQL's CONVERT function 4733 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4734 def convert_sql(self, expression: exp.Convert) -> str: 4735 to = expression.this 4736 value = expression.expression 4737 style = expression.args.get("style") 4738 safe = expression.args.get("safe") 4739 strict = expression.args.get("strict") 4740 4741 if not to or not value: 4742 return "" 4743 4744 # Retrieve length of datatype and override to default if not specified 4745 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4746 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4747 4748 transformed: t.Optional[exp.Expression] = None 4749 cast = exp.Cast if strict else exp.TryCast 4750 4751 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4752 if isinstance(style, exp.Literal) and style.is_int: 4753 from sqlglot.dialects.tsql import TSQL 4754 4755 style_value = style.name 4756 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4757 if not converted_style: 4758 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4759 4760 fmt = exp.Literal.string(converted_style) 4761 4762 if to.this == exp.DataType.Type.DATE: 4763 transformed = exp.StrToDate(this=value, format=fmt) 4764 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4765 transformed = exp.StrToTime(this=value, format=fmt) 4766 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4767 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4768 elif to.this == exp.DataType.Type.TEXT: 4769 transformed = exp.TimeToStr(this=value, format=fmt) 4770 4771 if not transformed: 4772 transformed = cast(this=value, to=to, safe=safe) 4773 4774 return self.sql(transformed) 4775 4776 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4777 this = expression.this 4778 if isinstance(this, exp.JSONPathWildcard): 4779 this = self.json_path_part(this) 4780 return f".{this}" if this else "" 4781 4782 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4783 return f".{this}" 4784 4785 this = self.json_path_part(this) 4786 return ( 4787 f"[{this}]" 4788 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4789 else f".{this}" 4790 ) 4791 4792 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4793 this = self.json_path_part(expression.this) 4794 return f"[{this}]" if this else "" 4795 4796 def _simplify_unless_literal(self, expression: E) -> E: 4797 if not isinstance(expression, exp.Literal): 4798 from sqlglot.optimizer.simplify import simplify 4799 4800 expression = simplify(expression, dialect=self.dialect) 4801 4802 return expression 4803 4804 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4805 this = expression.this 4806 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4807 self.unsupported( 4808 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4809 ) 4810 return self.sql(this) 4811 4812 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4813 # The first modifier here will be the one closest to the AggFunc's arg 4814 mods = sorted( 4815 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4816 key=lambda x: 0 4817 if isinstance(x, exp.HavingMax) 4818 else (1 if isinstance(x, exp.Order) else 2), 4819 ) 4820 4821 if mods: 4822 mod = mods[0] 4823 this = expression.__class__(this=mod.this.copy()) 4824 this.meta["inline"] = True 4825 mod.this.replace(this) 4826 return self.sql(expression.this) 4827 4828 agg_func = expression.find(exp.AggFunc) 4829 4830 if agg_func: 4831 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4832 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4833 4834 return f"{self.sql(expression, 'this')} {text}" 4835 4836 def _replace_line_breaks(self, string: str) -> str: 4837 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4838 if self.pretty: 4839 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4840 return string 4841 4842 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4843 option = self.sql(expression, "this") 4844 4845 if expression.expressions: 4846 upper = option.upper() 4847 4848 # Snowflake FILE_FORMAT options are separated by whitespace 4849 sep = " " if upper == "FILE_FORMAT" else ", " 4850 4851 # Databricks copy/format options do not set their list of values with EQ 4852 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4853 values = self.expressions(expression, flat=True, sep=sep) 4854 return f"{option}{op}({values})" 4855 4856 value = self.sql(expression, "expression") 4857 4858 if not value: 4859 return option 4860 4861 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4862 4863 return f"{option}{op}{value}" 4864 4865 def credentials_sql(self, expression: exp.Credentials) -> str: 4866 cred_expr = expression.args.get("credentials") 4867 if isinstance(cred_expr, exp.Literal): 4868 # Redshift case: CREDENTIALS <string> 4869 credentials = self.sql(expression, "credentials") 4870 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4871 else: 4872 # Snowflake case: CREDENTIALS = (...) 4873 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4874 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4875 4876 storage = self.sql(expression, "storage") 4877 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4878 4879 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4880 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4881 4882 iam_role = self.sql(expression, "iam_role") 4883 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4884 4885 region = self.sql(expression, "region") 4886 region = f" REGION {region}" if region else "" 4887 4888 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4889 4890 def copy_sql(self, expression: exp.Copy) -> str: 4891 this = self.sql(expression, "this") 4892 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4893 4894 credentials = self.sql(expression, "credentials") 4895 credentials = self.seg(credentials) if credentials else "" 4896 files = self.expressions(expression, key="files", flat=True) 4897 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4898 4899 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4900 params = self.expressions( 4901 expression, 4902 key="params", 4903 sep=sep, 4904 new_line=True, 4905 skip_last=True, 4906 skip_first=True, 4907 indent=self.COPY_PARAMS_ARE_WRAPPED, 4908 ) 4909 4910 if params: 4911 if self.COPY_PARAMS_ARE_WRAPPED: 4912 params = f" WITH ({params})" 4913 elif not self.pretty and (files or credentials): 4914 params = f" {params}" 4915 4916 return f"COPY{this}{kind} {files}{credentials}{params}" 4917 4918 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4919 return "" 4920 4921 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4922 on_sql = "ON" if expression.args.get("on") else "OFF" 4923 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4924 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4925 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4926 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4927 4928 if filter_col or retention_period: 4929 on_sql = self.func("ON", filter_col, retention_period) 4930 4931 return f"DATA_DELETION={on_sql}" 4932 4933 def maskingpolicycolumnconstraint_sql( 4934 self, expression: exp.MaskingPolicyColumnConstraint 4935 ) -> str: 4936 this = self.sql(expression, "this") 4937 expressions = self.expressions(expression, flat=True) 4938 expressions = f" USING ({expressions})" if expressions else "" 4939 return f"MASKING POLICY {this}{expressions}" 4940 4941 def gapfill_sql(self, expression: exp.GapFill) -> str: 4942 this = self.sql(expression, "this") 4943 this = f"TABLE {this}" 4944 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4945 4946 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4947 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4948 4949 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4950 this = self.sql(expression, "this") 4951 expr = expression.expression 4952 4953 if isinstance(expr, exp.Func): 4954 # T-SQL's CLR functions are case sensitive 4955 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4956 else: 4957 expr = self.sql(expression, "expression") 4958 4959 return self.scope_resolution(expr, this) 4960 4961 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4962 if self.PARSE_JSON_NAME is None: 4963 return self.sql(expression.this) 4964 4965 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4966 4967 def rand_sql(self, expression: exp.Rand) -> str: 4968 lower = self.sql(expression, "lower") 4969 upper = self.sql(expression, "upper") 4970 4971 if lower and upper: 4972 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4973 return self.func("RAND", expression.this) 4974 4975 def changes_sql(self, expression: exp.Changes) -> str: 4976 information = self.sql(expression, "information") 4977 information = f"INFORMATION => {information}" 4978 at_before = self.sql(expression, "at_before") 4979 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4980 end = self.sql(expression, "end") 4981 end = f"{self.seg('')}{end}" if end else "" 4982 4983 return f"CHANGES ({information}){at_before}{end}" 4984 4985 def pad_sql(self, expression: exp.Pad) -> str: 4986 prefix = "L" if expression.args.get("is_left") else "R" 4987 4988 fill_pattern = self.sql(expression, "fill_pattern") or None 4989 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4990 fill_pattern = "' '" 4991 4992 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4993 4994 def summarize_sql(self, expression: exp.Summarize) -> str: 4995 table = " TABLE" if expression.args.get("table") else "" 4996 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4997 4998 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4999 generate_series = exp.GenerateSeries(**expression.args) 5000 5001 parent = expression.parent 5002 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5003 parent = parent.parent 5004 5005 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5006 return self.sql(exp.Unnest(expressions=[generate_series])) 5007 5008 if isinstance(parent, exp.Select): 5009 self.unsupported("GenerateSeries projection unnesting is not supported.") 5010 5011 return self.sql(generate_series) 5012 5013 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5014 if self.SUPPORTS_CONVERT_TIMEZONE: 5015 return self.function_fallback_sql(expression) 5016 5017 source_tz = expression.args.get("source_tz") 5018 target_tz = expression.args.get("target_tz") 5019 timestamp = expression.args.get("timestamp") 5020 5021 if source_tz and timestamp: 5022 timestamp = exp.AtTimeZone( 5023 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 5024 ) 5025 5026 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5027 5028 return self.sql(expr) 5029 5030 def json_sql(self, expression: exp.JSON) -> str: 5031 this = self.sql(expression, "this") 5032 this = f" {this}" if this else "" 5033 5034 _with = expression.args.get("with_") 5035 5036 if _with is None: 5037 with_sql = "" 5038 elif not _with: 5039 with_sql = " WITHOUT" 5040 else: 5041 with_sql = " WITH" 5042 5043 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5044 5045 return f"JSON{this}{with_sql}{unique_sql}" 5046 5047 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5048 path = self.sql(expression, "path") 5049 returning = self.sql(expression, "returning") 5050 returning = f" RETURNING {returning}" if returning else "" 5051 5052 on_condition = self.sql(expression, "on_condition") 5053 on_condition = f" {on_condition}" if on_condition else "" 5054 5055 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5056 5057 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5058 else_ = "ELSE " if expression.args.get("else_") else "" 5059 condition = self.sql(expression, "expression") 5060 condition = f"WHEN {condition} THEN " if condition else else_ 5061 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5062 return f"{condition}{insert}" 5063 5064 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5065 kind = self.sql(expression, "kind") 5066 expressions = self.seg(self.expressions(expression, sep=" ")) 5067 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5068 return res 5069 5070 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5071 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5072 empty = expression.args.get("empty") 5073 empty = ( 5074 f"DEFAULT {empty} ON EMPTY" 5075 if isinstance(empty, exp.Expression) 5076 else self.sql(expression, "empty") 5077 ) 5078 5079 error = expression.args.get("error") 5080 error = ( 5081 f"DEFAULT {error} ON ERROR" 5082 if isinstance(error, exp.Expression) 5083 else self.sql(expression, "error") 5084 ) 5085 5086 if error and empty: 5087 error = ( 5088 f"{empty} {error}" 5089 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5090 else f"{error} {empty}" 5091 ) 5092 empty = "" 5093 5094 null = self.sql(expression, "null") 5095 5096 return f"{empty}{error}{null}" 5097 5098 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5099 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5100 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5101 5102 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5103 this = self.sql(expression, "this") 5104 path = self.sql(expression, "path") 5105 5106 passing = self.expressions(expression, "passing") 5107 passing = f" PASSING {passing}" if passing else "" 5108 5109 on_condition = self.sql(expression, "on_condition") 5110 on_condition = f" {on_condition}" if on_condition else "" 5111 5112 path = f"{path}{passing}{on_condition}" 5113 5114 return self.func("JSON_EXISTS", this, path) 5115 5116 def _add_arrayagg_null_filter( 5117 self, 5118 array_agg_sql: str, 5119 array_agg_expr: exp.ArrayAgg, 5120 column_expr: exp.Expression, 5121 ) -> str: 5122 """ 5123 Add NULL filter to ARRAY_AGG if dialect requires it. 5124 5125 Args: 5126 array_agg_sql: The generated ARRAY_AGG SQL string 5127 array_agg_expr: The ArrayAgg expression node 5128 column_expr: The column/expression to filter (before ORDER BY wrapping) 5129 5130 Returns: 5131 SQL string with FILTER clause added if needed 5132 """ 5133 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5134 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5135 if not ( 5136 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5137 ): 5138 return array_agg_sql 5139 5140 parent = array_agg_expr.parent 5141 if isinstance(parent, exp.Filter): 5142 parent_cond = parent.expression.this 5143 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5144 elif column_expr.find(exp.Column): 5145 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5146 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5147 this_sql = ( 5148 self.expressions(column_expr) 5149 if isinstance(column_expr, exp.Distinct) 5150 else self.sql(column_expr) 5151 ) 5152 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5153 5154 return array_agg_sql 5155 5156 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5157 array_agg = self.function_fallback_sql(expression) 5158 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5159 5160 def slice_sql(self, expression: exp.Slice) -> str: 5161 step = self.sql(expression, "step") 5162 end = self.sql(expression.expression) 5163 begin = self.sql(expression.this) 5164 5165 sql = f"{end}:{step}" if step else end 5166 return f"{begin}:{sql}" if sql else f"{begin}:" 5167 5168 def apply_sql(self, expression: exp.Apply) -> str: 5169 this = self.sql(expression, "this") 5170 expr = self.sql(expression, "expression") 5171 5172 return f"{this} APPLY({expr})" 5173 5174 def _grant_or_revoke_sql( 5175 self, 5176 expression: exp.Grant | exp.Revoke, 5177 keyword: str, 5178 preposition: str, 5179 grant_option_prefix: str = "", 5180 grant_option_suffix: str = "", 5181 ) -> str: 5182 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5183 5184 kind = self.sql(expression, "kind") 5185 kind = f" {kind}" if kind else "" 5186 5187 securable = self.sql(expression, "securable") 5188 securable = f" {securable}" if securable else "" 5189 5190 principals = self.expressions(expression, key="principals", flat=True) 5191 5192 if not expression.args.get("grant_option"): 5193 grant_option_prefix = grant_option_suffix = "" 5194 5195 # cascade for revoke only 5196 cascade = self.sql(expression, "cascade") 5197 cascade = f" {cascade}" if cascade else "" 5198 5199 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5200 5201 def grant_sql(self, expression: exp.Grant) -> str: 5202 return self._grant_or_revoke_sql( 5203 expression, 5204 keyword="GRANT", 5205 preposition="TO", 5206 grant_option_suffix=" WITH GRANT OPTION", 5207 ) 5208 5209 def revoke_sql(self, expression: exp.Revoke) -> str: 5210 return self._grant_or_revoke_sql( 5211 expression, 5212 keyword="REVOKE", 5213 preposition="FROM", 5214 grant_option_prefix="GRANT OPTION FOR ", 5215 ) 5216 5217 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5218 this = self.sql(expression, "this") 5219 columns = self.expressions(expression, flat=True) 5220 columns = f"({columns})" if columns else "" 5221 5222 return f"{this}{columns}" 5223 5224 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5225 this = self.sql(expression, "this") 5226 5227 kind = self.sql(expression, "kind") 5228 kind = f"{kind} " if kind else "" 5229 5230 return f"{kind}{this}" 5231 5232 def columns_sql(self, expression: exp.Columns) -> str: 5233 func = self.function_fallback_sql(expression) 5234 if expression.args.get("unpack"): 5235 func = f"*{func}" 5236 5237 return func 5238 5239 def overlay_sql(self, expression: exp.Overlay) -> str: 5240 this = self.sql(expression, "this") 5241 expr = self.sql(expression, "expression") 5242 from_sql = self.sql(expression, "from_") 5243 for_sql = self.sql(expression, "for_") 5244 for_sql = f" FOR {for_sql}" if for_sql else "" 5245 5246 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5247 5248 @unsupported_args("format") 5249 def todouble_sql(self, expression: exp.ToDouble) -> str: 5250 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5251 return self.sql(cast(this=expression.this, to=exp.DataType.build(exp.DataType.Type.DOUBLE))) 5252 5253 def string_sql(self, expression: exp.String) -> str: 5254 this = expression.this 5255 zone = expression.args.get("zone") 5256 5257 if zone: 5258 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5259 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5260 # set for source_tz to transpile the time conversion before the STRING cast 5261 this = exp.ConvertTimezone( 5262 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5263 ) 5264 5265 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 5266 5267 def median_sql(self, expression: exp.Median) -> str: 5268 if not self.SUPPORTS_MEDIAN: 5269 return self.sql( 5270 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5271 ) 5272 5273 return self.function_fallback_sql(expression) 5274 5275 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5276 filler = self.sql(expression, "this") 5277 filler = f" {filler}" if filler else "" 5278 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5279 return f"TRUNCATE{filler} {with_count}" 5280 5281 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5282 if self.SUPPORTS_UNIX_SECONDS: 5283 return self.function_fallback_sql(expression) 5284 5285 start_ts = exp.cast( 5286 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 5287 ) 5288 5289 return self.sql( 5290 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5291 ) 5292 5293 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5294 dim = expression.expression 5295 5296 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5297 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5298 if not (dim.is_int and dim.name == "1"): 5299 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5300 dim = None 5301 5302 # If dimension is required but not specified, default initialize it 5303 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5304 dim = exp.Literal.number(1) 5305 5306 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5307 5308 def attach_sql(self, expression: exp.Attach) -> str: 5309 this = self.sql(expression, "this") 5310 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5311 expressions = self.expressions(expression) 5312 expressions = f" ({expressions})" if expressions else "" 5313 5314 return f"ATTACH{exists_sql} {this}{expressions}" 5315 5316 def detach_sql(self, expression: exp.Detach) -> str: 5317 this = self.sql(expression, "this") 5318 # the DATABASE keyword is required if IF EXISTS is set 5319 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5320 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5321 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5322 5323 return f"DETACH{exists_sql} {this}" 5324 5325 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5326 this = self.sql(expression, "this") 5327 value = self.sql(expression, "expression") 5328 value = f" {value}" if value else "" 5329 return f"{this}{value}" 5330 5331 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5332 return ( 5333 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5334 ) 5335 5336 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5337 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5338 encode = f"{encode} {self.sql(expression, 'this')}" 5339 5340 properties = expression.args.get("properties") 5341 if properties: 5342 encode = f"{encode} {self.properties(properties)}" 5343 5344 return encode 5345 5346 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5347 this = self.sql(expression, "this") 5348 include = f"INCLUDE {this}" 5349 5350 column_def = self.sql(expression, "column_def") 5351 if column_def: 5352 include = f"{include} {column_def}" 5353 5354 alias = self.sql(expression, "alias") 5355 if alias: 5356 include = f"{include} AS {alias}" 5357 5358 return include 5359 5360 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5361 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5362 name = f"{prefix} {self.sql(expression, 'this')}" 5363 return self.func("XMLELEMENT", name, *expression.expressions) 5364 5365 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5366 this = self.sql(expression, "this") 5367 expr = self.sql(expression, "expression") 5368 expr = f"({expr})" if expr else "" 5369 return f"{this}{expr}" 5370 5371 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5372 partitions = self.expressions(expression, "partition_expressions") 5373 create = self.expressions(expression, "create_expressions") 5374 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5375 5376 def partitionbyrangepropertydynamic_sql( 5377 self, expression: exp.PartitionByRangePropertyDynamic 5378 ) -> str: 5379 start = self.sql(expression, "start") 5380 end = self.sql(expression, "end") 5381 5382 every = expression.args["every"] 5383 if isinstance(every, exp.Interval) and every.this.is_string: 5384 every.this.replace(exp.Literal.number(every.name)) 5385 5386 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5387 5388 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5389 name = self.sql(expression, "this") 5390 values = self.expressions(expression, flat=True) 5391 5392 return f"NAME {name} VALUE {values}" 5393 5394 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5395 kind = self.sql(expression, "kind") 5396 sample = self.sql(expression, "sample") 5397 return f"SAMPLE {sample} {kind}" 5398 5399 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5400 kind = self.sql(expression, "kind") 5401 option = self.sql(expression, "option") 5402 option = f" {option}" if option else "" 5403 this = self.sql(expression, "this") 5404 this = f" {this}" if this else "" 5405 columns = self.expressions(expression) 5406 columns = f" {columns}" if columns else "" 5407 return f"{kind}{option} STATISTICS{this}{columns}" 5408 5409 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5410 this = self.sql(expression, "this") 5411 columns = self.expressions(expression) 5412 inner_expression = self.sql(expression, "expression") 5413 inner_expression = f" {inner_expression}" if inner_expression else "" 5414 update_options = self.sql(expression, "update_options") 5415 update_options = f" {update_options} UPDATE" if update_options else "" 5416 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5417 5418 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5419 kind = self.sql(expression, "kind") 5420 kind = f" {kind}" if kind else "" 5421 return f"DELETE{kind} STATISTICS" 5422 5423 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5424 inner_expression = self.sql(expression, "expression") 5425 return f"LIST CHAINED ROWS{inner_expression}" 5426 5427 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5428 kind = self.sql(expression, "kind") 5429 this = self.sql(expression, "this") 5430 this = f" {this}" if this else "" 5431 inner_expression = self.sql(expression, "expression") 5432 return f"VALIDATE {kind}{this}{inner_expression}" 5433 5434 def analyze_sql(self, expression: exp.Analyze) -> str: 5435 options = self.expressions(expression, key="options", sep=" ") 5436 options = f" {options}" if options else "" 5437 kind = self.sql(expression, "kind") 5438 kind = f" {kind}" if kind else "" 5439 this = self.sql(expression, "this") 5440 this = f" {this}" if this else "" 5441 mode = self.sql(expression, "mode") 5442 mode = f" {mode}" if mode else "" 5443 properties = self.sql(expression, "properties") 5444 properties = f" {properties}" if properties else "" 5445 partition = self.sql(expression, "partition") 5446 partition = f" {partition}" if partition else "" 5447 inner_expression = self.sql(expression, "expression") 5448 inner_expression = f" {inner_expression}" if inner_expression else "" 5449 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5450 5451 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5452 this = self.sql(expression, "this") 5453 namespaces = self.expressions(expression, key="namespaces") 5454 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5455 passing = self.expressions(expression, key="passing") 5456 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5457 columns = self.expressions(expression, key="columns") 5458 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5459 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5460 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5461 5462 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5463 this = self.sql(expression, "this") 5464 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5465 5466 def export_sql(self, expression: exp.Export) -> str: 5467 this = self.sql(expression, "this") 5468 connection = self.sql(expression, "connection") 5469 connection = f"WITH CONNECTION {connection} " if connection else "" 5470 options = self.sql(expression, "options") 5471 return f"EXPORT DATA {connection}{options} AS {this}" 5472 5473 def declare_sql(self, expression: exp.Declare) -> str: 5474 return f"DECLARE {self.expressions(expression, flat=True)}" 5475 5476 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5477 variables = self.expressions(expression, "this") 5478 default = self.sql(expression, "default") 5479 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5480 5481 kind = self.sql(expression, "kind") 5482 if isinstance(expression.args.get("kind"), exp.Schema): 5483 kind = f"TABLE {kind}" 5484 5485 kind = f" {kind}" if kind else "" 5486 5487 return f"{variables}{kind}{default}" 5488 5489 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5490 kind = self.sql(expression, "kind") 5491 this = self.sql(expression, "this") 5492 set = self.sql(expression, "expression") 5493 using = self.sql(expression, "using") 5494 using = f" USING {using}" if using else "" 5495 5496 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5497 5498 return f"{kind_sql} {this} SET {set}{using}" 5499 5500 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5501 params = self.expressions(expression, key="params", flat=True) 5502 return self.func(expression.name, *expression.expressions) + f"({params})" 5503 5504 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5505 return self.func(expression.name, *expression.expressions) 5506 5507 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5508 return self.anonymousaggfunc_sql(expression) 5509 5510 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5511 return self.parameterizedagg_sql(expression) 5512 5513 def show_sql(self, expression: exp.Show) -> str: 5514 self.unsupported("Unsupported SHOW statement") 5515 return "" 5516 5517 def install_sql(self, expression: exp.Install) -> str: 5518 self.unsupported("Unsupported INSTALL statement") 5519 return "" 5520 5521 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5522 # Snowflake GET/PUT statements: 5523 # PUT <file> <internalStage> <properties> 5524 # GET <internalStage> <file> <properties> 5525 props = expression.args.get("properties") 5526 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5527 this = self.sql(expression, "this") 5528 target = self.sql(expression, "target") 5529 5530 if isinstance(expression, exp.Put): 5531 return f"PUT {this} {target}{props_sql}" 5532 else: 5533 return f"GET {target} {this}{props_sql}" 5534 5535 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 5536 this = self.sql(expression, "this") 5537 expr = self.sql(expression, "expression") 5538 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5539 return f"TRANSLATE({this} USING {expr}{with_error})" 5540 5541 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5542 if self.SUPPORTS_DECODE_CASE: 5543 return self.func("DECODE", *expression.expressions) 5544 5545 expression, *expressions = expression.expressions 5546 5547 ifs = [] 5548 for search, result in zip(expressions[::2], expressions[1::2]): 5549 if isinstance(search, exp.Literal): 5550 ifs.append(exp.If(this=expression.eq(search), true=result)) 5551 elif isinstance(search, exp.Null): 5552 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5553 else: 5554 if isinstance(search, exp.Binary): 5555 search = exp.paren(search) 5556 5557 cond = exp.or_( 5558 expression.eq(search), 5559 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5560 copy=False, 5561 ) 5562 ifs.append(exp.If(this=cond, true=result)) 5563 5564 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5565 return self.sql(case) 5566 5567 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5568 this = self.sql(expression, "this") 5569 this = self.seg(this, sep="") 5570 dimensions = self.expressions( 5571 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5572 ) 5573 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5574 metrics = self.expressions( 5575 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5576 ) 5577 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5578 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5579 facts = self.seg(f"FACTS {facts}") if facts else "" 5580 where = self.sql(expression, "where") 5581 where = self.seg(f"WHERE {where}") if where else "" 5582 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5583 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5584 5585 def getextract_sql(self, expression: exp.GetExtract) -> str: 5586 this = expression.this 5587 expr = expression.expression 5588 5589 if not this.type or not expression.type: 5590 from sqlglot.optimizer.annotate_types import annotate_types 5591 5592 this = annotate_types(this, dialect=self.dialect) 5593 5594 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5595 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5596 5597 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5598 5599 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5600 return self.sql( 5601 exp.DateAdd( 5602 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5603 expression=expression.this, 5604 unit=exp.var("DAY"), 5605 ) 5606 ) 5607 5608 def space_sql(self: Generator, expression: exp.Space) -> str: 5609 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5610 5611 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5612 return f"BUILD {self.sql(expression, 'this')}" 5613 5614 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5615 method = self.sql(expression, "method") 5616 kind = expression.args.get("kind") 5617 if not kind: 5618 return f"REFRESH {method}" 5619 5620 every = self.sql(expression, "every") 5621 unit = self.sql(expression, "unit") 5622 every = f" EVERY {every} {unit}" if every else "" 5623 starts = self.sql(expression, "starts") 5624 starts = f" STARTS {starts}" if starts else "" 5625 5626 return f"REFRESH {method} ON {kind}{every}{starts}" 5627 5628 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5629 self.unsupported("The model!attribute syntax is not supported") 5630 return "" 5631 5632 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5633 return self.func("DIRECTORY", expression.this) 5634 5635 def uuid_sql(self, expression: exp.Uuid) -> str: 5636 is_string = expression.args.get("is_string", False) 5637 uuid_func_sql = self.func("UUID") 5638 5639 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5640 return self.sql( 5641 exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect) 5642 ) 5643 5644 return uuid_func_sql 5645 5646 def initcap_sql(self, expression: exp.Initcap) -> str: 5647 delimiters = expression.expression 5648 5649 if delimiters: 5650 # do not generate delimiters arg if we are round-tripping from default delimiters 5651 if ( 5652 delimiters.is_string 5653 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5654 ): 5655 delimiters = None 5656 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5657 self.unsupported("INITCAP does not support custom delimiters") 5658 delimiters = None 5659 5660 return self.func("INITCAP", expression.this, delimiters) 5661 5662 def localtime_sql(self, expression: exp.Localtime) -> str: 5663 this = expression.this 5664 return self.func("LOCALTIME", this) if this else "LOCALTIME" 5665 5666 def localtimestamp_sql(self, expression: exp.Localtime) -> str: 5667 this = expression.this 5668 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 5669 5670 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5671 this = expression.this.name.upper() 5672 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5673 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5674 return "WEEK" 5675 5676 return self.func("WEEK", expression.this) 5677 5678 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 5679 this = self.expressions(expression) 5680 charset = self.sql(expression, "charset") 5681 using = f" USING {charset}" if charset else "" 5682 return self.func(name, this + using) 5683 5684 def block_sql(self, expression: exp.Block) -> str: 5685 expressions = self.expressions(expression, sep="; ", flat=True) 5686 return f"{expressions}" if expressions else "" 5687 5688 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 5689 self.unsupported("Unsupported Stored Procedure syntax") 5690 return "" 5691 5692 def ifblock_sql(self, expression: exp.IfBlock) -> str: 5693 self.unsupported("Unsupported If block syntax") 5694 return "" 5695 5696 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 5697 self.unsupported("Unsupported While block syntax") 5698 return "" 5699 5700 def execute_sql(self, expression: exp.Execute) -> str: 5701 self.unsupported("Unsupported Execute syntax") 5702 return "" 5703 5704 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 5705 self.unsupported("Unsupported Execute syntax") 5706 return ""
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]]:
31def unsupported_args( 32 *args: t.Union[str, t.Tuple[str, str]], 33) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 34 """ 35 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 36 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 37 """ 38 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 39 for arg in args: 40 if isinstance(arg, str): 41 diagnostic_by_arg[arg] = None 42 else: 43 diagnostic_by_arg[arg[0]] = arg[1] 44 45 def decorator(func: GeneratorMethod) -> GeneratorMethod: 46 @wraps(func) 47 def _func(generator: G, expression: E) -> str: 48 expression_name = expression.__class__.__name__ 49 dialect_name = generator.dialect.__class__.__name__ 50 51 for arg_name, diagnostic in diagnostic_by_arg.items(): 52 if expression.args.get(arg_name): 53 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 54 arg_name, expression_name, dialect_name 55 ) 56 generator.unsupported(diagnostic) 57 58 return func(generator, expression) 59 60 return _func 61 62 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:
76class Generator(metaclass=_Generator): 77 """ 78 Generator converts a given syntax tree to the corresponding SQL string. 79 80 Args: 81 pretty: Whether to format the produced SQL string. 82 Default: False. 83 identify: Determines when an identifier should be quoted. Possible values are: 84 False (default): Never quote, except in cases where it's mandatory by the dialect. 85 True: Always quote except for specials cases. 86 'safe': Only quote identifiers that are case insensitive. 87 normalize: Whether to normalize identifiers to lowercase. 88 Default: False. 89 pad: The pad size in a formatted string. For example, this affects the indentation of 90 a projection in a query, relative to its nesting level. 91 Default: 2. 92 indent: The indentation size in a formatted string. For example, this affects the 93 indentation of subqueries and filters under a `WHERE` clause. 94 Default: 2. 95 normalize_functions: How to normalize function names. Possible values are: 96 "upper" or True (default): Convert names to uppercase. 97 "lower": Convert names to lowercase. 98 False: Disables function name normalization. 99 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 100 Default ErrorLevel.WARN. 101 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 102 This is only relevant if unsupported_level is ErrorLevel.RAISE. 103 Default: 3 104 leading_comma: Whether the comma is leading or trailing in select expressions. 105 This is only relevant when generating in pretty mode. 106 Default: False 107 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 108 The default is on the smaller end because the length only represents a segment and not the true 109 line length. 110 Default: 80 111 comments: Whether to preserve comments in the output SQL code. 112 Default: True 113 """ 114 115 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 116 **JSON_PATH_PART_TRANSFORMS, 117 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 118 exp.AllowedValuesProperty: lambda self, 119 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 120 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 121 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 122 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 123 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 124 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 125 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 126 exp.CaseSpecificColumnConstraint: lambda _, 127 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 128 exp.Ceil: lambda self, e: self.ceil_floor(e), 129 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 130 exp.CharacterSetProperty: lambda self, 131 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 132 exp.ClusteredColumnConstraint: lambda self, 133 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 134 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 135 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 136 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 137 exp.ConvertToCharset: lambda self, e: self.func( 138 "CONVERT", e.this, e.args["dest"], e.args.get("source") 139 ), 140 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 141 exp.CredentialsProperty: lambda self, 142 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 143 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 144 exp.SessionUser: lambda *_: "SESSION_USER", 145 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 146 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 147 exp.DynamicProperty: lambda *_: "DYNAMIC", 148 exp.EmptyProperty: lambda *_: "EMPTY", 149 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 150 exp.EndStatement: lambda *_: "END", 151 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 152 exp.EphemeralColumnConstraint: lambda self, 153 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 154 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 155 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 156 exp.Except: lambda self, e: self.set_operations(e), 157 exp.ExternalProperty: lambda *_: "EXTERNAL", 158 exp.Floor: lambda self, e: self.ceil_floor(e), 159 exp.Get: lambda self, e: self.get_put_sql(e), 160 exp.GlobalProperty: lambda *_: "GLOBAL", 161 exp.HeapProperty: lambda *_: "HEAP", 162 exp.IcebergProperty: lambda *_: "ICEBERG", 163 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 164 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 165 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 166 exp.Intersect: lambda self, e: self.set_operations(e), 167 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 168 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 169 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 170 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 171 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 172 exp.LanguageProperty: lambda self, e: self.naked_property(e), 173 exp.LocationProperty: lambda self, e: self.naked_property(e), 174 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 175 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 176 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 177 exp.NonClusteredColumnConstraint: lambda self, 178 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 179 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 180 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 181 exp.OnCommitProperty: lambda _, 182 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 183 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 184 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 185 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 186 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 187 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 188 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 189 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 190 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 191 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 192 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 193 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 194 exp.ProjectionPolicyColumnConstraint: lambda self, 195 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 196 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 197 exp.Put: lambda self, e: self.get_put_sql(e), 198 exp.RemoteWithConnectionModelProperty: lambda self, 199 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 200 exp.ReturnsProperty: lambda self, e: ( 201 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 202 ), 203 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 204 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 205 exp.SecureProperty: lambda *_: "SECURE", 206 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 207 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 208 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 209 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 210 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 211 exp.SqlReadWriteProperty: lambda _, e: e.name, 212 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 213 exp.StabilityProperty: lambda _, e: e.name, 214 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 215 exp.StreamingTableProperty: lambda *_: "STREAMING", 216 exp.StrictProperty: lambda *_: "STRICT", 217 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 218 exp.TableColumn: lambda self, e: self.sql(e.this), 219 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 220 exp.TemporaryProperty: lambda *_: "TEMPORARY", 221 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 222 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 223 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 224 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 225 exp.TransientProperty: lambda *_: "TRANSIENT", 226 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 227 exp.Union: lambda self, e: self.set_operations(e), 228 exp.UnloggedProperty: lambda *_: "UNLOGGED", 229 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 230 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 231 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 232 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 233 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 234 exp.UtcTimestamp: lambda self, e: self.sql( 235 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 236 ), 237 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 238 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 239 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 240 exp.VolatileProperty: lambda *_: "VOLATILE", 241 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 242 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 243 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 244 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 245 exp.ForceProperty: lambda *_: "FORCE", 246 } 247 248 # Whether null ordering is supported in order by 249 # True: Full Support, None: No support, False: No support for certain cases 250 # such as window specifications, aggregate functions etc 251 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 252 253 # Whether ignore nulls is inside the agg or outside. 254 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 255 IGNORE_NULLS_IN_FUNC = False 256 257 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 258 LOCKING_READS_SUPPORTED = False 259 260 # Whether the EXCEPT and INTERSECT operations can return duplicates 261 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 262 263 # Wrap derived values in parens, usually standard but spark doesn't support it 264 WRAP_DERIVED_VALUES = True 265 266 # Whether create function uses an AS before the RETURN 267 CREATE_FUNCTION_RETURN_AS = True 268 269 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 270 MATCHED_BY_SOURCE = True 271 272 # Whether the INTERVAL expression works only with values like '1 day' 273 SINGLE_STRING_INTERVAL = False 274 275 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 276 INTERVAL_ALLOWS_PLURAL_FORM = True 277 278 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 279 LIMIT_FETCH = "ALL" 280 281 # Whether limit and fetch allows expresions or just limits 282 LIMIT_ONLY_LITERALS = False 283 284 # Whether a table is allowed to be renamed with a db 285 RENAME_TABLE_WITH_DB = True 286 287 # The separator for grouping sets and rollups 288 GROUPINGS_SEP = "," 289 290 # The string used for creating an index on a table 291 INDEX_ON = "ON" 292 293 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 294 INOUT_SEPARATOR = " " 295 296 # Whether join hints should be generated 297 JOIN_HINTS = True 298 299 # Whether directed joins are supported 300 DIRECTED_JOINS = False 301 302 # Whether table hints should be generated 303 TABLE_HINTS = True 304 305 # Whether query hints should be generated 306 QUERY_HINTS = True 307 308 # What kind of separator to use for query hints 309 QUERY_HINT_SEP = ", " 310 311 # Whether comparing against booleans (e.g. x IS TRUE) is supported 312 IS_BOOL_ALLOWED = True 313 314 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 315 DUPLICATE_KEY_UPDATE_WITH_SET = True 316 317 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 318 LIMIT_IS_TOP = False 319 320 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 321 RETURNING_END = True 322 323 # Whether to generate an unquoted value for EXTRACT's date part argument 324 EXTRACT_ALLOWS_QUOTES = True 325 326 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 327 TZ_TO_WITH_TIME_ZONE = False 328 329 # Whether the NVL2 function is supported 330 NVL2_SUPPORTED = True 331 332 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 333 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 334 335 # Whether VALUES statements can be used as derived tables. 336 # MySQL 5 and Redshift do not allow this, so when False, it will convert 337 # SELECT * VALUES into SELECT UNION 338 VALUES_AS_TABLE = True 339 340 # Whether the word COLUMN is included when adding a column with ALTER TABLE 341 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 342 343 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 344 UNNEST_WITH_ORDINALITY = True 345 346 # Whether FILTER (WHERE cond) can be used for conditional aggregation 347 AGGREGATE_FILTER_SUPPORTED = True 348 349 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 350 SEMI_ANTI_JOIN_WITH_SIDE = True 351 352 # Whether to include the type of a computed column in the CREATE DDL 353 COMPUTED_COLUMN_WITH_TYPE = True 354 355 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 356 SUPPORTS_TABLE_COPY = True 357 358 # Whether parentheses are required around the table sample's expression 359 TABLESAMPLE_REQUIRES_PARENS = True 360 361 # Whether a table sample clause's size needs to be followed by the ROWS keyword 362 TABLESAMPLE_SIZE_IS_ROWS = True 363 364 # The keyword(s) to use when generating a sample clause 365 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 366 367 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 368 TABLESAMPLE_WITH_METHOD = True 369 370 # The keyword to use when specifying the seed of a sample clause 371 TABLESAMPLE_SEED_KEYWORD = "SEED" 372 373 # Whether COLLATE is a function instead of a binary operator 374 COLLATE_IS_FUNC = False 375 376 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 377 DATA_TYPE_SPECIFIERS_ALLOWED = False 378 379 # Whether conditions require booleans WHERE x = 0 vs WHERE x 380 ENSURE_BOOLS = False 381 382 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 383 CTE_RECURSIVE_KEYWORD_REQUIRED = True 384 385 # Whether CONCAT requires >1 arguments 386 SUPPORTS_SINGLE_ARG_CONCAT = True 387 388 # Whether LAST_DAY function supports a date part argument 389 LAST_DAY_SUPPORTS_DATE_PART = True 390 391 # Whether named columns are allowed in table aliases 392 SUPPORTS_TABLE_ALIAS_COLUMNS = True 393 394 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 395 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 396 397 # What delimiter to use for separating JSON key/value pairs 398 JSON_KEY_VALUE_PAIR_SEP = ":" 399 400 # INSERT OVERWRITE TABLE x override 401 INSERT_OVERWRITE = " OVERWRITE TABLE" 402 403 # Whether the SELECT .. INTO syntax is used instead of CTAS 404 SUPPORTS_SELECT_INTO = False 405 406 # Whether UNLOGGED tables can be created 407 SUPPORTS_UNLOGGED_TABLES = False 408 409 # Whether the CREATE TABLE LIKE statement is supported 410 SUPPORTS_CREATE_TABLE_LIKE = True 411 412 # Whether the LikeProperty needs to be specified inside of the schema clause 413 LIKE_PROPERTY_INSIDE_SCHEMA = False 414 415 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 416 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 417 MULTI_ARG_DISTINCT = True 418 419 # Whether the JSON extraction operators expect a value of type JSON 420 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 421 422 # Whether bracketed keys like ["foo"] are supported in JSON paths 423 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 424 425 # Whether to escape keys using single quotes in JSON paths 426 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 427 428 # The JSONPathPart expressions supported by this dialect 429 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 430 431 # Whether any(f(x) for x in array) can be implemented by this dialect 432 CAN_IMPLEMENT_ARRAY_ANY = False 433 434 # Whether the function TO_NUMBER is supported 435 SUPPORTS_TO_NUMBER = True 436 437 # Whether EXCLUDE in window specification is supported 438 SUPPORTS_WINDOW_EXCLUDE = False 439 440 # Whether or not set op modifiers apply to the outer set op or select. 441 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 442 # True means limit 1 happens after the set op, False means it it happens on y. 443 SET_OP_MODIFIERS = True 444 445 # Whether parameters from COPY statement are wrapped in parentheses 446 COPY_PARAMS_ARE_WRAPPED = True 447 448 # Whether values of params are set with "=" token or empty space 449 COPY_PARAMS_EQ_REQUIRED = False 450 451 # Whether COPY statement has INTO keyword 452 COPY_HAS_INTO_KEYWORD = True 453 454 # Whether the conditional TRY(expression) function is supported 455 TRY_SUPPORTED = True 456 457 # Whether the UESCAPE syntax in unicode strings is supported 458 SUPPORTS_UESCAPE = True 459 460 # Function used to replace escaped unicode codes in unicode strings 461 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 462 463 # The keyword to use when generating a star projection with excluded columns 464 STAR_EXCEPT = "EXCEPT" 465 466 # The HEX function name 467 HEX_FUNC = "HEX" 468 469 # The keywords to use when prefixing & separating WITH based properties 470 WITH_PROPERTIES_PREFIX = "WITH" 471 472 # Whether to quote the generated expression of exp.JsonPath 473 QUOTE_JSON_PATH = True 474 475 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 476 PAD_FILL_PATTERN_IS_REQUIRED = False 477 478 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 479 SUPPORTS_EXPLODING_PROJECTIONS = True 480 481 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 482 ARRAY_CONCAT_IS_VAR_LEN = True 483 484 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 485 SUPPORTS_CONVERT_TIMEZONE = False 486 487 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 488 SUPPORTS_MEDIAN = True 489 490 # Whether UNIX_SECONDS(timestamp) is supported 491 SUPPORTS_UNIX_SECONDS = False 492 493 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 494 ALTER_SET_WRAPPED = False 495 496 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 497 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 498 # TODO: The normalization should be done by default once we've tested it across all dialects. 499 NORMALIZE_EXTRACT_DATE_PARTS = False 500 501 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 502 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 503 504 # The function name of the exp.ArraySize expression 505 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 506 507 # The syntax to use when altering the type of a column 508 ALTER_SET_TYPE = "SET DATA TYPE" 509 510 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 511 # None -> Doesn't support it at all 512 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 513 # True (Postgres) -> Explicitly requires it 514 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 515 516 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 517 SUPPORTS_DECODE_CASE = True 518 519 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 520 SUPPORTS_BETWEEN_FLAGS = False 521 522 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 523 SUPPORTS_LIKE_QUANTIFIERS = True 524 525 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 526 MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None 527 528 # Whether to include the VARIABLE keyword for SET assignments 529 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 530 531 # The keyword to use for default value assignment in DECLARE statements 532 DECLARE_DEFAULT_ASSIGNMENT = "=" 533 534 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 535 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 536 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 537 UPDATE_STATEMENT_SUPPORTS_FROM = True 538 539 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 540 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 541 542 TYPE_MAPPING = { 543 exp.DataType.Type.DATETIME2: "TIMESTAMP", 544 exp.DataType.Type.NCHAR: "CHAR", 545 exp.DataType.Type.NVARCHAR: "VARCHAR", 546 exp.DataType.Type.MEDIUMTEXT: "TEXT", 547 exp.DataType.Type.LONGTEXT: "TEXT", 548 exp.DataType.Type.TINYTEXT: "TEXT", 549 exp.DataType.Type.BLOB: "VARBINARY", 550 exp.DataType.Type.MEDIUMBLOB: "BLOB", 551 exp.DataType.Type.LONGBLOB: "BLOB", 552 exp.DataType.Type.TINYBLOB: "BLOB", 553 exp.DataType.Type.INET: "INET", 554 exp.DataType.Type.ROWVERSION: "VARBINARY", 555 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 556 } 557 558 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 559 560 TIME_PART_SINGULARS = { 561 "MICROSECONDS": "MICROSECOND", 562 "SECONDS": "SECOND", 563 "MINUTES": "MINUTE", 564 "HOURS": "HOUR", 565 "DAYS": "DAY", 566 "WEEKS": "WEEK", 567 "MONTHS": "MONTH", 568 "QUARTERS": "QUARTER", 569 "YEARS": "YEAR", 570 } 571 572 AFTER_HAVING_MODIFIER_TRANSFORMS = { 573 "cluster": lambda self, e: self.sql(e, "cluster"), 574 "distribute": lambda self, e: self.sql(e, "distribute"), 575 "sort": lambda self, e: self.sql(e, "sort"), 576 "windows": lambda self, e: ( 577 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 578 if e.args.get("windows") 579 else "" 580 ), 581 "qualify": lambda self, e: self.sql(e, "qualify"), 582 } 583 584 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 585 586 STRUCT_DELIMITER = ("<", ">") 587 588 PARAMETER_TOKEN = "@" 589 NAMED_PLACEHOLDER_TOKEN = ":" 590 591 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 592 593 PROPERTIES_LOCATION = { 594 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 595 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 596 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 600 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 602 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 605 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 609 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 611 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 612 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 614 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 615 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 618 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 620 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 622 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 623 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 624 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 625 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 626 exp.HeapProperty: exp.Properties.Location.POST_WITH, 627 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 628 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 629 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 630 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 631 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 632 exp.JournalProperty: exp.Properties.Location.POST_NAME, 633 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 634 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 635 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 636 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 637 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 638 exp.LogProperty: exp.Properties.Location.POST_NAME, 639 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 640 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 641 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 642 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 643 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 644 exp.Order: exp.Properties.Location.POST_SCHEMA, 645 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 646 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 647 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 648 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 649 exp.Property: exp.Properties.Location.POST_WITH, 650 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 651 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 652 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 653 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 654 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 655 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 656 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 657 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 658 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 659 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 660 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 661 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 662 exp.Set: exp.Properties.Location.POST_SCHEMA, 663 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 664 exp.SetProperty: exp.Properties.Location.POST_CREATE, 665 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 666 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 667 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 668 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 669 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 670 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 671 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 672 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 673 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 674 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 675 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 676 exp.Tags: exp.Properties.Location.POST_WITH, 677 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 678 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 679 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 680 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 681 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 682 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 683 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 684 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 685 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 686 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 687 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 688 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 689 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 690 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 691 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 692 } 693 694 # Keywords that can't be used as unquoted identifier names 695 RESERVED_KEYWORDS: t.Set[str] = set() 696 697 # Expressions whose comments are separated from them for better formatting 698 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 699 exp.Command, 700 exp.Create, 701 exp.Describe, 702 exp.Delete, 703 exp.Drop, 704 exp.From, 705 exp.Insert, 706 exp.Join, 707 exp.MultitableInserts, 708 exp.Order, 709 exp.Group, 710 exp.Having, 711 exp.Select, 712 exp.SetOperation, 713 exp.Update, 714 exp.Where, 715 exp.With, 716 ) 717 718 # Expressions that should not have their comments generated in maybe_comment 719 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 720 exp.Binary, 721 exp.SetOperation, 722 ) 723 724 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 725 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 726 exp.Column, 727 exp.Literal, 728 exp.Neg, 729 exp.Paren, 730 ) 731 732 PARAMETERIZABLE_TEXT_TYPES = { 733 exp.DataType.Type.NVARCHAR, 734 exp.DataType.Type.VARCHAR, 735 exp.DataType.Type.CHAR, 736 exp.DataType.Type.NCHAR, 737 } 738 739 # Expressions that need to have all CTEs under them bubbled up to them 740 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 741 742 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 743 744 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 745 746 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 747 748 __slots__ = ( 749 "pretty", 750 "identify", 751 "normalize", 752 "pad", 753 "_indent", 754 "normalize_functions", 755 "unsupported_level", 756 "max_unsupported", 757 "leading_comma", 758 "max_text_width", 759 "comments", 760 "dialect", 761 "unsupported_messages", 762 "_escaped_quote_end", 763 "_escaped_byte_quote_end", 764 "_escaped_identifier_end", 765 "_next_name", 766 "_identifier_start", 767 "_identifier_end", 768 "_quote_json_path_key_using_brackets", 769 ) 770 771 def __init__( 772 self, 773 pretty: t.Optional[bool] = None, 774 identify: str | bool = False, 775 normalize: bool = False, 776 pad: int = 2, 777 indent: int = 2, 778 normalize_functions: t.Optional[str | bool] = None, 779 unsupported_level: ErrorLevel = ErrorLevel.WARN, 780 max_unsupported: int = 3, 781 leading_comma: bool = False, 782 max_text_width: int = 80, 783 comments: bool = True, 784 dialect: DialectType = None, 785 ): 786 import sqlglot 787 from sqlglot.dialects import Dialect 788 789 self.pretty = pretty if pretty is not None else sqlglot.pretty 790 self.identify = identify 791 self.normalize = normalize 792 self.pad = pad 793 self._indent = indent 794 self.unsupported_level = unsupported_level 795 self.max_unsupported = max_unsupported 796 self.leading_comma = leading_comma 797 self.max_text_width = max_text_width 798 self.comments = comments 799 self.dialect = Dialect.get_or_raise(dialect) 800 801 # This is both a Dialect property and a Generator argument, so we prioritize the latter 802 self.normalize_functions = ( 803 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 804 ) 805 806 self.unsupported_messages: t.List[str] = [] 807 self._escaped_quote_end: str = ( 808 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 809 ) 810 self._escaped_byte_quote_end: str = ( 811 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 812 if self.dialect.BYTE_END 813 else "" 814 ) 815 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 816 817 self._next_name = name_sequence("_t") 818 819 self._identifier_start = self.dialect.IDENTIFIER_START 820 self._identifier_end = self.dialect.IDENTIFIER_END 821 822 self._quote_json_path_key_using_brackets = True 823 824 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 825 """ 826 Generates the SQL string corresponding to the given syntax tree. 827 828 Args: 829 expression: The syntax tree. 830 copy: Whether to copy the expression. The generator performs mutations so 831 it is safer to copy. 832 833 Returns: 834 The SQL string corresponding to `expression`. 835 """ 836 if copy: 837 expression = expression.copy() 838 839 expression = self.preprocess(expression) 840 841 self.unsupported_messages = [] 842 sql = self.sql(expression).strip() 843 844 if self.pretty: 845 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 846 847 if self.unsupported_level == ErrorLevel.IGNORE: 848 return sql 849 850 if self.unsupported_level == ErrorLevel.WARN: 851 for msg in self.unsupported_messages: 852 logger.warning(msg) 853 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 854 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 855 856 return sql 857 858 def preprocess(self, expression: exp.Expression) -> exp.Expression: 859 """Apply generic preprocessing transformations to a given expression.""" 860 expression = self._move_ctes_to_top_level(expression) 861 862 if self.ENSURE_BOOLS: 863 from sqlglot.transforms import ensure_bools 864 865 expression = ensure_bools(expression) 866 867 return expression 868 869 def _move_ctes_to_top_level(self, expression: E) -> E: 870 if ( 871 not expression.parent 872 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 873 and any(node.parent is not expression for node in expression.find_all(exp.With)) 874 ): 875 from sqlglot.transforms import move_ctes_to_top_level 876 877 expression = move_ctes_to_top_level(expression) 878 return expression 879 880 def unsupported(self, message: str) -> None: 881 if self.unsupported_level == ErrorLevel.IMMEDIATE: 882 raise UnsupportedError(message) 883 self.unsupported_messages.append(message) 884 885 def sep(self, sep: str = " ") -> str: 886 return f"{sep.strip()}\n" if self.pretty else sep 887 888 def seg(self, sql: str, sep: str = " ") -> str: 889 return f"{self.sep(sep)}{sql}" 890 891 def sanitize_comment(self, comment: str) -> str: 892 comment = " " + comment if comment[0].strip() else comment 893 comment = comment + " " if comment[-1].strip() else comment 894 895 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 896 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 897 comment = comment.replace("*/", "* /") 898 899 return comment 900 901 def maybe_comment( 902 self, 903 sql: str, 904 expression: t.Optional[exp.Expression] = None, 905 comments: t.Optional[t.List[str]] = None, 906 separated: bool = False, 907 ) -> str: 908 comments = ( 909 ((expression and expression.comments) if comments is None else comments) # type: ignore 910 if self.comments 911 else None 912 ) 913 914 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 915 return sql 916 917 comments_sql = " ".join( 918 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 919 ) 920 921 if not comments_sql: 922 return sql 923 924 comments_sql = self._replace_line_breaks(comments_sql) 925 926 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 927 return ( 928 f"{self.sep()}{comments_sql}{sql}" 929 if not sql or sql[0].isspace() 930 else f"{comments_sql}{self.sep()}{sql}" 931 ) 932 933 return f"{sql} {comments_sql}" 934 935 def wrap(self, expression: exp.Expression | str) -> str: 936 this_sql = ( 937 self.sql(expression) 938 if isinstance(expression, exp.UNWRAPPED_QUERIES) 939 else self.sql(expression, "this") 940 ) 941 if not this_sql: 942 return "()" 943 944 this_sql = self.indent(this_sql, level=1, pad=0) 945 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 946 947 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 948 original = self.identify 949 self.identify = False 950 result = func(*args, **kwargs) 951 self.identify = original 952 return result 953 954 def normalize_func(self, name: str) -> str: 955 if self.normalize_functions == "upper" or self.normalize_functions is True: 956 return name.upper() 957 if self.normalize_functions == "lower": 958 return name.lower() 959 return name 960 961 def indent( 962 self, 963 sql: str, 964 level: int = 0, 965 pad: t.Optional[int] = None, 966 skip_first: bool = False, 967 skip_last: bool = False, 968 ) -> str: 969 if not self.pretty or not sql: 970 return sql 971 972 pad = self.pad if pad is None else pad 973 lines = sql.split("\n") 974 975 return "\n".join( 976 ( 977 line 978 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 979 else f"{' ' * (level * self._indent + pad)}{line}" 980 ) 981 for i, line in enumerate(lines) 982 ) 983 984 def sql( 985 self, 986 expression: t.Optional[str | exp.Expression], 987 key: t.Optional[str] = None, 988 comment: bool = True, 989 ) -> str: 990 if not expression: 991 return "" 992 993 if isinstance(expression, str): 994 return expression 995 996 if key: 997 value = expression.args.get(key) 998 if value: 999 return self.sql(value) 1000 return "" 1001 1002 transform = self.TRANSFORMS.get(expression.__class__) 1003 1004 if transform: 1005 sql = transform(self, expression) 1006 else: 1007 exp_handler_name = expression.key + "_sql" 1008 1009 if handler := getattr(self, exp_handler_name, None): 1010 sql = handler(expression) 1011 elif isinstance(expression, exp.Func): 1012 sql = self.function_fallback_sql(expression) 1013 elif isinstance(expression, exp.Property): 1014 sql = self.property_sql(expression) 1015 else: 1016 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1017 1018 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1019 1020 def uncache_sql(self, expression: exp.Uncache) -> str: 1021 table = self.sql(expression, "this") 1022 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1023 return f"UNCACHE TABLE{exists_sql} {table}" 1024 1025 def cache_sql(self, expression: exp.Cache) -> str: 1026 lazy = " LAZY" if expression.args.get("lazy") else "" 1027 table = self.sql(expression, "this") 1028 options = expression.args.get("options") 1029 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1030 sql = self.sql(expression, "expression") 1031 sql = f" AS{self.sep()}{sql}" if sql else "" 1032 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1033 return self.prepend_ctes(expression, sql) 1034 1035 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1036 if isinstance(expression.parent, exp.Cast): 1037 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1038 default = "DEFAULT " if expression.args.get("default") else "" 1039 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1040 1041 def column_parts(self, expression: exp.Column) -> str: 1042 return ".".join( 1043 self.sql(part) 1044 for part in ( 1045 expression.args.get("catalog"), 1046 expression.args.get("db"), 1047 expression.args.get("table"), 1048 expression.args.get("this"), 1049 ) 1050 if part 1051 ) 1052 1053 def column_sql(self, expression: exp.Column) -> str: 1054 join_mark = " (+)" if expression.args.get("join_mark") else "" 1055 1056 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1057 join_mark = "" 1058 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1059 1060 return f"{self.column_parts(expression)}{join_mark}" 1061 1062 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1063 return self.column_sql(expression) 1064 1065 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1066 this = self.sql(expression, "this") 1067 this = f" {this}" if this else "" 1068 position = self.sql(expression, "position") 1069 return f"{position}{this}" 1070 1071 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1072 column = self.sql(expression, "this") 1073 kind = self.sql(expression, "kind") 1074 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1075 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1076 kind = f"{sep}{kind}" if kind else "" 1077 constraints = f" {constraints}" if constraints else "" 1078 position = self.sql(expression, "position") 1079 position = f" {position}" if position else "" 1080 1081 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1082 kind = "" 1083 1084 return f"{exists}{column}{kind}{constraints}{position}" 1085 1086 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1087 this = self.sql(expression, "this") 1088 kind_sql = self.sql(expression, "kind").strip() 1089 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1090 1091 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1092 this = self.sql(expression, "this") 1093 if expression.args.get("not_null"): 1094 persisted = " PERSISTED NOT NULL" 1095 elif expression.args.get("persisted"): 1096 persisted = " PERSISTED" 1097 else: 1098 persisted = "" 1099 1100 return f"AS {this}{persisted}" 1101 1102 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1103 return self.token_sql(TokenType.AUTO_INCREMENT) 1104 1105 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1106 if isinstance(expression.this, list): 1107 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1108 else: 1109 this = self.sql(expression, "this") 1110 1111 return f"COMPRESS {this}" 1112 1113 def generatedasidentitycolumnconstraint_sql( 1114 self, expression: exp.GeneratedAsIdentityColumnConstraint 1115 ) -> str: 1116 this = "" 1117 if expression.this is not None: 1118 on_null = " ON NULL" if expression.args.get("on_null") else "" 1119 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1120 1121 start = expression.args.get("start") 1122 start = f"START WITH {start}" if start else "" 1123 increment = expression.args.get("increment") 1124 increment = f" INCREMENT BY {increment}" if increment else "" 1125 minvalue = expression.args.get("minvalue") 1126 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1127 maxvalue = expression.args.get("maxvalue") 1128 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1129 cycle = expression.args.get("cycle") 1130 cycle_sql = "" 1131 1132 if cycle is not None: 1133 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1134 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1135 1136 sequence_opts = "" 1137 if start or increment or cycle_sql: 1138 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1139 sequence_opts = f" ({sequence_opts.strip()})" 1140 1141 expr = self.sql(expression, "expression") 1142 expr = f"({expr})" if expr else "IDENTITY" 1143 1144 return f"GENERATED{this} AS {expr}{sequence_opts}" 1145 1146 def generatedasrowcolumnconstraint_sql( 1147 self, expression: exp.GeneratedAsRowColumnConstraint 1148 ) -> str: 1149 start = "START" if expression.args.get("start") else "END" 1150 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1151 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1152 1153 def periodforsystemtimeconstraint_sql( 1154 self, expression: exp.PeriodForSystemTimeConstraint 1155 ) -> str: 1156 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1157 1158 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1159 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1160 1161 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1162 desc = expression.args.get("desc") 1163 if desc is not None: 1164 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1165 options = self.expressions(expression, key="options", flat=True, sep=" ") 1166 options = f" {options}" if options else "" 1167 return f"PRIMARY KEY{options}" 1168 1169 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1170 this = self.sql(expression, "this") 1171 this = f" {this}" if this else "" 1172 index_type = expression.args.get("index_type") 1173 index_type = f" USING {index_type}" if index_type else "" 1174 on_conflict = self.sql(expression, "on_conflict") 1175 on_conflict = f" {on_conflict}" if on_conflict else "" 1176 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1177 options = self.expressions(expression, key="options", flat=True, sep=" ") 1178 options = f" {options}" if options else "" 1179 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1180 1181 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1182 input_ = expression.args.get("input_") 1183 output = expression.args.get("output") 1184 variadic = expression.args.get("variadic") 1185 1186 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1187 if variadic: 1188 return "VARIADIC" 1189 1190 if input_ and output: 1191 return f"IN{self.INOUT_SEPARATOR}OUT" 1192 if input_: 1193 return "IN" 1194 if output: 1195 return "OUT" 1196 1197 return "" 1198 1199 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1200 return self.sql(expression, "this") 1201 1202 def create_sql(self, expression: exp.Create) -> str: 1203 kind = self.sql(expression, "kind") 1204 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1205 1206 properties = expression.args.get("properties") 1207 1208 if ( 1209 kind == "TRIGGER" 1210 and properties 1211 and properties.expressions 1212 and isinstance(properties.expressions[0], exp.TriggerProperties) 1213 and properties.expressions[0].args.get("constraint") 1214 ): 1215 kind = f"CONSTRAINT {kind}" 1216 1217 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1218 1219 this = self.createable_sql(expression, properties_locs) 1220 1221 properties_sql = "" 1222 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1223 exp.Properties.Location.POST_WITH 1224 ): 1225 props_ast = exp.Properties( 1226 expressions=[ 1227 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1228 *properties_locs[exp.Properties.Location.POST_WITH], 1229 ] 1230 ) 1231 props_ast.parent = expression 1232 properties_sql = self.sql(props_ast) 1233 1234 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1235 properties_sql = self.sep() + properties_sql 1236 elif not self.pretty: 1237 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1238 properties_sql = f" {properties_sql}" 1239 1240 begin = " BEGIN" if expression.args.get("begin") else "" 1241 1242 expression_sql = self.sql(expression, "expression") 1243 if expression_sql: 1244 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1245 1246 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1247 postalias_props_sql = "" 1248 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1249 postalias_props_sql = self.properties( 1250 exp.Properties( 1251 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1252 ), 1253 wrapped=False, 1254 ) 1255 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1256 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1257 1258 postindex_props_sql = "" 1259 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1260 postindex_props_sql = self.properties( 1261 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1262 wrapped=False, 1263 prefix=" ", 1264 ) 1265 1266 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1267 indexes = f" {indexes}" if indexes else "" 1268 index_sql = indexes + postindex_props_sql 1269 1270 replace = " OR REPLACE" if expression.args.get("replace") else "" 1271 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1272 unique = " UNIQUE" if expression.args.get("unique") else "" 1273 1274 clustered = expression.args.get("clustered") 1275 if clustered is None: 1276 clustered_sql = "" 1277 elif clustered: 1278 clustered_sql = " CLUSTERED COLUMNSTORE" 1279 else: 1280 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1281 1282 postcreate_props_sql = "" 1283 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1284 postcreate_props_sql = self.properties( 1285 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1286 sep=" ", 1287 prefix=" ", 1288 wrapped=False, 1289 ) 1290 1291 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1292 1293 postexpression_props_sql = "" 1294 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1295 postexpression_props_sql = self.properties( 1296 exp.Properties( 1297 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1298 ), 1299 sep=" ", 1300 prefix=" ", 1301 wrapped=False, 1302 ) 1303 1304 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1305 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1306 no_schema_binding = ( 1307 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1308 ) 1309 1310 clone = self.sql(expression, "clone") 1311 clone = f" {clone}" if clone else "" 1312 1313 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1314 properties_expression = f"{expression_sql}{properties_sql}" 1315 else: 1316 properties_expression = f"{properties_sql}{expression_sql}" 1317 1318 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1319 return self.prepend_ctes(expression, expression_sql) 1320 1321 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1322 start = self.sql(expression, "start") 1323 start = f"START WITH {start}" if start else "" 1324 increment = self.sql(expression, "increment") 1325 increment = f" INCREMENT BY {increment}" if increment else "" 1326 minvalue = self.sql(expression, "minvalue") 1327 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1328 maxvalue = self.sql(expression, "maxvalue") 1329 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1330 owned = self.sql(expression, "owned") 1331 owned = f" OWNED BY {owned}" if owned else "" 1332 1333 cache = expression.args.get("cache") 1334 if cache is None: 1335 cache_str = "" 1336 elif cache is True: 1337 cache_str = " CACHE" 1338 else: 1339 cache_str = f" CACHE {cache}" 1340 1341 options = self.expressions(expression, key="options", flat=True, sep=" ") 1342 options = f" {options}" if options else "" 1343 1344 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1345 1346 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1347 timing = expression.args.get("timing", "") 1348 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1349 timing_events = f"{timing} {events}".strip() if timing or events else "" 1350 1351 parts = [timing_events, "ON", self.sql(expression, "table")] 1352 1353 if referenced_table := expression.args.get("referenced_table"): 1354 parts.extend(["FROM", self.sql(referenced_table)]) 1355 1356 if deferrable := expression.args.get("deferrable"): 1357 parts.append(deferrable) 1358 1359 if initially := expression.args.get("initially"): 1360 parts.append(f"INITIALLY {initially}") 1361 1362 if referencing := expression.args.get("referencing"): 1363 parts.append(self.sql(referencing)) 1364 1365 if for_each := expression.args.get("for_each"): 1366 parts.append(f"FOR EACH {for_each}") 1367 1368 if when := expression.args.get("when"): 1369 parts.append(f"WHEN ({self.sql(when)})") 1370 1371 parts.append(self.sql(expression, "execute")) 1372 1373 return self.sep().join(parts) 1374 1375 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1376 parts = [] 1377 1378 if old_alias := expression.args.get("old"): 1379 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1380 1381 if new_alias := expression.args.get("new"): 1382 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1383 1384 return f"REFERENCING {' '.join(parts)}" 1385 1386 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1387 columns = expression.args.get("columns") 1388 if columns: 1389 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1390 1391 return self.sql(expression, "this") 1392 1393 def clone_sql(self, expression: exp.Clone) -> str: 1394 this = self.sql(expression, "this") 1395 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1396 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1397 return f"{shallow}{keyword} {this}" 1398 1399 def describe_sql(self, expression: exp.Describe) -> str: 1400 style = expression.args.get("style") 1401 style = f" {style}" if style else "" 1402 partition = self.sql(expression, "partition") 1403 partition = f" {partition}" if partition else "" 1404 format = self.sql(expression, "format") 1405 format = f" {format}" if format else "" 1406 as_json = " AS JSON" if expression.args.get("as_json") else "" 1407 1408 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1409 1410 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1411 tag = self.sql(expression, "tag") 1412 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1413 1414 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1415 with_ = self.sql(expression, "with_") 1416 if with_: 1417 sql = f"{with_}{self.sep()}{sql}" 1418 return sql 1419 1420 def with_sql(self, expression: exp.With) -> str: 1421 sql = self.expressions(expression, flat=True) 1422 recursive = ( 1423 "RECURSIVE " 1424 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1425 else "" 1426 ) 1427 search = self.sql(expression, "search") 1428 search = f" {search}" if search else "" 1429 1430 return f"WITH {recursive}{sql}{search}" 1431 1432 def cte_sql(self, expression: exp.CTE) -> str: 1433 alias = expression.args.get("alias") 1434 if alias: 1435 alias.add_comments(expression.pop_comments()) 1436 1437 alias_sql = self.sql(expression, "alias") 1438 1439 materialized = expression.args.get("materialized") 1440 if materialized is False: 1441 materialized = "NOT MATERIALIZED " 1442 elif materialized: 1443 materialized = "MATERIALIZED " 1444 1445 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1446 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1447 1448 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1449 1450 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1451 alias = self.sql(expression, "this") 1452 columns = self.expressions(expression, key="columns", flat=True) 1453 columns = f"({columns})" if columns else "" 1454 1455 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1456 columns = "" 1457 self.unsupported("Named columns are not supported in table alias.") 1458 1459 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1460 alias = self._next_name() 1461 1462 return f"{alias}{columns}" 1463 1464 def bitstring_sql(self, expression: exp.BitString) -> str: 1465 this = self.sql(expression, "this") 1466 if self.dialect.BIT_START: 1467 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1468 return f"{int(this, 2)}" 1469 1470 def hexstring_sql( 1471 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1472 ) -> str: 1473 this = self.sql(expression, "this") 1474 is_integer_type = expression.args.get("is_integer") 1475 1476 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1477 not self.dialect.HEX_START and not binary_function_repr 1478 ): 1479 # Integer representation will be returned if: 1480 # - The read dialect treats the hex value as integer literal but not the write 1481 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1482 return f"{int(this, 16)}" 1483 1484 if not is_integer_type: 1485 # Read dialect treats the hex value as BINARY/BLOB 1486 if binary_function_repr: 1487 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1488 return self.func(binary_function_repr, exp.Literal.string(this)) 1489 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1490 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1491 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1492 1493 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1494 1495 def bytestring_sql(self, expression: exp.ByteString) -> str: 1496 this = self.sql(expression, "this") 1497 if self.dialect.BYTE_START: 1498 escaped_byte_string = self.escape_str( 1499 this, 1500 escape_backslash=False, 1501 delimiter=self.dialect.BYTE_END, 1502 escaped_delimiter=self._escaped_byte_quote_end, 1503 is_byte_string=True, 1504 ) 1505 is_bytes = expression.args.get("is_bytes", False) 1506 delimited_byte_string = ( 1507 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1508 ) 1509 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1510 return self.sql( 1511 exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect) 1512 ) 1513 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1514 return self.sql( 1515 exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect) 1516 ) 1517 1518 return delimited_byte_string 1519 return this 1520 1521 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1522 this = self.sql(expression, "this") 1523 escape = expression.args.get("escape") 1524 1525 if self.dialect.UNICODE_START: 1526 escape_substitute = r"\\\1" 1527 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1528 else: 1529 escape_substitute = r"\\u\1" 1530 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1531 1532 if escape: 1533 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1534 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1535 else: 1536 escape_pattern = ESCAPED_UNICODE_RE 1537 escape_sql = "" 1538 1539 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1540 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1541 1542 return f"{left_quote}{this}{right_quote}{escape_sql}" 1543 1544 def rawstring_sql(self, expression: exp.RawString) -> str: 1545 string = expression.this 1546 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1547 string = string.replace("\\", "\\\\") 1548 1549 string = self.escape_str(string, escape_backslash=False) 1550 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1551 1552 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1553 this = self.sql(expression, "this") 1554 specifier = self.sql(expression, "expression") 1555 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1556 return f"{this}{specifier}" 1557 1558 def datatype_sql(self, expression: exp.DataType) -> str: 1559 nested = "" 1560 values = "" 1561 1562 expr_nested = expression.args.get("nested") 1563 interior = ( 1564 self.expressions( 1565 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1566 ) 1567 if expr_nested and self.pretty 1568 else self.expressions(expression, flat=True) 1569 ) 1570 1571 type_value = expression.this 1572 if type_value in self.UNSUPPORTED_TYPES: 1573 self.unsupported( 1574 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1575 ) 1576 1577 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1578 type_sql = self.sql(expression, "kind") 1579 else: 1580 type_sql = ( 1581 self.TYPE_MAPPING.get(type_value, type_value.value) 1582 if isinstance(type_value, exp.DataType.Type) 1583 else type_value 1584 ) 1585 1586 if interior: 1587 if expr_nested: 1588 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1589 if expression.args.get("values") is not None: 1590 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1591 values = self.expressions(expression, key="values", flat=True) 1592 values = f"{delimiters[0]}{values}{delimiters[1]}" 1593 elif type_value == exp.DataType.Type.INTERVAL: 1594 nested = f" {interior}" 1595 else: 1596 nested = f"({interior})" 1597 1598 type_sql = f"{type_sql}{nested}{values}" 1599 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1600 exp.DataType.Type.TIMETZ, 1601 exp.DataType.Type.TIMESTAMPTZ, 1602 ): 1603 type_sql = f"{type_sql} WITH TIME ZONE" 1604 1605 return type_sql 1606 1607 def directory_sql(self, expression: exp.Directory) -> str: 1608 local = "LOCAL " if expression.args.get("local") else "" 1609 row_format = self.sql(expression, "row_format") 1610 row_format = f" {row_format}" if row_format else "" 1611 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1612 1613 def delete_sql(self, expression: exp.Delete) -> str: 1614 this = self.sql(expression, "this") 1615 this = f" FROM {this}" if this else "" 1616 using = self.expressions(expression, key="using") 1617 using = f" USING {using}" if using else "" 1618 cluster = self.sql(expression, "cluster") 1619 cluster = f" {cluster}" if cluster else "" 1620 where = self.sql(expression, "where") 1621 returning = self.sql(expression, "returning") 1622 order = self.sql(expression, "order") 1623 limit = self.sql(expression, "limit") 1624 tables = self.expressions(expression, key="tables") 1625 tables = f" {tables}" if tables else "" 1626 if self.RETURNING_END: 1627 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1628 else: 1629 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1630 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1631 1632 def drop_sql(self, expression: exp.Drop) -> str: 1633 this = self.sql(expression, "this") 1634 expressions = self.expressions(expression, flat=True) 1635 expressions = f" ({expressions})" if expressions else "" 1636 kind = expression.args["kind"] 1637 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1638 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1639 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1640 on_cluster = self.sql(expression, "cluster") 1641 on_cluster = f" {on_cluster}" if on_cluster else "" 1642 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1643 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1644 cascade = " CASCADE" if expression.args.get("cascade") else "" 1645 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1646 purge = " PURGE" if expression.args.get("purge") else "" 1647 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1648 1649 def set_operation(self, expression: exp.SetOperation) -> str: 1650 op_type = type(expression) 1651 op_name = op_type.key.upper() 1652 1653 distinct = expression.args.get("distinct") 1654 if ( 1655 distinct is False 1656 and op_type in (exp.Except, exp.Intersect) 1657 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1658 ): 1659 self.unsupported(f"{op_name} ALL is not supported") 1660 1661 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1662 1663 if distinct is None: 1664 distinct = default_distinct 1665 if distinct is None: 1666 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1667 1668 if distinct is default_distinct: 1669 distinct_or_all = "" 1670 else: 1671 distinct_or_all = " DISTINCT" if distinct else " ALL" 1672 1673 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1674 side_kind = f"{side_kind} " if side_kind else "" 1675 1676 by_name = " BY NAME" if expression.args.get("by_name") else "" 1677 on = self.expressions(expression, key="on", flat=True) 1678 on = f" ON ({on})" if on else "" 1679 1680 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1681 1682 def set_operations(self, expression: exp.SetOperation) -> str: 1683 if not self.SET_OP_MODIFIERS: 1684 limit = expression.args.get("limit") 1685 order = expression.args.get("order") 1686 1687 if limit or order: 1688 select = self._move_ctes_to_top_level( 1689 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1690 ) 1691 1692 if limit: 1693 select = select.limit(limit.pop(), copy=False) 1694 if order: 1695 select = select.order_by(order.pop(), copy=False) 1696 return self.sql(select) 1697 1698 sqls: t.List[str] = [] 1699 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1700 1701 while stack: 1702 node = stack.pop() 1703 1704 if isinstance(node, exp.SetOperation): 1705 stack.append(node.expression) 1706 stack.append( 1707 self.maybe_comment( 1708 self.set_operation(node), comments=node.comments, separated=True 1709 ) 1710 ) 1711 stack.append(node.this) 1712 else: 1713 sqls.append(self.sql(node)) 1714 1715 this = self.sep().join(sqls) 1716 this = self.query_modifiers(expression, this) 1717 return self.prepend_ctes(expression, this) 1718 1719 def fetch_sql(self, expression: exp.Fetch) -> str: 1720 direction = expression.args.get("direction") 1721 direction = f" {direction}" if direction else "" 1722 count = self.sql(expression, "count") 1723 count = f" {count}" if count else "" 1724 limit_options = self.sql(expression, "limit_options") 1725 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1726 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1727 1728 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1729 percent = " PERCENT" if expression.args.get("percent") else "" 1730 rows = " ROWS" if expression.args.get("rows") else "" 1731 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1732 if not with_ties and rows: 1733 with_ties = " ONLY" 1734 return f"{percent}{rows}{with_ties}" 1735 1736 def filter_sql(self, expression: exp.Filter) -> str: 1737 if self.AGGREGATE_FILTER_SUPPORTED: 1738 this = self.sql(expression, "this") 1739 where = self.sql(expression, "expression").strip() 1740 return f"{this} FILTER({where})" 1741 1742 agg = expression.this 1743 agg_arg = agg.this 1744 cond = expression.expression.this 1745 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1746 return self.sql(agg) 1747 1748 def hint_sql(self, expression: exp.Hint) -> str: 1749 if not self.QUERY_HINTS: 1750 self.unsupported("Hints are not supported") 1751 return "" 1752 1753 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1754 1755 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1756 using = self.sql(expression, "using") 1757 using = f" USING {using}" if using else "" 1758 columns = self.expressions(expression, key="columns", flat=True) 1759 columns = f"({columns})" if columns else "" 1760 partition_by = self.expressions(expression, key="partition_by", flat=True) 1761 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1762 where = self.sql(expression, "where") 1763 include = self.expressions(expression, key="include", flat=True) 1764 if include: 1765 include = f" INCLUDE ({include})" 1766 with_storage = self.expressions(expression, key="with_storage", flat=True) 1767 with_storage = f" WITH ({with_storage})" if with_storage else "" 1768 tablespace = self.sql(expression, "tablespace") 1769 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1770 on = self.sql(expression, "on") 1771 on = f" ON {on}" if on else "" 1772 1773 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1774 1775 def index_sql(self, expression: exp.Index) -> str: 1776 unique = "UNIQUE " if expression.args.get("unique") else "" 1777 primary = "PRIMARY " if expression.args.get("primary") else "" 1778 amp = "AMP " if expression.args.get("amp") else "" 1779 name = self.sql(expression, "this") 1780 name = f"{name} " if name else "" 1781 table = self.sql(expression, "table") 1782 table = f"{self.INDEX_ON} {table}" if table else "" 1783 1784 index = "INDEX " if not table else "" 1785 1786 params = self.sql(expression, "params") 1787 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1788 1789 def identifier_sql(self, expression: exp.Identifier) -> str: 1790 text = expression.name 1791 lower = text.lower() 1792 quoted = expression.quoted 1793 text = lower if self.normalize and not quoted else text 1794 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1795 if ( 1796 quoted 1797 or self.dialect.can_quote(expression, self.identify) 1798 or lower in self.RESERVED_KEYWORDS 1799 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1800 ): 1801 text = f"{self._identifier_start}{text}{self._identifier_end}" 1802 return text 1803 1804 def hex_sql(self, expression: exp.Hex) -> str: 1805 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1806 if self.dialect.HEX_LOWERCASE: 1807 text = self.func("LOWER", text) 1808 1809 return text 1810 1811 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1812 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1813 if not self.dialect.HEX_LOWERCASE: 1814 text = self.func("LOWER", text) 1815 return text 1816 1817 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1818 input_format = self.sql(expression, "input_format") 1819 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1820 output_format = self.sql(expression, "output_format") 1821 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1822 return self.sep().join((input_format, output_format)) 1823 1824 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1825 string = self.sql(exp.Literal.string(expression.name)) 1826 return f"{prefix}{string}" 1827 1828 def partition_sql(self, expression: exp.Partition) -> str: 1829 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1830 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1831 1832 def properties_sql(self, expression: exp.Properties) -> str: 1833 root_properties = [] 1834 with_properties = [] 1835 1836 for p in expression.expressions: 1837 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1838 if p_loc == exp.Properties.Location.POST_WITH: 1839 with_properties.append(p) 1840 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1841 root_properties.append(p) 1842 1843 root_props_ast = exp.Properties(expressions=root_properties) 1844 root_props_ast.parent = expression.parent 1845 1846 with_props_ast = exp.Properties(expressions=with_properties) 1847 with_props_ast.parent = expression.parent 1848 1849 root_props = self.root_properties(root_props_ast) 1850 with_props = self.with_properties(with_props_ast) 1851 1852 if root_props and with_props and not self.pretty: 1853 with_props = " " + with_props 1854 1855 return root_props + with_props 1856 1857 def root_properties(self, properties: exp.Properties) -> str: 1858 if properties.expressions: 1859 return self.expressions(properties, indent=False, sep=" ") 1860 return "" 1861 1862 def properties( 1863 self, 1864 properties: exp.Properties, 1865 prefix: str = "", 1866 sep: str = ", ", 1867 suffix: str = "", 1868 wrapped: bool = True, 1869 ) -> str: 1870 if properties.expressions: 1871 expressions = self.expressions(properties, sep=sep, indent=False) 1872 if expressions: 1873 expressions = self.wrap(expressions) if wrapped else expressions 1874 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1875 return "" 1876 1877 def with_properties(self, properties: exp.Properties) -> str: 1878 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1879 1880 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1881 properties_locs = defaultdict(list) 1882 for p in properties.expressions: 1883 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1884 if p_loc != exp.Properties.Location.UNSUPPORTED: 1885 properties_locs[p_loc].append(p) 1886 else: 1887 self.unsupported(f"Unsupported property {p.key}") 1888 1889 return properties_locs 1890 1891 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1892 if isinstance(expression.this, exp.Dot): 1893 return self.sql(expression, "this") 1894 return f"'{expression.name}'" if string_key else expression.name 1895 1896 def property_sql(self, expression: exp.Property) -> str: 1897 property_cls = expression.__class__ 1898 if property_cls == exp.Property: 1899 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1900 1901 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1902 if not property_name: 1903 self.unsupported(f"Unsupported property {expression.key}") 1904 1905 return f"{property_name}={self.sql(expression, 'this')}" 1906 1907 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1908 if self.SUPPORTS_CREATE_TABLE_LIKE: 1909 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1910 options = f" {options}" if options else "" 1911 1912 like = f"LIKE {self.sql(expression, 'this')}{options}" 1913 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1914 like = f"({like})" 1915 1916 return like 1917 1918 if expression.expressions: 1919 self.unsupported("Transpilation of LIKE property options is unsupported") 1920 1921 select = exp.select("*").from_(expression.this).limit(0) 1922 return f"AS {self.sql(select)}" 1923 1924 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1925 no = "NO " if expression.args.get("no") else "" 1926 protection = " PROTECTION" if expression.args.get("protection") else "" 1927 return f"{no}FALLBACK{protection}" 1928 1929 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1930 no = "NO " if expression.args.get("no") else "" 1931 local = expression.args.get("local") 1932 local = f"{local} " if local else "" 1933 dual = "DUAL " if expression.args.get("dual") else "" 1934 before = "BEFORE " if expression.args.get("before") else "" 1935 after = "AFTER " if expression.args.get("after") else "" 1936 return f"{no}{local}{dual}{before}{after}JOURNAL" 1937 1938 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1939 freespace = self.sql(expression, "this") 1940 percent = " PERCENT" if expression.args.get("percent") else "" 1941 return f"FREESPACE={freespace}{percent}" 1942 1943 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1944 if expression.args.get("default"): 1945 property = "DEFAULT" 1946 elif expression.args.get("on"): 1947 property = "ON" 1948 else: 1949 property = "OFF" 1950 return f"CHECKSUM={property}" 1951 1952 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1953 if expression.args.get("no"): 1954 return "NO MERGEBLOCKRATIO" 1955 if expression.args.get("default"): 1956 return "DEFAULT MERGEBLOCKRATIO" 1957 1958 percent = " PERCENT" if expression.args.get("percent") else "" 1959 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1960 1961 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1962 default = expression.args.get("default") 1963 minimum = expression.args.get("minimum") 1964 maximum = expression.args.get("maximum") 1965 if default or minimum or maximum: 1966 if default: 1967 prop = "DEFAULT" 1968 elif minimum: 1969 prop = "MINIMUM" 1970 else: 1971 prop = "MAXIMUM" 1972 return f"{prop} DATABLOCKSIZE" 1973 units = expression.args.get("units") 1974 units = f" {units}" if units else "" 1975 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1976 1977 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1978 autotemp = expression.args.get("autotemp") 1979 always = expression.args.get("always") 1980 default = expression.args.get("default") 1981 manual = expression.args.get("manual") 1982 never = expression.args.get("never") 1983 1984 if autotemp is not None: 1985 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1986 elif always: 1987 prop = "ALWAYS" 1988 elif default: 1989 prop = "DEFAULT" 1990 elif manual: 1991 prop = "MANUAL" 1992 elif never: 1993 prop = "NEVER" 1994 return f"BLOCKCOMPRESSION={prop}" 1995 1996 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1997 no = expression.args.get("no") 1998 no = " NO" if no else "" 1999 concurrent = expression.args.get("concurrent") 2000 concurrent = " CONCURRENT" if concurrent else "" 2001 target = self.sql(expression, "target") 2002 target = f" {target}" if target else "" 2003 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2004 2005 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2006 if isinstance(expression.this, list): 2007 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2008 if expression.this: 2009 modulus = self.sql(expression, "this") 2010 remainder = self.sql(expression, "expression") 2011 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2012 2013 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2014 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2015 return f"FROM ({from_expressions}) TO ({to_expressions})" 2016 2017 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2018 this = self.sql(expression, "this") 2019 2020 for_values_or_default = expression.expression 2021 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2022 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2023 else: 2024 for_values_or_default = " DEFAULT" 2025 2026 return f"PARTITION OF {this}{for_values_or_default}" 2027 2028 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2029 kind = expression.args.get("kind") 2030 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2031 for_or_in = expression.args.get("for_or_in") 2032 for_or_in = f" {for_or_in}" if for_or_in else "" 2033 lock_type = expression.args.get("lock_type") 2034 override = " OVERRIDE" if expression.args.get("override") else "" 2035 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2036 2037 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2038 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2039 statistics = expression.args.get("statistics") 2040 statistics_sql = "" 2041 if statistics is not None: 2042 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2043 return f"{data_sql}{statistics_sql}" 2044 2045 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2046 this = self.sql(expression, "this") 2047 this = f"HISTORY_TABLE={this}" if this else "" 2048 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 2049 data_consistency = ( 2050 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2051 ) 2052 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 2053 retention_period = ( 2054 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2055 ) 2056 2057 if this: 2058 on_sql = self.func("ON", this, data_consistency, retention_period) 2059 else: 2060 on_sql = "ON" if expression.args.get("on") else "OFF" 2061 2062 sql = f"SYSTEM_VERSIONING={on_sql}" 2063 2064 return f"WITH({sql})" if expression.args.get("with_") else sql 2065 2066 def insert_sql(self, expression: exp.Insert) -> str: 2067 hint = self.sql(expression, "hint") 2068 overwrite = expression.args.get("overwrite") 2069 2070 if isinstance(expression.this, exp.Directory): 2071 this = " OVERWRITE" if overwrite else " INTO" 2072 else: 2073 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2074 2075 stored = self.sql(expression, "stored") 2076 stored = f" {stored}" if stored else "" 2077 alternative = expression.args.get("alternative") 2078 alternative = f" OR {alternative}" if alternative else "" 2079 ignore = " IGNORE" if expression.args.get("ignore") else "" 2080 is_function = expression.args.get("is_function") 2081 if is_function: 2082 this = f"{this} FUNCTION" 2083 this = f"{this} {self.sql(expression, 'this')}" 2084 2085 exists = " IF EXISTS" if expression.args.get("exists") else "" 2086 where = self.sql(expression, "where") 2087 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2088 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2089 on_conflict = self.sql(expression, "conflict") 2090 on_conflict = f" {on_conflict}" if on_conflict else "" 2091 by_name = " BY NAME" if expression.args.get("by_name") else "" 2092 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2093 returning = self.sql(expression, "returning") 2094 2095 if self.RETURNING_END: 2096 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2097 else: 2098 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2099 2100 partition_by = self.sql(expression, "partition") 2101 partition_by = f" {partition_by}" if partition_by else "" 2102 settings = self.sql(expression, "settings") 2103 settings = f" {settings}" if settings else "" 2104 2105 source = self.sql(expression, "source") 2106 source = f"TABLE {source}" if source else "" 2107 2108 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2109 return self.prepend_ctes(expression, sql) 2110 2111 def introducer_sql(self, expression: exp.Introducer) -> str: 2112 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2113 2114 def kill_sql(self, expression: exp.Kill) -> str: 2115 kind = self.sql(expression, "kind") 2116 kind = f" {kind}" if kind else "" 2117 this = self.sql(expression, "this") 2118 this = f" {this}" if this else "" 2119 return f"KILL{kind}{this}" 2120 2121 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2122 return expression.name 2123 2124 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2125 return expression.name 2126 2127 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2128 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2129 2130 constraint = self.sql(expression, "constraint") 2131 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2132 2133 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2134 if conflict_keys: 2135 conflict_keys = f"({conflict_keys})" 2136 2137 index_predicate = self.sql(expression, "index_predicate") 2138 conflict_keys = f"{conflict_keys}{index_predicate} " 2139 2140 action = self.sql(expression, "action") 2141 2142 expressions = self.expressions(expression, flat=True) 2143 if expressions: 2144 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2145 expressions = f" {set_keyword}{expressions}" 2146 2147 where = self.sql(expression, "where") 2148 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2149 2150 def returning_sql(self, expression: exp.Returning) -> str: 2151 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2152 2153 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2154 fields = self.sql(expression, "fields") 2155 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2156 escaped = self.sql(expression, "escaped") 2157 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2158 items = self.sql(expression, "collection_items") 2159 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2160 keys = self.sql(expression, "map_keys") 2161 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2162 lines = self.sql(expression, "lines") 2163 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2164 null = self.sql(expression, "null") 2165 null = f" NULL DEFINED AS {null}" if null else "" 2166 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2167 2168 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2169 return f"WITH ({self.expressions(expression, flat=True)})" 2170 2171 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2172 this = f"{self.sql(expression, 'this')} INDEX" 2173 target = self.sql(expression, "target") 2174 target = f" FOR {target}" if target else "" 2175 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2176 2177 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2178 this = self.sql(expression, "this") 2179 kind = self.sql(expression, "kind") 2180 expr = self.sql(expression, "expression") 2181 return f"{this} ({kind} => {expr})" 2182 2183 def table_parts(self, expression: exp.Table) -> str: 2184 return ".".join( 2185 self.sql(part) 2186 for part in ( 2187 expression.args.get("catalog"), 2188 expression.args.get("db"), 2189 expression.args.get("this"), 2190 ) 2191 if part is not None 2192 ) 2193 2194 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2195 table = self.table_parts(expression) 2196 only = "ONLY " if expression.args.get("only") else "" 2197 partition = self.sql(expression, "partition") 2198 partition = f" {partition}" if partition else "" 2199 version = self.sql(expression, "version") 2200 version = f" {version}" if version else "" 2201 alias = self.sql(expression, "alias") 2202 alias = f"{sep}{alias}" if alias else "" 2203 2204 sample = self.sql(expression, "sample") 2205 post_alias = "" 2206 pre_alias = "" 2207 2208 if self.dialect.ALIAS_POST_TABLESAMPLE: 2209 pre_alias = sample 2210 else: 2211 post_alias = sample 2212 2213 if self.dialect.ALIAS_POST_VERSION: 2214 pre_alias = f"{pre_alias}{version}" 2215 else: 2216 post_alias = f"{post_alias}{version}" 2217 2218 hints = self.expressions(expression, key="hints", sep=" ") 2219 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2220 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2221 joins = self.indent( 2222 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2223 ) 2224 laterals = self.expressions(expression, key="laterals", sep="") 2225 2226 file_format = self.sql(expression, "format") 2227 if file_format: 2228 pattern = self.sql(expression, "pattern") 2229 pattern = f", PATTERN => {pattern}" if pattern else "" 2230 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2231 2232 ordinality = expression.args.get("ordinality") or "" 2233 if ordinality: 2234 ordinality = f" WITH ORDINALITY{alias}" 2235 alias = "" 2236 2237 when = self.sql(expression, "when") 2238 if when: 2239 table = f"{table} {when}" 2240 2241 changes = self.sql(expression, "changes") 2242 changes = f" {changes}" if changes else "" 2243 2244 rows_from = self.expressions(expression, key="rows_from") 2245 if rows_from: 2246 table = f"ROWS FROM {self.wrap(rows_from)}" 2247 2248 indexed = expression.args.get("indexed") 2249 if indexed is not None: 2250 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2251 else: 2252 indexed = "" 2253 2254 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2255 2256 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2257 table = self.func("TABLE", expression.this) 2258 alias = self.sql(expression, "alias") 2259 alias = f" AS {alias}" if alias else "" 2260 sample = self.sql(expression, "sample") 2261 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2262 joins = self.indent( 2263 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2264 ) 2265 return f"{table}{alias}{pivots}{sample}{joins}" 2266 2267 def tablesample_sql( 2268 self, 2269 expression: exp.TableSample, 2270 tablesample_keyword: t.Optional[str] = None, 2271 ) -> str: 2272 method = self.sql(expression, "method") 2273 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2274 numerator = self.sql(expression, "bucket_numerator") 2275 denominator = self.sql(expression, "bucket_denominator") 2276 field = self.sql(expression, "bucket_field") 2277 field = f" ON {field}" if field else "" 2278 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2279 seed = self.sql(expression, "seed") 2280 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2281 2282 size = self.sql(expression, "size") 2283 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2284 size = f"{size} ROWS" 2285 2286 percent = self.sql(expression, "percent") 2287 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2288 percent = f"{percent} PERCENT" 2289 2290 expr = f"{bucket}{percent}{size}" 2291 if self.TABLESAMPLE_REQUIRES_PARENS: 2292 expr = f"({expr})" 2293 2294 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2295 2296 def pivot_sql(self, expression: exp.Pivot) -> str: 2297 expressions = self.expressions(expression, flat=True) 2298 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2299 2300 group = self.sql(expression, "group") 2301 2302 if expression.this: 2303 this = self.sql(expression, "this") 2304 if not expressions: 2305 sql = f"UNPIVOT {this}" 2306 else: 2307 on = f"{self.seg('ON')} {expressions}" 2308 into = self.sql(expression, "into") 2309 into = f"{self.seg('INTO')} {into}" if into else "" 2310 using = self.expressions(expression, key="using", flat=True) 2311 using = f"{self.seg('USING')} {using}" if using else "" 2312 sql = f"{direction} {this}{on}{into}{using}{group}" 2313 return self.prepend_ctes(expression, sql) 2314 2315 alias = self.sql(expression, "alias") 2316 alias = f" AS {alias}" if alias else "" 2317 2318 fields = self.expressions( 2319 expression, 2320 "fields", 2321 sep=" ", 2322 dynamic=True, 2323 new_line=True, 2324 skip_first=True, 2325 skip_last=True, 2326 ) 2327 2328 include_nulls = expression.args.get("include_nulls") 2329 if include_nulls is not None: 2330 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2331 else: 2332 nulls = "" 2333 2334 default_on_null = self.sql(expression, "default_on_null") 2335 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2336 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2337 return self.prepend_ctes(expression, sql) 2338 2339 def version_sql(self, expression: exp.Version) -> str: 2340 this = f"FOR {expression.name}" 2341 kind = expression.text("kind") 2342 expr = self.sql(expression, "expression") 2343 return f"{this} {kind} {expr}" 2344 2345 def tuple_sql(self, expression: exp.Tuple) -> str: 2346 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2347 2348 def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]: 2349 """ 2350 Returns (join_sql, from_sql) for UPDATE statements. 2351 - join_sql: placed after UPDATE table, before SET 2352 - from_sql: placed after SET clause (standard position) 2353 Dialects like MySQL need to convert FROM to JOIN syntax. 2354 """ 2355 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2356 return ("", self.sql(expression, "from_")) 2357 2358 # Qualify unqualified columns in SET clause with the target table 2359 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2360 target_table = expression.this 2361 if isinstance(target_table, exp.Table): 2362 target_name = exp.to_identifier(target_table.alias_or_name) 2363 for eq in expression.expressions: 2364 col = eq.this 2365 if isinstance(col, exp.Column) and not col.table: 2366 col.set("table", target_name) 2367 2368 table = from_expr.this 2369 if nested_joins := table.args.get("joins", []): 2370 table.set("joins", None) 2371 2372 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2373 for nested in nested_joins: 2374 if not nested.args.get("on") and not nested.args.get("using"): 2375 nested.set("on", exp.true()) 2376 join_sql += self.sql(nested) 2377 2378 return (join_sql, "") 2379 2380 def update_sql(self, expression: exp.Update) -> str: 2381 this = self.sql(expression, "this") 2382 join_sql, from_sql = self._update_from_joins_sql(expression) 2383 set_sql = self.expressions(expression, flat=True) 2384 where_sql = self.sql(expression, "where") 2385 returning = self.sql(expression, "returning") 2386 order = self.sql(expression, "order") 2387 limit = self.sql(expression, "limit") 2388 if self.RETURNING_END: 2389 expression_sql = f"{from_sql}{where_sql}{returning}" 2390 else: 2391 expression_sql = f"{returning}{from_sql}{where_sql}" 2392 options = self.expressions(expression, key="options") 2393 options = f" OPTION({options})" if options else "" 2394 sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2395 return self.prepend_ctes(expression, sql) 2396 2397 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2398 values_as_table = values_as_table and self.VALUES_AS_TABLE 2399 2400 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2401 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2402 args = self.expressions(expression) 2403 alias = self.sql(expression, "alias") 2404 values = f"VALUES{self.seg('')}{args}" 2405 values = ( 2406 f"({values})" 2407 if self.WRAP_DERIVED_VALUES 2408 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2409 else values 2410 ) 2411 values = self.query_modifiers(expression, values) 2412 return f"{values} AS {alias}" if alias else values 2413 2414 # Converts `VALUES...` expression into a series of select unions. 2415 alias_node = expression.args.get("alias") 2416 column_names = alias_node and alias_node.columns 2417 2418 selects: t.List[exp.Query] = [] 2419 2420 for i, tup in enumerate(expression.expressions): 2421 row = tup.expressions 2422 2423 if i == 0 and column_names: 2424 row = [ 2425 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2426 ] 2427 2428 selects.append(exp.Select(expressions=row)) 2429 2430 if self.pretty: 2431 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2432 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2433 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2434 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2435 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2436 2437 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2438 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2439 return f"({unions}){alias}" 2440 2441 def var_sql(self, expression: exp.Var) -> str: 2442 return self.sql(expression, "this") 2443 2444 @unsupported_args("expressions") 2445 def into_sql(self, expression: exp.Into) -> str: 2446 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2447 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2448 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2449 2450 def from_sql(self, expression: exp.From) -> str: 2451 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2452 2453 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2454 grouping_sets = self.expressions(expression, indent=False) 2455 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2456 2457 def rollup_sql(self, expression: exp.Rollup) -> str: 2458 expressions = self.expressions(expression, indent=False) 2459 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2460 2461 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2462 this = self.sql(expression, "this") 2463 2464 columns = self.expressions(expression, flat=True) 2465 2466 from_sql = self.sql(expression, "from_index") 2467 from_sql = f" FROM {from_sql}" if from_sql else "" 2468 2469 properties = expression.args.get("properties") 2470 properties_sql = ( 2471 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2472 ) 2473 2474 return f"{this}({columns}){from_sql}{properties_sql}" 2475 2476 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2477 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2478 2479 def cube_sql(self, expression: exp.Cube) -> str: 2480 expressions = self.expressions(expression, indent=False) 2481 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2482 2483 def group_sql(self, expression: exp.Group) -> str: 2484 group_by_all = expression.args.get("all") 2485 if group_by_all is True: 2486 modifier = " ALL" 2487 elif group_by_all is False: 2488 modifier = " DISTINCT" 2489 else: 2490 modifier = "" 2491 2492 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2493 2494 grouping_sets = self.expressions(expression, key="grouping_sets") 2495 cube = self.expressions(expression, key="cube") 2496 rollup = self.expressions(expression, key="rollup") 2497 2498 groupings = csv( 2499 self.seg(grouping_sets) if grouping_sets else "", 2500 self.seg(cube) if cube else "", 2501 self.seg(rollup) if rollup else "", 2502 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2503 sep=self.GROUPINGS_SEP, 2504 ) 2505 2506 if ( 2507 expression.expressions 2508 and groupings 2509 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2510 ): 2511 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2512 2513 return f"{group_by}{groupings}" 2514 2515 def having_sql(self, expression: exp.Having) -> str: 2516 this = self.indent(self.sql(expression, "this")) 2517 return f"{self.seg('HAVING')}{self.sep()}{this}" 2518 2519 def connect_sql(self, expression: exp.Connect) -> str: 2520 start = self.sql(expression, "start") 2521 start = self.seg(f"START WITH {start}") if start else "" 2522 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2523 connect = self.sql(expression, "connect") 2524 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2525 return start + connect 2526 2527 def prior_sql(self, expression: exp.Prior) -> str: 2528 return f"PRIOR {self.sql(expression, 'this')}" 2529 2530 def join_sql(self, expression: exp.Join) -> str: 2531 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2532 side = None 2533 else: 2534 side = expression.side 2535 2536 op_sql = " ".join( 2537 op 2538 for op in ( 2539 expression.method, 2540 "GLOBAL" if expression.args.get("global_") else None, 2541 side, 2542 expression.kind, 2543 expression.hint if self.JOIN_HINTS else None, 2544 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2545 ) 2546 if op 2547 ) 2548 match_cond = self.sql(expression, "match_condition") 2549 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2550 on_sql = self.sql(expression, "on") 2551 using = expression.args.get("using") 2552 2553 if not on_sql and using: 2554 on_sql = csv(*(self.sql(column) for column in using)) 2555 2556 this = expression.this 2557 this_sql = self.sql(this) 2558 2559 exprs = self.expressions(expression) 2560 if exprs: 2561 this_sql = f"{this_sql},{self.seg(exprs)}" 2562 2563 if on_sql: 2564 on_sql = self.indent(on_sql, skip_first=True) 2565 space = self.seg(" " * self.pad) if self.pretty else " " 2566 if using: 2567 on_sql = f"{space}USING ({on_sql})" 2568 else: 2569 on_sql = f"{space}ON {on_sql}" 2570 elif not op_sql: 2571 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2572 return f" {this_sql}" 2573 2574 return f", {this_sql}" 2575 2576 if op_sql != "STRAIGHT_JOIN": 2577 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2578 2579 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2580 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2581 2582 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2583 args = self.expressions(expression, flat=True) 2584 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2585 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2586 2587 def lateral_op(self, expression: exp.Lateral) -> str: 2588 cross_apply = expression.args.get("cross_apply") 2589 2590 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2591 if cross_apply is True: 2592 op = "INNER JOIN " 2593 elif cross_apply is False: 2594 op = "LEFT JOIN " 2595 else: 2596 op = "" 2597 2598 return f"{op}LATERAL" 2599 2600 def lateral_sql(self, expression: exp.Lateral) -> str: 2601 this = self.sql(expression, "this") 2602 2603 if expression.args.get("view"): 2604 alias = expression.args["alias"] 2605 columns = self.expressions(alias, key="columns", flat=True) 2606 table = f" {alias.name}" if alias.name else "" 2607 columns = f" AS {columns}" if columns else "" 2608 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2609 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2610 2611 alias = self.sql(expression, "alias") 2612 alias = f" AS {alias}" if alias else "" 2613 2614 ordinality = expression.args.get("ordinality") or "" 2615 if ordinality: 2616 ordinality = f" WITH ORDINALITY{alias}" 2617 alias = "" 2618 2619 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2620 2621 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2622 this = self.sql(expression, "this") 2623 2624 args = [ 2625 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2626 for e in (expression.args.get(k) for k in ("offset", "expression")) 2627 if e 2628 ] 2629 2630 args_sql = ", ".join(self.sql(e) for e in args) 2631 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2632 expressions = self.expressions(expression, flat=True) 2633 limit_options = self.sql(expression, "limit_options") 2634 expressions = f" BY {expressions}" if expressions else "" 2635 2636 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2637 2638 def offset_sql(self, expression: exp.Offset) -> str: 2639 this = self.sql(expression, "this") 2640 value = expression.expression 2641 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2642 expressions = self.expressions(expression, flat=True) 2643 expressions = f" BY {expressions}" if expressions else "" 2644 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2645 2646 def setitem_sql(self, expression: exp.SetItem) -> str: 2647 kind = self.sql(expression, "kind") 2648 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2649 kind = "" 2650 else: 2651 kind = f"{kind} " if kind else "" 2652 this = self.sql(expression, "this") 2653 expressions = self.expressions(expression) 2654 collate = self.sql(expression, "collate") 2655 collate = f" COLLATE {collate}" if collate else "" 2656 global_ = "GLOBAL " if expression.args.get("global_") else "" 2657 return f"{global_}{kind}{this}{expressions}{collate}" 2658 2659 def set_sql(self, expression: exp.Set) -> str: 2660 expressions = f" {self.expressions(expression, flat=True)}" 2661 tag = " TAG" if expression.args.get("tag") else "" 2662 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2663 2664 def queryband_sql(self, expression: exp.QueryBand) -> str: 2665 this = self.sql(expression, "this") 2666 update = " UPDATE" if expression.args.get("update") else "" 2667 scope = self.sql(expression, "scope") 2668 scope = f" FOR {scope}" if scope else "" 2669 2670 return f"QUERY_BAND = {this}{update}{scope}" 2671 2672 def pragma_sql(self, expression: exp.Pragma) -> str: 2673 return f"PRAGMA {self.sql(expression, 'this')}" 2674 2675 def lock_sql(self, expression: exp.Lock) -> str: 2676 if not self.LOCKING_READS_SUPPORTED: 2677 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2678 return "" 2679 2680 update = expression.args["update"] 2681 key = expression.args.get("key") 2682 if update: 2683 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2684 else: 2685 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2686 expressions = self.expressions(expression, flat=True) 2687 expressions = f" OF {expressions}" if expressions else "" 2688 wait = expression.args.get("wait") 2689 2690 if wait is not None: 2691 if isinstance(wait, exp.Literal): 2692 wait = f" WAIT {self.sql(wait)}" 2693 else: 2694 wait = " NOWAIT" if wait else " SKIP LOCKED" 2695 2696 return f"{lock_type}{expressions}{wait or ''}" 2697 2698 def literal_sql(self, expression: exp.Literal) -> str: 2699 text = expression.this or "" 2700 if expression.is_string: 2701 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2702 return text 2703 2704 def escape_str( 2705 self, 2706 text: str, 2707 escape_backslash: bool = True, 2708 delimiter: t.Optional[str] = None, 2709 escaped_delimiter: t.Optional[str] = None, 2710 is_byte_string: bool = False, 2711 ) -> str: 2712 if is_byte_string: 2713 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2714 else: 2715 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2716 2717 if supports_escape_sequences: 2718 text = "".join( 2719 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2720 for ch in text 2721 ) 2722 2723 delimiter = delimiter or self.dialect.QUOTE_END 2724 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2725 2726 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2727 2728 def loaddata_sql(self, expression: exp.LoadData) -> str: 2729 local = " LOCAL" if expression.args.get("local") else "" 2730 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2731 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2732 this = f" INTO TABLE {self.sql(expression, 'this')}" 2733 partition = self.sql(expression, "partition") 2734 partition = f" {partition}" if partition else "" 2735 input_format = self.sql(expression, "input_format") 2736 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2737 serde = self.sql(expression, "serde") 2738 serde = f" SERDE {serde}" if serde else "" 2739 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2740 2741 def null_sql(self, *_) -> str: 2742 return "NULL" 2743 2744 def boolean_sql(self, expression: exp.Boolean) -> str: 2745 return "TRUE" if expression.this else "FALSE" 2746 2747 def booland_sql(self, expression: exp.Booland) -> str: 2748 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 2749 2750 def boolor_sql(self, expression: exp.Boolor) -> str: 2751 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 2752 2753 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2754 this = self.sql(expression, "this") 2755 this = f"{this} " if this else this 2756 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2757 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2758 2759 def withfill_sql(self, expression: exp.WithFill) -> str: 2760 from_sql = self.sql(expression, "from_") 2761 from_sql = f" FROM {from_sql}" if from_sql else "" 2762 to_sql = self.sql(expression, "to") 2763 to_sql = f" TO {to_sql}" if to_sql else "" 2764 step_sql = self.sql(expression, "step") 2765 step_sql = f" STEP {step_sql}" if step_sql else "" 2766 interpolated_values = [ 2767 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2768 if isinstance(e, exp.Alias) 2769 else self.sql(e, "this") 2770 for e in expression.args.get("interpolate") or [] 2771 ] 2772 interpolate = ( 2773 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2774 ) 2775 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2776 2777 def cluster_sql(self, expression: exp.Cluster) -> str: 2778 return self.op_expressions("CLUSTER BY", expression) 2779 2780 def distribute_sql(self, expression: exp.Distribute) -> str: 2781 return self.op_expressions("DISTRIBUTE BY", expression) 2782 2783 def sort_sql(self, expression: exp.Sort) -> str: 2784 return self.op_expressions("SORT BY", expression) 2785 2786 def ordered_sql(self, expression: exp.Ordered) -> str: 2787 desc = expression.args.get("desc") 2788 asc = not desc 2789 2790 nulls_first = expression.args.get("nulls_first") 2791 nulls_last = not nulls_first 2792 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2793 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2794 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2795 2796 this = self.sql(expression, "this") 2797 2798 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2799 nulls_sort_change = "" 2800 if nulls_first and ( 2801 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2802 ): 2803 nulls_sort_change = " NULLS FIRST" 2804 elif ( 2805 nulls_last 2806 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2807 and not nulls_are_last 2808 ): 2809 nulls_sort_change = " NULLS LAST" 2810 2811 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2812 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2813 window = expression.find_ancestor(exp.Window, exp.Select) 2814 if isinstance(window, exp.Window) and window.args.get("spec"): 2815 self.unsupported( 2816 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2817 ) 2818 nulls_sort_change = "" 2819 elif self.NULL_ORDERING_SUPPORTED is False and ( 2820 (asc and nulls_sort_change == " NULLS LAST") 2821 or (desc and nulls_sort_change == " NULLS FIRST") 2822 ): 2823 # BigQuery does not allow these ordering/nulls combinations when used under 2824 # an aggregation func or under a window containing one 2825 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2826 2827 if isinstance(ancestor, exp.Window): 2828 ancestor = ancestor.this 2829 if isinstance(ancestor, exp.AggFunc): 2830 self.unsupported( 2831 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2832 ) 2833 nulls_sort_change = "" 2834 elif self.NULL_ORDERING_SUPPORTED is None: 2835 if expression.this.is_int: 2836 self.unsupported( 2837 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2838 ) 2839 elif not isinstance(expression.this, exp.Rand): 2840 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2841 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2842 nulls_sort_change = "" 2843 2844 with_fill = self.sql(expression, "with_fill") 2845 with_fill = f" {with_fill}" if with_fill else "" 2846 2847 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2848 2849 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2850 window_frame = self.sql(expression, "window_frame") 2851 window_frame = f"{window_frame} " if window_frame else "" 2852 2853 this = self.sql(expression, "this") 2854 2855 return f"{window_frame}{this}" 2856 2857 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2858 partition = self.partition_by_sql(expression) 2859 order = self.sql(expression, "order") 2860 measures = self.expressions(expression, key="measures") 2861 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2862 rows = self.sql(expression, "rows") 2863 rows = self.seg(rows) if rows else "" 2864 after = self.sql(expression, "after") 2865 after = self.seg(after) if after else "" 2866 pattern = self.sql(expression, "pattern") 2867 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2868 definition_sqls = [ 2869 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2870 for definition in expression.args.get("define", []) 2871 ] 2872 definitions = self.expressions(sqls=definition_sqls) 2873 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2874 body = "".join( 2875 ( 2876 partition, 2877 order, 2878 measures, 2879 rows, 2880 after, 2881 pattern, 2882 define, 2883 ) 2884 ) 2885 alias = self.sql(expression, "alias") 2886 alias = f" {alias}" if alias else "" 2887 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2888 2889 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2890 limit = expression.args.get("limit") 2891 2892 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2893 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2894 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2895 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2896 2897 return csv( 2898 *sqls, 2899 *[self.sql(join) for join in expression.args.get("joins") or []], 2900 self.sql(expression, "match"), 2901 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2902 self.sql(expression, "prewhere"), 2903 self.sql(expression, "where"), 2904 self.sql(expression, "connect"), 2905 self.sql(expression, "group"), 2906 self.sql(expression, "having"), 2907 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2908 self.sql(expression, "order"), 2909 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2910 *self.after_limit_modifiers(expression), 2911 self.options_modifier(expression), 2912 self.for_modifiers(expression), 2913 sep="", 2914 ) 2915 2916 def options_modifier(self, expression: exp.Expression) -> str: 2917 options = self.expressions(expression, key="options") 2918 return f" {options}" if options else "" 2919 2920 def for_modifiers(self, expression: exp.Expression) -> str: 2921 for_modifiers = self.expressions(expression, key="for_") 2922 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2923 2924 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2925 self.unsupported("Unsupported query option.") 2926 return "" 2927 2928 def offset_limit_modifiers( 2929 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2930 ) -> t.List[str]: 2931 return [ 2932 self.sql(expression, "offset") if fetch else self.sql(limit), 2933 self.sql(limit) if fetch else self.sql(expression, "offset"), 2934 ] 2935 2936 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2937 locks = self.expressions(expression, key="locks", sep=" ") 2938 locks = f" {locks}" if locks else "" 2939 return [locks, self.sql(expression, "sample")] 2940 2941 def select_sql(self, expression: exp.Select) -> str: 2942 into = expression.args.get("into") 2943 if not self.SUPPORTS_SELECT_INTO and into: 2944 into.pop() 2945 2946 hint = self.sql(expression, "hint") 2947 distinct = self.sql(expression, "distinct") 2948 distinct = f" {distinct}" if distinct else "" 2949 kind = self.sql(expression, "kind") 2950 2951 limit = expression.args.get("limit") 2952 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2953 top = self.limit_sql(limit, top=True) 2954 limit.pop() 2955 else: 2956 top = "" 2957 2958 expressions = self.expressions(expression) 2959 2960 if kind: 2961 if kind in self.SELECT_KINDS: 2962 kind = f" AS {kind}" 2963 else: 2964 if kind == "STRUCT": 2965 expressions = self.expressions( 2966 sqls=[ 2967 self.sql( 2968 exp.Struct( 2969 expressions=[ 2970 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2971 if isinstance(e, exp.Alias) 2972 else e 2973 for e in expression.expressions 2974 ] 2975 ) 2976 ) 2977 ] 2978 ) 2979 kind = "" 2980 2981 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2982 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2983 2984 exclude = expression.args.get("exclude") 2985 2986 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 2987 exclude_sql = self.expressions(sqls=exclude, flat=True) 2988 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 2989 2990 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2991 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2992 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2993 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2994 sql = self.query_modifiers( 2995 expression, 2996 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2997 self.sql(expression, "into", comment=False), 2998 self.sql(expression, "from_", comment=False), 2999 ) 3000 3001 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3002 if expression.args.get("with_"): 3003 sql = self.maybe_comment(sql, expression) 3004 expression.pop_comments() 3005 3006 sql = self.prepend_ctes(expression, sql) 3007 3008 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3009 expression.set("exclude", None) 3010 subquery = expression.subquery(copy=False) 3011 star = exp.Star(except_=exclude) 3012 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3013 3014 if not self.SUPPORTS_SELECT_INTO and into: 3015 if into.args.get("temporary"): 3016 table_kind = " TEMPORARY" 3017 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3018 table_kind = " UNLOGGED" 3019 else: 3020 table_kind = "" 3021 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3022 3023 return sql 3024 3025 def schema_sql(self, expression: exp.Schema) -> str: 3026 this = self.sql(expression, "this") 3027 sql = self.schema_columns_sql(expression) 3028 return f"{this} {sql}" if this and sql else this or sql 3029 3030 def schema_columns_sql(self, expression: exp.Schema) -> str: 3031 if expression.expressions: 3032 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3033 return "" 3034 3035 def star_sql(self, expression: exp.Star) -> str: 3036 except_ = self.expressions(expression, key="except_", flat=True) 3037 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3038 replace = self.expressions(expression, key="replace", flat=True) 3039 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3040 rename = self.expressions(expression, key="rename", flat=True) 3041 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3042 return f"*{except_}{replace}{rename}" 3043 3044 def parameter_sql(self, expression: exp.Parameter) -> str: 3045 this = self.sql(expression, "this") 3046 return f"{self.PARAMETER_TOKEN}{this}" 3047 3048 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3049 this = self.sql(expression, "this") 3050 kind = expression.text("kind") 3051 if kind: 3052 kind = f"{kind}." 3053 return f"@@{kind}{this}" 3054 3055 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3056 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3057 3058 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3059 alias = self.sql(expression, "alias") 3060 alias = f"{sep}{alias}" if alias else "" 3061 sample = self.sql(expression, "sample") 3062 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3063 alias = f"{sample}{alias}" 3064 3065 # Set to None so it's not generated again by self.query_modifiers() 3066 expression.set("sample", None) 3067 3068 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3069 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3070 return self.prepend_ctes(expression, sql) 3071 3072 def qualify_sql(self, expression: exp.Qualify) -> str: 3073 this = self.indent(self.sql(expression, "this")) 3074 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3075 3076 def unnest_sql(self, expression: exp.Unnest) -> str: 3077 args = self.expressions(expression, flat=True) 3078 3079 alias = expression.args.get("alias") 3080 offset = expression.args.get("offset") 3081 3082 if self.UNNEST_WITH_ORDINALITY: 3083 if alias and isinstance(offset, exp.Expression): 3084 alias.append("columns", offset) 3085 3086 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3087 columns = alias.columns 3088 alias = self.sql(columns[0]) if columns else "" 3089 else: 3090 alias = self.sql(alias) 3091 3092 alias = f" AS {alias}" if alias else alias 3093 if self.UNNEST_WITH_ORDINALITY: 3094 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3095 else: 3096 if isinstance(offset, exp.Expression): 3097 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3098 elif offset: 3099 suffix = f"{alias} WITH OFFSET" 3100 else: 3101 suffix = alias 3102 3103 return f"UNNEST({args}){suffix}" 3104 3105 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3106 return "" 3107 3108 def where_sql(self, expression: exp.Where) -> str: 3109 this = self.indent(self.sql(expression, "this")) 3110 return f"{self.seg('WHERE')}{self.sep()}{this}" 3111 3112 def window_sql(self, expression: exp.Window) -> str: 3113 this = self.sql(expression, "this") 3114 partition = self.partition_by_sql(expression) 3115 order = expression.args.get("order") 3116 order = self.order_sql(order, flat=True) if order else "" 3117 spec = self.sql(expression, "spec") 3118 alias = self.sql(expression, "alias") 3119 over = self.sql(expression, "over") or "OVER" 3120 3121 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3122 3123 first = expression.args.get("first") 3124 if first is None: 3125 first = "" 3126 else: 3127 first = "FIRST" if first else "LAST" 3128 3129 if not partition and not order and not spec and alias: 3130 return f"{this} {alias}" 3131 3132 args = self.format_args( 3133 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3134 ) 3135 return f"{this} ({args})" 3136 3137 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3138 partition = self.expressions(expression, key="partition_by", flat=True) 3139 return f"PARTITION BY {partition}" if partition else "" 3140 3141 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3142 kind = self.sql(expression, "kind") 3143 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3144 end = ( 3145 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3146 or "CURRENT ROW" 3147 ) 3148 3149 window_spec = f"{kind} BETWEEN {start} AND {end}" 3150 3151 exclude = self.sql(expression, "exclude") 3152 if exclude: 3153 if self.SUPPORTS_WINDOW_EXCLUDE: 3154 window_spec += f" EXCLUDE {exclude}" 3155 else: 3156 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3157 3158 return window_spec 3159 3160 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3161 this = self.sql(expression, "this") 3162 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3163 return f"{this} WITHIN GROUP ({expression_sql})" 3164 3165 def between_sql(self, expression: exp.Between) -> str: 3166 this = self.sql(expression, "this") 3167 low = self.sql(expression, "low") 3168 high = self.sql(expression, "high") 3169 symmetric = expression.args.get("symmetric") 3170 3171 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3172 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3173 3174 flag = ( 3175 " SYMMETRIC" 3176 if symmetric 3177 else " ASYMMETRIC" 3178 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3179 else "" # silently drop ASYMMETRIC – semantics identical 3180 ) 3181 return f"{this} BETWEEN{flag} {low} AND {high}" 3182 3183 def bracket_offset_expressions( 3184 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 3185 ) -> t.List[exp.Expression]: 3186 return apply_index_offset( 3187 expression.this, 3188 expression.expressions, 3189 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3190 dialect=self.dialect, 3191 ) 3192 3193 def bracket_sql(self, expression: exp.Bracket) -> str: 3194 expressions = self.bracket_offset_expressions(expression) 3195 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3196 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3197 3198 def all_sql(self, expression: exp.All) -> str: 3199 this = self.sql(expression, "this") 3200 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3201 this = self.wrap(this) 3202 return f"ALL {this}" 3203 3204 def any_sql(self, expression: exp.Any) -> str: 3205 this = self.sql(expression, "this") 3206 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3207 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3208 this = self.wrap(this) 3209 return f"ANY{this}" 3210 return f"ANY {this}" 3211 3212 def exists_sql(self, expression: exp.Exists) -> str: 3213 return f"EXISTS{self.wrap(expression)}" 3214 3215 def case_sql(self, expression: exp.Case) -> str: 3216 this = self.sql(expression, "this") 3217 statements = [f"CASE {this}" if this else "CASE"] 3218 3219 for e in expression.args["ifs"]: 3220 statements.append(f"WHEN {self.sql(e, 'this')}") 3221 statements.append(f"THEN {self.sql(e, 'true')}") 3222 3223 default = self.sql(expression, "default") 3224 3225 if default: 3226 statements.append(f"ELSE {default}") 3227 3228 statements.append("END") 3229 3230 if self.pretty and self.too_wide(statements): 3231 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3232 3233 return " ".join(statements) 3234 3235 def constraint_sql(self, expression: exp.Constraint) -> str: 3236 this = self.sql(expression, "this") 3237 expressions = self.expressions(expression, flat=True) 3238 return f"CONSTRAINT {this} {expressions}" 3239 3240 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3241 order = expression.args.get("order") 3242 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3243 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3244 3245 def extract_sql(self, expression: exp.Extract) -> str: 3246 from sqlglot.dialects.dialect import map_date_part 3247 3248 this = ( 3249 map_date_part(expression.this, self.dialect) 3250 if self.NORMALIZE_EXTRACT_DATE_PARTS 3251 else expression.this 3252 ) 3253 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3254 expression_sql = self.sql(expression, "expression") 3255 3256 return f"EXTRACT({this_sql} FROM {expression_sql})" 3257 3258 def trim_sql(self, expression: exp.Trim) -> str: 3259 trim_type = self.sql(expression, "position") 3260 3261 if trim_type == "LEADING": 3262 func_name = "LTRIM" 3263 elif trim_type == "TRAILING": 3264 func_name = "RTRIM" 3265 else: 3266 func_name = "TRIM" 3267 3268 return self.func(func_name, expression.this, expression.expression) 3269 3270 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3271 args = expression.expressions 3272 if isinstance(expression, exp.ConcatWs): 3273 args = args[1:] # Skip the delimiter 3274 3275 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3276 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3277 3278 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3279 3280 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3281 if not e.type: 3282 from sqlglot.optimizer.annotate_types import annotate_types 3283 3284 e = annotate_types(e, dialect=self.dialect) 3285 3286 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3287 return e 3288 3289 return exp.func("coalesce", e, exp.Literal.string("")) 3290 3291 args = [_wrap_with_coalesce(e) for e in args] 3292 3293 return args 3294 3295 def concat_sql(self, expression: exp.Concat) -> str: 3296 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3297 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3298 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3299 # instead of coalescing them to empty string. 3300 from sqlglot.dialects.dialect import concat_to_dpipe_sql 3301 3302 return concat_to_dpipe_sql(self, expression) 3303 3304 expressions = self.convert_concat_args(expression) 3305 3306 # Some dialects don't allow a single-argument CONCAT call 3307 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3308 return self.sql(expressions[0]) 3309 3310 return self.func("CONCAT", *expressions) 3311 3312 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3313 return self.func( 3314 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3315 ) 3316 3317 def check_sql(self, expression: exp.Check) -> str: 3318 this = self.sql(expression, key="this") 3319 return f"CHECK ({this})" 3320 3321 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3322 expressions = self.expressions(expression, flat=True) 3323 expressions = f" ({expressions})" if expressions else "" 3324 reference = self.sql(expression, "reference") 3325 reference = f" {reference}" if reference else "" 3326 delete = self.sql(expression, "delete") 3327 delete = f" ON DELETE {delete}" if delete else "" 3328 update = self.sql(expression, "update") 3329 update = f" ON UPDATE {update}" if update else "" 3330 options = self.expressions(expression, key="options", flat=True, sep=" ") 3331 options = f" {options}" if options else "" 3332 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3333 3334 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3335 this = self.sql(expression, "this") 3336 this = f" {this}" if this else "" 3337 expressions = self.expressions(expression, flat=True) 3338 include = self.sql(expression, "include") 3339 options = self.expressions(expression, key="options", flat=True, sep=" ") 3340 options = f" {options}" if options else "" 3341 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3342 3343 def if_sql(self, expression: exp.If) -> str: 3344 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3345 3346 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3347 if self.MATCH_AGAINST_TABLE_PREFIX: 3348 expressions = [] 3349 for expr in expression.expressions: 3350 if isinstance(expr, exp.Table): 3351 expressions.append(f"TABLE {self.sql(expr)}") 3352 else: 3353 expressions.append(expr) 3354 else: 3355 expressions = expression.expressions 3356 3357 modifier = expression.args.get("modifier") 3358 modifier = f" {modifier}" if modifier else "" 3359 return ( 3360 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3361 ) 3362 3363 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3364 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3365 3366 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3367 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3368 3369 if expression.args.get("escape"): 3370 path = self.escape_str(path) 3371 3372 if self.QUOTE_JSON_PATH: 3373 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3374 3375 return path 3376 3377 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3378 if isinstance(expression, exp.JSONPathPart): 3379 transform = self.TRANSFORMS.get(expression.__class__) 3380 if not callable(transform): 3381 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3382 return "" 3383 3384 return transform(self, expression) 3385 3386 if isinstance(expression, int): 3387 return str(expression) 3388 3389 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3390 escaped = expression.replace("'", "\\'") 3391 escaped = f"\\'{expression}\\'" 3392 else: 3393 escaped = expression.replace('"', '\\"') 3394 escaped = f'"{escaped}"' 3395 3396 return escaped 3397 3398 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3399 return f"{self.sql(expression, 'this')} FORMAT JSON" 3400 3401 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3402 # Output the Teradata column FORMAT override. 3403 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3404 this = self.sql(expression, "this") 3405 fmt = self.sql(expression, "format") 3406 return f"{this} (FORMAT {fmt})" 3407 3408 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3409 null_handling = expression.args.get("null_handling") 3410 null_handling = f" {null_handling}" if null_handling else "" 3411 3412 unique_keys = expression.args.get("unique_keys") 3413 if unique_keys is not None: 3414 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3415 else: 3416 unique_keys = "" 3417 3418 return_type = self.sql(expression, "return_type") 3419 return_type = f" RETURNING {return_type}" if return_type else "" 3420 encoding = self.sql(expression, "encoding") 3421 encoding = f" ENCODING {encoding}" if encoding else "" 3422 3423 return self.func( 3424 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3425 *expression.expressions, 3426 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3427 ) 3428 3429 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3430 return self.jsonobject_sql(expression) 3431 3432 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3433 null_handling = expression.args.get("null_handling") 3434 null_handling = f" {null_handling}" if null_handling else "" 3435 return_type = self.sql(expression, "return_type") 3436 return_type = f" RETURNING {return_type}" if return_type else "" 3437 strict = " STRICT" if expression.args.get("strict") else "" 3438 return self.func( 3439 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3440 ) 3441 3442 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3443 this = self.sql(expression, "this") 3444 order = self.sql(expression, "order") 3445 null_handling = expression.args.get("null_handling") 3446 null_handling = f" {null_handling}" if null_handling else "" 3447 return_type = self.sql(expression, "return_type") 3448 return_type = f" RETURNING {return_type}" if return_type else "" 3449 strict = " STRICT" if expression.args.get("strict") else "" 3450 return self.func( 3451 "JSON_ARRAYAGG", 3452 this, 3453 suffix=f"{order}{null_handling}{return_type}{strict})", 3454 ) 3455 3456 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3457 path = self.sql(expression, "path") 3458 path = f" PATH {path}" if path else "" 3459 nested_schema = self.sql(expression, "nested_schema") 3460 3461 if nested_schema: 3462 return f"NESTED{path} {nested_schema}" 3463 3464 this = self.sql(expression, "this") 3465 kind = self.sql(expression, "kind") 3466 kind = f" {kind}" if kind else "" 3467 3468 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3469 return f"{this}{kind}{path}{ordinality}" 3470 3471 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3472 return self.func("COLUMNS", *expression.expressions) 3473 3474 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3475 this = self.sql(expression, "this") 3476 path = self.sql(expression, "path") 3477 path = f", {path}" if path else "" 3478 error_handling = expression.args.get("error_handling") 3479 error_handling = f" {error_handling}" if error_handling else "" 3480 empty_handling = expression.args.get("empty_handling") 3481 empty_handling = f" {empty_handling}" if empty_handling else "" 3482 schema = self.sql(expression, "schema") 3483 return self.func( 3484 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3485 ) 3486 3487 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3488 this = self.sql(expression, "this") 3489 kind = self.sql(expression, "kind") 3490 path = self.sql(expression, "path") 3491 path = f" {path}" if path else "" 3492 as_json = " AS JSON" if expression.args.get("as_json") else "" 3493 return f"{this} {kind}{path}{as_json}" 3494 3495 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3496 this = self.sql(expression, "this") 3497 path = self.sql(expression, "path") 3498 path = f", {path}" if path else "" 3499 expressions = self.expressions(expression) 3500 with_ = ( 3501 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3502 if expressions 3503 else "" 3504 ) 3505 return f"OPENJSON({this}{path}){with_}" 3506 3507 def in_sql(self, expression: exp.In) -> str: 3508 query = expression.args.get("query") 3509 unnest = expression.args.get("unnest") 3510 field = expression.args.get("field") 3511 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3512 3513 if query: 3514 in_sql = self.sql(query) 3515 elif unnest: 3516 in_sql = self.in_unnest_op(unnest) 3517 elif field: 3518 in_sql = self.sql(field) 3519 else: 3520 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3521 3522 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3523 3524 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3525 return f"(SELECT {self.sql(unnest)})" 3526 3527 def interval_sql(self, expression: exp.Interval) -> str: 3528 unit_expression = expression.args.get("unit") 3529 unit = self.sql(unit_expression) if unit_expression else "" 3530 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3531 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3532 unit = f" {unit}" if unit else "" 3533 3534 if self.SINGLE_STRING_INTERVAL: 3535 this = expression.this.name if expression.this else "" 3536 if this: 3537 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3538 return f"INTERVAL '{this}'{unit}" 3539 return f"INTERVAL '{this}{unit}'" 3540 return f"INTERVAL{unit}" 3541 3542 this = self.sql(expression, "this") 3543 if this: 3544 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3545 this = f" {this}" if unwrapped else f" ({this})" 3546 3547 return f"INTERVAL{this}{unit}" 3548 3549 def return_sql(self, expression: exp.Return) -> str: 3550 return f"RETURN {self.sql(expression, 'this')}" 3551 3552 def reference_sql(self, expression: exp.Reference) -> str: 3553 this = self.sql(expression, "this") 3554 expressions = self.expressions(expression, flat=True) 3555 expressions = f"({expressions})" if expressions else "" 3556 options = self.expressions(expression, key="options", flat=True, sep=" ") 3557 options = f" {options}" if options else "" 3558 return f"REFERENCES {this}{expressions}{options}" 3559 3560 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3561 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3562 parent = expression.parent 3563 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3564 3565 return self.func( 3566 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3567 ) 3568 3569 def paren_sql(self, expression: exp.Paren) -> str: 3570 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3571 return f"({sql}{self.seg(')', sep='')}" 3572 3573 def neg_sql(self, expression: exp.Neg) -> str: 3574 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3575 this_sql = self.sql(expression, "this") 3576 sep = " " if this_sql[0] == "-" else "" 3577 return f"-{sep}{this_sql}" 3578 3579 def not_sql(self, expression: exp.Not) -> str: 3580 return f"NOT {self.sql(expression, 'this')}" 3581 3582 def alias_sql(self, expression: exp.Alias) -> str: 3583 alias = self.sql(expression, "alias") 3584 alias = f" AS {alias}" if alias else "" 3585 return f"{self.sql(expression, 'this')}{alias}" 3586 3587 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3588 alias = expression.args["alias"] 3589 3590 parent = expression.parent 3591 pivot = parent and parent.parent 3592 3593 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3594 identifier_alias = isinstance(alias, exp.Identifier) 3595 literal_alias = isinstance(alias, exp.Literal) 3596 3597 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3598 alias.replace(exp.Literal.string(alias.output_name)) 3599 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3600 alias.replace(exp.to_identifier(alias.output_name)) 3601 3602 return self.alias_sql(expression) 3603 3604 def aliases_sql(self, expression: exp.Aliases) -> str: 3605 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3606 3607 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3608 this = self.sql(expression, "this") 3609 index = self.sql(expression, "expression") 3610 return f"{this} AT {index}" 3611 3612 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3613 this = self.sql(expression, "this") 3614 zone = self.sql(expression, "zone") 3615 return f"{this} AT TIME ZONE {zone}" 3616 3617 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3618 this = self.sql(expression, "this") 3619 zone = self.sql(expression, "zone") 3620 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3621 3622 def add_sql(self, expression: exp.Add) -> str: 3623 return self.binary(expression, "+") 3624 3625 def and_sql( 3626 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3627 ) -> str: 3628 return self.connector_sql(expression, "AND", stack) 3629 3630 def or_sql( 3631 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3632 ) -> str: 3633 return self.connector_sql(expression, "OR", stack) 3634 3635 def xor_sql( 3636 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3637 ) -> str: 3638 return self.connector_sql(expression, "XOR", stack) 3639 3640 def connector_sql( 3641 self, 3642 expression: exp.Connector, 3643 op: str, 3644 stack: t.Optional[t.List[str | exp.Expression]] = None, 3645 ) -> str: 3646 if stack is not None: 3647 if expression.expressions: 3648 stack.append(self.expressions(expression, sep=f" {op} ")) 3649 else: 3650 stack.append(expression.right) 3651 if expression.comments and self.comments: 3652 for comment in expression.comments: 3653 if comment: 3654 op += f" /*{self.sanitize_comment(comment)}*/" 3655 stack.extend((op, expression.left)) 3656 return op 3657 3658 stack = [expression] 3659 sqls: t.List[str] = [] 3660 ops = set() 3661 3662 while stack: 3663 node = stack.pop() 3664 if isinstance(node, exp.Connector): 3665 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3666 else: 3667 sql = self.sql(node) 3668 if sqls and sqls[-1] in ops: 3669 sqls[-1] += f" {sql}" 3670 else: 3671 sqls.append(sql) 3672 3673 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3674 return sep.join(sqls) 3675 3676 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3677 return self.binary(expression, "&") 3678 3679 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3680 return self.binary(expression, "<<") 3681 3682 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3683 return f"~{self.sql(expression, 'this')}" 3684 3685 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3686 return self.binary(expression, "|") 3687 3688 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3689 return self.binary(expression, ">>") 3690 3691 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3692 return self.binary(expression, "^") 3693 3694 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3695 format_sql = self.sql(expression, "format") 3696 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3697 to_sql = self.sql(expression, "to") 3698 to_sql = f" {to_sql}" if to_sql else "" 3699 action = self.sql(expression, "action") 3700 action = f" {action}" if action else "" 3701 default = self.sql(expression, "default") 3702 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3703 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3704 3705 # Base implementation that excludes safe, zone, and target_type metadata args 3706 def strtotime_sql(self, expression: exp.StrToTime) -> str: 3707 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 3708 3709 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3710 zone = self.sql(expression, "this") 3711 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3712 3713 def collate_sql(self, expression: exp.Collate) -> str: 3714 if self.COLLATE_IS_FUNC: 3715 return self.function_fallback_sql(expression) 3716 return self.binary(expression, "COLLATE") 3717 3718 def command_sql(self, expression: exp.Command) -> str: 3719 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3720 3721 def comment_sql(self, expression: exp.Comment) -> str: 3722 this = self.sql(expression, "this") 3723 kind = expression.args["kind"] 3724 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3725 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3726 expression_sql = self.sql(expression, "expression") 3727 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3728 3729 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3730 this = self.sql(expression, "this") 3731 delete = " DELETE" if expression.args.get("delete") else "" 3732 recompress = self.sql(expression, "recompress") 3733 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3734 to_disk = self.sql(expression, "to_disk") 3735 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3736 to_volume = self.sql(expression, "to_volume") 3737 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3738 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3739 3740 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3741 where = self.sql(expression, "where") 3742 group = self.sql(expression, "group") 3743 aggregates = self.expressions(expression, key="aggregates") 3744 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3745 3746 if not (where or group or aggregates) and len(expression.expressions) == 1: 3747 return f"TTL {self.expressions(expression, flat=True)}" 3748 3749 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3750 3751 def transaction_sql(self, expression: exp.Transaction) -> str: 3752 modes = self.expressions(expression, key="modes") 3753 modes = f" {modes}" if modes else "" 3754 return f"BEGIN{modes}" 3755 3756 def commit_sql(self, expression: exp.Commit) -> str: 3757 chain = expression.args.get("chain") 3758 if chain is not None: 3759 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3760 3761 return f"COMMIT{chain or ''}" 3762 3763 def rollback_sql(self, expression: exp.Rollback) -> str: 3764 savepoint = expression.args.get("savepoint") 3765 savepoint = f" TO {savepoint}" if savepoint else "" 3766 return f"ROLLBACK{savepoint}" 3767 3768 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3769 this = self.sql(expression, "this") 3770 3771 dtype = self.sql(expression, "dtype") 3772 if dtype: 3773 collate = self.sql(expression, "collate") 3774 collate = f" COLLATE {collate}" if collate else "" 3775 using = self.sql(expression, "using") 3776 using = f" USING {using}" if using else "" 3777 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3778 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3779 3780 default = self.sql(expression, "default") 3781 if default: 3782 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3783 3784 comment = self.sql(expression, "comment") 3785 if comment: 3786 return f"ALTER COLUMN {this} COMMENT {comment}" 3787 3788 visible = expression.args.get("visible") 3789 if visible: 3790 return f"ALTER COLUMN {this} SET {visible}" 3791 3792 allow_null = expression.args.get("allow_null") 3793 drop = expression.args.get("drop") 3794 3795 if not drop and not allow_null: 3796 self.unsupported("Unsupported ALTER COLUMN syntax") 3797 3798 if allow_null is not None: 3799 keyword = "DROP" if drop else "SET" 3800 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3801 3802 return f"ALTER COLUMN {this} DROP DEFAULT" 3803 3804 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3805 this = self.sql(expression, "this") 3806 3807 visible = expression.args.get("visible") 3808 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3809 3810 return f"ALTER INDEX {this} {visible_sql}" 3811 3812 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3813 this = self.sql(expression, "this") 3814 if not isinstance(expression.this, exp.Var): 3815 this = f"KEY DISTKEY {this}" 3816 return f"ALTER DISTSTYLE {this}" 3817 3818 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3819 compound = " COMPOUND" if expression.args.get("compound") else "" 3820 this = self.sql(expression, "this") 3821 expressions = self.expressions(expression, flat=True) 3822 expressions = f"({expressions})" if expressions else "" 3823 return f"ALTER{compound} SORTKEY {this or expressions}" 3824 3825 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3826 if not self.RENAME_TABLE_WITH_DB: 3827 # Remove db from tables 3828 expression = expression.transform( 3829 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3830 ).assert_is(exp.AlterRename) 3831 this = self.sql(expression, "this") 3832 to_kw = " TO" if include_to else "" 3833 return f"RENAME{to_kw} {this}" 3834 3835 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3836 exists = " IF EXISTS" if expression.args.get("exists") else "" 3837 old_column = self.sql(expression, "this") 3838 new_column = self.sql(expression, "to") 3839 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3840 3841 def alterset_sql(self, expression: exp.AlterSet) -> str: 3842 exprs = self.expressions(expression, flat=True) 3843 if self.ALTER_SET_WRAPPED: 3844 exprs = f"({exprs})" 3845 3846 return f"SET {exprs}" 3847 3848 def alter_sql(self, expression: exp.Alter) -> str: 3849 actions = expression.args["actions"] 3850 3851 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3852 actions[0], exp.ColumnDef 3853 ): 3854 actions_sql = self.expressions(expression, key="actions", flat=True) 3855 actions_sql = f"ADD {actions_sql}" 3856 else: 3857 actions_list = [] 3858 for action in actions: 3859 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3860 action_sql = self.add_column_sql(action) 3861 else: 3862 action_sql = self.sql(action) 3863 if isinstance(action, exp.Query): 3864 action_sql = f"AS {action_sql}" 3865 3866 actions_list.append(action_sql) 3867 3868 actions_sql = self.format_args(*actions_list).lstrip("\n") 3869 3870 exists = " IF EXISTS" if expression.args.get("exists") else "" 3871 on_cluster = self.sql(expression, "cluster") 3872 on_cluster = f" {on_cluster}" if on_cluster else "" 3873 only = " ONLY" if expression.args.get("only") else "" 3874 options = self.expressions(expression, key="options") 3875 options = f", {options}" if options else "" 3876 kind = self.sql(expression, "kind") 3877 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3878 check = " WITH CHECK" if expression.args.get("check") else "" 3879 cascade = ( 3880 " CASCADE" 3881 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 3882 else "" 3883 ) 3884 this = self.sql(expression, "this") 3885 this = f" {this}" if this else "" 3886 3887 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 3888 3889 def altersession_sql(self, expression: exp.AlterSession) -> str: 3890 items_sql = self.expressions(expression, flat=True) 3891 keyword = "UNSET" if expression.args.get("unset") else "SET" 3892 return f"{keyword} {items_sql}" 3893 3894 def add_column_sql(self, expression: exp.Expression) -> str: 3895 sql = self.sql(expression) 3896 if isinstance(expression, exp.Schema): 3897 column_text = " COLUMNS" 3898 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3899 column_text = " COLUMN" 3900 else: 3901 column_text = "" 3902 3903 return f"ADD{column_text} {sql}" 3904 3905 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3906 expressions = self.expressions(expression) 3907 exists = " IF EXISTS " if expression.args.get("exists") else " " 3908 return f"DROP{exists}{expressions}" 3909 3910 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3911 return f"ADD {self.expressions(expression, indent=False)}" 3912 3913 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3914 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3915 location = self.sql(expression, "location") 3916 location = f" {location}" if location else "" 3917 return f"ADD {exists}{self.sql(expression.this)}{location}" 3918 3919 def distinct_sql(self, expression: exp.Distinct) -> str: 3920 this = self.expressions(expression, flat=True) 3921 3922 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3923 case = exp.case() 3924 for arg in expression.expressions: 3925 case = case.when(arg.is_(exp.null()), exp.null()) 3926 this = self.sql(case.else_(f"({this})")) 3927 3928 this = f" {this}" if this else "" 3929 3930 on = self.sql(expression, "on") 3931 on = f" ON {on}" if on else "" 3932 return f"DISTINCT{this}{on}" 3933 3934 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3935 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3936 3937 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3938 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3939 3940 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3941 this_sql = self.sql(expression, "this") 3942 expression_sql = self.sql(expression, "expression") 3943 kind = "MAX" if expression.args.get("max") else "MIN" 3944 return f"{this_sql} HAVING {kind} {expression_sql}" 3945 3946 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3947 return self.sql( 3948 exp.Cast( 3949 this=exp.Div(this=expression.this, expression=expression.expression), 3950 to=exp.DataType(this=exp.DataType.Type.INT), 3951 ) 3952 ) 3953 3954 def dpipe_sql(self, expression: exp.DPipe) -> str: 3955 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3956 return self.func( 3957 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3958 ) 3959 return self.binary(expression, "||") 3960 3961 def div_sql(self, expression: exp.Div) -> str: 3962 l, r = expression.left, expression.right 3963 3964 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3965 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3966 3967 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3968 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3969 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3970 3971 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3972 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3973 return self.sql( 3974 exp.cast( 3975 l / r, 3976 to=exp.DataType.Type.BIGINT, 3977 ) 3978 ) 3979 3980 return self.binary(expression, "/") 3981 3982 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3983 n = exp._wrap(expression.this, exp.Binary) 3984 d = exp._wrap(expression.expression, exp.Binary) 3985 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3986 3987 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3988 return self.binary(expression, "OVERLAPS") 3989 3990 def distance_sql(self, expression: exp.Distance) -> str: 3991 return self.binary(expression, "<->") 3992 3993 def dot_sql(self, expression: exp.Dot) -> str: 3994 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3995 3996 def eq_sql(self, expression: exp.EQ) -> str: 3997 return self.binary(expression, "=") 3998 3999 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4000 return self.binary(expression, ":=") 4001 4002 def escape_sql(self, expression: exp.Escape) -> str: 4003 return self.binary(expression, "ESCAPE") 4004 4005 def glob_sql(self, expression: exp.Glob) -> str: 4006 return self.binary(expression, "GLOB") 4007 4008 def gt_sql(self, expression: exp.GT) -> str: 4009 return self.binary(expression, ">") 4010 4011 def gte_sql(self, expression: exp.GTE) -> str: 4012 return self.binary(expression, ">=") 4013 4014 def is_sql(self, expression: exp.Is) -> str: 4015 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4016 return self.sql( 4017 expression.this if expression.expression.this else exp.not_(expression.this) 4018 ) 4019 return self.binary(expression, "IS") 4020 4021 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 4022 this = expression.this 4023 rhs = expression.expression 4024 4025 if isinstance(expression, exp.Like): 4026 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 4027 op = "LIKE" 4028 else: 4029 exp_class = exp.ILike 4030 op = "ILIKE" 4031 4032 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4033 exprs = rhs.this.unnest() 4034 4035 if isinstance(exprs, exp.Tuple): 4036 exprs = exprs.expressions 4037 4038 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4039 4040 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 4041 for expr in exprs[1:]: 4042 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 4043 4044 parent = expression.parent 4045 if not isinstance(parent, type(like_expr)) and isinstance(parent, exp.Condition): 4046 like_expr = exp.paren(like_expr, copy=False) 4047 4048 return self.sql(like_expr) 4049 4050 return self.binary(expression, op) 4051 4052 def like_sql(self, expression: exp.Like) -> str: 4053 return self._like_sql(expression) 4054 4055 def ilike_sql(self, expression: exp.ILike) -> str: 4056 return self._like_sql(expression) 4057 4058 def match_sql(self, expression: exp.Match) -> str: 4059 return self.binary(expression, "MATCH") 4060 4061 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4062 return self.binary(expression, "SIMILAR TO") 4063 4064 def lt_sql(self, expression: exp.LT) -> str: 4065 return self.binary(expression, "<") 4066 4067 def lte_sql(self, expression: exp.LTE) -> str: 4068 return self.binary(expression, "<=") 4069 4070 def mod_sql(self, expression: exp.Mod) -> str: 4071 return self.binary(expression, "%") 4072 4073 def mul_sql(self, expression: exp.Mul) -> str: 4074 return self.binary(expression, "*") 4075 4076 def neq_sql(self, expression: exp.NEQ) -> str: 4077 return self.binary(expression, "<>") 4078 4079 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4080 return self.binary(expression, "IS NOT DISTINCT FROM") 4081 4082 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4083 return self.binary(expression, "IS DISTINCT FROM") 4084 4085 def sub_sql(self, expression: exp.Sub) -> str: 4086 return self.binary(expression, "-") 4087 4088 def trycast_sql(self, expression: exp.TryCast) -> str: 4089 return self.cast_sql(expression, safe_prefix="TRY_") 4090 4091 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4092 return self.cast_sql(expression) 4093 4094 def try_sql(self, expression: exp.Try) -> str: 4095 if not self.TRY_SUPPORTED: 4096 self.unsupported("Unsupported TRY function") 4097 return self.sql(expression, "this") 4098 4099 return self.func("TRY", expression.this) 4100 4101 def log_sql(self, expression: exp.Log) -> str: 4102 this = expression.this 4103 expr = expression.expression 4104 4105 if self.dialect.LOG_BASE_FIRST is False: 4106 this, expr = expr, this 4107 elif self.dialect.LOG_BASE_FIRST is None and expr: 4108 if this.name in ("2", "10"): 4109 return self.func(f"LOG{this.name}", expr) 4110 4111 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4112 4113 return self.func("LOG", this, expr) 4114 4115 def use_sql(self, expression: exp.Use) -> str: 4116 kind = self.sql(expression, "kind") 4117 kind = f" {kind}" if kind else "" 4118 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4119 this = f" {this}" if this else "" 4120 return f"USE{kind}{this}" 4121 4122 def binary(self, expression: exp.Binary, op: str) -> str: 4123 sqls: t.List[str] = [] 4124 stack: t.List[t.Union[str, exp.Expression]] = [expression] 4125 binary_type = type(expression) 4126 4127 while stack: 4128 node = stack.pop() 4129 4130 if type(node) is binary_type: 4131 op_func = node.args.get("operator") 4132 if op_func: 4133 op = f"OPERATOR({self.sql(op_func)})" 4134 4135 stack.append(node.right) 4136 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4137 stack.append(node.left) 4138 else: 4139 sqls.append(self.sql(node)) 4140 4141 return "".join(sqls) 4142 4143 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4144 to_clause = self.sql(expression, "to") 4145 if to_clause: 4146 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4147 4148 return self.function_fallback_sql(expression) 4149 4150 def function_fallback_sql(self, expression: exp.Func) -> str: 4151 args = [] 4152 4153 for key in expression.arg_types: 4154 arg_value = expression.args.get(key) 4155 4156 if isinstance(arg_value, list): 4157 for value in arg_value: 4158 args.append(value) 4159 elif arg_value is not None: 4160 args.append(arg_value) 4161 4162 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4163 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4164 else: 4165 name = expression.sql_name() 4166 4167 return self.func(name, *args) 4168 4169 def func( 4170 self, 4171 name: str, 4172 *args: t.Optional[exp.Expression | str], 4173 prefix: str = "(", 4174 suffix: str = ")", 4175 normalize: bool = True, 4176 ) -> str: 4177 name = self.normalize_func(name) if normalize else name 4178 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4179 4180 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 4181 arg_sqls = tuple( 4182 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4183 ) 4184 if self.pretty and self.too_wide(arg_sqls): 4185 return self.indent( 4186 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4187 ) 4188 return sep.join(arg_sqls) 4189 4190 def too_wide(self, args: t.Iterable) -> bool: 4191 return sum(len(arg) for arg in args) > self.max_text_width 4192 4193 def format_time( 4194 self, 4195 expression: exp.Expression, 4196 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 4197 inverse_time_trie: t.Optional[t.Dict] = None, 4198 ) -> t.Optional[str]: 4199 return format_time( 4200 self.sql(expression, "format"), 4201 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4202 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4203 ) 4204 4205 def expressions( 4206 self, 4207 expression: t.Optional[exp.Expression] = None, 4208 key: t.Optional[str] = None, 4209 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 4210 flat: bool = False, 4211 indent: bool = True, 4212 skip_first: bool = False, 4213 skip_last: bool = False, 4214 sep: str = ", ", 4215 prefix: str = "", 4216 dynamic: bool = False, 4217 new_line: bool = False, 4218 ) -> str: 4219 expressions = expression.args.get(key or "expressions") if expression else sqls 4220 4221 if not expressions: 4222 return "" 4223 4224 if flat: 4225 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4226 4227 num_sqls = len(expressions) 4228 result_sqls = [] 4229 4230 for i, e in enumerate(expressions): 4231 sql = self.sql(e, comment=False) 4232 if not sql: 4233 continue 4234 4235 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 4236 4237 if self.pretty: 4238 if self.leading_comma: 4239 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4240 else: 4241 result_sqls.append( 4242 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4243 ) 4244 else: 4245 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4246 4247 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4248 if new_line: 4249 result_sqls.insert(0, "") 4250 result_sqls.append("") 4251 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4252 else: 4253 result_sql = "".join(result_sqls) 4254 4255 return ( 4256 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4257 if indent 4258 else result_sql 4259 ) 4260 4261 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 4262 flat = flat or isinstance(expression.parent, exp.Properties) 4263 expressions_sql = self.expressions(expression, flat=flat) 4264 if flat: 4265 return f"{op} {expressions_sql}" 4266 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4267 4268 def naked_property(self, expression: exp.Property) -> str: 4269 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4270 if not property_name: 4271 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4272 return f"{property_name} {self.sql(expression, 'this')}" 4273 4274 def tag_sql(self, expression: exp.Tag) -> str: 4275 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4276 4277 def token_sql(self, token_type: TokenType) -> str: 4278 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4279 4280 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4281 this = self.sql(expression, "this") 4282 expressions = self.no_identify(self.expressions, expression) 4283 expressions = ( 4284 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4285 ) 4286 return f"{this}{expressions}" if expressions.strip() != "" else this 4287 4288 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4289 this = self.sql(expression, "this") 4290 expressions = self.expressions(expression, flat=True) 4291 return f"{this}({expressions})" 4292 4293 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4294 return self.binary(expression, "=>") 4295 4296 def when_sql(self, expression: exp.When) -> str: 4297 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4298 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4299 condition = self.sql(expression, "condition") 4300 condition = f" AND {condition}" if condition else "" 4301 4302 then_expression = expression.args.get("then") 4303 if isinstance(then_expression, exp.Insert): 4304 this = self.sql(then_expression, "this") 4305 this = f"INSERT {this}" if this else "INSERT" 4306 then = self.sql(then_expression, "expression") 4307 then = f"{this} VALUES {then}" if then else this 4308 elif isinstance(then_expression, exp.Update): 4309 if isinstance(then_expression.args.get("expressions"), exp.Star): 4310 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4311 else: 4312 expressions_sql = self.expressions(then_expression) 4313 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4314 4315 else: 4316 then = self.sql(then_expression) 4317 return f"WHEN {matched}{source}{condition} THEN {then}" 4318 4319 def whens_sql(self, expression: exp.Whens) -> str: 4320 return self.expressions(expression, sep=" ", indent=False) 4321 4322 def merge_sql(self, expression: exp.Merge) -> str: 4323 table = expression.this 4324 table_alias = "" 4325 4326 hints = table.args.get("hints") 4327 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4328 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4329 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4330 4331 this = self.sql(table) 4332 using = f"USING {self.sql(expression, 'using')}" 4333 whens = self.sql(expression, "whens") 4334 4335 on = self.sql(expression, "on") 4336 on = f"ON {on}" if on else "" 4337 4338 if not on: 4339 on = self.expressions(expression, key="using_cond") 4340 on = f"USING ({on})" if on else "" 4341 4342 returning = self.sql(expression, "returning") 4343 if returning: 4344 whens = f"{whens}{returning}" 4345 4346 sep = self.sep() 4347 4348 return self.prepend_ctes( 4349 expression, 4350 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4351 ) 4352 4353 @unsupported_args("format") 4354 def tochar_sql(self, expression: exp.ToChar) -> str: 4355 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4356 4357 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4358 if not self.SUPPORTS_TO_NUMBER: 4359 self.unsupported("Unsupported TO_NUMBER function") 4360 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4361 4362 fmt = expression.args.get("format") 4363 if not fmt: 4364 self.unsupported("Conversion format is required for TO_NUMBER") 4365 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4366 4367 return self.func("TO_NUMBER", expression.this, fmt) 4368 4369 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4370 this = self.sql(expression, "this") 4371 kind = self.sql(expression, "kind") 4372 settings_sql = self.expressions(expression, key="settings", sep=" ") 4373 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4374 return f"{this}({kind}{args})" 4375 4376 def dictrange_sql(self, expression: exp.DictRange) -> str: 4377 this = self.sql(expression, "this") 4378 max = self.sql(expression, "max") 4379 min = self.sql(expression, "min") 4380 return f"{this}(MIN {min} MAX {max})" 4381 4382 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4383 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4384 4385 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4386 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4387 4388 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4389 def uniquekeyproperty_sql( 4390 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4391 ) -> str: 4392 return f"{prefix} ({self.expressions(expression, flat=True)})" 4393 4394 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4395 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4396 expressions = self.expressions(expression, flat=True) 4397 expressions = f" {self.wrap(expressions)}" if expressions else "" 4398 buckets = self.sql(expression, "buckets") 4399 kind = self.sql(expression, "kind") 4400 buckets = f" BUCKETS {buckets}" if buckets else "" 4401 order = self.sql(expression, "order") 4402 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4403 4404 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4405 return "" 4406 4407 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4408 expressions = self.expressions(expression, key="expressions", flat=True) 4409 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4410 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4411 buckets = self.sql(expression, "buckets") 4412 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4413 4414 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4415 this = self.sql(expression, "this") 4416 having = self.sql(expression, "having") 4417 4418 if having: 4419 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4420 4421 return self.func("ANY_VALUE", this) 4422 4423 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4424 transform = self.func("TRANSFORM", *expression.expressions) 4425 row_format_before = self.sql(expression, "row_format_before") 4426 row_format_before = f" {row_format_before}" if row_format_before else "" 4427 record_writer = self.sql(expression, "record_writer") 4428 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4429 using = f" USING {self.sql(expression, 'command_script')}" 4430 schema = self.sql(expression, "schema") 4431 schema = f" AS {schema}" if schema else "" 4432 row_format_after = self.sql(expression, "row_format_after") 4433 row_format_after = f" {row_format_after}" if row_format_after else "" 4434 record_reader = self.sql(expression, "record_reader") 4435 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4436 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4437 4438 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4439 key_block_size = self.sql(expression, "key_block_size") 4440 if key_block_size: 4441 return f"KEY_BLOCK_SIZE = {key_block_size}" 4442 4443 using = self.sql(expression, "using") 4444 if using: 4445 return f"USING {using}" 4446 4447 parser = self.sql(expression, "parser") 4448 if parser: 4449 return f"WITH PARSER {parser}" 4450 4451 comment = self.sql(expression, "comment") 4452 if comment: 4453 return f"COMMENT {comment}" 4454 4455 visible = expression.args.get("visible") 4456 if visible is not None: 4457 return "VISIBLE" if visible else "INVISIBLE" 4458 4459 engine_attr = self.sql(expression, "engine_attr") 4460 if engine_attr: 4461 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4462 4463 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4464 if secondary_engine_attr: 4465 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4466 4467 self.unsupported("Unsupported index constraint option.") 4468 return "" 4469 4470 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4471 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4472 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4473 4474 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4475 kind = self.sql(expression, "kind") 4476 kind = f"{kind} INDEX" if kind else "INDEX" 4477 this = self.sql(expression, "this") 4478 this = f" {this}" if this else "" 4479 index_type = self.sql(expression, "index_type") 4480 index_type = f" USING {index_type}" if index_type else "" 4481 expressions = self.expressions(expression, flat=True) 4482 expressions = f" ({expressions})" if expressions else "" 4483 options = self.expressions(expression, key="options", sep=" ") 4484 options = f" {options}" if options else "" 4485 return f"{kind}{this}{index_type}{expressions}{options}" 4486 4487 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4488 if self.NVL2_SUPPORTED: 4489 return self.function_fallback_sql(expression) 4490 4491 case = exp.Case().when( 4492 expression.this.is_(exp.null()).not_(copy=False), 4493 expression.args["true"], 4494 copy=False, 4495 ) 4496 else_cond = expression.args.get("false") 4497 if else_cond: 4498 case.else_(else_cond, copy=False) 4499 4500 return self.sql(case) 4501 4502 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4503 this = self.sql(expression, "this") 4504 expr = self.sql(expression, "expression") 4505 position = self.sql(expression, "position") 4506 position = f", {position}" if position else "" 4507 iterator = self.sql(expression, "iterator") 4508 condition = self.sql(expression, "condition") 4509 condition = f" IF {condition}" if condition else "" 4510 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4511 4512 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4513 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4514 4515 def opclass_sql(self, expression: exp.Opclass) -> str: 4516 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4517 4518 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4519 model = self.sql(expression, "this") 4520 model = f"MODEL {model}" 4521 expr = expression.expression 4522 if expr: 4523 expr_sql = self.sql(expression, "expression") 4524 expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql 4525 else: 4526 expr_sql = None 4527 4528 parameters = self.sql(expression, "params_struct") or None 4529 4530 return self.func(name, model, expr_sql, parameters) 4531 4532 def predict_sql(self, expression: exp.Predict) -> str: 4533 return self._ml_sql(expression, "PREDICT") 4534 4535 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4536 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4537 return self._ml_sql(expression, name) 4538 4539 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4540 return self._ml_sql(expression, "TRANSLATE") 4541 4542 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4543 return self._ml_sql(expression, "FORECAST") 4544 4545 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4546 this_sql = self.sql(expression, "this") 4547 if isinstance(expression.this, exp.Table): 4548 this_sql = f"TABLE {this_sql}" 4549 4550 return self.func( 4551 "FEATURES_AT_TIME", 4552 this_sql, 4553 expression.args.get("time"), 4554 expression.args.get("num_rows"), 4555 expression.args.get("ignore_feature_nulls"), 4556 ) 4557 4558 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4559 this_sql = self.sql(expression, "this") 4560 if isinstance(expression.this, exp.Table): 4561 this_sql = f"TABLE {this_sql}" 4562 4563 query_table = self.sql(expression, "query_table") 4564 if isinstance(expression.args["query_table"], exp.Table): 4565 query_table = f"TABLE {query_table}" 4566 4567 return self.func( 4568 "VECTOR_SEARCH", 4569 this_sql, 4570 expression.args.get("column_to_search"), 4571 query_table, 4572 expression.args.get("query_column_to_search"), 4573 expression.args.get("top_k"), 4574 expression.args.get("distance_type"), 4575 expression.args.get("options"), 4576 ) 4577 4578 def forin_sql(self, expression: exp.ForIn) -> str: 4579 this = self.sql(expression, "this") 4580 expression_sql = self.sql(expression, "expression") 4581 return f"FOR {this} DO {expression_sql}" 4582 4583 def refresh_sql(self, expression: exp.Refresh) -> str: 4584 this = self.sql(expression, "this") 4585 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 4586 return f"REFRESH {kind}{this}" 4587 4588 def toarray_sql(self, expression: exp.ToArray) -> str: 4589 arg = expression.this 4590 if not arg.type: 4591 from sqlglot.optimizer.annotate_types import annotate_types 4592 4593 arg = annotate_types(arg, dialect=self.dialect) 4594 4595 if arg.is_type(exp.DataType.Type.ARRAY): 4596 return self.sql(arg) 4597 4598 cond_for_null = arg.is_(exp.null()) 4599 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4600 4601 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4602 this = expression.this 4603 time_format = self.format_time(expression) 4604 4605 if time_format: 4606 return self.sql( 4607 exp.cast( 4608 exp.StrToTime(this=this, format=expression.args["format"]), 4609 exp.DataType.Type.TIME, 4610 ) 4611 ) 4612 4613 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4614 return self.sql(this) 4615 4616 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4617 4618 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4619 this = expression.this 4620 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4621 return self.sql(this) 4622 4623 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4624 4625 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4626 this = expression.this 4627 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4628 return self.sql(this) 4629 4630 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4631 4632 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4633 this = expression.this 4634 time_format = self.format_time(expression) 4635 safe = expression.args.get("safe") 4636 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4637 return self.sql( 4638 exp.cast( 4639 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4640 exp.DataType.Type.DATE, 4641 ) 4642 ) 4643 4644 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4645 return self.sql(this) 4646 4647 if safe: 4648 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DataType.Type.DATE))) 4649 4650 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4651 4652 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4653 return self.sql( 4654 exp.func( 4655 "DATEDIFF", 4656 expression.this, 4657 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4658 "day", 4659 ) 4660 ) 4661 4662 def lastday_sql(self, expression: exp.LastDay) -> str: 4663 if self.LAST_DAY_SUPPORTS_DATE_PART: 4664 return self.function_fallback_sql(expression) 4665 4666 unit = expression.text("unit") 4667 if unit and unit != "MONTH": 4668 self.unsupported("Date parts are not supported in LAST_DAY.") 4669 4670 return self.func("LAST_DAY", expression.this) 4671 4672 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4673 from sqlglot.dialects.dialect import unit_to_str 4674 4675 return self.func( 4676 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4677 ) 4678 4679 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4680 if self.CAN_IMPLEMENT_ARRAY_ANY: 4681 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4682 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4683 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4684 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4685 4686 from sqlglot.dialects import Dialect 4687 4688 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4689 if self.dialect.__class__ != Dialect: 4690 self.unsupported("ARRAY_ANY is unsupported") 4691 4692 return self.function_fallback_sql(expression) 4693 4694 def struct_sql(self, expression: exp.Struct) -> str: 4695 expression.set( 4696 "expressions", 4697 [ 4698 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4699 if isinstance(e, exp.PropertyEQ) 4700 else e 4701 for e in expression.expressions 4702 ], 4703 ) 4704 4705 return self.function_fallback_sql(expression) 4706 4707 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4708 low = self.sql(expression, "this") 4709 high = self.sql(expression, "expression") 4710 4711 return f"{low} TO {high}" 4712 4713 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4714 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4715 tables = f" {self.expressions(expression)}" 4716 4717 exists = " IF EXISTS" if expression.args.get("exists") else "" 4718 4719 on_cluster = self.sql(expression, "cluster") 4720 on_cluster = f" {on_cluster}" if on_cluster else "" 4721 4722 identity = self.sql(expression, "identity") 4723 identity = f" {identity} IDENTITY" if identity else "" 4724 4725 option = self.sql(expression, "option") 4726 option = f" {option}" if option else "" 4727 4728 partition = self.sql(expression, "partition") 4729 partition = f" {partition}" if partition else "" 4730 4731 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4732 4733 # This transpiles T-SQL's CONVERT function 4734 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4735 def convert_sql(self, expression: exp.Convert) -> str: 4736 to = expression.this 4737 value = expression.expression 4738 style = expression.args.get("style") 4739 safe = expression.args.get("safe") 4740 strict = expression.args.get("strict") 4741 4742 if not to or not value: 4743 return "" 4744 4745 # Retrieve length of datatype and override to default if not specified 4746 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4747 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4748 4749 transformed: t.Optional[exp.Expression] = None 4750 cast = exp.Cast if strict else exp.TryCast 4751 4752 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4753 if isinstance(style, exp.Literal) and style.is_int: 4754 from sqlglot.dialects.tsql import TSQL 4755 4756 style_value = style.name 4757 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4758 if not converted_style: 4759 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4760 4761 fmt = exp.Literal.string(converted_style) 4762 4763 if to.this == exp.DataType.Type.DATE: 4764 transformed = exp.StrToDate(this=value, format=fmt) 4765 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4766 transformed = exp.StrToTime(this=value, format=fmt) 4767 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4768 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4769 elif to.this == exp.DataType.Type.TEXT: 4770 transformed = exp.TimeToStr(this=value, format=fmt) 4771 4772 if not transformed: 4773 transformed = cast(this=value, to=to, safe=safe) 4774 4775 return self.sql(transformed) 4776 4777 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4778 this = expression.this 4779 if isinstance(this, exp.JSONPathWildcard): 4780 this = self.json_path_part(this) 4781 return f".{this}" if this else "" 4782 4783 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4784 return f".{this}" 4785 4786 this = self.json_path_part(this) 4787 return ( 4788 f"[{this}]" 4789 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4790 else f".{this}" 4791 ) 4792 4793 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4794 this = self.json_path_part(expression.this) 4795 return f"[{this}]" if this else "" 4796 4797 def _simplify_unless_literal(self, expression: E) -> E: 4798 if not isinstance(expression, exp.Literal): 4799 from sqlglot.optimizer.simplify import simplify 4800 4801 expression = simplify(expression, dialect=self.dialect) 4802 4803 return expression 4804 4805 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4806 this = expression.this 4807 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4808 self.unsupported( 4809 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4810 ) 4811 return self.sql(this) 4812 4813 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4814 # The first modifier here will be the one closest to the AggFunc's arg 4815 mods = sorted( 4816 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4817 key=lambda x: 0 4818 if isinstance(x, exp.HavingMax) 4819 else (1 if isinstance(x, exp.Order) else 2), 4820 ) 4821 4822 if mods: 4823 mod = mods[0] 4824 this = expression.__class__(this=mod.this.copy()) 4825 this.meta["inline"] = True 4826 mod.this.replace(this) 4827 return self.sql(expression.this) 4828 4829 agg_func = expression.find(exp.AggFunc) 4830 4831 if agg_func: 4832 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4833 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4834 4835 return f"{self.sql(expression, 'this')} {text}" 4836 4837 def _replace_line_breaks(self, string: str) -> str: 4838 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4839 if self.pretty: 4840 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4841 return string 4842 4843 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4844 option = self.sql(expression, "this") 4845 4846 if expression.expressions: 4847 upper = option.upper() 4848 4849 # Snowflake FILE_FORMAT options are separated by whitespace 4850 sep = " " if upper == "FILE_FORMAT" else ", " 4851 4852 # Databricks copy/format options do not set their list of values with EQ 4853 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4854 values = self.expressions(expression, flat=True, sep=sep) 4855 return f"{option}{op}({values})" 4856 4857 value = self.sql(expression, "expression") 4858 4859 if not value: 4860 return option 4861 4862 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4863 4864 return f"{option}{op}{value}" 4865 4866 def credentials_sql(self, expression: exp.Credentials) -> str: 4867 cred_expr = expression.args.get("credentials") 4868 if isinstance(cred_expr, exp.Literal): 4869 # Redshift case: CREDENTIALS <string> 4870 credentials = self.sql(expression, "credentials") 4871 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4872 else: 4873 # Snowflake case: CREDENTIALS = (...) 4874 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4875 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4876 4877 storage = self.sql(expression, "storage") 4878 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4879 4880 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4881 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4882 4883 iam_role = self.sql(expression, "iam_role") 4884 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4885 4886 region = self.sql(expression, "region") 4887 region = f" REGION {region}" if region else "" 4888 4889 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4890 4891 def copy_sql(self, expression: exp.Copy) -> str: 4892 this = self.sql(expression, "this") 4893 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4894 4895 credentials = self.sql(expression, "credentials") 4896 credentials = self.seg(credentials) if credentials else "" 4897 files = self.expressions(expression, key="files", flat=True) 4898 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4899 4900 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4901 params = self.expressions( 4902 expression, 4903 key="params", 4904 sep=sep, 4905 new_line=True, 4906 skip_last=True, 4907 skip_first=True, 4908 indent=self.COPY_PARAMS_ARE_WRAPPED, 4909 ) 4910 4911 if params: 4912 if self.COPY_PARAMS_ARE_WRAPPED: 4913 params = f" WITH ({params})" 4914 elif not self.pretty and (files or credentials): 4915 params = f" {params}" 4916 4917 return f"COPY{this}{kind} {files}{credentials}{params}" 4918 4919 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4920 return "" 4921 4922 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4923 on_sql = "ON" if expression.args.get("on") else "OFF" 4924 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4925 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4926 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4927 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4928 4929 if filter_col or retention_period: 4930 on_sql = self.func("ON", filter_col, retention_period) 4931 4932 return f"DATA_DELETION={on_sql}" 4933 4934 def maskingpolicycolumnconstraint_sql( 4935 self, expression: exp.MaskingPolicyColumnConstraint 4936 ) -> str: 4937 this = self.sql(expression, "this") 4938 expressions = self.expressions(expression, flat=True) 4939 expressions = f" USING ({expressions})" if expressions else "" 4940 return f"MASKING POLICY {this}{expressions}" 4941 4942 def gapfill_sql(self, expression: exp.GapFill) -> str: 4943 this = self.sql(expression, "this") 4944 this = f"TABLE {this}" 4945 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4946 4947 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4948 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4949 4950 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4951 this = self.sql(expression, "this") 4952 expr = expression.expression 4953 4954 if isinstance(expr, exp.Func): 4955 # T-SQL's CLR functions are case sensitive 4956 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4957 else: 4958 expr = self.sql(expression, "expression") 4959 4960 return self.scope_resolution(expr, this) 4961 4962 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4963 if self.PARSE_JSON_NAME is None: 4964 return self.sql(expression.this) 4965 4966 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4967 4968 def rand_sql(self, expression: exp.Rand) -> str: 4969 lower = self.sql(expression, "lower") 4970 upper = self.sql(expression, "upper") 4971 4972 if lower and upper: 4973 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4974 return self.func("RAND", expression.this) 4975 4976 def changes_sql(self, expression: exp.Changes) -> str: 4977 information = self.sql(expression, "information") 4978 information = f"INFORMATION => {information}" 4979 at_before = self.sql(expression, "at_before") 4980 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4981 end = self.sql(expression, "end") 4982 end = f"{self.seg('')}{end}" if end else "" 4983 4984 return f"CHANGES ({information}){at_before}{end}" 4985 4986 def pad_sql(self, expression: exp.Pad) -> str: 4987 prefix = "L" if expression.args.get("is_left") else "R" 4988 4989 fill_pattern = self.sql(expression, "fill_pattern") or None 4990 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4991 fill_pattern = "' '" 4992 4993 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4994 4995 def summarize_sql(self, expression: exp.Summarize) -> str: 4996 table = " TABLE" if expression.args.get("table") else "" 4997 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4998 4999 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5000 generate_series = exp.GenerateSeries(**expression.args) 5001 5002 parent = expression.parent 5003 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5004 parent = parent.parent 5005 5006 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5007 return self.sql(exp.Unnest(expressions=[generate_series])) 5008 5009 if isinstance(parent, exp.Select): 5010 self.unsupported("GenerateSeries projection unnesting is not supported.") 5011 5012 return self.sql(generate_series) 5013 5014 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5015 if self.SUPPORTS_CONVERT_TIMEZONE: 5016 return self.function_fallback_sql(expression) 5017 5018 source_tz = expression.args.get("source_tz") 5019 target_tz = expression.args.get("target_tz") 5020 timestamp = expression.args.get("timestamp") 5021 5022 if source_tz and timestamp: 5023 timestamp = exp.AtTimeZone( 5024 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 5025 ) 5026 5027 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5028 5029 return self.sql(expr) 5030 5031 def json_sql(self, expression: exp.JSON) -> str: 5032 this = self.sql(expression, "this") 5033 this = f" {this}" if this else "" 5034 5035 _with = expression.args.get("with_") 5036 5037 if _with is None: 5038 with_sql = "" 5039 elif not _with: 5040 with_sql = " WITHOUT" 5041 else: 5042 with_sql = " WITH" 5043 5044 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5045 5046 return f"JSON{this}{with_sql}{unique_sql}" 5047 5048 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5049 path = self.sql(expression, "path") 5050 returning = self.sql(expression, "returning") 5051 returning = f" RETURNING {returning}" if returning else "" 5052 5053 on_condition = self.sql(expression, "on_condition") 5054 on_condition = f" {on_condition}" if on_condition else "" 5055 5056 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5057 5058 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5059 else_ = "ELSE " if expression.args.get("else_") else "" 5060 condition = self.sql(expression, "expression") 5061 condition = f"WHEN {condition} THEN " if condition else else_ 5062 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5063 return f"{condition}{insert}" 5064 5065 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5066 kind = self.sql(expression, "kind") 5067 expressions = self.seg(self.expressions(expression, sep=" ")) 5068 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5069 return res 5070 5071 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5072 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5073 empty = expression.args.get("empty") 5074 empty = ( 5075 f"DEFAULT {empty} ON EMPTY" 5076 if isinstance(empty, exp.Expression) 5077 else self.sql(expression, "empty") 5078 ) 5079 5080 error = expression.args.get("error") 5081 error = ( 5082 f"DEFAULT {error} ON ERROR" 5083 if isinstance(error, exp.Expression) 5084 else self.sql(expression, "error") 5085 ) 5086 5087 if error and empty: 5088 error = ( 5089 f"{empty} {error}" 5090 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5091 else f"{error} {empty}" 5092 ) 5093 empty = "" 5094 5095 null = self.sql(expression, "null") 5096 5097 return f"{empty}{error}{null}" 5098 5099 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5100 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5101 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5102 5103 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5104 this = self.sql(expression, "this") 5105 path = self.sql(expression, "path") 5106 5107 passing = self.expressions(expression, "passing") 5108 passing = f" PASSING {passing}" if passing else "" 5109 5110 on_condition = self.sql(expression, "on_condition") 5111 on_condition = f" {on_condition}" if on_condition else "" 5112 5113 path = f"{path}{passing}{on_condition}" 5114 5115 return self.func("JSON_EXISTS", this, path) 5116 5117 def _add_arrayagg_null_filter( 5118 self, 5119 array_agg_sql: str, 5120 array_agg_expr: exp.ArrayAgg, 5121 column_expr: exp.Expression, 5122 ) -> str: 5123 """ 5124 Add NULL filter to ARRAY_AGG if dialect requires it. 5125 5126 Args: 5127 array_agg_sql: The generated ARRAY_AGG SQL string 5128 array_agg_expr: The ArrayAgg expression node 5129 column_expr: The column/expression to filter (before ORDER BY wrapping) 5130 5131 Returns: 5132 SQL string with FILTER clause added if needed 5133 """ 5134 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5135 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5136 if not ( 5137 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5138 ): 5139 return array_agg_sql 5140 5141 parent = array_agg_expr.parent 5142 if isinstance(parent, exp.Filter): 5143 parent_cond = parent.expression.this 5144 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5145 elif column_expr.find(exp.Column): 5146 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5147 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5148 this_sql = ( 5149 self.expressions(column_expr) 5150 if isinstance(column_expr, exp.Distinct) 5151 else self.sql(column_expr) 5152 ) 5153 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5154 5155 return array_agg_sql 5156 5157 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5158 array_agg = self.function_fallback_sql(expression) 5159 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5160 5161 def slice_sql(self, expression: exp.Slice) -> str: 5162 step = self.sql(expression, "step") 5163 end = self.sql(expression.expression) 5164 begin = self.sql(expression.this) 5165 5166 sql = f"{end}:{step}" if step else end 5167 return f"{begin}:{sql}" if sql else f"{begin}:" 5168 5169 def apply_sql(self, expression: exp.Apply) -> str: 5170 this = self.sql(expression, "this") 5171 expr = self.sql(expression, "expression") 5172 5173 return f"{this} APPLY({expr})" 5174 5175 def _grant_or_revoke_sql( 5176 self, 5177 expression: exp.Grant | exp.Revoke, 5178 keyword: str, 5179 preposition: str, 5180 grant_option_prefix: str = "", 5181 grant_option_suffix: str = "", 5182 ) -> str: 5183 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5184 5185 kind = self.sql(expression, "kind") 5186 kind = f" {kind}" if kind else "" 5187 5188 securable = self.sql(expression, "securable") 5189 securable = f" {securable}" if securable else "" 5190 5191 principals = self.expressions(expression, key="principals", flat=True) 5192 5193 if not expression.args.get("grant_option"): 5194 grant_option_prefix = grant_option_suffix = "" 5195 5196 # cascade for revoke only 5197 cascade = self.sql(expression, "cascade") 5198 cascade = f" {cascade}" if cascade else "" 5199 5200 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5201 5202 def grant_sql(self, expression: exp.Grant) -> str: 5203 return self._grant_or_revoke_sql( 5204 expression, 5205 keyword="GRANT", 5206 preposition="TO", 5207 grant_option_suffix=" WITH GRANT OPTION", 5208 ) 5209 5210 def revoke_sql(self, expression: exp.Revoke) -> str: 5211 return self._grant_or_revoke_sql( 5212 expression, 5213 keyword="REVOKE", 5214 preposition="FROM", 5215 grant_option_prefix="GRANT OPTION FOR ", 5216 ) 5217 5218 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5219 this = self.sql(expression, "this") 5220 columns = self.expressions(expression, flat=True) 5221 columns = f"({columns})" if columns else "" 5222 5223 return f"{this}{columns}" 5224 5225 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5226 this = self.sql(expression, "this") 5227 5228 kind = self.sql(expression, "kind") 5229 kind = f"{kind} " if kind else "" 5230 5231 return f"{kind}{this}" 5232 5233 def columns_sql(self, expression: exp.Columns) -> str: 5234 func = self.function_fallback_sql(expression) 5235 if expression.args.get("unpack"): 5236 func = f"*{func}" 5237 5238 return func 5239 5240 def overlay_sql(self, expression: exp.Overlay) -> str: 5241 this = self.sql(expression, "this") 5242 expr = self.sql(expression, "expression") 5243 from_sql = self.sql(expression, "from_") 5244 for_sql = self.sql(expression, "for_") 5245 for_sql = f" FOR {for_sql}" if for_sql else "" 5246 5247 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5248 5249 @unsupported_args("format") 5250 def todouble_sql(self, expression: exp.ToDouble) -> str: 5251 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5252 return self.sql(cast(this=expression.this, to=exp.DataType.build(exp.DataType.Type.DOUBLE))) 5253 5254 def string_sql(self, expression: exp.String) -> str: 5255 this = expression.this 5256 zone = expression.args.get("zone") 5257 5258 if zone: 5259 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5260 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5261 # set for source_tz to transpile the time conversion before the STRING cast 5262 this = exp.ConvertTimezone( 5263 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5264 ) 5265 5266 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 5267 5268 def median_sql(self, expression: exp.Median) -> str: 5269 if not self.SUPPORTS_MEDIAN: 5270 return self.sql( 5271 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5272 ) 5273 5274 return self.function_fallback_sql(expression) 5275 5276 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5277 filler = self.sql(expression, "this") 5278 filler = f" {filler}" if filler else "" 5279 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5280 return f"TRUNCATE{filler} {with_count}" 5281 5282 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5283 if self.SUPPORTS_UNIX_SECONDS: 5284 return self.function_fallback_sql(expression) 5285 5286 start_ts = exp.cast( 5287 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 5288 ) 5289 5290 return self.sql( 5291 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5292 ) 5293 5294 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5295 dim = expression.expression 5296 5297 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5298 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5299 if not (dim.is_int and dim.name == "1"): 5300 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5301 dim = None 5302 5303 # If dimension is required but not specified, default initialize it 5304 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5305 dim = exp.Literal.number(1) 5306 5307 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5308 5309 def attach_sql(self, expression: exp.Attach) -> str: 5310 this = self.sql(expression, "this") 5311 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5312 expressions = self.expressions(expression) 5313 expressions = f" ({expressions})" if expressions else "" 5314 5315 return f"ATTACH{exists_sql} {this}{expressions}" 5316 5317 def detach_sql(self, expression: exp.Detach) -> str: 5318 this = self.sql(expression, "this") 5319 # the DATABASE keyword is required if IF EXISTS is set 5320 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5321 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5322 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5323 5324 return f"DETACH{exists_sql} {this}" 5325 5326 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5327 this = self.sql(expression, "this") 5328 value = self.sql(expression, "expression") 5329 value = f" {value}" if value else "" 5330 return f"{this}{value}" 5331 5332 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5333 return ( 5334 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5335 ) 5336 5337 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5338 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5339 encode = f"{encode} {self.sql(expression, 'this')}" 5340 5341 properties = expression.args.get("properties") 5342 if properties: 5343 encode = f"{encode} {self.properties(properties)}" 5344 5345 return encode 5346 5347 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5348 this = self.sql(expression, "this") 5349 include = f"INCLUDE {this}" 5350 5351 column_def = self.sql(expression, "column_def") 5352 if column_def: 5353 include = f"{include} {column_def}" 5354 5355 alias = self.sql(expression, "alias") 5356 if alias: 5357 include = f"{include} AS {alias}" 5358 5359 return include 5360 5361 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5362 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5363 name = f"{prefix} {self.sql(expression, 'this')}" 5364 return self.func("XMLELEMENT", name, *expression.expressions) 5365 5366 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5367 this = self.sql(expression, "this") 5368 expr = self.sql(expression, "expression") 5369 expr = f"({expr})" if expr else "" 5370 return f"{this}{expr}" 5371 5372 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5373 partitions = self.expressions(expression, "partition_expressions") 5374 create = self.expressions(expression, "create_expressions") 5375 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5376 5377 def partitionbyrangepropertydynamic_sql( 5378 self, expression: exp.PartitionByRangePropertyDynamic 5379 ) -> str: 5380 start = self.sql(expression, "start") 5381 end = self.sql(expression, "end") 5382 5383 every = expression.args["every"] 5384 if isinstance(every, exp.Interval) and every.this.is_string: 5385 every.this.replace(exp.Literal.number(every.name)) 5386 5387 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5388 5389 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5390 name = self.sql(expression, "this") 5391 values = self.expressions(expression, flat=True) 5392 5393 return f"NAME {name} VALUE {values}" 5394 5395 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5396 kind = self.sql(expression, "kind") 5397 sample = self.sql(expression, "sample") 5398 return f"SAMPLE {sample} {kind}" 5399 5400 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5401 kind = self.sql(expression, "kind") 5402 option = self.sql(expression, "option") 5403 option = f" {option}" if option else "" 5404 this = self.sql(expression, "this") 5405 this = f" {this}" if this else "" 5406 columns = self.expressions(expression) 5407 columns = f" {columns}" if columns else "" 5408 return f"{kind}{option} STATISTICS{this}{columns}" 5409 5410 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5411 this = self.sql(expression, "this") 5412 columns = self.expressions(expression) 5413 inner_expression = self.sql(expression, "expression") 5414 inner_expression = f" {inner_expression}" if inner_expression else "" 5415 update_options = self.sql(expression, "update_options") 5416 update_options = f" {update_options} UPDATE" if update_options else "" 5417 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5418 5419 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5420 kind = self.sql(expression, "kind") 5421 kind = f" {kind}" if kind else "" 5422 return f"DELETE{kind} STATISTICS" 5423 5424 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5425 inner_expression = self.sql(expression, "expression") 5426 return f"LIST CHAINED ROWS{inner_expression}" 5427 5428 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5429 kind = self.sql(expression, "kind") 5430 this = self.sql(expression, "this") 5431 this = f" {this}" if this else "" 5432 inner_expression = self.sql(expression, "expression") 5433 return f"VALIDATE {kind}{this}{inner_expression}" 5434 5435 def analyze_sql(self, expression: exp.Analyze) -> str: 5436 options = self.expressions(expression, key="options", sep=" ") 5437 options = f" {options}" if options else "" 5438 kind = self.sql(expression, "kind") 5439 kind = f" {kind}" if kind else "" 5440 this = self.sql(expression, "this") 5441 this = f" {this}" if this else "" 5442 mode = self.sql(expression, "mode") 5443 mode = f" {mode}" if mode else "" 5444 properties = self.sql(expression, "properties") 5445 properties = f" {properties}" if properties else "" 5446 partition = self.sql(expression, "partition") 5447 partition = f" {partition}" if partition else "" 5448 inner_expression = self.sql(expression, "expression") 5449 inner_expression = f" {inner_expression}" if inner_expression else "" 5450 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5451 5452 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5453 this = self.sql(expression, "this") 5454 namespaces = self.expressions(expression, key="namespaces") 5455 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5456 passing = self.expressions(expression, key="passing") 5457 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5458 columns = self.expressions(expression, key="columns") 5459 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5460 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5461 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5462 5463 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5464 this = self.sql(expression, "this") 5465 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5466 5467 def export_sql(self, expression: exp.Export) -> str: 5468 this = self.sql(expression, "this") 5469 connection = self.sql(expression, "connection") 5470 connection = f"WITH CONNECTION {connection} " if connection else "" 5471 options = self.sql(expression, "options") 5472 return f"EXPORT DATA {connection}{options} AS {this}" 5473 5474 def declare_sql(self, expression: exp.Declare) -> str: 5475 return f"DECLARE {self.expressions(expression, flat=True)}" 5476 5477 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5478 variables = self.expressions(expression, "this") 5479 default = self.sql(expression, "default") 5480 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5481 5482 kind = self.sql(expression, "kind") 5483 if isinstance(expression.args.get("kind"), exp.Schema): 5484 kind = f"TABLE {kind}" 5485 5486 kind = f" {kind}" if kind else "" 5487 5488 return f"{variables}{kind}{default}" 5489 5490 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5491 kind = self.sql(expression, "kind") 5492 this = self.sql(expression, "this") 5493 set = self.sql(expression, "expression") 5494 using = self.sql(expression, "using") 5495 using = f" USING {using}" if using else "" 5496 5497 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5498 5499 return f"{kind_sql} {this} SET {set}{using}" 5500 5501 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5502 params = self.expressions(expression, key="params", flat=True) 5503 return self.func(expression.name, *expression.expressions) + f"({params})" 5504 5505 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5506 return self.func(expression.name, *expression.expressions) 5507 5508 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5509 return self.anonymousaggfunc_sql(expression) 5510 5511 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5512 return self.parameterizedagg_sql(expression) 5513 5514 def show_sql(self, expression: exp.Show) -> str: 5515 self.unsupported("Unsupported SHOW statement") 5516 return "" 5517 5518 def install_sql(self, expression: exp.Install) -> str: 5519 self.unsupported("Unsupported INSTALL statement") 5520 return "" 5521 5522 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5523 # Snowflake GET/PUT statements: 5524 # PUT <file> <internalStage> <properties> 5525 # GET <internalStage> <file> <properties> 5526 props = expression.args.get("properties") 5527 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5528 this = self.sql(expression, "this") 5529 target = self.sql(expression, "target") 5530 5531 if isinstance(expression, exp.Put): 5532 return f"PUT {this} {target}{props_sql}" 5533 else: 5534 return f"GET {target} {this}{props_sql}" 5535 5536 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 5537 this = self.sql(expression, "this") 5538 expr = self.sql(expression, "expression") 5539 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5540 return f"TRANSLATE({this} USING {expr}{with_error})" 5541 5542 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5543 if self.SUPPORTS_DECODE_CASE: 5544 return self.func("DECODE", *expression.expressions) 5545 5546 expression, *expressions = expression.expressions 5547 5548 ifs = [] 5549 for search, result in zip(expressions[::2], expressions[1::2]): 5550 if isinstance(search, exp.Literal): 5551 ifs.append(exp.If(this=expression.eq(search), true=result)) 5552 elif isinstance(search, exp.Null): 5553 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5554 else: 5555 if isinstance(search, exp.Binary): 5556 search = exp.paren(search) 5557 5558 cond = exp.or_( 5559 expression.eq(search), 5560 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5561 copy=False, 5562 ) 5563 ifs.append(exp.If(this=cond, true=result)) 5564 5565 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5566 return self.sql(case) 5567 5568 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5569 this = self.sql(expression, "this") 5570 this = self.seg(this, sep="") 5571 dimensions = self.expressions( 5572 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5573 ) 5574 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5575 metrics = self.expressions( 5576 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5577 ) 5578 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5579 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5580 facts = self.seg(f"FACTS {facts}") if facts else "" 5581 where = self.sql(expression, "where") 5582 where = self.seg(f"WHERE {where}") if where else "" 5583 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5584 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5585 5586 def getextract_sql(self, expression: exp.GetExtract) -> str: 5587 this = expression.this 5588 expr = expression.expression 5589 5590 if not this.type or not expression.type: 5591 from sqlglot.optimizer.annotate_types import annotate_types 5592 5593 this = annotate_types(this, dialect=self.dialect) 5594 5595 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5596 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5597 5598 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5599 5600 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5601 return self.sql( 5602 exp.DateAdd( 5603 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5604 expression=expression.this, 5605 unit=exp.var("DAY"), 5606 ) 5607 ) 5608 5609 def space_sql(self: Generator, expression: exp.Space) -> str: 5610 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5611 5612 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5613 return f"BUILD {self.sql(expression, 'this')}" 5614 5615 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5616 method = self.sql(expression, "method") 5617 kind = expression.args.get("kind") 5618 if not kind: 5619 return f"REFRESH {method}" 5620 5621 every = self.sql(expression, "every") 5622 unit = self.sql(expression, "unit") 5623 every = f" EVERY {every} {unit}" if every else "" 5624 starts = self.sql(expression, "starts") 5625 starts = f" STARTS {starts}" if starts else "" 5626 5627 return f"REFRESH {method} ON {kind}{every}{starts}" 5628 5629 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5630 self.unsupported("The model!attribute syntax is not supported") 5631 return "" 5632 5633 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5634 return self.func("DIRECTORY", expression.this) 5635 5636 def uuid_sql(self, expression: exp.Uuid) -> str: 5637 is_string = expression.args.get("is_string", False) 5638 uuid_func_sql = self.func("UUID") 5639 5640 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5641 return self.sql( 5642 exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect) 5643 ) 5644 5645 return uuid_func_sql 5646 5647 def initcap_sql(self, expression: exp.Initcap) -> str: 5648 delimiters = expression.expression 5649 5650 if delimiters: 5651 # do not generate delimiters arg if we are round-tripping from default delimiters 5652 if ( 5653 delimiters.is_string 5654 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5655 ): 5656 delimiters = None 5657 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5658 self.unsupported("INITCAP does not support custom delimiters") 5659 delimiters = None 5660 5661 return self.func("INITCAP", expression.this, delimiters) 5662 5663 def localtime_sql(self, expression: exp.Localtime) -> str: 5664 this = expression.this 5665 return self.func("LOCALTIME", this) if this else "LOCALTIME" 5666 5667 def localtimestamp_sql(self, expression: exp.Localtime) -> str: 5668 this = expression.this 5669 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 5670 5671 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5672 this = expression.this.name.upper() 5673 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5674 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5675 return "WEEK" 5676 5677 return self.func("WEEK", expression.this) 5678 5679 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 5680 this = self.expressions(expression) 5681 charset = self.sql(expression, "charset") 5682 using = f" USING {charset}" if charset else "" 5683 return self.func(name, this + using) 5684 5685 def block_sql(self, expression: exp.Block) -> str: 5686 expressions = self.expressions(expression, sep="; ", flat=True) 5687 return f"{expressions}" if expressions else "" 5688 5689 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 5690 self.unsupported("Unsupported Stored Procedure syntax") 5691 return "" 5692 5693 def ifblock_sql(self, expression: exp.IfBlock) -> str: 5694 self.unsupported("Unsupported If block syntax") 5695 return "" 5696 5697 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 5698 self.unsupported("Unsupported While block syntax") 5699 return "" 5700 5701 def execute_sql(self, expression: exp.Execute) -> str: 5702 self.unsupported("Unsupported Execute syntax") 5703 return "" 5704 5705 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 5706 self.unsupported("Unsupported Execute syntax") 5707 return ""
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)
771 def __init__( 772 self, 773 pretty: t.Optional[bool] = None, 774 identify: str | bool = False, 775 normalize: bool = False, 776 pad: int = 2, 777 indent: int = 2, 778 normalize_functions: t.Optional[str | bool] = None, 779 unsupported_level: ErrorLevel = ErrorLevel.WARN, 780 max_unsupported: int = 3, 781 leading_comma: bool = False, 782 max_text_width: int = 80, 783 comments: bool = True, 784 dialect: DialectType = None, 785 ): 786 import sqlglot 787 from sqlglot.dialects import Dialect 788 789 self.pretty = pretty if pretty is not None else sqlglot.pretty 790 self.identify = identify 791 self.normalize = normalize 792 self.pad = pad 793 self._indent = indent 794 self.unsupported_level = unsupported_level 795 self.max_unsupported = max_unsupported 796 self.leading_comma = leading_comma 797 self.max_text_width = max_text_width 798 self.comments = comments 799 self.dialect = Dialect.get_or_raise(dialect) 800 801 # This is both a Dialect property and a Generator argument, so we prioritize the latter 802 self.normalize_functions = ( 803 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 804 ) 805 806 self.unsupported_messages: t.List[str] = [] 807 self._escaped_quote_end: str = ( 808 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 809 ) 810 self._escaped_byte_quote_end: str = ( 811 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 812 if self.dialect.BYTE_END 813 else "" 814 ) 815 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 816 817 self._next_name = name_sequence("_t") 818 819 self._identifier_start = self.dialect.IDENTIFIER_START 820 self._identifier_end = self.dialect.IDENTIFIER_END 821 822 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.EndStatement'>: <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.TriggerExecute'>: <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.TriggerProperties'>: <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.NVARCHAR: 'NVARCHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.CHAR: 'CHAR'>, <Type.NCHAR: 'NCHAR'>}
824 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 825 """ 826 Generates the SQL string corresponding to the given syntax tree. 827 828 Args: 829 expression: The syntax tree. 830 copy: Whether to copy the expression. The generator performs mutations so 831 it is safer to copy. 832 833 Returns: 834 The SQL string corresponding to `expression`. 835 """ 836 if copy: 837 expression = expression.copy() 838 839 expression = self.preprocess(expression) 840 841 self.unsupported_messages = [] 842 sql = self.sql(expression).strip() 843 844 if self.pretty: 845 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 846 847 if self.unsupported_level == ErrorLevel.IGNORE: 848 return sql 849 850 if self.unsupported_level == ErrorLevel.WARN: 851 for msg in self.unsupported_messages: 852 logger.warning(msg) 853 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 854 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 855 856 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:
858 def preprocess(self, expression: exp.Expression) -> exp.Expression: 859 """Apply generic preprocessing transformations to a given expression.""" 860 expression = self._move_ctes_to_top_level(expression) 861 862 if self.ENSURE_BOOLS: 863 from sqlglot.transforms import ensure_bools 864 865 expression = ensure_bools(expression) 866 867 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
891 def sanitize_comment(self, comment: str) -> str: 892 comment = " " + comment if comment[0].strip() else comment 893 comment = comment + " " if comment[-1].strip() else comment 894 895 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 896 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 897 comment = comment.replace("*/", "* /") 898 899 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
901 def maybe_comment( 902 self, 903 sql: str, 904 expression: t.Optional[exp.Expression] = None, 905 comments: t.Optional[t.List[str]] = None, 906 separated: bool = False, 907 ) -> str: 908 comments = ( 909 ((expression and expression.comments) if comments is None else comments) # type: ignore 910 if self.comments 911 else None 912 ) 913 914 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 915 return sql 916 917 comments_sql = " ".join( 918 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 919 ) 920 921 if not comments_sql: 922 return sql 923 924 comments_sql = self._replace_line_breaks(comments_sql) 925 926 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 927 return ( 928 f"{self.sep()}{comments_sql}{sql}" 929 if not sql or sql[0].isspace() 930 else f"{comments_sql}{self.sep()}{sql}" 931 ) 932 933 return f"{sql} {comments_sql}"
935 def wrap(self, expression: exp.Expression | str) -> str: 936 this_sql = ( 937 self.sql(expression) 938 if isinstance(expression, exp.UNWRAPPED_QUERIES) 939 else self.sql(expression, "this") 940 ) 941 if not this_sql: 942 return "()" 943 944 this_sql = self.indent(this_sql, level=1, pad=0) 945 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:
961 def indent( 962 self, 963 sql: str, 964 level: int = 0, 965 pad: t.Optional[int] = None, 966 skip_first: bool = False, 967 skip_last: bool = False, 968 ) -> str: 969 if not self.pretty or not sql: 970 return sql 971 972 pad = self.pad if pad is None else pad 973 lines = sql.split("\n") 974 975 return "\n".join( 976 ( 977 line 978 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 979 else f"{' ' * (level * self._indent + pad)}{line}" 980 ) 981 for i, line in enumerate(lines) 982 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
984 def sql( 985 self, 986 expression: t.Optional[str | exp.Expression], 987 key: t.Optional[str] = None, 988 comment: bool = True, 989 ) -> str: 990 if not expression: 991 return "" 992 993 if isinstance(expression, str): 994 return expression 995 996 if key: 997 value = expression.args.get(key) 998 if value: 999 return self.sql(value) 1000 return "" 1001 1002 transform = self.TRANSFORMS.get(expression.__class__) 1003 1004 if transform: 1005 sql = transform(self, expression) 1006 else: 1007 exp_handler_name = expression.key + "_sql" 1008 1009 if handler := getattr(self, exp_handler_name, None): 1010 sql = handler(expression) 1011 elif isinstance(expression, exp.Func): 1012 sql = self.function_fallback_sql(expression) 1013 elif isinstance(expression, exp.Property): 1014 sql = self.property_sql(expression) 1015 else: 1016 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1017 1018 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1025 def cache_sql(self, expression: exp.Cache) -> str: 1026 lazy = " LAZY" if expression.args.get("lazy") else "" 1027 table = self.sql(expression, "this") 1028 options = expression.args.get("options") 1029 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1030 sql = self.sql(expression, "expression") 1031 sql = f" AS{self.sep()}{sql}" if sql else "" 1032 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1033 return self.prepend_ctes(expression, sql)
1035 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1036 if isinstance(expression.parent, exp.Cast): 1037 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1038 default = "DEFAULT " if expression.args.get("default") else "" 1039 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1053 def column_sql(self, expression: exp.Column) -> str: 1054 join_mark = " (+)" if expression.args.get("join_mark") else "" 1055 1056 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1057 join_mark = "" 1058 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1059 1060 return f"{self.column_parts(expression)}{join_mark}"
1071 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1072 column = self.sql(expression, "this") 1073 kind = self.sql(expression, "kind") 1074 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1075 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1076 kind = f"{sep}{kind}" if kind else "" 1077 constraints = f" {constraints}" if constraints else "" 1078 position = self.sql(expression, "position") 1079 position = f" {position}" if position else "" 1080 1081 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1082 kind = "" 1083 1084 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1091 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1092 this = self.sql(expression, "this") 1093 if expression.args.get("not_null"): 1094 persisted = " PERSISTED NOT NULL" 1095 elif expression.args.get("persisted"): 1096 persisted = " PERSISTED" 1097 else: 1098 persisted = "" 1099 1100 return f"AS {this}{persisted}"
def
autoincrementcolumnconstraint_sql(self, _: sqlglot.expressions.AutoIncrementColumnConstraint) -> str:
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1113 def generatedasidentitycolumnconstraint_sql( 1114 self, expression: exp.GeneratedAsIdentityColumnConstraint 1115 ) -> str: 1116 this = "" 1117 if expression.this is not None: 1118 on_null = " ON NULL" if expression.args.get("on_null") else "" 1119 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1120 1121 start = expression.args.get("start") 1122 start = f"START WITH {start}" if start else "" 1123 increment = expression.args.get("increment") 1124 increment = f" INCREMENT BY {increment}" if increment else "" 1125 minvalue = expression.args.get("minvalue") 1126 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1127 maxvalue = expression.args.get("maxvalue") 1128 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1129 cycle = expression.args.get("cycle") 1130 cycle_sql = "" 1131 1132 if cycle is not None: 1133 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1134 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1135 1136 sequence_opts = "" 1137 if start or increment or cycle_sql: 1138 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1139 sequence_opts = f" ({sequence_opts.strip()})" 1140 1141 expr = self.sql(expression, "expression") 1142 expr = f"({expr})" if expr else "IDENTITY" 1143 1144 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1146 def generatedasrowcolumnconstraint_sql( 1147 self, expression: exp.GeneratedAsRowColumnConstraint 1148 ) -> str: 1149 start = "START" if expression.args.get("start") else "END" 1150 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1151 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:
1161 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1162 desc = expression.args.get("desc") 1163 if desc is not None: 1164 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1165 options = self.expressions(expression, key="options", flat=True, sep=" ") 1166 options = f" {options}" if options else "" 1167 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1169 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1170 this = self.sql(expression, "this") 1171 this = f" {this}" if this else "" 1172 index_type = expression.args.get("index_type") 1173 index_type = f" USING {index_type}" if index_type else "" 1174 on_conflict = self.sql(expression, "on_conflict") 1175 on_conflict = f" {on_conflict}" if on_conflict else "" 1176 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1177 options = self.expressions(expression, key="options", flat=True, sep=" ") 1178 options = f" {options}" if options else "" 1179 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1181 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1182 input_ = expression.args.get("input_") 1183 output = expression.args.get("output") 1184 variadic = expression.args.get("variadic") 1185 1186 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1187 if variadic: 1188 return "VARIADIC" 1189 1190 if input_ and output: 1191 return f"IN{self.INOUT_SEPARATOR}OUT" 1192 if input_: 1193 return "IN" 1194 if output: 1195 return "OUT" 1196 1197 return ""
1202 def create_sql(self, expression: exp.Create) -> str: 1203 kind = self.sql(expression, "kind") 1204 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1205 1206 properties = expression.args.get("properties") 1207 1208 if ( 1209 kind == "TRIGGER" 1210 and properties 1211 and properties.expressions 1212 and isinstance(properties.expressions[0], exp.TriggerProperties) 1213 and properties.expressions[0].args.get("constraint") 1214 ): 1215 kind = f"CONSTRAINT {kind}" 1216 1217 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1218 1219 this = self.createable_sql(expression, properties_locs) 1220 1221 properties_sql = "" 1222 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1223 exp.Properties.Location.POST_WITH 1224 ): 1225 props_ast = exp.Properties( 1226 expressions=[ 1227 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1228 *properties_locs[exp.Properties.Location.POST_WITH], 1229 ] 1230 ) 1231 props_ast.parent = expression 1232 properties_sql = self.sql(props_ast) 1233 1234 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1235 properties_sql = self.sep() + properties_sql 1236 elif not self.pretty: 1237 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1238 properties_sql = f" {properties_sql}" 1239 1240 begin = " BEGIN" if expression.args.get("begin") else "" 1241 1242 expression_sql = self.sql(expression, "expression") 1243 if expression_sql: 1244 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1245 1246 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1247 postalias_props_sql = "" 1248 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1249 postalias_props_sql = self.properties( 1250 exp.Properties( 1251 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1252 ), 1253 wrapped=False, 1254 ) 1255 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1256 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1257 1258 postindex_props_sql = "" 1259 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1260 postindex_props_sql = self.properties( 1261 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1262 wrapped=False, 1263 prefix=" ", 1264 ) 1265 1266 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1267 indexes = f" {indexes}" if indexes else "" 1268 index_sql = indexes + postindex_props_sql 1269 1270 replace = " OR REPLACE" if expression.args.get("replace") else "" 1271 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1272 unique = " UNIQUE" if expression.args.get("unique") else "" 1273 1274 clustered = expression.args.get("clustered") 1275 if clustered is None: 1276 clustered_sql = "" 1277 elif clustered: 1278 clustered_sql = " CLUSTERED COLUMNSTORE" 1279 else: 1280 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1281 1282 postcreate_props_sql = "" 1283 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1284 postcreate_props_sql = self.properties( 1285 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1286 sep=" ", 1287 prefix=" ", 1288 wrapped=False, 1289 ) 1290 1291 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1292 1293 postexpression_props_sql = "" 1294 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1295 postexpression_props_sql = self.properties( 1296 exp.Properties( 1297 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1298 ), 1299 sep=" ", 1300 prefix=" ", 1301 wrapped=False, 1302 ) 1303 1304 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1305 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1306 no_schema_binding = ( 1307 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1308 ) 1309 1310 clone = self.sql(expression, "clone") 1311 clone = f" {clone}" if clone else "" 1312 1313 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1314 properties_expression = f"{expression_sql}{properties_sql}" 1315 else: 1316 properties_expression = f"{properties_sql}{expression_sql}" 1317 1318 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1319 return self.prepend_ctes(expression, expression_sql)
1321 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1322 start = self.sql(expression, "start") 1323 start = f"START WITH {start}" if start else "" 1324 increment = self.sql(expression, "increment") 1325 increment = f" INCREMENT BY {increment}" if increment else "" 1326 minvalue = self.sql(expression, "minvalue") 1327 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1328 maxvalue = self.sql(expression, "maxvalue") 1329 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1330 owned = self.sql(expression, "owned") 1331 owned = f" OWNED BY {owned}" if owned else "" 1332 1333 cache = expression.args.get("cache") 1334 if cache is None: 1335 cache_str = "" 1336 elif cache is True: 1337 cache_str = " CACHE" 1338 else: 1339 cache_str = f" CACHE {cache}" 1340 1341 options = self.expressions(expression, key="options", flat=True, sep=" ") 1342 options = f" {options}" if options else "" 1343 1344 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1346 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1347 timing = expression.args.get("timing", "") 1348 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1349 timing_events = f"{timing} {events}".strip() if timing or events else "" 1350 1351 parts = [timing_events, "ON", self.sql(expression, "table")] 1352 1353 if referenced_table := expression.args.get("referenced_table"): 1354 parts.extend(["FROM", self.sql(referenced_table)]) 1355 1356 if deferrable := expression.args.get("deferrable"): 1357 parts.append(deferrable) 1358 1359 if initially := expression.args.get("initially"): 1360 parts.append(f"INITIALLY {initially}") 1361 1362 if referencing := expression.args.get("referencing"): 1363 parts.append(self.sql(referencing)) 1364 1365 if for_each := expression.args.get("for_each"): 1366 parts.append(f"FOR EACH {for_each}") 1367 1368 if when := expression.args.get("when"): 1369 parts.append(f"WHEN ({self.sql(when)})") 1370 1371 parts.append(self.sql(expression, "execute")) 1372 1373 return self.sep().join(parts)
1375 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1376 parts = [] 1377 1378 if old_alias := expression.args.get("old"): 1379 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1380 1381 if new_alias := expression.args.get("new"): 1382 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1383 1384 return f"REFERENCING {' '.join(parts)}"
1393 def clone_sql(self, expression: exp.Clone) -> str: 1394 this = self.sql(expression, "this") 1395 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1396 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1397 return f"{shallow}{keyword} {this}"
1399 def describe_sql(self, expression: exp.Describe) -> str: 1400 style = expression.args.get("style") 1401 style = f" {style}" if style else "" 1402 partition = self.sql(expression, "partition") 1403 partition = f" {partition}" if partition else "" 1404 format = self.sql(expression, "format") 1405 format = f" {format}" if format else "" 1406 as_json = " AS JSON" if expression.args.get("as_json") else "" 1407 1408 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1420 def with_sql(self, expression: exp.With) -> str: 1421 sql = self.expressions(expression, flat=True) 1422 recursive = ( 1423 "RECURSIVE " 1424 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1425 else "" 1426 ) 1427 search = self.sql(expression, "search") 1428 search = f" {search}" if search else "" 1429 1430 return f"WITH {recursive}{sql}{search}"
1432 def cte_sql(self, expression: exp.CTE) -> str: 1433 alias = expression.args.get("alias") 1434 if alias: 1435 alias.add_comments(expression.pop_comments()) 1436 1437 alias_sql = self.sql(expression, "alias") 1438 1439 materialized = expression.args.get("materialized") 1440 if materialized is False: 1441 materialized = "NOT MATERIALIZED " 1442 elif materialized: 1443 materialized = "MATERIALIZED " 1444 1445 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1446 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1447 1448 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1450 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1451 alias = self.sql(expression, "this") 1452 columns = self.expressions(expression, key="columns", flat=True) 1453 columns = f"({columns})" if columns else "" 1454 1455 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1456 columns = "" 1457 self.unsupported("Named columns are not supported in table alias.") 1458 1459 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1460 alias = self._next_name() 1461 1462 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1470 def hexstring_sql( 1471 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1472 ) -> str: 1473 this = self.sql(expression, "this") 1474 is_integer_type = expression.args.get("is_integer") 1475 1476 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1477 not self.dialect.HEX_START and not binary_function_repr 1478 ): 1479 # Integer representation will be returned if: 1480 # - The read dialect treats the hex value as integer literal but not the write 1481 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1482 return f"{int(this, 16)}" 1483 1484 if not is_integer_type: 1485 # Read dialect treats the hex value as BINARY/BLOB 1486 if binary_function_repr: 1487 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1488 return self.func(binary_function_repr, exp.Literal.string(this)) 1489 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1490 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1491 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1492 1493 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1495 def bytestring_sql(self, expression: exp.ByteString) -> str: 1496 this = self.sql(expression, "this") 1497 if self.dialect.BYTE_START: 1498 escaped_byte_string = self.escape_str( 1499 this, 1500 escape_backslash=False, 1501 delimiter=self.dialect.BYTE_END, 1502 escaped_delimiter=self._escaped_byte_quote_end, 1503 is_byte_string=True, 1504 ) 1505 is_bytes = expression.args.get("is_bytes", False) 1506 delimited_byte_string = ( 1507 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1508 ) 1509 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1510 return self.sql( 1511 exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect) 1512 ) 1513 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1514 return self.sql( 1515 exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect) 1516 ) 1517 1518 return delimited_byte_string 1519 return this
1521 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1522 this = self.sql(expression, "this") 1523 escape = expression.args.get("escape") 1524 1525 if self.dialect.UNICODE_START: 1526 escape_substitute = r"\\\1" 1527 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1528 else: 1529 escape_substitute = r"\\u\1" 1530 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1531 1532 if escape: 1533 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1534 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1535 else: 1536 escape_pattern = ESCAPED_UNICODE_RE 1537 escape_sql = "" 1538 1539 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1540 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1541 1542 return f"{left_quote}{this}{right_quote}{escape_sql}"
1544 def rawstring_sql(self, expression: exp.RawString) -> str: 1545 string = expression.this 1546 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1547 string = string.replace("\\", "\\\\") 1548 1549 string = self.escape_str(string, escape_backslash=False) 1550 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1558 def datatype_sql(self, expression: exp.DataType) -> str: 1559 nested = "" 1560 values = "" 1561 1562 expr_nested = expression.args.get("nested") 1563 interior = ( 1564 self.expressions( 1565 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1566 ) 1567 if expr_nested and self.pretty 1568 else self.expressions(expression, flat=True) 1569 ) 1570 1571 type_value = expression.this 1572 if type_value in self.UNSUPPORTED_TYPES: 1573 self.unsupported( 1574 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1575 ) 1576 1577 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1578 type_sql = self.sql(expression, "kind") 1579 else: 1580 type_sql = ( 1581 self.TYPE_MAPPING.get(type_value, type_value.value) 1582 if isinstance(type_value, exp.DataType.Type) 1583 else type_value 1584 ) 1585 1586 if interior: 1587 if expr_nested: 1588 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1589 if expression.args.get("values") is not None: 1590 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1591 values = self.expressions(expression, key="values", flat=True) 1592 values = f"{delimiters[0]}{values}{delimiters[1]}" 1593 elif type_value == exp.DataType.Type.INTERVAL: 1594 nested = f" {interior}" 1595 else: 1596 nested = f"({interior})" 1597 1598 type_sql = f"{type_sql}{nested}{values}" 1599 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1600 exp.DataType.Type.TIMETZ, 1601 exp.DataType.Type.TIMESTAMPTZ, 1602 ): 1603 type_sql = f"{type_sql} WITH TIME ZONE" 1604 1605 return type_sql
1607 def directory_sql(self, expression: exp.Directory) -> str: 1608 local = "LOCAL " if expression.args.get("local") else "" 1609 row_format = self.sql(expression, "row_format") 1610 row_format = f" {row_format}" if row_format else "" 1611 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1613 def delete_sql(self, expression: exp.Delete) -> str: 1614 this = self.sql(expression, "this") 1615 this = f" FROM {this}" if this else "" 1616 using = self.expressions(expression, key="using") 1617 using = f" USING {using}" if using else "" 1618 cluster = self.sql(expression, "cluster") 1619 cluster = f" {cluster}" if cluster else "" 1620 where = self.sql(expression, "where") 1621 returning = self.sql(expression, "returning") 1622 order = self.sql(expression, "order") 1623 limit = self.sql(expression, "limit") 1624 tables = self.expressions(expression, key="tables") 1625 tables = f" {tables}" if tables else "" 1626 if self.RETURNING_END: 1627 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1628 else: 1629 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1630 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1632 def drop_sql(self, expression: exp.Drop) -> str: 1633 this = self.sql(expression, "this") 1634 expressions = self.expressions(expression, flat=True) 1635 expressions = f" ({expressions})" if expressions else "" 1636 kind = expression.args["kind"] 1637 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1638 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1639 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1640 on_cluster = self.sql(expression, "cluster") 1641 on_cluster = f" {on_cluster}" if on_cluster else "" 1642 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1643 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1644 cascade = " CASCADE" if expression.args.get("cascade") else "" 1645 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1646 purge = " PURGE" if expression.args.get("purge") else "" 1647 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1649 def set_operation(self, expression: exp.SetOperation) -> str: 1650 op_type = type(expression) 1651 op_name = op_type.key.upper() 1652 1653 distinct = expression.args.get("distinct") 1654 if ( 1655 distinct is False 1656 and op_type in (exp.Except, exp.Intersect) 1657 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1658 ): 1659 self.unsupported(f"{op_name} ALL is not supported") 1660 1661 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1662 1663 if distinct is None: 1664 distinct = default_distinct 1665 if distinct is None: 1666 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1667 1668 if distinct is default_distinct: 1669 distinct_or_all = "" 1670 else: 1671 distinct_or_all = " DISTINCT" if distinct else " ALL" 1672 1673 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1674 side_kind = f"{side_kind} " if side_kind else "" 1675 1676 by_name = " BY NAME" if expression.args.get("by_name") else "" 1677 on = self.expressions(expression, key="on", flat=True) 1678 on = f" ON ({on})" if on else "" 1679 1680 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1682 def set_operations(self, expression: exp.SetOperation) -> str: 1683 if not self.SET_OP_MODIFIERS: 1684 limit = expression.args.get("limit") 1685 order = expression.args.get("order") 1686 1687 if limit or order: 1688 select = self._move_ctes_to_top_level( 1689 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1690 ) 1691 1692 if limit: 1693 select = select.limit(limit.pop(), copy=False) 1694 if order: 1695 select = select.order_by(order.pop(), copy=False) 1696 return self.sql(select) 1697 1698 sqls: t.List[str] = [] 1699 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1700 1701 while stack: 1702 node = stack.pop() 1703 1704 if isinstance(node, exp.SetOperation): 1705 stack.append(node.expression) 1706 stack.append( 1707 self.maybe_comment( 1708 self.set_operation(node), comments=node.comments, separated=True 1709 ) 1710 ) 1711 stack.append(node.this) 1712 else: 1713 sqls.append(self.sql(node)) 1714 1715 this = self.sep().join(sqls) 1716 this = self.query_modifiers(expression, this) 1717 return self.prepend_ctes(expression, this)
1719 def fetch_sql(self, expression: exp.Fetch) -> str: 1720 direction = expression.args.get("direction") 1721 direction = f" {direction}" if direction else "" 1722 count = self.sql(expression, "count") 1723 count = f" {count}" if count else "" 1724 limit_options = self.sql(expression, "limit_options") 1725 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1726 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1728 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1729 percent = " PERCENT" if expression.args.get("percent") else "" 1730 rows = " ROWS" if expression.args.get("rows") else "" 1731 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1732 if not with_ties and rows: 1733 with_ties = " ONLY" 1734 return f"{percent}{rows}{with_ties}"
1736 def filter_sql(self, expression: exp.Filter) -> str: 1737 if self.AGGREGATE_FILTER_SUPPORTED: 1738 this = self.sql(expression, "this") 1739 where = self.sql(expression, "expression").strip() 1740 return f"{this} FILTER({where})" 1741 1742 agg = expression.this 1743 agg_arg = agg.this 1744 cond = expression.expression.this 1745 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1746 return self.sql(agg)
1755 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1756 using = self.sql(expression, "using") 1757 using = f" USING {using}" if using else "" 1758 columns = self.expressions(expression, key="columns", flat=True) 1759 columns = f"({columns})" if columns else "" 1760 partition_by = self.expressions(expression, key="partition_by", flat=True) 1761 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1762 where = self.sql(expression, "where") 1763 include = self.expressions(expression, key="include", flat=True) 1764 if include: 1765 include = f" INCLUDE ({include})" 1766 with_storage = self.expressions(expression, key="with_storage", flat=True) 1767 with_storage = f" WITH ({with_storage})" if with_storage else "" 1768 tablespace = self.sql(expression, "tablespace") 1769 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1770 on = self.sql(expression, "on") 1771 on = f" ON {on}" if on else "" 1772 1773 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1775 def index_sql(self, expression: exp.Index) -> str: 1776 unique = "UNIQUE " if expression.args.get("unique") else "" 1777 primary = "PRIMARY " if expression.args.get("primary") else "" 1778 amp = "AMP " if expression.args.get("amp") else "" 1779 name = self.sql(expression, "this") 1780 name = f"{name} " if name else "" 1781 table = self.sql(expression, "table") 1782 table = f"{self.INDEX_ON} {table}" if table else "" 1783 1784 index = "INDEX " if not table else "" 1785 1786 params = self.sql(expression, "params") 1787 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1789 def identifier_sql(self, expression: exp.Identifier) -> str: 1790 text = expression.name 1791 lower = text.lower() 1792 quoted = expression.quoted 1793 text = lower if self.normalize and not quoted else text 1794 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1795 if ( 1796 quoted 1797 or self.dialect.can_quote(expression, self.identify) 1798 or lower in self.RESERVED_KEYWORDS 1799 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1800 ): 1801 text = f"{self._identifier_start}{text}{self._identifier_end}" 1802 return text
1817 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1818 input_format = self.sql(expression, "input_format") 1819 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1820 output_format = self.sql(expression, "output_format") 1821 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1822 return self.sep().join((input_format, output_format))
1832 def properties_sql(self, expression: exp.Properties) -> str: 1833 root_properties = [] 1834 with_properties = [] 1835 1836 for p in expression.expressions: 1837 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1838 if p_loc == exp.Properties.Location.POST_WITH: 1839 with_properties.append(p) 1840 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1841 root_properties.append(p) 1842 1843 root_props_ast = exp.Properties(expressions=root_properties) 1844 root_props_ast.parent = expression.parent 1845 1846 with_props_ast = exp.Properties(expressions=with_properties) 1847 with_props_ast.parent = expression.parent 1848 1849 root_props = self.root_properties(root_props_ast) 1850 with_props = self.with_properties(with_props_ast) 1851 1852 if root_props and with_props and not self.pretty: 1853 with_props = " " + with_props 1854 1855 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1862 def properties( 1863 self, 1864 properties: exp.Properties, 1865 prefix: str = "", 1866 sep: str = ", ", 1867 suffix: str = "", 1868 wrapped: bool = True, 1869 ) -> str: 1870 if properties.expressions: 1871 expressions = self.expressions(properties, sep=sep, indent=False) 1872 if expressions: 1873 expressions = self.wrap(expressions) if wrapped else expressions 1874 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1875 return ""
1880 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1881 properties_locs = defaultdict(list) 1882 for p in properties.expressions: 1883 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1884 if p_loc != exp.Properties.Location.UNSUPPORTED: 1885 properties_locs[p_loc].append(p) 1886 else: 1887 self.unsupported(f"Unsupported property {p.key}") 1888 1889 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1896 def property_sql(self, expression: exp.Property) -> str: 1897 property_cls = expression.__class__ 1898 if property_cls == exp.Property: 1899 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1900 1901 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1902 if not property_name: 1903 self.unsupported(f"Unsupported property {expression.key}") 1904 1905 return f"{property_name}={self.sql(expression, 'this')}"
1907 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1908 if self.SUPPORTS_CREATE_TABLE_LIKE: 1909 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1910 options = f" {options}" if options else "" 1911 1912 like = f"LIKE {self.sql(expression, 'this')}{options}" 1913 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1914 like = f"({like})" 1915 1916 return like 1917 1918 if expression.expressions: 1919 self.unsupported("Transpilation of LIKE property options is unsupported") 1920 1921 select = exp.select("*").from_(expression.this).limit(0) 1922 return f"AS {self.sql(select)}"
1929 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1930 no = "NO " if expression.args.get("no") else "" 1931 local = expression.args.get("local") 1932 local = f"{local} " if local else "" 1933 dual = "DUAL " if expression.args.get("dual") else "" 1934 before = "BEFORE " if expression.args.get("before") else "" 1935 after = "AFTER " if expression.args.get("after") else "" 1936 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1952 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1953 if expression.args.get("no"): 1954 return "NO MERGEBLOCKRATIO" 1955 if expression.args.get("default"): 1956 return "DEFAULT MERGEBLOCKRATIO" 1957 1958 percent = " PERCENT" if expression.args.get("percent") else "" 1959 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1961 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1962 default = expression.args.get("default") 1963 minimum = expression.args.get("minimum") 1964 maximum = expression.args.get("maximum") 1965 if default or minimum or maximum: 1966 if default: 1967 prop = "DEFAULT" 1968 elif minimum: 1969 prop = "MINIMUM" 1970 else: 1971 prop = "MAXIMUM" 1972 return f"{prop} DATABLOCKSIZE" 1973 units = expression.args.get("units") 1974 units = f" {units}" if units else "" 1975 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1977 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1978 autotemp = expression.args.get("autotemp") 1979 always = expression.args.get("always") 1980 default = expression.args.get("default") 1981 manual = expression.args.get("manual") 1982 never = expression.args.get("never") 1983 1984 if autotemp is not None: 1985 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1986 elif always: 1987 prop = "ALWAYS" 1988 elif default: 1989 prop = "DEFAULT" 1990 elif manual: 1991 prop = "MANUAL" 1992 elif never: 1993 prop = "NEVER" 1994 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1996 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1997 no = expression.args.get("no") 1998 no = " NO" if no else "" 1999 concurrent = expression.args.get("concurrent") 2000 concurrent = " CONCURRENT" if concurrent else "" 2001 target = self.sql(expression, "target") 2002 target = f" {target}" if target else "" 2003 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
2005 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2006 if isinstance(expression.this, list): 2007 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2008 if expression.this: 2009 modulus = self.sql(expression, "this") 2010 remainder = self.sql(expression, "expression") 2011 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2012 2013 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2014 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2015 return f"FROM ({from_expressions}) TO ({to_expressions})"
2017 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2018 this = self.sql(expression, "this") 2019 2020 for_values_or_default = expression.expression 2021 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2022 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2023 else: 2024 for_values_or_default = " DEFAULT" 2025 2026 return f"PARTITION OF {this}{for_values_or_default}"
2028 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2029 kind = expression.args.get("kind") 2030 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2031 for_or_in = expression.args.get("for_or_in") 2032 for_or_in = f" {for_or_in}" if for_or_in else "" 2033 lock_type = expression.args.get("lock_type") 2034 override = " OVERRIDE" if expression.args.get("override") else "" 2035 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2037 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2038 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2039 statistics = expression.args.get("statistics") 2040 statistics_sql = "" 2041 if statistics is not None: 2042 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2043 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
2045 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2046 this = self.sql(expression, "this") 2047 this = f"HISTORY_TABLE={this}" if this else "" 2048 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 2049 data_consistency = ( 2050 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2051 ) 2052 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 2053 retention_period = ( 2054 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2055 ) 2056 2057 if this: 2058 on_sql = self.func("ON", this, data_consistency, retention_period) 2059 else: 2060 on_sql = "ON" if expression.args.get("on") else "OFF" 2061 2062 sql = f"SYSTEM_VERSIONING={on_sql}" 2063 2064 return f"WITH({sql})" if expression.args.get("with_") else sql
2066 def insert_sql(self, expression: exp.Insert) -> str: 2067 hint = self.sql(expression, "hint") 2068 overwrite = expression.args.get("overwrite") 2069 2070 if isinstance(expression.this, exp.Directory): 2071 this = " OVERWRITE" if overwrite else " INTO" 2072 else: 2073 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2074 2075 stored = self.sql(expression, "stored") 2076 stored = f" {stored}" if stored else "" 2077 alternative = expression.args.get("alternative") 2078 alternative = f" OR {alternative}" if alternative else "" 2079 ignore = " IGNORE" if expression.args.get("ignore") else "" 2080 is_function = expression.args.get("is_function") 2081 if is_function: 2082 this = f"{this} FUNCTION" 2083 this = f"{this} {self.sql(expression, 'this')}" 2084 2085 exists = " IF EXISTS" if expression.args.get("exists") else "" 2086 where = self.sql(expression, "where") 2087 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2088 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2089 on_conflict = self.sql(expression, "conflict") 2090 on_conflict = f" {on_conflict}" if on_conflict else "" 2091 by_name = " BY NAME" if expression.args.get("by_name") else "" 2092 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2093 returning = self.sql(expression, "returning") 2094 2095 if self.RETURNING_END: 2096 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2097 else: 2098 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2099 2100 partition_by = self.sql(expression, "partition") 2101 partition_by = f" {partition_by}" if partition_by else "" 2102 settings = self.sql(expression, "settings") 2103 settings = f" {settings}" if settings else "" 2104 2105 source = self.sql(expression, "source") 2106 source = f"TABLE {source}" if source else "" 2107 2108 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2109 return self.prepend_ctes(expression, sql)
2127 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2128 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2129 2130 constraint = self.sql(expression, "constraint") 2131 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2132 2133 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2134 if conflict_keys: 2135 conflict_keys = f"({conflict_keys})" 2136 2137 index_predicate = self.sql(expression, "index_predicate") 2138 conflict_keys = f"{conflict_keys}{index_predicate} " 2139 2140 action = self.sql(expression, "action") 2141 2142 expressions = self.expressions(expression, flat=True) 2143 if expressions: 2144 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2145 expressions = f" {set_keyword}{expressions}" 2146 2147 where = self.sql(expression, "where") 2148 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
2153 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2154 fields = self.sql(expression, "fields") 2155 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2156 escaped = self.sql(expression, "escaped") 2157 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2158 items = self.sql(expression, "collection_items") 2159 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2160 keys = self.sql(expression, "map_keys") 2161 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2162 lines = self.sql(expression, "lines") 2163 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2164 null = self.sql(expression, "null") 2165 null = f" NULL DEFINED AS {null}" if null else "" 2166 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2194 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2195 table = self.table_parts(expression) 2196 only = "ONLY " if expression.args.get("only") else "" 2197 partition = self.sql(expression, "partition") 2198 partition = f" {partition}" if partition else "" 2199 version = self.sql(expression, "version") 2200 version = f" {version}" if version else "" 2201 alias = self.sql(expression, "alias") 2202 alias = f"{sep}{alias}" if alias else "" 2203 2204 sample = self.sql(expression, "sample") 2205 post_alias = "" 2206 pre_alias = "" 2207 2208 if self.dialect.ALIAS_POST_TABLESAMPLE: 2209 pre_alias = sample 2210 else: 2211 post_alias = sample 2212 2213 if self.dialect.ALIAS_POST_VERSION: 2214 pre_alias = f"{pre_alias}{version}" 2215 else: 2216 post_alias = f"{post_alias}{version}" 2217 2218 hints = self.expressions(expression, key="hints", sep=" ") 2219 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2220 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2221 joins = self.indent( 2222 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2223 ) 2224 laterals = self.expressions(expression, key="laterals", sep="") 2225 2226 file_format = self.sql(expression, "format") 2227 if file_format: 2228 pattern = self.sql(expression, "pattern") 2229 pattern = f", PATTERN => {pattern}" if pattern else "" 2230 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2231 2232 ordinality = expression.args.get("ordinality") or "" 2233 if ordinality: 2234 ordinality = f" WITH ORDINALITY{alias}" 2235 alias = "" 2236 2237 when = self.sql(expression, "when") 2238 if when: 2239 table = f"{table} {when}" 2240 2241 changes = self.sql(expression, "changes") 2242 changes = f" {changes}" if changes else "" 2243 2244 rows_from = self.expressions(expression, key="rows_from") 2245 if rows_from: 2246 table = f"ROWS FROM {self.wrap(rows_from)}" 2247 2248 indexed = expression.args.get("indexed") 2249 if indexed is not None: 2250 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2251 else: 2252 indexed = "" 2253 2254 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2256 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2257 table = self.func("TABLE", expression.this) 2258 alias = self.sql(expression, "alias") 2259 alias = f" AS {alias}" if alias else "" 2260 sample = self.sql(expression, "sample") 2261 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2262 joins = self.indent( 2263 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2264 ) 2265 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2267 def tablesample_sql( 2268 self, 2269 expression: exp.TableSample, 2270 tablesample_keyword: t.Optional[str] = None, 2271 ) -> str: 2272 method = self.sql(expression, "method") 2273 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2274 numerator = self.sql(expression, "bucket_numerator") 2275 denominator = self.sql(expression, "bucket_denominator") 2276 field = self.sql(expression, "bucket_field") 2277 field = f" ON {field}" if field else "" 2278 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2279 seed = self.sql(expression, "seed") 2280 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2281 2282 size = self.sql(expression, "size") 2283 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2284 size = f"{size} ROWS" 2285 2286 percent = self.sql(expression, "percent") 2287 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2288 percent = f"{percent} PERCENT" 2289 2290 expr = f"{bucket}{percent}{size}" 2291 if self.TABLESAMPLE_REQUIRES_PARENS: 2292 expr = f"({expr})" 2293 2294 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2296 def pivot_sql(self, expression: exp.Pivot) -> str: 2297 expressions = self.expressions(expression, flat=True) 2298 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2299 2300 group = self.sql(expression, "group") 2301 2302 if expression.this: 2303 this = self.sql(expression, "this") 2304 if not expressions: 2305 sql = f"UNPIVOT {this}" 2306 else: 2307 on = f"{self.seg('ON')} {expressions}" 2308 into = self.sql(expression, "into") 2309 into = f"{self.seg('INTO')} {into}" if into else "" 2310 using = self.expressions(expression, key="using", flat=True) 2311 using = f"{self.seg('USING')} {using}" if using else "" 2312 sql = f"{direction} {this}{on}{into}{using}{group}" 2313 return self.prepend_ctes(expression, sql) 2314 2315 alias = self.sql(expression, "alias") 2316 alias = f" AS {alias}" if alias else "" 2317 2318 fields = self.expressions( 2319 expression, 2320 "fields", 2321 sep=" ", 2322 dynamic=True, 2323 new_line=True, 2324 skip_first=True, 2325 skip_last=True, 2326 ) 2327 2328 include_nulls = expression.args.get("include_nulls") 2329 if include_nulls is not None: 2330 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2331 else: 2332 nulls = "" 2333 2334 default_on_null = self.sql(expression, "default_on_null") 2335 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2336 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2337 return self.prepend_ctes(expression, sql)
2380 def update_sql(self, expression: exp.Update) -> str: 2381 this = self.sql(expression, "this") 2382 join_sql, from_sql = self._update_from_joins_sql(expression) 2383 set_sql = self.expressions(expression, flat=True) 2384 where_sql = self.sql(expression, "where") 2385 returning = self.sql(expression, "returning") 2386 order = self.sql(expression, "order") 2387 limit = self.sql(expression, "limit") 2388 if self.RETURNING_END: 2389 expression_sql = f"{from_sql}{where_sql}{returning}" 2390 else: 2391 expression_sql = f"{returning}{from_sql}{where_sql}" 2392 options = self.expressions(expression, key="options") 2393 options = f" OPTION({options})" if options else "" 2394 sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2395 return self.prepend_ctes(expression, sql)
2397 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2398 values_as_table = values_as_table and self.VALUES_AS_TABLE 2399 2400 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2401 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2402 args = self.expressions(expression) 2403 alias = self.sql(expression, "alias") 2404 values = f"VALUES{self.seg('')}{args}" 2405 values = ( 2406 f"({values})" 2407 if self.WRAP_DERIVED_VALUES 2408 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2409 else values 2410 ) 2411 values = self.query_modifiers(expression, values) 2412 return f"{values} AS {alias}" if alias else values 2413 2414 # Converts `VALUES...` expression into a series of select unions. 2415 alias_node = expression.args.get("alias") 2416 column_names = alias_node and alias_node.columns 2417 2418 selects: t.List[exp.Query] = [] 2419 2420 for i, tup in enumerate(expression.expressions): 2421 row = tup.expressions 2422 2423 if i == 0 and column_names: 2424 row = [ 2425 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2426 ] 2427 2428 selects.append(exp.Select(expressions=row)) 2429 2430 if self.pretty: 2431 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2432 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2433 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2434 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2435 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2436 2437 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2438 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2439 return f"({unions}){alias}"
2444 @unsupported_args("expressions") 2445 def into_sql(self, expression: exp.Into) -> str: 2446 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2447 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2448 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2461 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2462 this = self.sql(expression, "this") 2463 2464 columns = self.expressions(expression, flat=True) 2465 2466 from_sql = self.sql(expression, "from_index") 2467 from_sql = f" FROM {from_sql}" if from_sql else "" 2468 2469 properties = expression.args.get("properties") 2470 properties_sql = ( 2471 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2472 ) 2473 2474 return f"{this}({columns}){from_sql}{properties_sql}"
2483 def group_sql(self, expression: exp.Group) -> str: 2484 group_by_all = expression.args.get("all") 2485 if group_by_all is True: 2486 modifier = " ALL" 2487 elif group_by_all is False: 2488 modifier = " DISTINCT" 2489 else: 2490 modifier = "" 2491 2492 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2493 2494 grouping_sets = self.expressions(expression, key="grouping_sets") 2495 cube = self.expressions(expression, key="cube") 2496 rollup = self.expressions(expression, key="rollup") 2497 2498 groupings = csv( 2499 self.seg(grouping_sets) if grouping_sets else "", 2500 self.seg(cube) if cube else "", 2501 self.seg(rollup) if rollup else "", 2502 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2503 sep=self.GROUPINGS_SEP, 2504 ) 2505 2506 if ( 2507 expression.expressions 2508 and groupings 2509 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2510 ): 2511 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2512 2513 return f"{group_by}{groupings}"
2519 def connect_sql(self, expression: exp.Connect) -> str: 2520 start = self.sql(expression, "start") 2521 start = self.seg(f"START WITH {start}") if start else "" 2522 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2523 connect = self.sql(expression, "connect") 2524 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2525 return start + connect
2530 def join_sql(self, expression: exp.Join) -> str: 2531 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2532 side = None 2533 else: 2534 side = expression.side 2535 2536 op_sql = " ".join( 2537 op 2538 for op in ( 2539 expression.method, 2540 "GLOBAL" if expression.args.get("global_") else None, 2541 side, 2542 expression.kind, 2543 expression.hint if self.JOIN_HINTS else None, 2544 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2545 ) 2546 if op 2547 ) 2548 match_cond = self.sql(expression, "match_condition") 2549 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2550 on_sql = self.sql(expression, "on") 2551 using = expression.args.get("using") 2552 2553 if not on_sql and using: 2554 on_sql = csv(*(self.sql(column) for column in using)) 2555 2556 this = expression.this 2557 this_sql = self.sql(this) 2558 2559 exprs = self.expressions(expression) 2560 if exprs: 2561 this_sql = f"{this_sql},{self.seg(exprs)}" 2562 2563 if on_sql: 2564 on_sql = self.indent(on_sql, skip_first=True) 2565 space = self.seg(" " * self.pad) if self.pretty else " " 2566 if using: 2567 on_sql = f"{space}USING ({on_sql})" 2568 else: 2569 on_sql = f"{space}ON {on_sql}" 2570 elif not op_sql: 2571 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2572 return f" {this_sql}" 2573 2574 return f", {this_sql}" 2575 2576 if op_sql != "STRAIGHT_JOIN": 2577 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2578 2579 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2580 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:
2587 def lateral_op(self, expression: exp.Lateral) -> str: 2588 cross_apply = expression.args.get("cross_apply") 2589 2590 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2591 if cross_apply is True: 2592 op = "INNER JOIN " 2593 elif cross_apply is False: 2594 op = "LEFT JOIN " 2595 else: 2596 op = "" 2597 2598 return f"{op}LATERAL"
2600 def lateral_sql(self, expression: exp.Lateral) -> str: 2601 this = self.sql(expression, "this") 2602 2603 if expression.args.get("view"): 2604 alias = expression.args["alias"] 2605 columns = self.expressions(alias, key="columns", flat=True) 2606 table = f" {alias.name}" if alias.name else "" 2607 columns = f" AS {columns}" if columns else "" 2608 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2609 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2610 2611 alias = self.sql(expression, "alias") 2612 alias = f" AS {alias}" if alias else "" 2613 2614 ordinality = expression.args.get("ordinality") or "" 2615 if ordinality: 2616 ordinality = f" WITH ORDINALITY{alias}" 2617 alias = "" 2618 2619 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2621 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2622 this = self.sql(expression, "this") 2623 2624 args = [ 2625 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2626 for e in (expression.args.get(k) for k in ("offset", "expression")) 2627 if e 2628 ] 2629 2630 args_sql = ", ".join(self.sql(e) for e in args) 2631 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2632 expressions = self.expressions(expression, flat=True) 2633 limit_options = self.sql(expression, "limit_options") 2634 expressions = f" BY {expressions}" if expressions else "" 2635 2636 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2638 def offset_sql(self, expression: exp.Offset) -> str: 2639 this = self.sql(expression, "this") 2640 value = expression.expression 2641 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2642 expressions = self.expressions(expression, flat=True) 2643 expressions = f" BY {expressions}" if expressions else "" 2644 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2646 def setitem_sql(self, expression: exp.SetItem) -> str: 2647 kind = self.sql(expression, "kind") 2648 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2649 kind = "" 2650 else: 2651 kind = f"{kind} " if kind else "" 2652 this = self.sql(expression, "this") 2653 expressions = self.expressions(expression) 2654 collate = self.sql(expression, "collate") 2655 collate = f" COLLATE {collate}" if collate else "" 2656 global_ = "GLOBAL " if expression.args.get("global_") else "" 2657 return f"{global_}{kind}{this}{expressions}{collate}"
2664 def queryband_sql(self, expression: exp.QueryBand) -> str: 2665 this = self.sql(expression, "this") 2666 update = " UPDATE" if expression.args.get("update") else "" 2667 scope = self.sql(expression, "scope") 2668 scope = f" FOR {scope}" if scope else "" 2669 2670 return f"QUERY_BAND = {this}{update}{scope}"
2675 def lock_sql(self, expression: exp.Lock) -> str: 2676 if not self.LOCKING_READS_SUPPORTED: 2677 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2678 return "" 2679 2680 update = expression.args["update"] 2681 key = expression.args.get("key") 2682 if update: 2683 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2684 else: 2685 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2686 expressions = self.expressions(expression, flat=True) 2687 expressions = f" OF {expressions}" if expressions else "" 2688 wait = expression.args.get("wait") 2689 2690 if wait is not None: 2691 if isinstance(wait, exp.Literal): 2692 wait = f" WAIT {self.sql(wait)}" 2693 else: 2694 wait = " NOWAIT" if wait else " SKIP LOCKED" 2695 2696 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:
2704 def escape_str( 2705 self, 2706 text: str, 2707 escape_backslash: bool = True, 2708 delimiter: t.Optional[str] = None, 2709 escaped_delimiter: t.Optional[str] = None, 2710 is_byte_string: bool = False, 2711 ) -> str: 2712 if is_byte_string: 2713 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2714 else: 2715 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2716 2717 if supports_escape_sequences: 2718 text = "".join( 2719 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2720 for ch in text 2721 ) 2722 2723 delimiter = delimiter or self.dialect.QUOTE_END 2724 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2725 2726 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2728 def loaddata_sql(self, expression: exp.LoadData) -> str: 2729 local = " LOCAL" if expression.args.get("local") else "" 2730 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2731 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2732 this = f" INTO TABLE {self.sql(expression, 'this')}" 2733 partition = self.sql(expression, "partition") 2734 partition = f" {partition}" if partition else "" 2735 input_format = self.sql(expression, "input_format") 2736 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2737 serde = self.sql(expression, "serde") 2738 serde = f" SERDE {serde}" if serde else "" 2739 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2753 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2754 this = self.sql(expression, "this") 2755 this = f"{this} " if this else this 2756 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2757 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2759 def withfill_sql(self, expression: exp.WithFill) -> str: 2760 from_sql = self.sql(expression, "from_") 2761 from_sql = f" FROM {from_sql}" if from_sql else "" 2762 to_sql = self.sql(expression, "to") 2763 to_sql = f" TO {to_sql}" if to_sql else "" 2764 step_sql = self.sql(expression, "step") 2765 step_sql = f" STEP {step_sql}" if step_sql else "" 2766 interpolated_values = [ 2767 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2768 if isinstance(e, exp.Alias) 2769 else self.sql(e, "this") 2770 for e in expression.args.get("interpolate") or [] 2771 ] 2772 interpolate = ( 2773 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2774 ) 2775 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2786 def ordered_sql(self, expression: exp.Ordered) -> str: 2787 desc = expression.args.get("desc") 2788 asc = not desc 2789 2790 nulls_first = expression.args.get("nulls_first") 2791 nulls_last = not nulls_first 2792 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2793 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2794 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2795 2796 this = self.sql(expression, "this") 2797 2798 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2799 nulls_sort_change = "" 2800 if nulls_first and ( 2801 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2802 ): 2803 nulls_sort_change = " NULLS FIRST" 2804 elif ( 2805 nulls_last 2806 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2807 and not nulls_are_last 2808 ): 2809 nulls_sort_change = " NULLS LAST" 2810 2811 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2812 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2813 window = expression.find_ancestor(exp.Window, exp.Select) 2814 if isinstance(window, exp.Window) and window.args.get("spec"): 2815 self.unsupported( 2816 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2817 ) 2818 nulls_sort_change = "" 2819 elif self.NULL_ORDERING_SUPPORTED is False and ( 2820 (asc and nulls_sort_change == " NULLS LAST") 2821 or (desc and nulls_sort_change == " NULLS FIRST") 2822 ): 2823 # BigQuery does not allow these ordering/nulls combinations when used under 2824 # an aggregation func or under a window containing one 2825 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2826 2827 if isinstance(ancestor, exp.Window): 2828 ancestor = ancestor.this 2829 if isinstance(ancestor, exp.AggFunc): 2830 self.unsupported( 2831 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2832 ) 2833 nulls_sort_change = "" 2834 elif self.NULL_ORDERING_SUPPORTED is None: 2835 if expression.this.is_int: 2836 self.unsupported( 2837 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2838 ) 2839 elif not isinstance(expression.this, exp.Rand): 2840 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2841 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2842 nulls_sort_change = "" 2843 2844 with_fill = self.sql(expression, "with_fill") 2845 with_fill = f" {with_fill}" if with_fill else "" 2846 2847 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2857 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2858 partition = self.partition_by_sql(expression) 2859 order = self.sql(expression, "order") 2860 measures = self.expressions(expression, key="measures") 2861 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2862 rows = self.sql(expression, "rows") 2863 rows = self.seg(rows) if rows else "" 2864 after = self.sql(expression, "after") 2865 after = self.seg(after) if after else "" 2866 pattern = self.sql(expression, "pattern") 2867 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2868 definition_sqls = [ 2869 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2870 for definition in expression.args.get("define", []) 2871 ] 2872 definitions = self.expressions(sqls=definition_sqls) 2873 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2874 body = "".join( 2875 ( 2876 partition, 2877 order, 2878 measures, 2879 rows, 2880 after, 2881 pattern, 2882 define, 2883 ) 2884 ) 2885 alias = self.sql(expression, "alias") 2886 alias = f" {alias}" if alias else "" 2887 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2889 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2890 limit = expression.args.get("limit") 2891 2892 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2893 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2894 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2895 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2896 2897 return csv( 2898 *sqls, 2899 *[self.sql(join) for join in expression.args.get("joins") or []], 2900 self.sql(expression, "match"), 2901 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2902 self.sql(expression, "prewhere"), 2903 self.sql(expression, "where"), 2904 self.sql(expression, "connect"), 2905 self.sql(expression, "group"), 2906 self.sql(expression, "having"), 2907 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2908 self.sql(expression, "order"), 2909 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2910 *self.after_limit_modifiers(expression), 2911 self.options_modifier(expression), 2912 self.for_modifiers(expression), 2913 sep="", 2914 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2928 def offset_limit_modifiers( 2929 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2930 ) -> t.List[str]: 2931 return [ 2932 self.sql(expression, "offset") if fetch else self.sql(limit), 2933 self.sql(limit) if fetch else self.sql(expression, "offset"), 2934 ]
2941 def select_sql(self, expression: exp.Select) -> str: 2942 into = expression.args.get("into") 2943 if not self.SUPPORTS_SELECT_INTO and into: 2944 into.pop() 2945 2946 hint = self.sql(expression, "hint") 2947 distinct = self.sql(expression, "distinct") 2948 distinct = f" {distinct}" if distinct else "" 2949 kind = self.sql(expression, "kind") 2950 2951 limit = expression.args.get("limit") 2952 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2953 top = self.limit_sql(limit, top=True) 2954 limit.pop() 2955 else: 2956 top = "" 2957 2958 expressions = self.expressions(expression) 2959 2960 if kind: 2961 if kind in self.SELECT_KINDS: 2962 kind = f" AS {kind}" 2963 else: 2964 if kind == "STRUCT": 2965 expressions = self.expressions( 2966 sqls=[ 2967 self.sql( 2968 exp.Struct( 2969 expressions=[ 2970 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2971 if isinstance(e, exp.Alias) 2972 else e 2973 for e in expression.expressions 2974 ] 2975 ) 2976 ) 2977 ] 2978 ) 2979 kind = "" 2980 2981 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2982 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2983 2984 exclude = expression.args.get("exclude") 2985 2986 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 2987 exclude_sql = self.expressions(sqls=exclude, flat=True) 2988 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 2989 2990 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2991 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2992 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2993 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2994 sql = self.query_modifiers( 2995 expression, 2996 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2997 self.sql(expression, "into", comment=False), 2998 self.sql(expression, "from_", comment=False), 2999 ) 3000 3001 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3002 if expression.args.get("with_"): 3003 sql = self.maybe_comment(sql, expression) 3004 expression.pop_comments() 3005 3006 sql = self.prepend_ctes(expression, sql) 3007 3008 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3009 expression.set("exclude", None) 3010 subquery = expression.subquery(copy=False) 3011 star = exp.Star(except_=exclude) 3012 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3013 3014 if not self.SUPPORTS_SELECT_INTO and into: 3015 if into.args.get("temporary"): 3016 table_kind = " TEMPORARY" 3017 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3018 table_kind = " UNLOGGED" 3019 else: 3020 table_kind = "" 3021 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3022 3023 return sql
3035 def star_sql(self, expression: exp.Star) -> str: 3036 except_ = self.expressions(expression, key="except_", flat=True) 3037 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3038 replace = self.expressions(expression, key="replace", flat=True) 3039 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3040 rename = self.expressions(expression, key="rename", flat=True) 3041 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3042 return f"*{except_}{replace}{rename}"
3058 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3059 alias = self.sql(expression, "alias") 3060 alias = f"{sep}{alias}" if alias else "" 3061 sample = self.sql(expression, "sample") 3062 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3063 alias = f"{sample}{alias}" 3064 3065 # Set to None so it's not generated again by self.query_modifiers() 3066 expression.set("sample", None) 3067 3068 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3069 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3070 return self.prepend_ctes(expression, sql)
3076 def unnest_sql(self, expression: exp.Unnest) -> str: 3077 args = self.expressions(expression, flat=True) 3078 3079 alias = expression.args.get("alias") 3080 offset = expression.args.get("offset") 3081 3082 if self.UNNEST_WITH_ORDINALITY: 3083 if alias and isinstance(offset, exp.Expression): 3084 alias.append("columns", offset) 3085 3086 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3087 columns = alias.columns 3088 alias = self.sql(columns[0]) if columns else "" 3089 else: 3090 alias = self.sql(alias) 3091 3092 alias = f" AS {alias}" if alias else alias 3093 if self.UNNEST_WITH_ORDINALITY: 3094 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3095 else: 3096 if isinstance(offset, exp.Expression): 3097 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3098 elif offset: 3099 suffix = f"{alias} WITH OFFSET" 3100 else: 3101 suffix = alias 3102 3103 return f"UNNEST({args}){suffix}"
3112 def window_sql(self, expression: exp.Window) -> str: 3113 this = self.sql(expression, "this") 3114 partition = self.partition_by_sql(expression) 3115 order = expression.args.get("order") 3116 order = self.order_sql(order, flat=True) if order else "" 3117 spec = self.sql(expression, "spec") 3118 alias = self.sql(expression, "alias") 3119 over = self.sql(expression, "over") or "OVER" 3120 3121 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3122 3123 first = expression.args.get("first") 3124 if first is None: 3125 first = "" 3126 else: 3127 first = "FIRST" if first else "LAST" 3128 3129 if not partition and not order and not spec and alias: 3130 return f"{this} {alias}" 3131 3132 args = self.format_args( 3133 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3134 ) 3135 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
3141 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3142 kind = self.sql(expression, "kind") 3143 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3144 end = ( 3145 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3146 or "CURRENT ROW" 3147 ) 3148 3149 window_spec = f"{kind} BETWEEN {start} AND {end}" 3150 3151 exclude = self.sql(expression, "exclude") 3152 if exclude: 3153 if self.SUPPORTS_WINDOW_EXCLUDE: 3154 window_spec += f" EXCLUDE {exclude}" 3155 else: 3156 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3157 3158 return window_spec
3165 def between_sql(self, expression: exp.Between) -> str: 3166 this = self.sql(expression, "this") 3167 low = self.sql(expression, "low") 3168 high = self.sql(expression, "high") 3169 symmetric = expression.args.get("symmetric") 3170 3171 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3172 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3173 3174 flag = ( 3175 " SYMMETRIC" 3176 if symmetric 3177 else " ASYMMETRIC" 3178 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3179 else "" # silently drop ASYMMETRIC – semantics identical 3180 ) 3181 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]:
3183 def bracket_offset_expressions( 3184 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 3185 ) -> t.List[exp.Expression]: 3186 return apply_index_offset( 3187 expression.this, 3188 expression.expressions, 3189 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3190 dialect=self.dialect, 3191 )
3204 def any_sql(self, expression: exp.Any) -> str: 3205 this = self.sql(expression, "this") 3206 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3207 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3208 this = self.wrap(this) 3209 return f"ANY{this}" 3210 return f"ANY {this}"
3215 def case_sql(self, expression: exp.Case) -> str: 3216 this = self.sql(expression, "this") 3217 statements = [f"CASE {this}" if this else "CASE"] 3218 3219 for e in expression.args["ifs"]: 3220 statements.append(f"WHEN {self.sql(e, 'this')}") 3221 statements.append(f"THEN {self.sql(e, 'true')}") 3222 3223 default = self.sql(expression, "default") 3224 3225 if default: 3226 statements.append(f"ELSE {default}") 3227 3228 statements.append("END") 3229 3230 if self.pretty and self.too_wide(statements): 3231 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3232 3233 return " ".join(statements)
3245 def extract_sql(self, expression: exp.Extract) -> str: 3246 from sqlglot.dialects.dialect import map_date_part 3247 3248 this = ( 3249 map_date_part(expression.this, self.dialect) 3250 if self.NORMALIZE_EXTRACT_DATE_PARTS 3251 else expression.this 3252 ) 3253 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3254 expression_sql = self.sql(expression, "expression") 3255 3256 return f"EXTRACT({this_sql} FROM {expression_sql})"
3258 def trim_sql(self, expression: exp.Trim) -> str: 3259 trim_type = self.sql(expression, "position") 3260 3261 if trim_type == "LEADING": 3262 func_name = "LTRIM" 3263 elif trim_type == "TRAILING": 3264 func_name = "RTRIM" 3265 else: 3266 func_name = "TRIM" 3267 3268 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]:
3270 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3271 args = expression.expressions 3272 if isinstance(expression, exp.ConcatWs): 3273 args = args[1:] # Skip the delimiter 3274 3275 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3276 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3277 3278 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3279 3280 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3281 if not e.type: 3282 from sqlglot.optimizer.annotate_types import annotate_types 3283 3284 e = annotate_types(e, dialect=self.dialect) 3285 3286 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3287 return e 3288 3289 return exp.func("coalesce", e, exp.Literal.string("")) 3290 3291 args = [_wrap_with_coalesce(e) for e in args] 3292 3293 return args
3295 def concat_sql(self, expression: exp.Concat) -> str: 3296 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3297 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3298 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3299 # instead of coalescing them to empty string. 3300 from sqlglot.dialects.dialect import concat_to_dpipe_sql 3301 3302 return concat_to_dpipe_sql(self, expression) 3303 3304 expressions = self.convert_concat_args(expression) 3305 3306 # Some dialects don't allow a single-argument CONCAT call 3307 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3308 return self.sql(expressions[0]) 3309 3310 return self.func("CONCAT", *expressions)
3321 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3322 expressions = self.expressions(expression, flat=True) 3323 expressions = f" ({expressions})" if expressions else "" 3324 reference = self.sql(expression, "reference") 3325 reference = f" {reference}" if reference else "" 3326 delete = self.sql(expression, "delete") 3327 delete = f" ON DELETE {delete}" if delete else "" 3328 update = self.sql(expression, "update") 3329 update = f" ON UPDATE {update}" if update else "" 3330 options = self.expressions(expression, key="options", flat=True, sep=" ") 3331 options = f" {options}" if options else "" 3332 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3334 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3335 this = self.sql(expression, "this") 3336 this = f" {this}" if this else "" 3337 expressions = self.expressions(expression, flat=True) 3338 include = self.sql(expression, "include") 3339 options = self.expressions(expression, key="options", flat=True, sep=" ") 3340 options = f" {options}" if options else "" 3341 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3346 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3347 if self.MATCH_AGAINST_TABLE_PREFIX: 3348 expressions = [] 3349 for expr in expression.expressions: 3350 if isinstance(expr, exp.Table): 3351 expressions.append(f"TABLE {self.sql(expr)}") 3352 else: 3353 expressions.append(expr) 3354 else: 3355 expressions = expression.expressions 3356 3357 modifier = expression.args.get("modifier") 3358 modifier = f" {modifier}" if modifier else "" 3359 return ( 3360 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3361 )
3366 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3367 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3368 3369 if expression.args.get("escape"): 3370 path = self.escape_str(path) 3371 3372 if self.QUOTE_JSON_PATH: 3373 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3374 3375 return path
3377 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3378 if isinstance(expression, exp.JSONPathPart): 3379 transform = self.TRANSFORMS.get(expression.__class__) 3380 if not callable(transform): 3381 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3382 return "" 3383 3384 return transform(self, expression) 3385 3386 if isinstance(expression, int): 3387 return str(expression) 3388 3389 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3390 escaped = expression.replace("'", "\\'") 3391 escaped = f"\\'{expression}\\'" 3392 else: 3393 escaped = expression.replace('"', '\\"') 3394 escaped = f'"{escaped}"' 3395 3396 return escaped
3401 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3402 # Output the Teradata column FORMAT override. 3403 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3404 this = self.sql(expression, "this") 3405 fmt = self.sql(expression, "format") 3406 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3408 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3409 null_handling = expression.args.get("null_handling") 3410 null_handling = f" {null_handling}" if null_handling else "" 3411 3412 unique_keys = expression.args.get("unique_keys") 3413 if unique_keys is not None: 3414 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3415 else: 3416 unique_keys = "" 3417 3418 return_type = self.sql(expression, "return_type") 3419 return_type = f" RETURNING {return_type}" if return_type else "" 3420 encoding = self.sql(expression, "encoding") 3421 encoding = f" ENCODING {encoding}" if encoding else "" 3422 3423 return self.func( 3424 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3425 *expression.expressions, 3426 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3427 )
3432 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3433 null_handling = expression.args.get("null_handling") 3434 null_handling = f" {null_handling}" if null_handling else "" 3435 return_type = self.sql(expression, "return_type") 3436 return_type = f" RETURNING {return_type}" if return_type else "" 3437 strict = " STRICT" if expression.args.get("strict") else "" 3438 return self.func( 3439 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3440 )
3442 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3443 this = self.sql(expression, "this") 3444 order = self.sql(expression, "order") 3445 null_handling = expression.args.get("null_handling") 3446 null_handling = f" {null_handling}" if null_handling else "" 3447 return_type = self.sql(expression, "return_type") 3448 return_type = f" RETURNING {return_type}" if return_type else "" 3449 strict = " STRICT" if expression.args.get("strict") else "" 3450 return self.func( 3451 "JSON_ARRAYAGG", 3452 this, 3453 suffix=f"{order}{null_handling}{return_type}{strict})", 3454 )
3456 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3457 path = self.sql(expression, "path") 3458 path = f" PATH {path}" if path else "" 3459 nested_schema = self.sql(expression, "nested_schema") 3460 3461 if nested_schema: 3462 return f"NESTED{path} {nested_schema}" 3463 3464 this = self.sql(expression, "this") 3465 kind = self.sql(expression, "kind") 3466 kind = f" {kind}" if kind else "" 3467 3468 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3469 return f"{this}{kind}{path}{ordinality}"
3474 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3475 this = self.sql(expression, "this") 3476 path = self.sql(expression, "path") 3477 path = f", {path}" if path else "" 3478 error_handling = expression.args.get("error_handling") 3479 error_handling = f" {error_handling}" if error_handling else "" 3480 empty_handling = expression.args.get("empty_handling") 3481 empty_handling = f" {empty_handling}" if empty_handling else "" 3482 schema = self.sql(expression, "schema") 3483 return self.func( 3484 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3485 )
3487 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3488 this = self.sql(expression, "this") 3489 kind = self.sql(expression, "kind") 3490 path = self.sql(expression, "path") 3491 path = f" {path}" if path else "" 3492 as_json = " AS JSON" if expression.args.get("as_json") else "" 3493 return f"{this} {kind}{path}{as_json}"
3495 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3496 this = self.sql(expression, "this") 3497 path = self.sql(expression, "path") 3498 path = f", {path}" if path else "" 3499 expressions = self.expressions(expression) 3500 with_ = ( 3501 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3502 if expressions 3503 else "" 3504 ) 3505 return f"OPENJSON({this}{path}){with_}"
3507 def in_sql(self, expression: exp.In) -> str: 3508 query = expression.args.get("query") 3509 unnest = expression.args.get("unnest") 3510 field = expression.args.get("field") 3511 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3512 3513 if query: 3514 in_sql = self.sql(query) 3515 elif unnest: 3516 in_sql = self.in_unnest_op(unnest) 3517 elif field: 3518 in_sql = self.sql(field) 3519 else: 3520 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3521 3522 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3527 def interval_sql(self, expression: exp.Interval) -> str: 3528 unit_expression = expression.args.get("unit") 3529 unit = self.sql(unit_expression) if unit_expression else "" 3530 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3531 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3532 unit = f" {unit}" if unit else "" 3533 3534 if self.SINGLE_STRING_INTERVAL: 3535 this = expression.this.name if expression.this else "" 3536 if this: 3537 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3538 return f"INTERVAL '{this}'{unit}" 3539 return f"INTERVAL '{this}{unit}'" 3540 return f"INTERVAL{unit}" 3541 3542 this = self.sql(expression, "this") 3543 if this: 3544 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3545 this = f" {this}" if unwrapped else f" ({this})" 3546 3547 return f"INTERVAL{this}{unit}"
3552 def reference_sql(self, expression: exp.Reference) -> str: 3553 this = self.sql(expression, "this") 3554 expressions = self.expressions(expression, flat=True) 3555 expressions = f"({expressions})" if expressions else "" 3556 options = self.expressions(expression, key="options", flat=True, sep=" ") 3557 options = f" {options}" if options else "" 3558 return f"REFERENCES {this}{expressions}{options}"
3560 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3561 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3562 parent = expression.parent 3563 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3564 3565 return self.func( 3566 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3567 )
3587 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3588 alias = expression.args["alias"] 3589 3590 parent = expression.parent 3591 pivot = parent and parent.parent 3592 3593 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3594 identifier_alias = isinstance(alias, exp.Identifier) 3595 literal_alias = isinstance(alias, exp.Literal) 3596 3597 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3598 alias.replace(exp.Literal.string(alias.output_name)) 3599 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3600 alias.replace(exp.to_identifier(alias.output_name)) 3601 3602 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:
3640 def connector_sql( 3641 self, 3642 expression: exp.Connector, 3643 op: str, 3644 stack: t.Optional[t.List[str | exp.Expression]] = None, 3645 ) -> str: 3646 if stack is not None: 3647 if expression.expressions: 3648 stack.append(self.expressions(expression, sep=f" {op} ")) 3649 else: 3650 stack.append(expression.right) 3651 if expression.comments and self.comments: 3652 for comment in expression.comments: 3653 if comment: 3654 op += f" /*{self.sanitize_comment(comment)}*/" 3655 stack.extend((op, expression.left)) 3656 return op 3657 3658 stack = [expression] 3659 sqls: t.List[str] = [] 3660 ops = set() 3661 3662 while stack: 3663 node = stack.pop() 3664 if isinstance(node, exp.Connector): 3665 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3666 else: 3667 sql = self.sql(node) 3668 if sqls and sqls[-1] in ops: 3669 sqls[-1] += f" {sql}" 3670 else: 3671 sqls.append(sql) 3672 3673 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3674 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3694 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3695 format_sql = self.sql(expression, "format") 3696 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3697 to_sql = self.sql(expression, "to") 3698 to_sql = f" {to_sql}" if to_sql else "" 3699 action = self.sql(expression, "action") 3700 action = f" {action}" if action else "" 3701 default = self.sql(expression, "default") 3702 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3703 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3721 def comment_sql(self, expression: exp.Comment) -> str: 3722 this = self.sql(expression, "this") 3723 kind = expression.args["kind"] 3724 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3725 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3726 expression_sql = self.sql(expression, "expression") 3727 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3729 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3730 this = self.sql(expression, "this") 3731 delete = " DELETE" if expression.args.get("delete") else "" 3732 recompress = self.sql(expression, "recompress") 3733 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3734 to_disk = self.sql(expression, "to_disk") 3735 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3736 to_volume = self.sql(expression, "to_volume") 3737 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3738 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3740 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3741 where = self.sql(expression, "where") 3742 group = self.sql(expression, "group") 3743 aggregates = self.expressions(expression, key="aggregates") 3744 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3745 3746 if not (where or group or aggregates) and len(expression.expressions) == 1: 3747 return f"TTL {self.expressions(expression, flat=True)}" 3748 3749 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3768 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3769 this = self.sql(expression, "this") 3770 3771 dtype = self.sql(expression, "dtype") 3772 if dtype: 3773 collate = self.sql(expression, "collate") 3774 collate = f" COLLATE {collate}" if collate else "" 3775 using = self.sql(expression, "using") 3776 using = f" USING {using}" if using else "" 3777 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3778 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3779 3780 default = self.sql(expression, "default") 3781 if default: 3782 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3783 3784 comment = self.sql(expression, "comment") 3785 if comment: 3786 return f"ALTER COLUMN {this} COMMENT {comment}" 3787 3788 visible = expression.args.get("visible") 3789 if visible: 3790 return f"ALTER COLUMN {this} SET {visible}" 3791 3792 allow_null = expression.args.get("allow_null") 3793 drop = expression.args.get("drop") 3794 3795 if not drop and not allow_null: 3796 self.unsupported("Unsupported ALTER COLUMN syntax") 3797 3798 if allow_null is not None: 3799 keyword = "DROP" if drop else "SET" 3800 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3801 3802 return f"ALTER COLUMN {this} DROP DEFAULT"
3818 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3819 compound = " COMPOUND" if expression.args.get("compound") else "" 3820 this = self.sql(expression, "this") 3821 expressions = self.expressions(expression, flat=True) 3822 expressions = f"({expressions})" if expressions else "" 3823 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.AlterRename, include_to: bool = True) -> str:
3825 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3826 if not self.RENAME_TABLE_WITH_DB: 3827 # Remove db from tables 3828 expression = expression.transform( 3829 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3830 ).assert_is(exp.AlterRename) 3831 this = self.sql(expression, "this") 3832 to_kw = " TO" if include_to else "" 3833 return f"RENAME{to_kw} {this}"
3848 def alter_sql(self, expression: exp.Alter) -> str: 3849 actions = expression.args["actions"] 3850 3851 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3852 actions[0], exp.ColumnDef 3853 ): 3854 actions_sql = self.expressions(expression, key="actions", flat=True) 3855 actions_sql = f"ADD {actions_sql}" 3856 else: 3857 actions_list = [] 3858 for action in actions: 3859 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3860 action_sql = self.add_column_sql(action) 3861 else: 3862 action_sql = self.sql(action) 3863 if isinstance(action, exp.Query): 3864 action_sql = f"AS {action_sql}" 3865 3866 actions_list.append(action_sql) 3867 3868 actions_sql = self.format_args(*actions_list).lstrip("\n") 3869 3870 exists = " IF EXISTS" if expression.args.get("exists") else "" 3871 on_cluster = self.sql(expression, "cluster") 3872 on_cluster = f" {on_cluster}" if on_cluster else "" 3873 only = " ONLY" if expression.args.get("only") else "" 3874 options = self.expressions(expression, key="options") 3875 options = f", {options}" if options else "" 3876 kind = self.sql(expression, "kind") 3877 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3878 check = " WITH CHECK" if expression.args.get("check") else "" 3879 cascade = ( 3880 " CASCADE" 3881 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 3882 else "" 3883 ) 3884 this = self.sql(expression, "this") 3885 this = f" {this}" if this else "" 3886 3887 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
3894 def add_column_sql(self, expression: exp.Expression) -> str: 3895 sql = self.sql(expression) 3896 if isinstance(expression, exp.Schema): 3897 column_text = " COLUMNS" 3898 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3899 column_text = " COLUMN" 3900 else: 3901 column_text = "" 3902 3903 return f"ADD{column_text} {sql}"
3913 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3914 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3915 location = self.sql(expression, "location") 3916 location = f" {location}" if location else "" 3917 return f"ADD {exists}{self.sql(expression.this)}{location}"
3919 def distinct_sql(self, expression: exp.Distinct) -> str: 3920 this = self.expressions(expression, flat=True) 3921 3922 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3923 case = exp.case() 3924 for arg in expression.expressions: 3925 case = case.when(arg.is_(exp.null()), exp.null()) 3926 this = self.sql(case.else_(f"({this})")) 3927 3928 this = f" {this}" if this else "" 3929 3930 on = self.sql(expression, "on") 3931 on = f" ON {on}" if on else "" 3932 return f"DISTINCT{this}{on}"
3961 def div_sql(self, expression: exp.Div) -> str: 3962 l, r = expression.left, expression.right 3963 3964 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3965 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3966 3967 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3968 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3969 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3970 3971 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3972 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3973 return self.sql( 3974 exp.cast( 3975 l / r, 3976 to=exp.DataType.Type.BIGINT, 3977 ) 3978 ) 3979 3980 return self.binary(expression, "/")
4101 def log_sql(self, expression: exp.Log) -> str: 4102 this = expression.this 4103 expr = expression.expression 4104 4105 if self.dialect.LOG_BASE_FIRST is False: 4106 this, expr = expr, this 4107 elif self.dialect.LOG_BASE_FIRST is None and expr: 4108 if this.name in ("2", "10"): 4109 return self.func(f"LOG{this.name}", expr) 4110 4111 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4112 4113 return self.func("LOG", this, expr)
4122 def binary(self, expression: exp.Binary, op: str) -> str: 4123 sqls: t.List[str] = [] 4124 stack: t.List[t.Union[str, exp.Expression]] = [expression] 4125 binary_type = type(expression) 4126 4127 while stack: 4128 node = stack.pop() 4129 4130 if type(node) is binary_type: 4131 op_func = node.args.get("operator") 4132 if op_func: 4133 op = f"OPERATOR({self.sql(op_func)})" 4134 4135 stack.append(node.right) 4136 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4137 stack.append(node.left) 4138 else: 4139 sqls.append(self.sql(node)) 4140 4141 return "".join(sqls)
4150 def function_fallback_sql(self, expression: exp.Func) -> str: 4151 args = [] 4152 4153 for key in expression.arg_types: 4154 arg_value = expression.args.get(key) 4155 4156 if isinstance(arg_value, list): 4157 for value in arg_value: 4158 args.append(value) 4159 elif arg_value is not None: 4160 args.append(arg_value) 4161 4162 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4163 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4164 else: 4165 name = expression.sql_name() 4166 4167 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:
4169 def func( 4170 self, 4171 name: str, 4172 *args: t.Optional[exp.Expression | str], 4173 prefix: str = "(", 4174 suffix: str = ")", 4175 normalize: bool = True, 4176 ) -> str: 4177 name = self.normalize_func(name) if normalize else name 4178 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
4180 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 4181 arg_sqls = tuple( 4182 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4183 ) 4184 if self.pretty and self.too_wide(arg_sqls): 4185 return self.indent( 4186 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4187 ) 4188 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]:
4193 def format_time( 4194 self, 4195 expression: exp.Expression, 4196 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 4197 inverse_time_trie: t.Optional[t.Dict] = None, 4198 ) -> t.Optional[str]: 4199 return format_time( 4200 self.sql(expression, "format"), 4201 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4202 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4203 )
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:
4205 def expressions( 4206 self, 4207 expression: t.Optional[exp.Expression] = None, 4208 key: t.Optional[str] = None, 4209 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 4210 flat: bool = False, 4211 indent: bool = True, 4212 skip_first: bool = False, 4213 skip_last: bool = False, 4214 sep: str = ", ", 4215 prefix: str = "", 4216 dynamic: bool = False, 4217 new_line: bool = False, 4218 ) -> str: 4219 expressions = expression.args.get(key or "expressions") if expression else sqls 4220 4221 if not expressions: 4222 return "" 4223 4224 if flat: 4225 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4226 4227 num_sqls = len(expressions) 4228 result_sqls = [] 4229 4230 for i, e in enumerate(expressions): 4231 sql = self.sql(e, comment=False) 4232 if not sql: 4233 continue 4234 4235 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 4236 4237 if self.pretty: 4238 if self.leading_comma: 4239 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4240 else: 4241 result_sqls.append( 4242 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4243 ) 4244 else: 4245 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4246 4247 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4248 if new_line: 4249 result_sqls.insert(0, "") 4250 result_sqls.append("") 4251 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4252 else: 4253 result_sql = "".join(result_sqls) 4254 4255 return ( 4256 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4257 if indent 4258 else result_sql 4259 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
4261 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 4262 flat = flat or isinstance(expression.parent, exp.Properties) 4263 expressions_sql = self.expressions(expression, flat=flat) 4264 if flat: 4265 return f"{op} {expressions_sql}" 4266 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4268 def naked_property(self, expression: exp.Property) -> str: 4269 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4270 if not property_name: 4271 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4272 return f"{property_name} {self.sql(expression, 'this')}"
4280 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4281 this = self.sql(expression, "this") 4282 expressions = self.no_identify(self.expressions, expression) 4283 expressions = ( 4284 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4285 ) 4286 return f"{this}{expressions}" if expressions.strip() != "" else this
4296 def when_sql(self, expression: exp.When) -> str: 4297 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4298 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4299 condition = self.sql(expression, "condition") 4300 condition = f" AND {condition}" if condition else "" 4301 4302 then_expression = expression.args.get("then") 4303 if isinstance(then_expression, exp.Insert): 4304 this = self.sql(then_expression, "this") 4305 this = f"INSERT {this}" if this else "INSERT" 4306 then = self.sql(then_expression, "expression") 4307 then = f"{this} VALUES {then}" if then else this 4308 elif isinstance(then_expression, exp.Update): 4309 if isinstance(then_expression.args.get("expressions"), exp.Star): 4310 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4311 else: 4312 expressions_sql = self.expressions(then_expression) 4313 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4314 4315 else: 4316 then = self.sql(then_expression) 4317 return f"WHEN {matched}{source}{condition} THEN {then}"
4322 def merge_sql(self, expression: exp.Merge) -> str: 4323 table = expression.this 4324 table_alias = "" 4325 4326 hints = table.args.get("hints") 4327 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4328 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4329 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4330 4331 this = self.sql(table) 4332 using = f"USING {self.sql(expression, 'using')}" 4333 whens = self.sql(expression, "whens") 4334 4335 on = self.sql(expression, "on") 4336 on = f"ON {on}" if on else "" 4337 4338 if not on: 4339 on = self.expressions(expression, key="using_cond") 4340 on = f"USING ({on})" if on else "" 4341 4342 returning = self.sql(expression, "returning") 4343 if returning: 4344 whens = f"{whens}{returning}" 4345 4346 sep = self.sep() 4347 4348 return self.prepend_ctes( 4349 expression, 4350 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4351 )
4357 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4358 if not self.SUPPORTS_TO_NUMBER: 4359 self.unsupported("Unsupported TO_NUMBER function") 4360 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4361 4362 fmt = expression.args.get("format") 4363 if not fmt: 4364 self.unsupported("Conversion format is required for TO_NUMBER") 4365 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4366 4367 return self.func("TO_NUMBER", expression.this, fmt)
4369 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4370 this = self.sql(expression, "this") 4371 kind = self.sql(expression, "kind") 4372 settings_sql = self.expressions(expression, key="settings", sep=" ") 4373 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4374 return f"{this}({kind}{args})"
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
4395 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4396 expressions = self.expressions(expression, flat=True) 4397 expressions = f" {self.wrap(expressions)}" if expressions else "" 4398 buckets = self.sql(expression, "buckets") 4399 kind = self.sql(expression, "kind") 4400 buckets = f" BUCKETS {buckets}" if buckets else "" 4401 order = self.sql(expression, "order") 4402 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4407 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4408 expressions = self.expressions(expression, key="expressions", flat=True) 4409 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4410 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4411 buckets = self.sql(expression, "buckets") 4412 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4414 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4415 this = self.sql(expression, "this") 4416 having = self.sql(expression, "having") 4417 4418 if having: 4419 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4420 4421 return self.func("ANY_VALUE", this)
4423 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4424 transform = self.func("TRANSFORM", *expression.expressions) 4425 row_format_before = self.sql(expression, "row_format_before") 4426 row_format_before = f" {row_format_before}" if row_format_before else "" 4427 record_writer = self.sql(expression, "record_writer") 4428 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4429 using = f" USING {self.sql(expression, 'command_script')}" 4430 schema = self.sql(expression, "schema") 4431 schema = f" AS {schema}" if schema else "" 4432 row_format_after = self.sql(expression, "row_format_after") 4433 row_format_after = f" {row_format_after}" if row_format_after else "" 4434 record_reader = self.sql(expression, "record_reader") 4435 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4436 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4438 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4439 key_block_size = self.sql(expression, "key_block_size") 4440 if key_block_size: 4441 return f"KEY_BLOCK_SIZE = {key_block_size}" 4442 4443 using = self.sql(expression, "using") 4444 if using: 4445 return f"USING {using}" 4446 4447 parser = self.sql(expression, "parser") 4448 if parser: 4449 return f"WITH PARSER {parser}" 4450 4451 comment = self.sql(expression, "comment") 4452 if comment: 4453 return f"COMMENT {comment}" 4454 4455 visible = expression.args.get("visible") 4456 if visible is not None: 4457 return "VISIBLE" if visible else "INVISIBLE" 4458 4459 engine_attr = self.sql(expression, "engine_attr") 4460 if engine_attr: 4461 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4462 4463 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4464 if secondary_engine_attr: 4465 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4466 4467 self.unsupported("Unsupported index constraint option.") 4468 return ""
4474 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4475 kind = self.sql(expression, "kind") 4476 kind = f"{kind} INDEX" if kind else "INDEX" 4477 this = self.sql(expression, "this") 4478 this = f" {this}" if this else "" 4479 index_type = self.sql(expression, "index_type") 4480 index_type = f" USING {index_type}" if index_type else "" 4481 expressions = self.expressions(expression, flat=True) 4482 expressions = f" ({expressions})" if expressions else "" 4483 options = self.expressions(expression, key="options", sep=" ") 4484 options = f" {options}" if options else "" 4485 return f"{kind}{this}{index_type}{expressions}{options}"
4487 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4488 if self.NVL2_SUPPORTED: 4489 return self.function_fallback_sql(expression) 4490 4491 case = exp.Case().when( 4492 expression.this.is_(exp.null()).not_(copy=False), 4493 expression.args["true"], 4494 copy=False, 4495 ) 4496 else_cond = expression.args.get("false") 4497 if else_cond: 4498 case.else_(else_cond, copy=False) 4499 4500 return self.sql(case)
4502 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4503 this = self.sql(expression, "this") 4504 expr = self.sql(expression, "expression") 4505 position = self.sql(expression, "position") 4506 position = f", {position}" if position else "" 4507 iterator = self.sql(expression, "iterator") 4508 condition = self.sql(expression, "condition") 4509 condition = f" IF {condition}" if condition else "" 4510 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4545 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4546 this_sql = self.sql(expression, "this") 4547 if isinstance(expression.this, exp.Table): 4548 this_sql = f"TABLE {this_sql}" 4549 4550 return self.func( 4551 "FEATURES_AT_TIME", 4552 this_sql, 4553 expression.args.get("time"), 4554 expression.args.get("num_rows"), 4555 expression.args.get("ignore_feature_nulls"), 4556 )
4558 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4559 this_sql = self.sql(expression, "this") 4560 if isinstance(expression.this, exp.Table): 4561 this_sql = f"TABLE {this_sql}" 4562 4563 query_table = self.sql(expression, "query_table") 4564 if isinstance(expression.args["query_table"], exp.Table): 4565 query_table = f"TABLE {query_table}" 4566 4567 return self.func( 4568 "VECTOR_SEARCH", 4569 this_sql, 4570 expression.args.get("column_to_search"), 4571 query_table, 4572 expression.args.get("query_column_to_search"), 4573 expression.args.get("top_k"), 4574 expression.args.get("distance_type"), 4575 expression.args.get("options"), 4576 )
4588 def toarray_sql(self, expression: exp.ToArray) -> str: 4589 arg = expression.this 4590 if not arg.type: 4591 from sqlglot.optimizer.annotate_types import annotate_types 4592 4593 arg = annotate_types(arg, dialect=self.dialect) 4594 4595 if arg.is_type(exp.DataType.Type.ARRAY): 4596 return self.sql(arg) 4597 4598 cond_for_null = arg.is_(exp.null()) 4599 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4601 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4602 this = expression.this 4603 time_format = self.format_time(expression) 4604 4605 if time_format: 4606 return self.sql( 4607 exp.cast( 4608 exp.StrToTime(this=this, format=expression.args["format"]), 4609 exp.DataType.Type.TIME, 4610 ) 4611 ) 4612 4613 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4614 return self.sql(this) 4615 4616 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4618 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4619 this = expression.this 4620 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4621 return self.sql(this) 4622 4623 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4625 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4626 this = expression.this 4627 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4628 return self.sql(this) 4629 4630 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4632 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4633 this = expression.this 4634 time_format = self.format_time(expression) 4635 safe = expression.args.get("safe") 4636 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4637 return self.sql( 4638 exp.cast( 4639 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4640 exp.DataType.Type.DATE, 4641 ) 4642 ) 4643 4644 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4645 return self.sql(this) 4646 4647 if safe: 4648 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DataType.Type.DATE))) 4649 4650 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4662 def lastday_sql(self, expression: exp.LastDay) -> str: 4663 if self.LAST_DAY_SUPPORTS_DATE_PART: 4664 return self.function_fallback_sql(expression) 4665 4666 unit = expression.text("unit") 4667 if unit and unit != "MONTH": 4668 self.unsupported("Date parts are not supported in LAST_DAY.") 4669 4670 return self.func("LAST_DAY", expression.this)
4679 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4680 if self.CAN_IMPLEMENT_ARRAY_ANY: 4681 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4682 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4683 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4684 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4685 4686 from sqlglot.dialects import Dialect 4687 4688 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4689 if self.dialect.__class__ != Dialect: 4690 self.unsupported("ARRAY_ANY is unsupported") 4691 4692 return self.function_fallback_sql(expression)
4694 def struct_sql(self, expression: exp.Struct) -> str: 4695 expression.set( 4696 "expressions", 4697 [ 4698 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4699 if isinstance(e, exp.PropertyEQ) 4700 else e 4701 for e in expression.expressions 4702 ], 4703 ) 4704 4705 return self.function_fallback_sql(expression)
4713 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4714 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4715 tables = f" {self.expressions(expression)}" 4716 4717 exists = " IF EXISTS" if expression.args.get("exists") else "" 4718 4719 on_cluster = self.sql(expression, "cluster") 4720 on_cluster = f" {on_cluster}" if on_cluster else "" 4721 4722 identity = self.sql(expression, "identity") 4723 identity = f" {identity} IDENTITY" if identity else "" 4724 4725 option = self.sql(expression, "option") 4726 option = f" {option}" if option else "" 4727 4728 partition = self.sql(expression, "partition") 4729 partition = f" {partition}" if partition else "" 4730 4731 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4735 def convert_sql(self, expression: exp.Convert) -> str: 4736 to = expression.this 4737 value = expression.expression 4738 style = expression.args.get("style") 4739 safe = expression.args.get("safe") 4740 strict = expression.args.get("strict") 4741 4742 if not to or not value: 4743 return "" 4744 4745 # Retrieve length of datatype and override to default if not specified 4746 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4747 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4748 4749 transformed: t.Optional[exp.Expression] = None 4750 cast = exp.Cast if strict else exp.TryCast 4751 4752 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4753 if isinstance(style, exp.Literal) and style.is_int: 4754 from sqlglot.dialects.tsql import TSQL 4755 4756 style_value = style.name 4757 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4758 if not converted_style: 4759 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4760 4761 fmt = exp.Literal.string(converted_style) 4762 4763 if to.this == exp.DataType.Type.DATE: 4764 transformed = exp.StrToDate(this=value, format=fmt) 4765 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4766 transformed = exp.StrToTime(this=value, format=fmt) 4767 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4768 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4769 elif to.this == exp.DataType.Type.TEXT: 4770 transformed = exp.TimeToStr(this=value, format=fmt) 4771 4772 if not transformed: 4773 transformed = cast(this=value, to=to, safe=safe) 4774 4775 return self.sql(transformed)
4843 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4844 option = self.sql(expression, "this") 4845 4846 if expression.expressions: 4847 upper = option.upper() 4848 4849 # Snowflake FILE_FORMAT options are separated by whitespace 4850 sep = " " if upper == "FILE_FORMAT" else ", " 4851 4852 # Databricks copy/format options do not set their list of values with EQ 4853 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4854 values = self.expressions(expression, flat=True, sep=sep) 4855 return f"{option}{op}({values})" 4856 4857 value = self.sql(expression, "expression") 4858 4859 if not value: 4860 return option 4861 4862 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4863 4864 return f"{option}{op}{value}"
4866 def credentials_sql(self, expression: exp.Credentials) -> str: 4867 cred_expr = expression.args.get("credentials") 4868 if isinstance(cred_expr, exp.Literal): 4869 # Redshift case: CREDENTIALS <string> 4870 credentials = self.sql(expression, "credentials") 4871 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4872 else: 4873 # Snowflake case: CREDENTIALS = (...) 4874 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4875 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4876 4877 storage = self.sql(expression, "storage") 4878 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4879 4880 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4881 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4882 4883 iam_role = self.sql(expression, "iam_role") 4884 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4885 4886 region = self.sql(expression, "region") 4887 region = f" REGION {region}" if region else "" 4888 4889 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4891 def copy_sql(self, expression: exp.Copy) -> str: 4892 this = self.sql(expression, "this") 4893 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4894 4895 credentials = self.sql(expression, "credentials") 4896 credentials = self.seg(credentials) if credentials else "" 4897 files = self.expressions(expression, key="files", flat=True) 4898 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4899 4900 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4901 params = self.expressions( 4902 expression, 4903 key="params", 4904 sep=sep, 4905 new_line=True, 4906 skip_last=True, 4907 skip_first=True, 4908 indent=self.COPY_PARAMS_ARE_WRAPPED, 4909 ) 4910 4911 if params: 4912 if self.COPY_PARAMS_ARE_WRAPPED: 4913 params = f" WITH ({params})" 4914 elif not self.pretty and (files or credentials): 4915 params = f" {params}" 4916 4917 return f"COPY{this}{kind} {files}{credentials}{params}"
4922 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4923 on_sql = "ON" if expression.args.get("on") else "OFF" 4924 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4925 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4926 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4927 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4928 4929 if filter_col or retention_period: 4930 on_sql = self.func("ON", filter_col, retention_period) 4931 4932 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4934 def maskingpolicycolumnconstraint_sql( 4935 self, expression: exp.MaskingPolicyColumnConstraint 4936 ) -> str: 4937 this = self.sql(expression, "this") 4938 expressions = self.expressions(expression, flat=True) 4939 expressions = f" USING ({expressions})" if expressions else "" 4940 return f"MASKING POLICY {this}{expressions}"
4950 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4951 this = self.sql(expression, "this") 4952 expr = expression.expression 4953 4954 if isinstance(expr, exp.Func): 4955 # T-SQL's CLR functions are case sensitive 4956 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4957 else: 4958 expr = self.sql(expression, "expression") 4959 4960 return self.scope_resolution(expr, this)
4968 def rand_sql(self, expression: exp.Rand) -> str: 4969 lower = self.sql(expression, "lower") 4970 upper = self.sql(expression, "upper") 4971 4972 if lower and upper: 4973 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4974 return self.func("RAND", expression.this)
4976 def changes_sql(self, expression: exp.Changes) -> str: 4977 information = self.sql(expression, "information") 4978 information = f"INFORMATION => {information}" 4979 at_before = self.sql(expression, "at_before") 4980 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4981 end = self.sql(expression, "end") 4982 end = f"{self.seg('')}{end}" if end else "" 4983 4984 return f"CHANGES ({information}){at_before}{end}"
4986 def pad_sql(self, expression: exp.Pad) -> str: 4987 prefix = "L" if expression.args.get("is_left") else "R" 4988 4989 fill_pattern = self.sql(expression, "fill_pattern") or None 4990 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4991 fill_pattern = "' '" 4992 4993 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4999 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5000 generate_series = exp.GenerateSeries(**expression.args) 5001 5002 parent = expression.parent 5003 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5004 parent = parent.parent 5005 5006 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5007 return self.sql(exp.Unnest(expressions=[generate_series])) 5008 5009 if isinstance(parent, exp.Select): 5010 self.unsupported("GenerateSeries projection unnesting is not supported.") 5011 5012 return self.sql(generate_series)
5014 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5015 if self.SUPPORTS_CONVERT_TIMEZONE: 5016 return self.function_fallback_sql(expression) 5017 5018 source_tz = expression.args.get("source_tz") 5019 target_tz = expression.args.get("target_tz") 5020 timestamp = expression.args.get("timestamp") 5021 5022 if source_tz and timestamp: 5023 timestamp = exp.AtTimeZone( 5024 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 5025 ) 5026 5027 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5028 5029 return self.sql(expr)
5031 def json_sql(self, expression: exp.JSON) -> str: 5032 this = self.sql(expression, "this") 5033 this = f" {this}" if this else "" 5034 5035 _with = expression.args.get("with_") 5036 5037 if _with is None: 5038 with_sql = "" 5039 elif not _with: 5040 with_sql = " WITHOUT" 5041 else: 5042 with_sql = " WITH" 5043 5044 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5045 5046 return f"JSON{this}{with_sql}{unique_sql}"
5048 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5049 path = self.sql(expression, "path") 5050 returning = self.sql(expression, "returning") 5051 returning = f" RETURNING {returning}" if returning else "" 5052 5053 on_condition = self.sql(expression, "on_condition") 5054 on_condition = f" {on_condition}" if on_condition else "" 5055 5056 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5058 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5059 else_ = "ELSE " if expression.args.get("else_") else "" 5060 condition = self.sql(expression, "expression") 5061 condition = f"WHEN {condition} THEN " if condition else else_ 5062 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5063 return f"{condition}{insert}"
5071 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5072 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5073 empty = expression.args.get("empty") 5074 empty = ( 5075 f"DEFAULT {empty} ON EMPTY" 5076 if isinstance(empty, exp.Expression) 5077 else self.sql(expression, "empty") 5078 ) 5079 5080 error = expression.args.get("error") 5081 error = ( 5082 f"DEFAULT {error} ON ERROR" 5083 if isinstance(error, exp.Expression) 5084 else self.sql(expression, "error") 5085 ) 5086 5087 if error and empty: 5088 error = ( 5089 f"{empty} {error}" 5090 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5091 else f"{error} {empty}" 5092 ) 5093 empty = "" 5094 5095 null = self.sql(expression, "null") 5096 5097 return f"{empty}{error}{null}"
5103 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5104 this = self.sql(expression, "this") 5105 path = self.sql(expression, "path") 5106 5107 passing = self.expressions(expression, "passing") 5108 passing = f" PASSING {passing}" if passing else "" 5109 5110 on_condition = self.sql(expression, "on_condition") 5111 on_condition = f" {on_condition}" if on_condition else "" 5112 5113 path = f"{path}{passing}{on_condition}" 5114 5115 return self.func("JSON_EXISTS", this, path)
5240 def overlay_sql(self, expression: exp.Overlay) -> str: 5241 this = self.sql(expression, "this") 5242 expr = self.sql(expression, "expression") 5243 from_sql = self.sql(expression, "from_") 5244 for_sql = self.sql(expression, "for_") 5245 for_sql = f" FOR {for_sql}" if for_sql else "" 5246 5247 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
5254 def string_sql(self, expression: exp.String) -> str: 5255 this = expression.this 5256 zone = expression.args.get("zone") 5257 5258 if zone: 5259 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5260 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5261 # set for source_tz to transpile the time conversion before the STRING cast 5262 this = exp.ConvertTimezone( 5263 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5264 ) 5265 5266 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
5276 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5277 filler = self.sql(expression, "this") 5278 filler = f" {filler}" if filler else "" 5279 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5280 return f"TRUNCATE{filler} {with_count}"
5282 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5283 if self.SUPPORTS_UNIX_SECONDS: 5284 return self.function_fallback_sql(expression) 5285 5286 start_ts = exp.cast( 5287 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 5288 ) 5289 5290 return self.sql( 5291 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5292 )
5294 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5295 dim = expression.expression 5296 5297 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5298 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5299 if not (dim.is_int and dim.name == "1"): 5300 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5301 dim = None 5302 5303 # If dimension is required but not specified, default initialize it 5304 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5305 dim = exp.Literal.number(1) 5306 5307 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5309 def attach_sql(self, expression: exp.Attach) -> str: 5310 this = self.sql(expression, "this") 5311 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5312 expressions = self.expressions(expression) 5313 expressions = f" ({expressions})" if expressions else "" 5314 5315 return f"ATTACH{exists_sql} {this}{expressions}"
5317 def detach_sql(self, expression: exp.Detach) -> str: 5318 this = self.sql(expression, "this") 5319 # the DATABASE keyword is required if IF EXISTS is set 5320 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5321 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5322 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5323 5324 return f"DETACH{exists_sql} {this}"
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
5337 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5338 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5339 encode = f"{encode} {self.sql(expression, 'this')}" 5340 5341 properties = expression.args.get("properties") 5342 if properties: 5343 encode = f"{encode} {self.properties(properties)}" 5344 5345 return encode
5347 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5348 this = self.sql(expression, "this") 5349 include = f"INCLUDE {this}" 5350 5351 column_def = self.sql(expression, "column_def") 5352 if column_def: 5353 include = f"{include} {column_def}" 5354 5355 alias = self.sql(expression, "alias") 5356 if alias: 5357 include = f"{include} AS {alias}" 5358 5359 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
5372 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5373 partitions = self.expressions(expression, "partition_expressions") 5374 create = self.expressions(expression, "create_expressions") 5375 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
5377 def partitionbyrangepropertydynamic_sql( 5378 self, expression: exp.PartitionByRangePropertyDynamic 5379 ) -> str: 5380 start = self.sql(expression, "start") 5381 end = self.sql(expression, "end") 5382 5383 every = expression.args["every"] 5384 if isinstance(every, exp.Interval) and every.this.is_string: 5385 every.this.replace(exp.Literal.number(every.name)) 5386 5387 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5400 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5401 kind = self.sql(expression, "kind") 5402 option = self.sql(expression, "option") 5403 option = f" {option}" if option else "" 5404 this = self.sql(expression, "this") 5405 this = f" {this}" if this else "" 5406 columns = self.expressions(expression) 5407 columns = f" {columns}" if columns else "" 5408 return f"{kind}{option} STATISTICS{this}{columns}"
5410 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5411 this = self.sql(expression, "this") 5412 columns = self.expressions(expression) 5413 inner_expression = self.sql(expression, "expression") 5414 inner_expression = f" {inner_expression}" if inner_expression else "" 5415 update_options = self.sql(expression, "update_options") 5416 update_options = f" {update_options} UPDATE" if update_options else "" 5417 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
5428 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5429 kind = self.sql(expression, "kind") 5430 this = self.sql(expression, "this") 5431 this = f" {this}" if this else "" 5432 inner_expression = self.sql(expression, "expression") 5433 return f"VALIDATE {kind}{this}{inner_expression}"
5435 def analyze_sql(self, expression: exp.Analyze) -> str: 5436 options = self.expressions(expression, key="options", sep=" ") 5437 options = f" {options}" if options else "" 5438 kind = self.sql(expression, "kind") 5439 kind = f" {kind}" if kind else "" 5440 this = self.sql(expression, "this") 5441 this = f" {this}" if this else "" 5442 mode = self.sql(expression, "mode") 5443 mode = f" {mode}" if mode else "" 5444 properties = self.sql(expression, "properties") 5445 properties = f" {properties}" if properties else "" 5446 partition = self.sql(expression, "partition") 5447 partition = f" {partition}" if partition else "" 5448 inner_expression = self.sql(expression, "expression") 5449 inner_expression = f" {inner_expression}" if inner_expression else "" 5450 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5452 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5453 this = self.sql(expression, "this") 5454 namespaces = self.expressions(expression, key="namespaces") 5455 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5456 passing = self.expressions(expression, key="passing") 5457 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5458 columns = self.expressions(expression, key="columns") 5459 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5460 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5461 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5467 def export_sql(self, expression: exp.Export) -> str: 5468 this = self.sql(expression, "this") 5469 connection = self.sql(expression, "connection") 5470 connection = f"WITH CONNECTION {connection} " if connection else "" 5471 options = self.sql(expression, "options") 5472 return f"EXPORT DATA {connection}{options} AS {this}"
5477 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5478 variables = self.expressions(expression, "this") 5479 default = self.sql(expression, "default") 5480 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5481 5482 kind = self.sql(expression, "kind") 5483 if isinstance(expression.args.get("kind"), exp.Schema): 5484 kind = f"TABLE {kind}" 5485 5486 kind = f" {kind}" if kind else "" 5487 5488 return f"{variables}{kind}{default}"
5490 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5491 kind = self.sql(expression, "kind") 5492 this = self.sql(expression, "this") 5493 set = self.sql(expression, "expression") 5494 using = self.sql(expression, "using") 5495 using = f" USING {using}" if using else "" 5496 5497 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5498 5499 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5522 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5523 # Snowflake GET/PUT statements: 5524 # PUT <file> <internalStage> <properties> 5525 # GET <internalStage> <file> <properties> 5526 props = expression.args.get("properties") 5527 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5528 this = self.sql(expression, "this") 5529 target = self.sql(expression, "target") 5530 5531 if isinstance(expression, exp.Put): 5532 return f"PUT {this} {target}{props_sql}" 5533 else: 5534 return f"GET {target} {this}{props_sql}"
5536 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 5537 this = self.sql(expression, "this") 5538 expr = self.sql(expression, "expression") 5539 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5540 return f"TRANSLATE({this} USING {expr}{with_error})"
5542 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5543 if self.SUPPORTS_DECODE_CASE: 5544 return self.func("DECODE", *expression.expressions) 5545 5546 expression, *expressions = expression.expressions 5547 5548 ifs = [] 5549 for search, result in zip(expressions[::2], expressions[1::2]): 5550 if isinstance(search, exp.Literal): 5551 ifs.append(exp.If(this=expression.eq(search), true=result)) 5552 elif isinstance(search, exp.Null): 5553 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5554 else: 5555 if isinstance(search, exp.Binary): 5556 search = exp.paren(search) 5557 5558 cond = exp.or_( 5559 expression.eq(search), 5560 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5561 copy=False, 5562 ) 5563 ifs.append(exp.If(this=cond, true=result)) 5564 5565 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5566 return self.sql(case)
5568 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5569 this = self.sql(expression, "this") 5570 this = self.seg(this, sep="") 5571 dimensions = self.expressions( 5572 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5573 ) 5574 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5575 metrics = self.expressions( 5576 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5577 ) 5578 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5579 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5580 facts = self.seg(f"FACTS {facts}") if facts else "" 5581 where = self.sql(expression, "where") 5582 where = self.seg(f"WHERE {where}") if where else "" 5583 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5584 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5586 def getextract_sql(self, expression: exp.GetExtract) -> str: 5587 this = expression.this 5588 expr = expression.expression 5589 5590 if not this.type or not expression.type: 5591 from sqlglot.optimizer.annotate_types import annotate_types 5592 5593 this = annotate_types(this, dialect=self.dialect) 5594 5595 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5596 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5597 5598 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql(self, expression: sqlglot.expressions.RefreshTriggerProperty) -> str:
5615 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5616 method = self.sql(expression, "method") 5617 kind = expression.args.get("kind") 5618 if not kind: 5619 return f"REFRESH {method}" 5620 5621 every = self.sql(expression, "every") 5622 unit = self.sql(expression, "unit") 5623 every = f" EVERY {every} {unit}" if every else "" 5624 starts = self.sql(expression, "starts") 5625 starts = f" STARTS {starts}" if starts else "" 5626 5627 return f"REFRESH {method} ON {kind}{every}{starts}"
5636 def uuid_sql(self, expression: exp.Uuid) -> str: 5637 is_string = expression.args.get("is_string", False) 5638 uuid_func_sql = self.func("UUID") 5639 5640 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5641 return self.sql( 5642 exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect) 5643 ) 5644 5645 return uuid_func_sql
5647 def initcap_sql(self, expression: exp.Initcap) -> str: 5648 delimiters = expression.expression 5649 5650 if delimiters: 5651 # do not generate delimiters arg if we are round-tripping from default delimiters 5652 if ( 5653 delimiters.is_string 5654 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5655 ): 5656 delimiters = None 5657 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5658 self.unsupported("INITCAP does not support custom delimiters") 5659 delimiters = None 5660 5661 return self.func("INITCAP", expression.this, delimiters)
5671 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5672 this = expression.this.name.upper() 5673 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5674 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5675 return "WEEK" 5676 5677 return self.func("WEEK", expression.this)