sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from functools import reduce, wraps 8 9from sqlglot import exp 10from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 11from sqlglot.helper import apply_index_offset, csv, name_sequence, seq_get 12from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 13from sqlglot.time import format_time 14from sqlglot.tokens import TokenType 15 16if t.TYPE_CHECKING: 17 from sqlglot._typing import E 18 from sqlglot.dialects.dialect import DialectType 19 20 G = t.TypeVar("G", bound="Generator") 21 GeneratorMethod = t.Callable[[G, E], str] 22 23logger = logging.getLogger("sqlglot") 24 25ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 26UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 27 28 29def unsupported_args( 30 *args: t.Union[str, t.Tuple[str, str]], 31) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 32 """ 33 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 34 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 35 """ 36 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 37 for arg in args: 38 if isinstance(arg, str): 39 diagnostic_by_arg[arg] = None 40 else: 41 diagnostic_by_arg[arg[0]] = arg[1] 42 43 def decorator(func: GeneratorMethod) -> GeneratorMethod: 44 @wraps(func) 45 def _func(generator: G, expression: E) -> str: 46 expression_name = expression.__class__.__name__ 47 dialect_name = generator.dialect.__class__.__name__ 48 49 for arg_name, diagnostic in diagnostic_by_arg.items(): 50 if expression.args.get(arg_name): 51 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 52 arg_name, expression_name, dialect_name 53 ) 54 generator.unsupported(diagnostic) 55 56 return func(generator, expression) 57 58 return _func 59 60 return decorator 61 62 63class _Generator(type): 64 def __new__(cls, clsname, bases, attrs): 65 klass = super().__new__(cls, clsname, bases, attrs) 66 67 # Remove transforms that correspond to unsupported JSONPathPart expressions 68 for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS: 69 klass.TRANSFORMS.pop(part, None) 70 71 return klass 72 73 74class Generator(metaclass=_Generator): 75 """ 76 Generator converts a given syntax tree to the corresponding SQL string. 77 78 Args: 79 pretty: Whether to format the produced SQL string. 80 Default: False. 81 identify: Determines when an identifier should be quoted. Possible values are: 82 False (default): Never quote, except in cases where it's mandatory by the dialect. 83 True or 'always': Always quote. 84 'safe': Only quote identifiers that are case insensitive. 85 normalize: Whether to normalize identifiers to lowercase. 86 Default: False. 87 pad: The pad size in a formatted string. For example, this affects the indentation of 88 a projection in a query, relative to its nesting level. 89 Default: 2. 90 indent: The indentation size in a formatted string. For example, this affects the 91 indentation of subqueries and filters under a `WHERE` clause. 92 Default: 2. 93 normalize_functions: How to normalize function names. Possible values are: 94 "upper" or True (default): Convert names to uppercase. 95 "lower": Convert names to lowercase. 96 False: Disables function name normalization. 97 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 98 Default ErrorLevel.WARN. 99 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 100 This is only relevant if unsupported_level is ErrorLevel.RAISE. 101 Default: 3 102 leading_comma: Whether the comma is leading or trailing in select expressions. 103 This is only relevant when generating in pretty mode. 104 Default: False 105 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 106 The default is on the smaller end because the length only represents a segment and not the true 107 line length. 108 Default: 80 109 comments: Whether to preserve comments in the output SQL code. 110 Default: True 111 """ 112 113 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 114 **JSON_PATH_PART_TRANSFORMS, 115 exp.AllowedValuesProperty: lambda self, 116 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 117 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 118 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 119 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 120 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 121 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 122 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 123 exp.CaseSpecificColumnConstraint: lambda _, 124 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 125 exp.Ceil: lambda self, e: self.ceil_floor(e), 126 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 127 exp.CharacterSetProperty: lambda self, 128 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 129 exp.ClusteredColumnConstraint: lambda self, 130 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 131 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 132 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 133 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 134 exp.ConvertToCharset: lambda self, e: self.func( 135 "CONVERT", e.this, e.args["dest"], e.args.get("source") 136 ), 137 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 138 exp.CredentialsProperty: lambda self, 139 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 140 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 141 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 142 exp.DynamicProperty: lambda *_: "DYNAMIC", 143 exp.EmptyProperty: lambda *_: "EMPTY", 144 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 145 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 146 exp.EphemeralColumnConstraint: lambda self, 147 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 148 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 149 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 150 exp.Except: lambda self, e: self.set_operations(e), 151 exp.ExternalProperty: lambda *_: "EXTERNAL", 152 exp.Floor: lambda self, e: self.ceil_floor(e), 153 exp.Get: lambda self, e: self.get_put_sql(e), 154 exp.GlobalProperty: lambda *_: "GLOBAL", 155 exp.HeapProperty: lambda *_: "HEAP", 156 exp.IcebergProperty: lambda *_: "ICEBERG", 157 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 158 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 159 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 160 exp.Intersect: lambda self, e: self.set_operations(e), 161 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 162 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 163 exp.LanguageProperty: lambda self, e: self.naked_property(e), 164 exp.LocationProperty: lambda self, e: self.naked_property(e), 165 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 166 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 167 exp.NonClusteredColumnConstraint: lambda self, 168 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 169 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 170 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 171 exp.OnCommitProperty: lambda _, 172 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 173 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 174 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 175 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 176 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 177 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 178 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 179 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 180 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 181 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 182 exp.ProjectionPolicyColumnConstraint: lambda self, 183 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 184 exp.Put: lambda self, e: self.get_put_sql(e), 185 exp.RemoteWithConnectionModelProperty: lambda self, 186 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 187 exp.ReturnsProperty: lambda self, e: ( 188 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 189 ), 190 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 191 exp.SecureProperty: lambda *_: "SECURE", 192 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 193 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 194 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 195 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 196 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 197 exp.SqlReadWriteProperty: lambda _, e: e.name, 198 exp.SqlSecurityProperty: lambda _, 199 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 200 exp.StabilityProperty: lambda _, e: e.name, 201 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 202 exp.StreamingTableProperty: lambda *_: "STREAMING", 203 exp.StrictProperty: lambda *_: "STRICT", 204 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 205 exp.TableColumn: lambda self, e: self.sql(e.this), 206 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 207 exp.TemporaryProperty: lambda *_: "TEMPORARY", 208 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 209 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 210 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 211 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 212 exp.TransientProperty: lambda *_: "TRANSIENT", 213 exp.Union: lambda self, e: self.set_operations(e), 214 exp.UnloggedProperty: lambda *_: "UNLOGGED", 215 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 216 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 217 exp.Uuid: lambda *_: "UUID()", 218 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 219 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 220 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 221 exp.VolatileProperty: lambda *_: "VOLATILE", 222 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 223 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 224 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 225 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 226 exp.ForceProperty: lambda *_: "FORCE", 227 } 228 229 # Whether null ordering is supported in order by 230 # True: Full Support, None: No support, False: No support for certain cases 231 # such as window specifications, aggregate functions etc 232 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 233 234 # Whether ignore nulls is inside the agg or outside. 235 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 236 IGNORE_NULLS_IN_FUNC = False 237 238 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 239 LOCKING_READS_SUPPORTED = False 240 241 # Whether the EXCEPT and INTERSECT operations can return duplicates 242 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 243 244 # Wrap derived values in parens, usually standard but spark doesn't support it 245 WRAP_DERIVED_VALUES = True 246 247 # Whether create function uses an AS before the RETURN 248 CREATE_FUNCTION_RETURN_AS = True 249 250 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 251 MATCHED_BY_SOURCE = True 252 253 # Whether the INTERVAL expression works only with values like '1 day' 254 SINGLE_STRING_INTERVAL = False 255 256 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 257 INTERVAL_ALLOWS_PLURAL_FORM = True 258 259 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 260 LIMIT_FETCH = "ALL" 261 262 # Whether limit and fetch allows expresions or just limits 263 LIMIT_ONLY_LITERALS = False 264 265 # Whether a table is allowed to be renamed with a db 266 RENAME_TABLE_WITH_DB = True 267 268 # The separator for grouping sets and rollups 269 GROUPINGS_SEP = "," 270 271 # The string used for creating an index on a table 272 INDEX_ON = "ON" 273 274 # Whether join hints should be generated 275 JOIN_HINTS = True 276 277 # Whether table hints should be generated 278 TABLE_HINTS = True 279 280 # Whether query hints should be generated 281 QUERY_HINTS = True 282 283 # What kind of separator to use for query hints 284 QUERY_HINT_SEP = ", " 285 286 # Whether comparing against booleans (e.g. x IS TRUE) is supported 287 IS_BOOL_ALLOWED = True 288 289 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 290 DUPLICATE_KEY_UPDATE_WITH_SET = True 291 292 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 293 LIMIT_IS_TOP = False 294 295 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 296 RETURNING_END = True 297 298 # Whether to generate an unquoted value for EXTRACT's date part argument 299 EXTRACT_ALLOWS_QUOTES = True 300 301 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 302 TZ_TO_WITH_TIME_ZONE = False 303 304 # Whether the NVL2 function is supported 305 NVL2_SUPPORTED = True 306 307 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 308 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 309 310 # Whether VALUES statements can be used as derived tables. 311 # MySQL 5 and Redshift do not allow this, so when False, it will convert 312 # SELECT * VALUES into SELECT UNION 313 VALUES_AS_TABLE = True 314 315 # Whether the word COLUMN is included when adding a column with ALTER TABLE 316 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 317 318 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 319 UNNEST_WITH_ORDINALITY = True 320 321 # Whether FILTER (WHERE cond) can be used for conditional aggregation 322 AGGREGATE_FILTER_SUPPORTED = True 323 324 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 325 SEMI_ANTI_JOIN_WITH_SIDE = True 326 327 # Whether to include the type of a computed column in the CREATE DDL 328 COMPUTED_COLUMN_WITH_TYPE = True 329 330 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 331 SUPPORTS_TABLE_COPY = True 332 333 # Whether parentheses are required around the table sample's expression 334 TABLESAMPLE_REQUIRES_PARENS = True 335 336 # Whether a table sample clause's size needs to be followed by the ROWS keyword 337 TABLESAMPLE_SIZE_IS_ROWS = True 338 339 # The keyword(s) to use when generating a sample clause 340 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 341 342 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 343 TABLESAMPLE_WITH_METHOD = True 344 345 # The keyword to use when specifying the seed of a sample clause 346 TABLESAMPLE_SEED_KEYWORD = "SEED" 347 348 # Whether COLLATE is a function instead of a binary operator 349 COLLATE_IS_FUNC = False 350 351 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 352 DATA_TYPE_SPECIFIERS_ALLOWED = False 353 354 # Whether conditions require booleans WHERE x = 0 vs WHERE x 355 ENSURE_BOOLS = False 356 357 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 358 CTE_RECURSIVE_KEYWORD_REQUIRED = True 359 360 # Whether CONCAT requires >1 arguments 361 SUPPORTS_SINGLE_ARG_CONCAT = True 362 363 # Whether LAST_DAY function supports a date part argument 364 LAST_DAY_SUPPORTS_DATE_PART = True 365 366 # Whether named columns are allowed in table aliases 367 SUPPORTS_TABLE_ALIAS_COLUMNS = True 368 369 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 370 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 371 372 # What delimiter to use for separating JSON key/value pairs 373 JSON_KEY_VALUE_PAIR_SEP = ":" 374 375 # INSERT OVERWRITE TABLE x override 376 INSERT_OVERWRITE = " OVERWRITE TABLE" 377 378 # Whether the SELECT .. INTO syntax is used instead of CTAS 379 SUPPORTS_SELECT_INTO = False 380 381 # Whether UNLOGGED tables can be created 382 SUPPORTS_UNLOGGED_TABLES = False 383 384 # Whether the CREATE TABLE LIKE statement is supported 385 SUPPORTS_CREATE_TABLE_LIKE = True 386 387 # Whether the LikeProperty needs to be specified inside of the schema clause 388 LIKE_PROPERTY_INSIDE_SCHEMA = False 389 390 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 391 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 392 MULTI_ARG_DISTINCT = True 393 394 # Whether the JSON extraction operators expect a value of type JSON 395 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 396 397 # Whether bracketed keys like ["foo"] are supported in JSON paths 398 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 399 400 # Whether to escape keys using single quotes in JSON paths 401 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 402 403 # The JSONPathPart expressions supported by this dialect 404 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 405 406 # Whether any(f(x) for x in array) can be implemented by this dialect 407 CAN_IMPLEMENT_ARRAY_ANY = False 408 409 # Whether the function TO_NUMBER is supported 410 SUPPORTS_TO_NUMBER = True 411 412 # Whether EXCLUDE in window specification is supported 413 SUPPORTS_WINDOW_EXCLUDE = False 414 415 # Whether or not set op modifiers apply to the outer set op or select. 416 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 417 # True means limit 1 happens after the set op, False means it it happens on y. 418 SET_OP_MODIFIERS = True 419 420 # Whether parameters from COPY statement are wrapped in parentheses 421 COPY_PARAMS_ARE_WRAPPED = True 422 423 # Whether values of params are set with "=" token or empty space 424 COPY_PARAMS_EQ_REQUIRED = False 425 426 # Whether COPY statement has INTO keyword 427 COPY_HAS_INTO_KEYWORD = True 428 429 # Whether the conditional TRY(expression) function is supported 430 TRY_SUPPORTED = True 431 432 # Whether the UESCAPE syntax in unicode strings is supported 433 SUPPORTS_UESCAPE = True 434 435 # The keyword to use when generating a star projection with excluded columns 436 STAR_EXCEPT = "EXCEPT" 437 438 # The HEX function name 439 HEX_FUNC = "HEX" 440 441 # The keywords to use when prefixing & separating WITH based properties 442 WITH_PROPERTIES_PREFIX = "WITH" 443 444 # Whether to quote the generated expression of exp.JsonPath 445 QUOTE_JSON_PATH = True 446 447 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 448 PAD_FILL_PATTERN_IS_REQUIRED = False 449 450 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 451 SUPPORTS_EXPLODING_PROJECTIONS = True 452 453 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 454 ARRAY_CONCAT_IS_VAR_LEN = True 455 456 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 457 SUPPORTS_CONVERT_TIMEZONE = False 458 459 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 460 SUPPORTS_MEDIAN = True 461 462 # Whether UNIX_SECONDS(timestamp) is supported 463 SUPPORTS_UNIX_SECONDS = False 464 465 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 466 ALTER_SET_WRAPPED = False 467 468 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 469 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 470 # TODO: The normalization should be done by default once we've tested it across all dialects. 471 NORMALIZE_EXTRACT_DATE_PARTS = False 472 473 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 474 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 475 476 # The function name of the exp.ArraySize expression 477 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 478 479 # The syntax to use when altering the type of a column 480 ALTER_SET_TYPE = "SET DATA TYPE" 481 482 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 483 # None -> Doesn't support it at all 484 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 485 # True (Postgres) -> Explicitly requires it 486 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 487 488 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 489 SUPPORTS_DECODE_CASE = True 490 491 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 492 SUPPORTS_BETWEEN_FLAGS = False 493 494 TYPE_MAPPING = { 495 exp.DataType.Type.DATETIME2: "TIMESTAMP", 496 exp.DataType.Type.NCHAR: "CHAR", 497 exp.DataType.Type.NVARCHAR: "VARCHAR", 498 exp.DataType.Type.MEDIUMTEXT: "TEXT", 499 exp.DataType.Type.LONGTEXT: "TEXT", 500 exp.DataType.Type.TINYTEXT: "TEXT", 501 exp.DataType.Type.BLOB: "VARBINARY", 502 exp.DataType.Type.MEDIUMBLOB: "BLOB", 503 exp.DataType.Type.LONGBLOB: "BLOB", 504 exp.DataType.Type.TINYBLOB: "BLOB", 505 exp.DataType.Type.INET: "INET", 506 exp.DataType.Type.ROWVERSION: "VARBINARY", 507 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 508 } 509 510 TIME_PART_SINGULARS = { 511 "MICROSECONDS": "MICROSECOND", 512 "SECONDS": "SECOND", 513 "MINUTES": "MINUTE", 514 "HOURS": "HOUR", 515 "DAYS": "DAY", 516 "WEEKS": "WEEK", 517 "MONTHS": "MONTH", 518 "QUARTERS": "QUARTER", 519 "YEARS": "YEAR", 520 } 521 522 AFTER_HAVING_MODIFIER_TRANSFORMS = { 523 "cluster": lambda self, e: self.sql(e, "cluster"), 524 "distribute": lambda self, e: self.sql(e, "distribute"), 525 "sort": lambda self, e: self.sql(e, "sort"), 526 "windows": lambda self, e: ( 527 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 528 if e.args.get("windows") 529 else "" 530 ), 531 "qualify": lambda self, e: self.sql(e, "qualify"), 532 } 533 534 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 535 536 STRUCT_DELIMITER = ("<", ">") 537 538 PARAMETER_TOKEN = "@" 539 NAMED_PLACEHOLDER_TOKEN = ":" 540 541 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 542 543 PROPERTIES_LOCATION = { 544 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 545 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 546 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 547 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 548 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 549 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 550 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 551 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 552 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 553 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 555 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 556 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 557 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 558 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 559 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 560 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 561 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 562 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 563 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 564 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 568 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 572 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 573 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 574 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 575 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 576 exp.HeapProperty: exp.Properties.Location.POST_WITH, 577 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 579 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 580 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 582 exp.JournalProperty: exp.Properties.Location.POST_NAME, 583 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 584 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 588 exp.LogProperty: exp.Properties.Location.POST_NAME, 589 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 590 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 591 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 592 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 593 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 594 exp.Order: exp.Properties.Location.POST_SCHEMA, 595 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 596 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 597 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 599 exp.Property: exp.Properties.Location.POST_WITH, 600 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 608 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 609 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 610 exp.Set: exp.Properties.Location.POST_SCHEMA, 611 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 612 exp.SetProperty: exp.Properties.Location.POST_CREATE, 613 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 614 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 615 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 616 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 619 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 620 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 622 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.Tags: exp.Properties.Location.POST_WITH, 624 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 625 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 626 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 627 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 628 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 629 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 630 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 631 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 632 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 633 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 634 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 635 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 636 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 637 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 639 } 640 641 # Keywords that can't be used as unquoted identifier names 642 RESERVED_KEYWORDS: t.Set[str] = set() 643 644 # Expressions whose comments are separated from them for better formatting 645 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 646 exp.Command, 647 exp.Create, 648 exp.Describe, 649 exp.Delete, 650 exp.Drop, 651 exp.From, 652 exp.Insert, 653 exp.Join, 654 exp.MultitableInserts, 655 exp.Order, 656 exp.Group, 657 exp.Having, 658 exp.Select, 659 exp.SetOperation, 660 exp.Update, 661 exp.Where, 662 exp.With, 663 ) 664 665 # Expressions that should not have their comments generated in maybe_comment 666 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 667 exp.Binary, 668 exp.SetOperation, 669 ) 670 671 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 672 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 673 exp.Column, 674 exp.Literal, 675 exp.Neg, 676 exp.Paren, 677 ) 678 679 PARAMETERIZABLE_TEXT_TYPES = { 680 exp.DataType.Type.NVARCHAR, 681 exp.DataType.Type.VARCHAR, 682 exp.DataType.Type.CHAR, 683 exp.DataType.Type.NCHAR, 684 } 685 686 # Expressions that need to have all CTEs under them bubbled up to them 687 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 688 689 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 690 691 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 692 693 __slots__ = ( 694 "pretty", 695 "identify", 696 "normalize", 697 "pad", 698 "_indent", 699 "normalize_functions", 700 "unsupported_level", 701 "max_unsupported", 702 "leading_comma", 703 "max_text_width", 704 "comments", 705 "dialect", 706 "unsupported_messages", 707 "_escaped_quote_end", 708 "_escaped_identifier_end", 709 "_next_name", 710 "_identifier_start", 711 "_identifier_end", 712 "_quote_json_path_key_using_brackets", 713 ) 714 715 def __init__( 716 self, 717 pretty: t.Optional[bool] = None, 718 identify: str | bool = False, 719 normalize: bool = False, 720 pad: int = 2, 721 indent: int = 2, 722 normalize_functions: t.Optional[str | bool] = None, 723 unsupported_level: ErrorLevel = ErrorLevel.WARN, 724 max_unsupported: int = 3, 725 leading_comma: bool = False, 726 max_text_width: int = 80, 727 comments: bool = True, 728 dialect: DialectType = None, 729 ): 730 import sqlglot 731 from sqlglot.dialects import Dialect 732 733 self.pretty = pretty if pretty is not None else sqlglot.pretty 734 self.identify = identify 735 self.normalize = normalize 736 self.pad = pad 737 self._indent = indent 738 self.unsupported_level = unsupported_level 739 self.max_unsupported = max_unsupported 740 self.leading_comma = leading_comma 741 self.max_text_width = max_text_width 742 self.comments = comments 743 self.dialect = Dialect.get_or_raise(dialect) 744 745 # This is both a Dialect property and a Generator argument, so we prioritize the latter 746 self.normalize_functions = ( 747 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 748 ) 749 750 self.unsupported_messages: t.List[str] = [] 751 self._escaped_quote_end: str = ( 752 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 753 ) 754 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 755 756 self._next_name = name_sequence("_t") 757 758 self._identifier_start = self.dialect.IDENTIFIER_START 759 self._identifier_end = self.dialect.IDENTIFIER_END 760 761 self._quote_json_path_key_using_brackets = True 762 763 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 764 """ 765 Generates the SQL string corresponding to the given syntax tree. 766 767 Args: 768 expression: The syntax tree. 769 copy: Whether to copy the expression. The generator performs mutations so 770 it is safer to copy. 771 772 Returns: 773 The SQL string corresponding to `expression`. 774 """ 775 if copy: 776 expression = expression.copy() 777 778 expression = self.preprocess(expression) 779 780 self.unsupported_messages = [] 781 sql = self.sql(expression).strip() 782 783 if self.pretty: 784 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 785 786 if self.unsupported_level == ErrorLevel.IGNORE: 787 return sql 788 789 if self.unsupported_level == ErrorLevel.WARN: 790 for msg in self.unsupported_messages: 791 logger.warning(msg) 792 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 793 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 794 795 return sql 796 797 def preprocess(self, expression: exp.Expression) -> exp.Expression: 798 """Apply generic preprocessing transformations to a given expression.""" 799 expression = self._move_ctes_to_top_level(expression) 800 801 if self.ENSURE_BOOLS: 802 from sqlglot.transforms import ensure_bools 803 804 expression = ensure_bools(expression) 805 806 return expression 807 808 def _move_ctes_to_top_level(self, expression: E) -> E: 809 if ( 810 not expression.parent 811 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 812 and any(node.parent is not expression for node in expression.find_all(exp.With)) 813 ): 814 from sqlglot.transforms import move_ctes_to_top_level 815 816 expression = move_ctes_to_top_level(expression) 817 return expression 818 819 def unsupported(self, message: str) -> None: 820 if self.unsupported_level == ErrorLevel.IMMEDIATE: 821 raise UnsupportedError(message) 822 self.unsupported_messages.append(message) 823 824 def sep(self, sep: str = " ") -> str: 825 return f"{sep.strip()}\n" if self.pretty else sep 826 827 def seg(self, sql: str, sep: str = " ") -> str: 828 return f"{self.sep(sep)}{sql}" 829 830 def sanitize_comment(self, comment: str) -> str: 831 comment = " " + comment if comment[0].strip() else comment 832 comment = comment + " " if comment[-1].strip() else comment 833 834 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 835 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 836 comment = comment.replace("*/", "* /") 837 838 return comment 839 840 def maybe_comment( 841 self, 842 sql: str, 843 expression: t.Optional[exp.Expression] = None, 844 comments: t.Optional[t.List[str]] = None, 845 separated: bool = False, 846 ) -> str: 847 comments = ( 848 ((expression and expression.comments) if comments is None else comments) # type: ignore 849 if self.comments 850 else None 851 ) 852 853 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 854 return sql 855 856 comments_sql = " ".join( 857 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 858 ) 859 860 if not comments_sql: 861 return sql 862 863 comments_sql = self._replace_line_breaks(comments_sql) 864 865 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 866 return ( 867 f"{self.sep()}{comments_sql}{sql}" 868 if not sql or sql[0].isspace() 869 else f"{comments_sql}{self.sep()}{sql}" 870 ) 871 872 return f"{sql} {comments_sql}" 873 874 def wrap(self, expression: exp.Expression | str) -> str: 875 this_sql = ( 876 self.sql(expression) 877 if isinstance(expression, exp.UNWRAPPED_QUERIES) 878 else self.sql(expression, "this") 879 ) 880 if not this_sql: 881 return "()" 882 883 this_sql = self.indent(this_sql, level=1, pad=0) 884 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 885 886 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 887 original = self.identify 888 self.identify = False 889 result = func(*args, **kwargs) 890 self.identify = original 891 return result 892 893 def normalize_func(self, name: str) -> str: 894 if self.normalize_functions == "upper" or self.normalize_functions is True: 895 return name.upper() 896 if self.normalize_functions == "lower": 897 return name.lower() 898 return name 899 900 def indent( 901 self, 902 sql: str, 903 level: int = 0, 904 pad: t.Optional[int] = None, 905 skip_first: bool = False, 906 skip_last: bool = False, 907 ) -> str: 908 if not self.pretty or not sql: 909 return sql 910 911 pad = self.pad if pad is None else pad 912 lines = sql.split("\n") 913 914 return "\n".join( 915 ( 916 line 917 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 918 else f"{' ' * (level * self._indent + pad)}{line}" 919 ) 920 for i, line in enumerate(lines) 921 ) 922 923 def sql( 924 self, 925 expression: t.Optional[str | exp.Expression], 926 key: t.Optional[str] = None, 927 comment: bool = True, 928 ) -> str: 929 if not expression: 930 return "" 931 932 if isinstance(expression, str): 933 return expression 934 935 if key: 936 value = expression.args.get(key) 937 if value: 938 return self.sql(value) 939 return "" 940 941 transform = self.TRANSFORMS.get(expression.__class__) 942 943 if callable(transform): 944 sql = transform(self, expression) 945 elif isinstance(expression, exp.Expression): 946 exp_handler_name = f"{expression.key}_sql" 947 948 if hasattr(self, exp_handler_name): 949 sql = getattr(self, exp_handler_name)(expression) 950 elif isinstance(expression, exp.Func): 951 sql = self.function_fallback_sql(expression) 952 elif isinstance(expression, exp.Property): 953 sql = self.property_sql(expression) 954 else: 955 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 956 else: 957 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 958 959 return self.maybe_comment(sql, expression) if self.comments and comment else sql 960 961 def uncache_sql(self, expression: exp.Uncache) -> str: 962 table = self.sql(expression, "this") 963 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 964 return f"UNCACHE TABLE{exists_sql} {table}" 965 966 def cache_sql(self, expression: exp.Cache) -> str: 967 lazy = " LAZY" if expression.args.get("lazy") else "" 968 table = self.sql(expression, "this") 969 options = expression.args.get("options") 970 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 971 sql = self.sql(expression, "expression") 972 sql = f" AS{self.sep()}{sql}" if sql else "" 973 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 974 return self.prepend_ctes(expression, sql) 975 976 def characterset_sql(self, expression: exp.CharacterSet) -> str: 977 if isinstance(expression.parent, exp.Cast): 978 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 979 default = "DEFAULT " if expression.args.get("default") else "" 980 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 981 982 def column_parts(self, expression: exp.Column) -> str: 983 return ".".join( 984 self.sql(part) 985 for part in ( 986 expression.args.get("catalog"), 987 expression.args.get("db"), 988 expression.args.get("table"), 989 expression.args.get("this"), 990 ) 991 if part 992 ) 993 994 def column_sql(self, expression: exp.Column) -> str: 995 join_mark = " (+)" if expression.args.get("join_mark") else "" 996 997 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 998 join_mark = "" 999 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1000 1001 return f"{self.column_parts(expression)}{join_mark}" 1002 1003 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1004 this = self.sql(expression, "this") 1005 this = f" {this}" if this else "" 1006 position = self.sql(expression, "position") 1007 return f"{position}{this}" 1008 1009 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1010 column = self.sql(expression, "this") 1011 kind = self.sql(expression, "kind") 1012 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1013 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1014 kind = f"{sep}{kind}" if kind else "" 1015 constraints = f" {constraints}" if constraints else "" 1016 position = self.sql(expression, "position") 1017 position = f" {position}" if position else "" 1018 1019 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1020 kind = "" 1021 1022 return f"{exists}{column}{kind}{constraints}{position}" 1023 1024 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1025 this = self.sql(expression, "this") 1026 kind_sql = self.sql(expression, "kind").strip() 1027 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1028 1029 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1030 this = self.sql(expression, "this") 1031 if expression.args.get("not_null"): 1032 persisted = " PERSISTED NOT NULL" 1033 elif expression.args.get("persisted"): 1034 persisted = " PERSISTED" 1035 else: 1036 persisted = "" 1037 1038 return f"AS {this}{persisted}" 1039 1040 def autoincrementcolumnconstraint_sql(self, _) -> str: 1041 return self.token_sql(TokenType.AUTO_INCREMENT) 1042 1043 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1044 if isinstance(expression.this, list): 1045 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1046 else: 1047 this = self.sql(expression, "this") 1048 1049 return f"COMPRESS {this}" 1050 1051 def generatedasidentitycolumnconstraint_sql( 1052 self, expression: exp.GeneratedAsIdentityColumnConstraint 1053 ) -> str: 1054 this = "" 1055 if expression.this is not None: 1056 on_null = " ON NULL" if expression.args.get("on_null") else "" 1057 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1058 1059 start = expression.args.get("start") 1060 start = f"START WITH {start}" if start else "" 1061 increment = expression.args.get("increment") 1062 increment = f" INCREMENT BY {increment}" if increment else "" 1063 minvalue = expression.args.get("minvalue") 1064 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1065 maxvalue = expression.args.get("maxvalue") 1066 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1067 cycle = expression.args.get("cycle") 1068 cycle_sql = "" 1069 1070 if cycle is not None: 1071 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1072 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1073 1074 sequence_opts = "" 1075 if start or increment or cycle_sql: 1076 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1077 sequence_opts = f" ({sequence_opts.strip()})" 1078 1079 expr = self.sql(expression, "expression") 1080 expr = f"({expr})" if expr else "IDENTITY" 1081 1082 return f"GENERATED{this} AS {expr}{sequence_opts}" 1083 1084 def generatedasrowcolumnconstraint_sql( 1085 self, expression: exp.GeneratedAsRowColumnConstraint 1086 ) -> str: 1087 start = "START" if expression.args.get("start") else "END" 1088 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1089 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1090 1091 def periodforsystemtimeconstraint_sql( 1092 self, expression: exp.PeriodForSystemTimeConstraint 1093 ) -> str: 1094 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1095 1096 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1097 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1098 1099 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1100 desc = expression.args.get("desc") 1101 if desc is not None: 1102 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1103 options = self.expressions(expression, key="options", flat=True, sep=" ") 1104 options = f" {options}" if options else "" 1105 return f"PRIMARY KEY{options}" 1106 1107 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1108 this = self.sql(expression, "this") 1109 this = f" {this}" if this else "" 1110 index_type = expression.args.get("index_type") 1111 index_type = f" USING {index_type}" if index_type else "" 1112 on_conflict = self.sql(expression, "on_conflict") 1113 on_conflict = f" {on_conflict}" if on_conflict else "" 1114 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1115 options = self.expressions(expression, key="options", flat=True, sep=" ") 1116 options = f" {options}" if options else "" 1117 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1118 1119 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1120 return self.sql(expression, "this") 1121 1122 def create_sql(self, expression: exp.Create) -> str: 1123 kind = self.sql(expression, "kind") 1124 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1125 properties = expression.args.get("properties") 1126 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1127 1128 this = self.createable_sql(expression, properties_locs) 1129 1130 properties_sql = "" 1131 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1132 exp.Properties.Location.POST_WITH 1133 ): 1134 properties_sql = self.sql( 1135 exp.Properties( 1136 expressions=[ 1137 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1138 *properties_locs[exp.Properties.Location.POST_WITH], 1139 ] 1140 ) 1141 ) 1142 1143 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1144 properties_sql = self.sep() + properties_sql 1145 elif not self.pretty: 1146 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1147 properties_sql = f" {properties_sql}" 1148 1149 begin = " BEGIN" if expression.args.get("begin") else "" 1150 end = " END" if expression.args.get("end") else "" 1151 1152 expression_sql = self.sql(expression, "expression") 1153 if expression_sql: 1154 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1155 1156 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1157 postalias_props_sql = "" 1158 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1159 postalias_props_sql = self.properties( 1160 exp.Properties( 1161 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1162 ), 1163 wrapped=False, 1164 ) 1165 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1166 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1167 1168 postindex_props_sql = "" 1169 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1170 postindex_props_sql = self.properties( 1171 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1172 wrapped=False, 1173 prefix=" ", 1174 ) 1175 1176 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1177 indexes = f" {indexes}" if indexes else "" 1178 index_sql = indexes + postindex_props_sql 1179 1180 replace = " OR REPLACE" if expression.args.get("replace") else "" 1181 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1182 unique = " UNIQUE" if expression.args.get("unique") else "" 1183 1184 clustered = expression.args.get("clustered") 1185 if clustered is None: 1186 clustered_sql = "" 1187 elif clustered: 1188 clustered_sql = " CLUSTERED COLUMNSTORE" 1189 else: 1190 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1191 1192 postcreate_props_sql = "" 1193 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1194 postcreate_props_sql = self.properties( 1195 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1196 sep=" ", 1197 prefix=" ", 1198 wrapped=False, 1199 ) 1200 1201 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1202 1203 postexpression_props_sql = "" 1204 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1205 postexpression_props_sql = self.properties( 1206 exp.Properties( 1207 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1208 ), 1209 sep=" ", 1210 prefix=" ", 1211 wrapped=False, 1212 ) 1213 1214 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1215 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1216 no_schema_binding = ( 1217 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1218 ) 1219 1220 clone = self.sql(expression, "clone") 1221 clone = f" {clone}" if clone else "" 1222 1223 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1224 properties_expression = f"{expression_sql}{properties_sql}" 1225 else: 1226 properties_expression = f"{properties_sql}{expression_sql}" 1227 1228 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1229 return self.prepend_ctes(expression, expression_sql) 1230 1231 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1232 start = self.sql(expression, "start") 1233 start = f"START WITH {start}" if start else "" 1234 increment = self.sql(expression, "increment") 1235 increment = f" INCREMENT BY {increment}" if increment else "" 1236 minvalue = self.sql(expression, "minvalue") 1237 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1238 maxvalue = self.sql(expression, "maxvalue") 1239 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1240 owned = self.sql(expression, "owned") 1241 owned = f" OWNED BY {owned}" if owned else "" 1242 1243 cache = expression.args.get("cache") 1244 if cache is None: 1245 cache_str = "" 1246 elif cache is True: 1247 cache_str = " CACHE" 1248 else: 1249 cache_str = f" CACHE {cache}" 1250 1251 options = self.expressions(expression, key="options", flat=True, sep=" ") 1252 options = f" {options}" if options else "" 1253 1254 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1255 1256 def clone_sql(self, expression: exp.Clone) -> str: 1257 this = self.sql(expression, "this") 1258 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1259 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1260 return f"{shallow}{keyword} {this}" 1261 1262 def describe_sql(self, expression: exp.Describe) -> str: 1263 style = expression.args.get("style") 1264 style = f" {style}" if style else "" 1265 partition = self.sql(expression, "partition") 1266 partition = f" {partition}" if partition else "" 1267 format = self.sql(expression, "format") 1268 format = f" {format}" if format else "" 1269 1270 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1271 1272 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1273 tag = self.sql(expression, "tag") 1274 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1275 1276 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1277 with_ = self.sql(expression, "with") 1278 if with_: 1279 sql = f"{with_}{self.sep()}{sql}" 1280 return sql 1281 1282 def with_sql(self, expression: exp.With) -> str: 1283 sql = self.expressions(expression, flat=True) 1284 recursive = ( 1285 "RECURSIVE " 1286 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1287 else "" 1288 ) 1289 search = self.sql(expression, "search") 1290 search = f" {search}" if search else "" 1291 1292 return f"WITH {recursive}{sql}{search}" 1293 1294 def cte_sql(self, expression: exp.CTE) -> str: 1295 alias = expression.args.get("alias") 1296 if alias: 1297 alias.add_comments(expression.pop_comments()) 1298 1299 alias_sql = self.sql(expression, "alias") 1300 1301 materialized = expression.args.get("materialized") 1302 if materialized is False: 1303 materialized = "NOT MATERIALIZED " 1304 elif materialized: 1305 materialized = "MATERIALIZED " 1306 1307 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1308 1309 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1310 alias = self.sql(expression, "this") 1311 columns = self.expressions(expression, key="columns", flat=True) 1312 columns = f"({columns})" if columns else "" 1313 1314 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1315 columns = "" 1316 self.unsupported("Named columns are not supported in table alias.") 1317 1318 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1319 alias = self._next_name() 1320 1321 return f"{alias}{columns}" 1322 1323 def bitstring_sql(self, expression: exp.BitString) -> str: 1324 this = self.sql(expression, "this") 1325 if self.dialect.BIT_START: 1326 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1327 return f"{int(this, 2)}" 1328 1329 def hexstring_sql( 1330 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1331 ) -> str: 1332 this = self.sql(expression, "this") 1333 is_integer_type = expression.args.get("is_integer") 1334 1335 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1336 not self.dialect.HEX_START and not binary_function_repr 1337 ): 1338 # Integer representation will be returned if: 1339 # - The read dialect treats the hex value as integer literal but not the write 1340 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1341 return f"{int(this, 16)}" 1342 1343 if not is_integer_type: 1344 # Read dialect treats the hex value as BINARY/BLOB 1345 if binary_function_repr: 1346 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1347 return self.func(binary_function_repr, exp.Literal.string(this)) 1348 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1349 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1350 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1351 1352 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1353 1354 def bytestring_sql(self, expression: exp.ByteString) -> str: 1355 this = self.sql(expression, "this") 1356 if self.dialect.BYTE_START: 1357 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1358 return this 1359 1360 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1361 this = self.sql(expression, "this") 1362 escape = expression.args.get("escape") 1363 1364 if self.dialect.UNICODE_START: 1365 escape_substitute = r"\\\1" 1366 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1367 else: 1368 escape_substitute = r"\\u\1" 1369 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1370 1371 if escape: 1372 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1373 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1374 else: 1375 escape_pattern = ESCAPED_UNICODE_RE 1376 escape_sql = "" 1377 1378 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1379 this = escape_pattern.sub(escape_substitute, this) 1380 1381 return f"{left_quote}{this}{right_quote}{escape_sql}" 1382 1383 def rawstring_sql(self, expression: exp.RawString) -> str: 1384 string = expression.this 1385 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1386 string = string.replace("\\", "\\\\") 1387 1388 string = self.escape_str(string, escape_backslash=False) 1389 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1390 1391 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1392 this = self.sql(expression, "this") 1393 specifier = self.sql(expression, "expression") 1394 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1395 return f"{this}{specifier}" 1396 1397 def datatype_sql(self, expression: exp.DataType) -> str: 1398 nested = "" 1399 values = "" 1400 interior = self.expressions(expression, flat=True) 1401 1402 type_value = expression.this 1403 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1404 type_sql = self.sql(expression, "kind") 1405 else: 1406 type_sql = ( 1407 self.TYPE_MAPPING.get(type_value, type_value.value) 1408 if isinstance(type_value, exp.DataType.Type) 1409 else type_value 1410 ) 1411 1412 if interior: 1413 if expression.args.get("nested"): 1414 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1415 if expression.args.get("values") is not None: 1416 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1417 values = self.expressions(expression, key="values", flat=True) 1418 values = f"{delimiters[0]}{values}{delimiters[1]}" 1419 elif type_value == exp.DataType.Type.INTERVAL: 1420 nested = f" {interior}" 1421 else: 1422 nested = f"({interior})" 1423 1424 type_sql = f"{type_sql}{nested}{values}" 1425 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1426 exp.DataType.Type.TIMETZ, 1427 exp.DataType.Type.TIMESTAMPTZ, 1428 ): 1429 type_sql = f"{type_sql} WITH TIME ZONE" 1430 1431 return type_sql 1432 1433 def directory_sql(self, expression: exp.Directory) -> str: 1434 local = "LOCAL " if expression.args.get("local") else "" 1435 row_format = self.sql(expression, "row_format") 1436 row_format = f" {row_format}" if row_format else "" 1437 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1438 1439 def delete_sql(self, expression: exp.Delete) -> str: 1440 this = self.sql(expression, "this") 1441 this = f" FROM {this}" if this else "" 1442 using = self.sql(expression, "using") 1443 using = f" USING {using}" if using else "" 1444 cluster = self.sql(expression, "cluster") 1445 cluster = f" {cluster}" if cluster else "" 1446 where = self.sql(expression, "where") 1447 returning = self.sql(expression, "returning") 1448 limit = self.sql(expression, "limit") 1449 tables = self.expressions(expression, key="tables") 1450 tables = f" {tables}" if tables else "" 1451 if self.RETURNING_END: 1452 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1453 else: 1454 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1455 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1456 1457 def drop_sql(self, expression: exp.Drop) -> str: 1458 this = self.sql(expression, "this") 1459 expressions = self.expressions(expression, flat=True) 1460 expressions = f" ({expressions})" if expressions else "" 1461 kind = expression.args["kind"] 1462 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1463 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1464 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1465 on_cluster = self.sql(expression, "cluster") 1466 on_cluster = f" {on_cluster}" if on_cluster else "" 1467 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1468 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1469 cascade = " CASCADE" if expression.args.get("cascade") else "" 1470 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1471 purge = " PURGE" if expression.args.get("purge") else "" 1472 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1473 1474 def set_operation(self, expression: exp.SetOperation) -> str: 1475 op_type = type(expression) 1476 op_name = op_type.key.upper() 1477 1478 distinct = expression.args.get("distinct") 1479 if ( 1480 distinct is False 1481 and op_type in (exp.Except, exp.Intersect) 1482 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1483 ): 1484 self.unsupported(f"{op_name} ALL is not supported") 1485 1486 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1487 1488 if distinct is None: 1489 distinct = default_distinct 1490 if distinct is None: 1491 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1492 1493 if distinct is default_distinct: 1494 distinct_or_all = "" 1495 else: 1496 distinct_or_all = " DISTINCT" if distinct else " ALL" 1497 1498 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1499 side_kind = f"{side_kind} " if side_kind else "" 1500 1501 by_name = " BY NAME" if expression.args.get("by_name") else "" 1502 on = self.expressions(expression, key="on", flat=True) 1503 on = f" ON ({on})" if on else "" 1504 1505 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1506 1507 def set_operations(self, expression: exp.SetOperation) -> str: 1508 if not self.SET_OP_MODIFIERS: 1509 limit = expression.args.get("limit") 1510 order = expression.args.get("order") 1511 1512 if limit or order: 1513 select = self._move_ctes_to_top_level( 1514 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1515 ) 1516 1517 if limit: 1518 select = select.limit(limit.pop(), copy=False) 1519 if order: 1520 select = select.order_by(order.pop(), copy=False) 1521 return self.sql(select) 1522 1523 sqls: t.List[str] = [] 1524 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1525 1526 while stack: 1527 node = stack.pop() 1528 1529 if isinstance(node, exp.SetOperation): 1530 stack.append(node.expression) 1531 stack.append( 1532 self.maybe_comment( 1533 self.set_operation(node), comments=node.comments, separated=True 1534 ) 1535 ) 1536 stack.append(node.this) 1537 else: 1538 sqls.append(self.sql(node)) 1539 1540 this = self.sep().join(sqls) 1541 this = self.query_modifiers(expression, this) 1542 return self.prepend_ctes(expression, this) 1543 1544 def fetch_sql(self, expression: exp.Fetch) -> str: 1545 direction = expression.args.get("direction") 1546 direction = f" {direction}" if direction else "" 1547 count = self.sql(expression, "count") 1548 count = f" {count}" if count else "" 1549 limit_options = self.sql(expression, "limit_options") 1550 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1551 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1552 1553 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1554 percent = " PERCENT" if expression.args.get("percent") else "" 1555 rows = " ROWS" if expression.args.get("rows") else "" 1556 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1557 if not with_ties and rows: 1558 with_ties = " ONLY" 1559 return f"{percent}{rows}{with_ties}" 1560 1561 def filter_sql(self, expression: exp.Filter) -> str: 1562 if self.AGGREGATE_FILTER_SUPPORTED: 1563 this = self.sql(expression, "this") 1564 where = self.sql(expression, "expression").strip() 1565 return f"{this} FILTER({where})" 1566 1567 agg = expression.this 1568 agg_arg = agg.this 1569 cond = expression.expression.this 1570 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1571 return self.sql(agg) 1572 1573 def hint_sql(self, expression: exp.Hint) -> str: 1574 if not self.QUERY_HINTS: 1575 self.unsupported("Hints are not supported") 1576 return "" 1577 1578 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1579 1580 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1581 using = self.sql(expression, "using") 1582 using = f" USING {using}" if using else "" 1583 columns = self.expressions(expression, key="columns", flat=True) 1584 columns = f"({columns})" if columns else "" 1585 partition_by = self.expressions(expression, key="partition_by", flat=True) 1586 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1587 where = self.sql(expression, "where") 1588 include = self.expressions(expression, key="include", flat=True) 1589 if include: 1590 include = f" INCLUDE ({include})" 1591 with_storage = self.expressions(expression, key="with_storage", flat=True) 1592 with_storage = f" WITH ({with_storage})" if with_storage else "" 1593 tablespace = self.sql(expression, "tablespace") 1594 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1595 on = self.sql(expression, "on") 1596 on = f" ON {on}" if on else "" 1597 1598 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1599 1600 def index_sql(self, expression: exp.Index) -> str: 1601 unique = "UNIQUE " if expression.args.get("unique") else "" 1602 primary = "PRIMARY " if expression.args.get("primary") else "" 1603 amp = "AMP " if expression.args.get("amp") else "" 1604 name = self.sql(expression, "this") 1605 name = f"{name} " if name else "" 1606 table = self.sql(expression, "table") 1607 table = f"{self.INDEX_ON} {table}" if table else "" 1608 1609 index = "INDEX " if not table else "" 1610 1611 params = self.sql(expression, "params") 1612 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1613 1614 def identifier_sql(self, expression: exp.Identifier) -> str: 1615 text = expression.name 1616 lower = text.lower() 1617 text = lower if self.normalize and not expression.quoted else text 1618 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1619 if ( 1620 expression.quoted 1621 or self.dialect.can_identify(text, self.identify) 1622 or lower in self.RESERVED_KEYWORDS 1623 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1624 ): 1625 text = f"{self._identifier_start}{text}{self._identifier_end}" 1626 return text 1627 1628 def hex_sql(self, expression: exp.Hex) -> str: 1629 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1630 if self.dialect.HEX_LOWERCASE: 1631 text = self.func("LOWER", text) 1632 1633 return text 1634 1635 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1636 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1637 if not self.dialect.HEX_LOWERCASE: 1638 text = self.func("LOWER", text) 1639 return text 1640 1641 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1642 input_format = self.sql(expression, "input_format") 1643 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1644 output_format = self.sql(expression, "output_format") 1645 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1646 return self.sep().join((input_format, output_format)) 1647 1648 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1649 string = self.sql(exp.Literal.string(expression.name)) 1650 return f"{prefix}{string}" 1651 1652 def partition_sql(self, expression: exp.Partition) -> str: 1653 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1654 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1655 1656 def properties_sql(self, expression: exp.Properties) -> str: 1657 root_properties = [] 1658 with_properties = [] 1659 1660 for p in expression.expressions: 1661 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1662 if p_loc == exp.Properties.Location.POST_WITH: 1663 with_properties.append(p) 1664 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1665 root_properties.append(p) 1666 1667 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1668 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1669 1670 if root_props and with_props and not self.pretty: 1671 with_props = " " + with_props 1672 1673 return root_props + with_props 1674 1675 def root_properties(self, properties: exp.Properties) -> str: 1676 if properties.expressions: 1677 return self.expressions(properties, indent=False, sep=" ") 1678 return "" 1679 1680 def properties( 1681 self, 1682 properties: exp.Properties, 1683 prefix: str = "", 1684 sep: str = ", ", 1685 suffix: str = "", 1686 wrapped: bool = True, 1687 ) -> str: 1688 if properties.expressions: 1689 expressions = self.expressions(properties, sep=sep, indent=False) 1690 if expressions: 1691 expressions = self.wrap(expressions) if wrapped else expressions 1692 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1693 return "" 1694 1695 def with_properties(self, properties: exp.Properties) -> str: 1696 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1697 1698 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1699 properties_locs = defaultdict(list) 1700 for p in properties.expressions: 1701 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1702 if p_loc != exp.Properties.Location.UNSUPPORTED: 1703 properties_locs[p_loc].append(p) 1704 else: 1705 self.unsupported(f"Unsupported property {p.key}") 1706 1707 return properties_locs 1708 1709 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1710 if isinstance(expression.this, exp.Dot): 1711 return self.sql(expression, "this") 1712 return f"'{expression.name}'" if string_key else expression.name 1713 1714 def property_sql(self, expression: exp.Property) -> str: 1715 property_cls = expression.__class__ 1716 if property_cls == exp.Property: 1717 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1718 1719 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1720 if not property_name: 1721 self.unsupported(f"Unsupported property {expression.key}") 1722 1723 return f"{property_name}={self.sql(expression, 'this')}" 1724 1725 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1726 if self.SUPPORTS_CREATE_TABLE_LIKE: 1727 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1728 options = f" {options}" if options else "" 1729 1730 like = f"LIKE {self.sql(expression, 'this')}{options}" 1731 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1732 like = f"({like})" 1733 1734 return like 1735 1736 if expression.expressions: 1737 self.unsupported("Transpilation of LIKE property options is unsupported") 1738 1739 select = exp.select("*").from_(expression.this).limit(0) 1740 return f"AS {self.sql(select)}" 1741 1742 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1743 no = "NO " if expression.args.get("no") else "" 1744 protection = " PROTECTION" if expression.args.get("protection") else "" 1745 return f"{no}FALLBACK{protection}" 1746 1747 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1748 no = "NO " if expression.args.get("no") else "" 1749 local = expression.args.get("local") 1750 local = f"{local} " if local else "" 1751 dual = "DUAL " if expression.args.get("dual") else "" 1752 before = "BEFORE " if expression.args.get("before") else "" 1753 after = "AFTER " if expression.args.get("after") else "" 1754 return f"{no}{local}{dual}{before}{after}JOURNAL" 1755 1756 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1757 freespace = self.sql(expression, "this") 1758 percent = " PERCENT" if expression.args.get("percent") else "" 1759 return f"FREESPACE={freespace}{percent}" 1760 1761 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1762 if expression.args.get("default"): 1763 property = "DEFAULT" 1764 elif expression.args.get("on"): 1765 property = "ON" 1766 else: 1767 property = "OFF" 1768 return f"CHECKSUM={property}" 1769 1770 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1771 if expression.args.get("no"): 1772 return "NO MERGEBLOCKRATIO" 1773 if expression.args.get("default"): 1774 return "DEFAULT MERGEBLOCKRATIO" 1775 1776 percent = " PERCENT" if expression.args.get("percent") else "" 1777 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1778 1779 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1780 default = expression.args.get("default") 1781 minimum = expression.args.get("minimum") 1782 maximum = expression.args.get("maximum") 1783 if default or minimum or maximum: 1784 if default: 1785 prop = "DEFAULT" 1786 elif minimum: 1787 prop = "MINIMUM" 1788 else: 1789 prop = "MAXIMUM" 1790 return f"{prop} DATABLOCKSIZE" 1791 units = expression.args.get("units") 1792 units = f" {units}" if units else "" 1793 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1794 1795 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1796 autotemp = expression.args.get("autotemp") 1797 always = expression.args.get("always") 1798 default = expression.args.get("default") 1799 manual = expression.args.get("manual") 1800 never = expression.args.get("never") 1801 1802 if autotemp is not None: 1803 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1804 elif always: 1805 prop = "ALWAYS" 1806 elif default: 1807 prop = "DEFAULT" 1808 elif manual: 1809 prop = "MANUAL" 1810 elif never: 1811 prop = "NEVER" 1812 return f"BLOCKCOMPRESSION={prop}" 1813 1814 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1815 no = expression.args.get("no") 1816 no = " NO" if no else "" 1817 concurrent = expression.args.get("concurrent") 1818 concurrent = " CONCURRENT" if concurrent else "" 1819 target = self.sql(expression, "target") 1820 target = f" {target}" if target else "" 1821 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1822 1823 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1824 if isinstance(expression.this, list): 1825 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1826 if expression.this: 1827 modulus = self.sql(expression, "this") 1828 remainder = self.sql(expression, "expression") 1829 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1830 1831 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1832 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1833 return f"FROM ({from_expressions}) TO ({to_expressions})" 1834 1835 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1836 this = self.sql(expression, "this") 1837 1838 for_values_or_default = expression.expression 1839 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1840 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1841 else: 1842 for_values_or_default = " DEFAULT" 1843 1844 return f"PARTITION OF {this}{for_values_or_default}" 1845 1846 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1847 kind = expression.args.get("kind") 1848 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1849 for_or_in = expression.args.get("for_or_in") 1850 for_or_in = f" {for_or_in}" if for_or_in else "" 1851 lock_type = expression.args.get("lock_type") 1852 override = " OVERRIDE" if expression.args.get("override") else "" 1853 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1854 1855 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1856 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1857 statistics = expression.args.get("statistics") 1858 statistics_sql = "" 1859 if statistics is not None: 1860 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1861 return f"{data_sql}{statistics_sql}" 1862 1863 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1864 this = self.sql(expression, "this") 1865 this = f"HISTORY_TABLE={this}" if this else "" 1866 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1867 data_consistency = ( 1868 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1869 ) 1870 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1871 retention_period = ( 1872 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1873 ) 1874 1875 if this: 1876 on_sql = self.func("ON", this, data_consistency, retention_period) 1877 else: 1878 on_sql = "ON" if expression.args.get("on") else "OFF" 1879 1880 sql = f"SYSTEM_VERSIONING={on_sql}" 1881 1882 return f"WITH({sql})" if expression.args.get("with") else sql 1883 1884 def insert_sql(self, expression: exp.Insert) -> str: 1885 hint = self.sql(expression, "hint") 1886 overwrite = expression.args.get("overwrite") 1887 1888 if isinstance(expression.this, exp.Directory): 1889 this = " OVERWRITE" if overwrite else " INTO" 1890 else: 1891 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1892 1893 stored = self.sql(expression, "stored") 1894 stored = f" {stored}" if stored else "" 1895 alternative = expression.args.get("alternative") 1896 alternative = f" OR {alternative}" if alternative else "" 1897 ignore = " IGNORE" if expression.args.get("ignore") else "" 1898 is_function = expression.args.get("is_function") 1899 if is_function: 1900 this = f"{this} FUNCTION" 1901 this = f"{this} {self.sql(expression, 'this')}" 1902 1903 exists = " IF EXISTS" if expression.args.get("exists") else "" 1904 where = self.sql(expression, "where") 1905 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1906 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1907 on_conflict = self.sql(expression, "conflict") 1908 on_conflict = f" {on_conflict}" if on_conflict else "" 1909 by_name = " BY NAME" if expression.args.get("by_name") else "" 1910 returning = self.sql(expression, "returning") 1911 1912 if self.RETURNING_END: 1913 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1914 else: 1915 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1916 1917 partition_by = self.sql(expression, "partition") 1918 partition_by = f" {partition_by}" if partition_by else "" 1919 settings = self.sql(expression, "settings") 1920 settings = f" {settings}" if settings else "" 1921 1922 source = self.sql(expression, "source") 1923 source = f"TABLE {source}" if source else "" 1924 1925 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1926 return self.prepend_ctes(expression, sql) 1927 1928 def introducer_sql(self, expression: exp.Introducer) -> str: 1929 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1930 1931 def kill_sql(self, expression: exp.Kill) -> str: 1932 kind = self.sql(expression, "kind") 1933 kind = f" {kind}" if kind else "" 1934 this = self.sql(expression, "this") 1935 this = f" {this}" if this else "" 1936 return f"KILL{kind}{this}" 1937 1938 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1939 return expression.name 1940 1941 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1942 return expression.name 1943 1944 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1945 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1946 1947 constraint = self.sql(expression, "constraint") 1948 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1949 1950 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1951 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1952 action = self.sql(expression, "action") 1953 1954 expressions = self.expressions(expression, flat=True) 1955 if expressions: 1956 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1957 expressions = f" {set_keyword}{expressions}" 1958 1959 where = self.sql(expression, "where") 1960 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1961 1962 def returning_sql(self, expression: exp.Returning) -> str: 1963 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1964 1965 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1966 fields = self.sql(expression, "fields") 1967 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1968 escaped = self.sql(expression, "escaped") 1969 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1970 items = self.sql(expression, "collection_items") 1971 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1972 keys = self.sql(expression, "map_keys") 1973 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1974 lines = self.sql(expression, "lines") 1975 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1976 null = self.sql(expression, "null") 1977 null = f" NULL DEFINED AS {null}" if null else "" 1978 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1979 1980 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1981 return f"WITH ({self.expressions(expression, flat=True)})" 1982 1983 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1984 this = f"{self.sql(expression, 'this')} INDEX" 1985 target = self.sql(expression, "target") 1986 target = f" FOR {target}" if target else "" 1987 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1988 1989 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1990 this = self.sql(expression, "this") 1991 kind = self.sql(expression, "kind") 1992 expr = self.sql(expression, "expression") 1993 return f"{this} ({kind} => {expr})" 1994 1995 def table_parts(self, expression: exp.Table) -> str: 1996 return ".".join( 1997 self.sql(part) 1998 for part in ( 1999 expression.args.get("catalog"), 2000 expression.args.get("db"), 2001 expression.args.get("this"), 2002 ) 2003 if part is not None 2004 ) 2005 2006 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2007 table = self.table_parts(expression) 2008 only = "ONLY " if expression.args.get("only") else "" 2009 partition = self.sql(expression, "partition") 2010 partition = f" {partition}" if partition else "" 2011 version = self.sql(expression, "version") 2012 version = f" {version}" if version else "" 2013 alias = self.sql(expression, "alias") 2014 alias = f"{sep}{alias}" if alias else "" 2015 2016 sample = self.sql(expression, "sample") 2017 if self.dialect.ALIAS_POST_TABLESAMPLE: 2018 sample_pre_alias = sample 2019 sample_post_alias = "" 2020 else: 2021 sample_pre_alias = "" 2022 sample_post_alias = sample 2023 2024 hints = self.expressions(expression, key="hints", sep=" ") 2025 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2026 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2027 joins = self.indent( 2028 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2029 ) 2030 laterals = self.expressions(expression, key="laterals", sep="") 2031 2032 file_format = self.sql(expression, "format") 2033 if file_format: 2034 pattern = self.sql(expression, "pattern") 2035 pattern = f", PATTERN => {pattern}" if pattern else "" 2036 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2037 2038 ordinality = expression.args.get("ordinality") or "" 2039 if ordinality: 2040 ordinality = f" WITH ORDINALITY{alias}" 2041 alias = "" 2042 2043 when = self.sql(expression, "when") 2044 if when: 2045 table = f"{table} {when}" 2046 2047 changes = self.sql(expression, "changes") 2048 changes = f" {changes}" if changes else "" 2049 2050 rows_from = self.expressions(expression, key="rows_from") 2051 if rows_from: 2052 table = f"ROWS FROM {self.wrap(rows_from)}" 2053 2054 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2055 2056 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2057 table = self.func("TABLE", expression.this) 2058 alias = self.sql(expression, "alias") 2059 alias = f" AS {alias}" if alias else "" 2060 sample = self.sql(expression, "sample") 2061 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2062 joins = self.indent( 2063 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2064 ) 2065 return f"{table}{alias}{pivots}{sample}{joins}" 2066 2067 def tablesample_sql( 2068 self, 2069 expression: exp.TableSample, 2070 tablesample_keyword: t.Optional[str] = None, 2071 ) -> str: 2072 method = self.sql(expression, "method") 2073 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2074 numerator = self.sql(expression, "bucket_numerator") 2075 denominator = self.sql(expression, "bucket_denominator") 2076 field = self.sql(expression, "bucket_field") 2077 field = f" ON {field}" if field else "" 2078 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2079 seed = self.sql(expression, "seed") 2080 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2081 2082 size = self.sql(expression, "size") 2083 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2084 size = f"{size} ROWS" 2085 2086 percent = self.sql(expression, "percent") 2087 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2088 percent = f"{percent} PERCENT" 2089 2090 expr = f"{bucket}{percent}{size}" 2091 if self.TABLESAMPLE_REQUIRES_PARENS: 2092 expr = f"({expr})" 2093 2094 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2095 2096 def pivot_sql(self, expression: exp.Pivot) -> str: 2097 expressions = self.expressions(expression, flat=True) 2098 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2099 2100 group = self.sql(expression, "group") 2101 2102 if expression.this: 2103 this = self.sql(expression, "this") 2104 if not expressions: 2105 return f"UNPIVOT {this}" 2106 2107 on = f"{self.seg('ON')} {expressions}" 2108 into = self.sql(expression, "into") 2109 into = f"{self.seg('INTO')} {into}" if into else "" 2110 using = self.expressions(expression, key="using", flat=True) 2111 using = f"{self.seg('USING')} {using}" if using else "" 2112 return f"{direction} {this}{on}{into}{using}{group}" 2113 2114 alias = self.sql(expression, "alias") 2115 alias = f" AS {alias}" if alias else "" 2116 2117 fields = self.expressions( 2118 expression, 2119 "fields", 2120 sep=" ", 2121 dynamic=True, 2122 new_line=True, 2123 skip_first=True, 2124 skip_last=True, 2125 ) 2126 2127 include_nulls = expression.args.get("include_nulls") 2128 if include_nulls is not None: 2129 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2130 else: 2131 nulls = "" 2132 2133 default_on_null = self.sql(expression, "default_on_null") 2134 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2135 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2136 2137 def version_sql(self, expression: exp.Version) -> str: 2138 this = f"FOR {expression.name}" 2139 kind = expression.text("kind") 2140 expr = self.sql(expression, "expression") 2141 return f"{this} {kind} {expr}" 2142 2143 def tuple_sql(self, expression: exp.Tuple) -> str: 2144 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2145 2146 def update_sql(self, expression: exp.Update) -> str: 2147 this = self.sql(expression, "this") 2148 set_sql = self.expressions(expression, flat=True) 2149 from_sql = self.sql(expression, "from") 2150 where_sql = self.sql(expression, "where") 2151 returning = self.sql(expression, "returning") 2152 order = self.sql(expression, "order") 2153 limit = self.sql(expression, "limit") 2154 if self.RETURNING_END: 2155 expression_sql = f"{from_sql}{where_sql}{returning}" 2156 else: 2157 expression_sql = f"{returning}{from_sql}{where_sql}" 2158 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2159 return self.prepend_ctes(expression, sql) 2160 2161 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2162 values_as_table = values_as_table and self.VALUES_AS_TABLE 2163 2164 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2165 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2166 args = self.expressions(expression) 2167 alias = self.sql(expression, "alias") 2168 values = f"VALUES{self.seg('')}{args}" 2169 values = ( 2170 f"({values})" 2171 if self.WRAP_DERIVED_VALUES 2172 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2173 else values 2174 ) 2175 return f"{values} AS {alias}" if alias else values 2176 2177 # Converts `VALUES...` expression into a series of select unions. 2178 alias_node = expression.args.get("alias") 2179 column_names = alias_node and alias_node.columns 2180 2181 selects: t.List[exp.Query] = [] 2182 2183 for i, tup in enumerate(expression.expressions): 2184 row = tup.expressions 2185 2186 if i == 0 and column_names: 2187 row = [ 2188 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2189 ] 2190 2191 selects.append(exp.Select(expressions=row)) 2192 2193 if self.pretty: 2194 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2195 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2196 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2197 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2198 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2199 2200 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2201 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2202 return f"({unions}){alias}" 2203 2204 def var_sql(self, expression: exp.Var) -> str: 2205 return self.sql(expression, "this") 2206 2207 @unsupported_args("expressions") 2208 def into_sql(self, expression: exp.Into) -> str: 2209 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2210 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2211 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2212 2213 def from_sql(self, expression: exp.From) -> str: 2214 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2215 2216 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2217 grouping_sets = self.expressions(expression, indent=False) 2218 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2219 2220 def rollup_sql(self, expression: exp.Rollup) -> str: 2221 expressions = self.expressions(expression, indent=False) 2222 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2223 2224 def cube_sql(self, expression: exp.Cube) -> str: 2225 expressions = self.expressions(expression, indent=False) 2226 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2227 2228 def group_sql(self, expression: exp.Group) -> str: 2229 group_by_all = expression.args.get("all") 2230 if group_by_all is True: 2231 modifier = " ALL" 2232 elif group_by_all is False: 2233 modifier = " DISTINCT" 2234 else: 2235 modifier = "" 2236 2237 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2238 2239 grouping_sets = self.expressions(expression, key="grouping_sets") 2240 cube = self.expressions(expression, key="cube") 2241 rollup = self.expressions(expression, key="rollup") 2242 2243 groupings = csv( 2244 self.seg(grouping_sets) if grouping_sets else "", 2245 self.seg(cube) if cube else "", 2246 self.seg(rollup) if rollup else "", 2247 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2248 sep=self.GROUPINGS_SEP, 2249 ) 2250 2251 if ( 2252 expression.expressions 2253 and groupings 2254 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2255 ): 2256 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2257 2258 return f"{group_by}{groupings}" 2259 2260 def having_sql(self, expression: exp.Having) -> str: 2261 this = self.indent(self.sql(expression, "this")) 2262 return f"{self.seg('HAVING')}{self.sep()}{this}" 2263 2264 def connect_sql(self, expression: exp.Connect) -> str: 2265 start = self.sql(expression, "start") 2266 start = self.seg(f"START WITH {start}") if start else "" 2267 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2268 connect = self.sql(expression, "connect") 2269 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2270 return start + connect 2271 2272 def prior_sql(self, expression: exp.Prior) -> str: 2273 return f"PRIOR {self.sql(expression, 'this')}" 2274 2275 def join_sql(self, expression: exp.Join) -> str: 2276 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2277 side = None 2278 else: 2279 side = expression.side 2280 2281 op_sql = " ".join( 2282 op 2283 for op in ( 2284 expression.method, 2285 "GLOBAL" if expression.args.get("global") else None, 2286 side, 2287 expression.kind, 2288 expression.hint if self.JOIN_HINTS else None, 2289 ) 2290 if op 2291 ) 2292 match_cond = self.sql(expression, "match_condition") 2293 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2294 on_sql = self.sql(expression, "on") 2295 using = expression.args.get("using") 2296 2297 if not on_sql and using: 2298 on_sql = csv(*(self.sql(column) for column in using)) 2299 2300 this = expression.this 2301 this_sql = self.sql(this) 2302 2303 exprs = self.expressions(expression) 2304 if exprs: 2305 this_sql = f"{this_sql},{self.seg(exprs)}" 2306 2307 if on_sql: 2308 on_sql = self.indent(on_sql, skip_first=True) 2309 space = self.seg(" " * self.pad) if self.pretty else " " 2310 if using: 2311 on_sql = f"{space}USING ({on_sql})" 2312 else: 2313 on_sql = f"{space}ON {on_sql}" 2314 elif not op_sql: 2315 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2316 return f" {this_sql}" 2317 2318 return f", {this_sql}" 2319 2320 if op_sql != "STRAIGHT_JOIN": 2321 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2322 2323 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2324 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2325 2326 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2327 args = self.expressions(expression, flat=True) 2328 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2329 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2330 2331 def lateral_op(self, expression: exp.Lateral) -> str: 2332 cross_apply = expression.args.get("cross_apply") 2333 2334 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2335 if cross_apply is True: 2336 op = "INNER JOIN " 2337 elif cross_apply is False: 2338 op = "LEFT JOIN " 2339 else: 2340 op = "" 2341 2342 return f"{op}LATERAL" 2343 2344 def lateral_sql(self, expression: exp.Lateral) -> str: 2345 this = self.sql(expression, "this") 2346 2347 if expression.args.get("view"): 2348 alias = expression.args["alias"] 2349 columns = self.expressions(alias, key="columns", flat=True) 2350 table = f" {alias.name}" if alias.name else "" 2351 columns = f" AS {columns}" if columns else "" 2352 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2353 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2354 2355 alias = self.sql(expression, "alias") 2356 alias = f" AS {alias}" if alias else "" 2357 2358 ordinality = expression.args.get("ordinality") or "" 2359 if ordinality: 2360 ordinality = f" WITH ORDINALITY{alias}" 2361 alias = "" 2362 2363 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2364 2365 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2366 this = self.sql(expression, "this") 2367 2368 args = [ 2369 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2370 for e in (expression.args.get(k) for k in ("offset", "expression")) 2371 if e 2372 ] 2373 2374 args_sql = ", ".join(self.sql(e) for e in args) 2375 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2376 expressions = self.expressions(expression, flat=True) 2377 limit_options = self.sql(expression, "limit_options") 2378 expressions = f" BY {expressions}" if expressions else "" 2379 2380 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2381 2382 def offset_sql(self, expression: exp.Offset) -> str: 2383 this = self.sql(expression, "this") 2384 value = expression.expression 2385 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2386 expressions = self.expressions(expression, flat=True) 2387 expressions = f" BY {expressions}" if expressions else "" 2388 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2389 2390 def setitem_sql(self, expression: exp.SetItem) -> str: 2391 kind = self.sql(expression, "kind") 2392 kind = f"{kind} " if kind else "" 2393 this = self.sql(expression, "this") 2394 expressions = self.expressions(expression) 2395 collate = self.sql(expression, "collate") 2396 collate = f" COLLATE {collate}" if collate else "" 2397 global_ = "GLOBAL " if expression.args.get("global") else "" 2398 return f"{global_}{kind}{this}{expressions}{collate}" 2399 2400 def set_sql(self, expression: exp.Set) -> str: 2401 expressions = f" {self.expressions(expression, flat=True)}" 2402 tag = " TAG" if expression.args.get("tag") else "" 2403 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2404 2405 def pragma_sql(self, expression: exp.Pragma) -> str: 2406 return f"PRAGMA {self.sql(expression, 'this')}" 2407 2408 def lock_sql(self, expression: exp.Lock) -> str: 2409 if not self.LOCKING_READS_SUPPORTED: 2410 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2411 return "" 2412 2413 update = expression.args["update"] 2414 key = expression.args.get("key") 2415 if update: 2416 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2417 else: 2418 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2419 expressions = self.expressions(expression, flat=True) 2420 expressions = f" OF {expressions}" if expressions else "" 2421 wait = expression.args.get("wait") 2422 2423 if wait is not None: 2424 if isinstance(wait, exp.Literal): 2425 wait = f" WAIT {self.sql(wait)}" 2426 else: 2427 wait = " NOWAIT" if wait else " SKIP LOCKED" 2428 2429 return f"{lock_type}{expressions}{wait or ''}" 2430 2431 def literal_sql(self, expression: exp.Literal) -> str: 2432 text = expression.this or "" 2433 if expression.is_string: 2434 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2435 return text 2436 2437 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2438 if self.dialect.ESCAPED_SEQUENCES: 2439 to_escaped = self.dialect.ESCAPED_SEQUENCES 2440 text = "".join( 2441 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2442 ) 2443 2444 return self._replace_line_breaks(text).replace( 2445 self.dialect.QUOTE_END, self._escaped_quote_end 2446 ) 2447 2448 def loaddata_sql(self, expression: exp.LoadData) -> str: 2449 local = " LOCAL" if expression.args.get("local") else "" 2450 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2451 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2452 this = f" INTO TABLE {self.sql(expression, 'this')}" 2453 partition = self.sql(expression, "partition") 2454 partition = f" {partition}" if partition else "" 2455 input_format = self.sql(expression, "input_format") 2456 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2457 serde = self.sql(expression, "serde") 2458 serde = f" SERDE {serde}" if serde else "" 2459 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2460 2461 def null_sql(self, *_) -> str: 2462 return "NULL" 2463 2464 def boolean_sql(self, expression: exp.Boolean) -> str: 2465 return "TRUE" if expression.this else "FALSE" 2466 2467 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2468 this = self.sql(expression, "this") 2469 this = f"{this} " if this else this 2470 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2471 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2472 2473 def withfill_sql(self, expression: exp.WithFill) -> str: 2474 from_sql = self.sql(expression, "from") 2475 from_sql = f" FROM {from_sql}" if from_sql else "" 2476 to_sql = self.sql(expression, "to") 2477 to_sql = f" TO {to_sql}" if to_sql else "" 2478 step_sql = self.sql(expression, "step") 2479 step_sql = f" STEP {step_sql}" if step_sql else "" 2480 interpolated_values = [ 2481 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2482 if isinstance(e, exp.Alias) 2483 else self.sql(e, "this") 2484 for e in expression.args.get("interpolate") or [] 2485 ] 2486 interpolate = ( 2487 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2488 ) 2489 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2490 2491 def cluster_sql(self, expression: exp.Cluster) -> str: 2492 return self.op_expressions("CLUSTER BY", expression) 2493 2494 def distribute_sql(self, expression: exp.Distribute) -> str: 2495 return self.op_expressions("DISTRIBUTE BY", expression) 2496 2497 def sort_sql(self, expression: exp.Sort) -> str: 2498 return self.op_expressions("SORT BY", expression) 2499 2500 def ordered_sql(self, expression: exp.Ordered) -> str: 2501 desc = expression.args.get("desc") 2502 asc = not desc 2503 2504 nulls_first = expression.args.get("nulls_first") 2505 nulls_last = not nulls_first 2506 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2507 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2508 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2509 2510 this = self.sql(expression, "this") 2511 2512 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2513 nulls_sort_change = "" 2514 if nulls_first and ( 2515 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2516 ): 2517 nulls_sort_change = " NULLS FIRST" 2518 elif ( 2519 nulls_last 2520 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2521 and not nulls_are_last 2522 ): 2523 nulls_sort_change = " NULLS LAST" 2524 2525 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2526 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2527 window = expression.find_ancestor(exp.Window, exp.Select) 2528 if isinstance(window, exp.Window) and window.args.get("spec"): 2529 self.unsupported( 2530 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2531 ) 2532 nulls_sort_change = "" 2533 elif self.NULL_ORDERING_SUPPORTED is False and ( 2534 (asc and nulls_sort_change == " NULLS LAST") 2535 or (desc and nulls_sort_change == " NULLS FIRST") 2536 ): 2537 # BigQuery does not allow these ordering/nulls combinations when used under 2538 # an aggregation func or under a window containing one 2539 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2540 2541 if isinstance(ancestor, exp.Window): 2542 ancestor = ancestor.this 2543 if isinstance(ancestor, exp.AggFunc): 2544 self.unsupported( 2545 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2546 ) 2547 nulls_sort_change = "" 2548 elif self.NULL_ORDERING_SUPPORTED is None: 2549 if expression.this.is_int: 2550 self.unsupported( 2551 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2552 ) 2553 elif not isinstance(expression.this, exp.Rand): 2554 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2555 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2556 nulls_sort_change = "" 2557 2558 with_fill = self.sql(expression, "with_fill") 2559 with_fill = f" {with_fill}" if with_fill else "" 2560 2561 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2562 2563 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2564 window_frame = self.sql(expression, "window_frame") 2565 window_frame = f"{window_frame} " if window_frame else "" 2566 2567 this = self.sql(expression, "this") 2568 2569 return f"{window_frame}{this}" 2570 2571 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2572 partition = self.partition_by_sql(expression) 2573 order = self.sql(expression, "order") 2574 measures = self.expressions(expression, key="measures") 2575 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2576 rows = self.sql(expression, "rows") 2577 rows = self.seg(rows) if rows else "" 2578 after = self.sql(expression, "after") 2579 after = self.seg(after) if after else "" 2580 pattern = self.sql(expression, "pattern") 2581 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2582 definition_sqls = [ 2583 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2584 for definition in expression.args.get("define", []) 2585 ] 2586 definitions = self.expressions(sqls=definition_sqls) 2587 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2588 body = "".join( 2589 ( 2590 partition, 2591 order, 2592 measures, 2593 rows, 2594 after, 2595 pattern, 2596 define, 2597 ) 2598 ) 2599 alias = self.sql(expression, "alias") 2600 alias = f" {alias}" if alias else "" 2601 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2602 2603 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2604 limit = expression.args.get("limit") 2605 2606 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2607 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2608 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2609 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2610 2611 return csv( 2612 *sqls, 2613 *[self.sql(join) for join in expression.args.get("joins") or []], 2614 self.sql(expression, "match"), 2615 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2616 self.sql(expression, "prewhere"), 2617 self.sql(expression, "where"), 2618 self.sql(expression, "connect"), 2619 self.sql(expression, "group"), 2620 self.sql(expression, "having"), 2621 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2622 self.sql(expression, "order"), 2623 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2624 *self.after_limit_modifiers(expression), 2625 self.options_modifier(expression), 2626 self.for_modifiers(expression), 2627 sep="", 2628 ) 2629 2630 def options_modifier(self, expression: exp.Expression) -> str: 2631 options = self.expressions(expression, key="options") 2632 return f" {options}" if options else "" 2633 2634 def for_modifiers(self, expression: exp.Expression) -> str: 2635 for_modifiers = self.expressions(expression, key="for") 2636 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2637 2638 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2639 self.unsupported("Unsupported query option.") 2640 return "" 2641 2642 def offset_limit_modifiers( 2643 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2644 ) -> t.List[str]: 2645 return [ 2646 self.sql(expression, "offset") if fetch else self.sql(limit), 2647 self.sql(limit) if fetch else self.sql(expression, "offset"), 2648 ] 2649 2650 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2651 locks = self.expressions(expression, key="locks", sep=" ") 2652 locks = f" {locks}" if locks else "" 2653 return [locks, self.sql(expression, "sample")] 2654 2655 def select_sql(self, expression: exp.Select) -> str: 2656 into = expression.args.get("into") 2657 if not self.SUPPORTS_SELECT_INTO and into: 2658 into.pop() 2659 2660 hint = self.sql(expression, "hint") 2661 distinct = self.sql(expression, "distinct") 2662 distinct = f" {distinct}" if distinct else "" 2663 kind = self.sql(expression, "kind") 2664 2665 limit = expression.args.get("limit") 2666 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2667 top = self.limit_sql(limit, top=True) 2668 limit.pop() 2669 else: 2670 top = "" 2671 2672 expressions = self.expressions(expression) 2673 2674 if kind: 2675 if kind in self.SELECT_KINDS: 2676 kind = f" AS {kind}" 2677 else: 2678 if kind == "STRUCT": 2679 expressions = self.expressions( 2680 sqls=[ 2681 self.sql( 2682 exp.Struct( 2683 expressions=[ 2684 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2685 if isinstance(e, exp.Alias) 2686 else e 2687 for e in expression.expressions 2688 ] 2689 ) 2690 ) 2691 ] 2692 ) 2693 kind = "" 2694 2695 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2696 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2697 2698 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2699 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2700 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2701 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2702 sql = self.query_modifiers( 2703 expression, 2704 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2705 self.sql(expression, "into", comment=False), 2706 self.sql(expression, "from", comment=False), 2707 ) 2708 2709 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2710 if expression.args.get("with"): 2711 sql = self.maybe_comment(sql, expression) 2712 expression.pop_comments() 2713 2714 sql = self.prepend_ctes(expression, sql) 2715 2716 if not self.SUPPORTS_SELECT_INTO and into: 2717 if into.args.get("temporary"): 2718 table_kind = " TEMPORARY" 2719 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2720 table_kind = " UNLOGGED" 2721 else: 2722 table_kind = "" 2723 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2724 2725 return sql 2726 2727 def schema_sql(self, expression: exp.Schema) -> str: 2728 this = self.sql(expression, "this") 2729 sql = self.schema_columns_sql(expression) 2730 return f"{this} {sql}" if this and sql else this or sql 2731 2732 def schema_columns_sql(self, expression: exp.Schema) -> str: 2733 if expression.expressions: 2734 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2735 return "" 2736 2737 def star_sql(self, expression: exp.Star) -> str: 2738 except_ = self.expressions(expression, key="except", flat=True) 2739 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2740 replace = self.expressions(expression, key="replace", flat=True) 2741 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2742 rename = self.expressions(expression, key="rename", flat=True) 2743 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2744 return f"*{except_}{replace}{rename}" 2745 2746 def parameter_sql(self, expression: exp.Parameter) -> str: 2747 this = self.sql(expression, "this") 2748 return f"{self.PARAMETER_TOKEN}{this}" 2749 2750 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2751 this = self.sql(expression, "this") 2752 kind = expression.text("kind") 2753 if kind: 2754 kind = f"{kind}." 2755 return f"@@{kind}{this}" 2756 2757 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2758 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2759 2760 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2761 alias = self.sql(expression, "alias") 2762 alias = f"{sep}{alias}" if alias else "" 2763 sample = self.sql(expression, "sample") 2764 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2765 alias = f"{sample}{alias}" 2766 2767 # Set to None so it's not generated again by self.query_modifiers() 2768 expression.set("sample", None) 2769 2770 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2771 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2772 return self.prepend_ctes(expression, sql) 2773 2774 def qualify_sql(self, expression: exp.Qualify) -> str: 2775 this = self.indent(self.sql(expression, "this")) 2776 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2777 2778 def unnest_sql(self, expression: exp.Unnest) -> str: 2779 args = self.expressions(expression, flat=True) 2780 2781 alias = expression.args.get("alias") 2782 offset = expression.args.get("offset") 2783 2784 if self.UNNEST_WITH_ORDINALITY: 2785 if alias and isinstance(offset, exp.Expression): 2786 alias.append("columns", offset) 2787 2788 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2789 columns = alias.columns 2790 alias = self.sql(columns[0]) if columns else "" 2791 else: 2792 alias = self.sql(alias) 2793 2794 alias = f" AS {alias}" if alias else alias 2795 if self.UNNEST_WITH_ORDINALITY: 2796 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2797 else: 2798 if isinstance(offset, exp.Expression): 2799 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2800 elif offset: 2801 suffix = f"{alias} WITH OFFSET" 2802 else: 2803 suffix = alias 2804 2805 return f"UNNEST({args}){suffix}" 2806 2807 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2808 return "" 2809 2810 def where_sql(self, expression: exp.Where) -> str: 2811 this = self.indent(self.sql(expression, "this")) 2812 return f"{self.seg('WHERE')}{self.sep()}{this}" 2813 2814 def window_sql(self, expression: exp.Window) -> str: 2815 this = self.sql(expression, "this") 2816 partition = self.partition_by_sql(expression) 2817 order = expression.args.get("order") 2818 order = self.order_sql(order, flat=True) if order else "" 2819 spec = self.sql(expression, "spec") 2820 alias = self.sql(expression, "alias") 2821 over = self.sql(expression, "over") or "OVER" 2822 2823 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2824 2825 first = expression.args.get("first") 2826 if first is None: 2827 first = "" 2828 else: 2829 first = "FIRST" if first else "LAST" 2830 2831 if not partition and not order and not spec and alias: 2832 return f"{this} {alias}" 2833 2834 args = self.format_args( 2835 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2836 ) 2837 return f"{this} ({args})" 2838 2839 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2840 partition = self.expressions(expression, key="partition_by", flat=True) 2841 return f"PARTITION BY {partition}" if partition else "" 2842 2843 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2844 kind = self.sql(expression, "kind") 2845 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2846 end = ( 2847 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2848 or "CURRENT ROW" 2849 ) 2850 2851 window_spec = f"{kind} BETWEEN {start} AND {end}" 2852 2853 exclude = self.sql(expression, "exclude") 2854 if exclude: 2855 if self.SUPPORTS_WINDOW_EXCLUDE: 2856 window_spec += f" EXCLUDE {exclude}" 2857 else: 2858 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2859 2860 return window_spec 2861 2862 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2863 this = self.sql(expression, "this") 2864 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2865 return f"{this} WITHIN GROUP ({expression_sql})" 2866 2867 def between_sql(self, expression: exp.Between) -> str: 2868 this = self.sql(expression, "this") 2869 low = self.sql(expression, "low") 2870 high = self.sql(expression, "high") 2871 symmetric = expression.args.get("symmetric") 2872 2873 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2874 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2875 2876 flag = ( 2877 " SYMMETRIC" 2878 if symmetric 2879 else " ASYMMETRIC" 2880 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2881 else "" # silently drop ASYMMETRIC – semantics identical 2882 ) 2883 return f"{this} BETWEEN{flag} {low} AND {high}" 2884 2885 def bracket_offset_expressions( 2886 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2887 ) -> t.List[exp.Expression]: 2888 return apply_index_offset( 2889 expression.this, 2890 expression.expressions, 2891 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2892 dialect=self.dialect, 2893 ) 2894 2895 def bracket_sql(self, expression: exp.Bracket) -> str: 2896 expressions = self.bracket_offset_expressions(expression) 2897 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2898 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2899 2900 def all_sql(self, expression: exp.All) -> str: 2901 return f"ALL {self.wrap(expression)}" 2902 2903 def any_sql(self, expression: exp.Any) -> str: 2904 this = self.sql(expression, "this") 2905 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2906 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2907 this = self.wrap(this) 2908 return f"ANY{this}" 2909 return f"ANY {this}" 2910 2911 def exists_sql(self, expression: exp.Exists) -> str: 2912 return f"EXISTS{self.wrap(expression)}" 2913 2914 def case_sql(self, expression: exp.Case) -> str: 2915 this = self.sql(expression, "this") 2916 statements = [f"CASE {this}" if this else "CASE"] 2917 2918 for e in expression.args["ifs"]: 2919 statements.append(f"WHEN {self.sql(e, 'this')}") 2920 statements.append(f"THEN {self.sql(e, 'true')}") 2921 2922 default = self.sql(expression, "default") 2923 2924 if default: 2925 statements.append(f"ELSE {default}") 2926 2927 statements.append("END") 2928 2929 if self.pretty and self.too_wide(statements): 2930 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2931 2932 return " ".join(statements) 2933 2934 def constraint_sql(self, expression: exp.Constraint) -> str: 2935 this = self.sql(expression, "this") 2936 expressions = self.expressions(expression, flat=True) 2937 return f"CONSTRAINT {this} {expressions}" 2938 2939 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2940 order = expression.args.get("order") 2941 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2942 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2943 2944 def extract_sql(self, expression: exp.Extract) -> str: 2945 from sqlglot.dialects.dialect import map_date_part 2946 2947 this = ( 2948 map_date_part(expression.this, self.dialect) 2949 if self.NORMALIZE_EXTRACT_DATE_PARTS 2950 else expression.this 2951 ) 2952 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2953 expression_sql = self.sql(expression, "expression") 2954 2955 return f"EXTRACT({this_sql} FROM {expression_sql})" 2956 2957 def trim_sql(self, expression: exp.Trim) -> str: 2958 trim_type = self.sql(expression, "position") 2959 2960 if trim_type == "LEADING": 2961 func_name = "LTRIM" 2962 elif trim_type == "TRAILING": 2963 func_name = "RTRIM" 2964 else: 2965 func_name = "TRIM" 2966 2967 return self.func(func_name, expression.this, expression.expression) 2968 2969 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2970 args = expression.expressions 2971 if isinstance(expression, exp.ConcatWs): 2972 args = args[1:] # Skip the delimiter 2973 2974 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2975 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2976 2977 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2978 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2979 2980 return args 2981 2982 def concat_sql(self, expression: exp.Concat) -> str: 2983 expressions = self.convert_concat_args(expression) 2984 2985 # Some dialects don't allow a single-argument CONCAT call 2986 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2987 return self.sql(expressions[0]) 2988 2989 return self.func("CONCAT", *expressions) 2990 2991 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2992 return self.func( 2993 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2994 ) 2995 2996 def check_sql(self, expression: exp.Check) -> str: 2997 this = self.sql(expression, key="this") 2998 return f"CHECK ({this})" 2999 3000 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3001 expressions = self.expressions(expression, flat=True) 3002 expressions = f" ({expressions})" if expressions else "" 3003 reference = self.sql(expression, "reference") 3004 reference = f" {reference}" if reference else "" 3005 delete = self.sql(expression, "delete") 3006 delete = f" ON DELETE {delete}" if delete else "" 3007 update = self.sql(expression, "update") 3008 update = f" ON UPDATE {update}" if update else "" 3009 options = self.expressions(expression, key="options", flat=True, sep=" ") 3010 options = f" {options}" if options else "" 3011 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3012 3013 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3014 expressions = self.expressions(expression, flat=True) 3015 include = self.sql(expression, "include") 3016 options = self.expressions(expression, key="options", flat=True, sep=" ") 3017 options = f" {options}" if options else "" 3018 return f"PRIMARY KEY ({expressions}){include}{options}" 3019 3020 def if_sql(self, expression: exp.If) -> str: 3021 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3022 3023 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3024 modifier = expression.args.get("modifier") 3025 modifier = f" {modifier}" if modifier else "" 3026 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3027 3028 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3029 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3030 3031 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3032 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3033 3034 if expression.args.get("escape"): 3035 path = self.escape_str(path) 3036 3037 if self.QUOTE_JSON_PATH: 3038 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3039 3040 return path 3041 3042 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3043 if isinstance(expression, exp.JSONPathPart): 3044 transform = self.TRANSFORMS.get(expression.__class__) 3045 if not callable(transform): 3046 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3047 return "" 3048 3049 return transform(self, expression) 3050 3051 if isinstance(expression, int): 3052 return str(expression) 3053 3054 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3055 escaped = expression.replace("'", "\\'") 3056 escaped = f"\\'{expression}\\'" 3057 else: 3058 escaped = expression.replace('"', '\\"') 3059 escaped = f'"{escaped}"' 3060 3061 return escaped 3062 3063 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3064 return f"{self.sql(expression, 'this')} FORMAT JSON" 3065 3066 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3067 # Output the Teradata column FORMAT override. 3068 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3069 this = self.sql(expression, "this") 3070 fmt = self.sql(expression, "format") 3071 return f"{this} (FORMAT {fmt})" 3072 3073 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3074 null_handling = expression.args.get("null_handling") 3075 null_handling = f" {null_handling}" if null_handling else "" 3076 3077 unique_keys = expression.args.get("unique_keys") 3078 if unique_keys is not None: 3079 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3080 else: 3081 unique_keys = "" 3082 3083 return_type = self.sql(expression, "return_type") 3084 return_type = f" RETURNING {return_type}" if return_type else "" 3085 encoding = self.sql(expression, "encoding") 3086 encoding = f" ENCODING {encoding}" if encoding else "" 3087 3088 return self.func( 3089 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3090 *expression.expressions, 3091 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3092 ) 3093 3094 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3095 return self.jsonobject_sql(expression) 3096 3097 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3098 null_handling = expression.args.get("null_handling") 3099 null_handling = f" {null_handling}" if null_handling else "" 3100 return_type = self.sql(expression, "return_type") 3101 return_type = f" RETURNING {return_type}" if return_type else "" 3102 strict = " STRICT" if expression.args.get("strict") else "" 3103 return self.func( 3104 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3105 ) 3106 3107 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3108 this = self.sql(expression, "this") 3109 order = self.sql(expression, "order") 3110 null_handling = expression.args.get("null_handling") 3111 null_handling = f" {null_handling}" if null_handling else "" 3112 return_type = self.sql(expression, "return_type") 3113 return_type = f" RETURNING {return_type}" if return_type else "" 3114 strict = " STRICT" if expression.args.get("strict") else "" 3115 return self.func( 3116 "JSON_ARRAYAGG", 3117 this, 3118 suffix=f"{order}{null_handling}{return_type}{strict})", 3119 ) 3120 3121 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3122 path = self.sql(expression, "path") 3123 path = f" PATH {path}" if path else "" 3124 nested_schema = self.sql(expression, "nested_schema") 3125 3126 if nested_schema: 3127 return f"NESTED{path} {nested_schema}" 3128 3129 this = self.sql(expression, "this") 3130 kind = self.sql(expression, "kind") 3131 kind = f" {kind}" if kind else "" 3132 return f"{this}{kind}{path}" 3133 3134 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3135 return self.func("COLUMNS", *expression.expressions) 3136 3137 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3138 this = self.sql(expression, "this") 3139 path = self.sql(expression, "path") 3140 path = f", {path}" if path else "" 3141 error_handling = expression.args.get("error_handling") 3142 error_handling = f" {error_handling}" if error_handling else "" 3143 empty_handling = expression.args.get("empty_handling") 3144 empty_handling = f" {empty_handling}" if empty_handling else "" 3145 schema = self.sql(expression, "schema") 3146 return self.func( 3147 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3148 ) 3149 3150 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3151 this = self.sql(expression, "this") 3152 kind = self.sql(expression, "kind") 3153 path = self.sql(expression, "path") 3154 path = f" {path}" if path else "" 3155 as_json = " AS JSON" if expression.args.get("as_json") else "" 3156 return f"{this} {kind}{path}{as_json}" 3157 3158 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3159 this = self.sql(expression, "this") 3160 path = self.sql(expression, "path") 3161 path = f", {path}" if path else "" 3162 expressions = self.expressions(expression) 3163 with_ = ( 3164 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3165 if expressions 3166 else "" 3167 ) 3168 return f"OPENJSON({this}{path}){with_}" 3169 3170 def in_sql(self, expression: exp.In) -> str: 3171 query = expression.args.get("query") 3172 unnest = expression.args.get("unnest") 3173 field = expression.args.get("field") 3174 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3175 3176 if query: 3177 in_sql = self.sql(query) 3178 elif unnest: 3179 in_sql = self.in_unnest_op(unnest) 3180 elif field: 3181 in_sql = self.sql(field) 3182 else: 3183 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3184 3185 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3186 3187 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3188 return f"(SELECT {self.sql(unnest)})" 3189 3190 def interval_sql(self, expression: exp.Interval) -> str: 3191 unit = self.sql(expression, "unit") 3192 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3193 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3194 unit = f" {unit}" if unit else "" 3195 3196 if self.SINGLE_STRING_INTERVAL: 3197 this = expression.this.name if expression.this else "" 3198 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3199 3200 this = self.sql(expression, "this") 3201 if this: 3202 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3203 this = f" {this}" if unwrapped else f" ({this})" 3204 3205 return f"INTERVAL{this}{unit}" 3206 3207 def return_sql(self, expression: exp.Return) -> str: 3208 return f"RETURN {self.sql(expression, 'this')}" 3209 3210 def reference_sql(self, expression: exp.Reference) -> str: 3211 this = self.sql(expression, "this") 3212 expressions = self.expressions(expression, flat=True) 3213 expressions = f"({expressions})" if expressions else "" 3214 options = self.expressions(expression, key="options", flat=True, sep=" ") 3215 options = f" {options}" if options else "" 3216 return f"REFERENCES {this}{expressions}{options}" 3217 3218 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3219 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3220 parent = expression.parent 3221 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3222 return self.func( 3223 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3224 ) 3225 3226 def paren_sql(self, expression: exp.Paren) -> str: 3227 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3228 return f"({sql}{self.seg(')', sep='')}" 3229 3230 def neg_sql(self, expression: exp.Neg) -> str: 3231 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3232 this_sql = self.sql(expression, "this") 3233 sep = " " if this_sql[0] == "-" else "" 3234 return f"-{sep}{this_sql}" 3235 3236 def not_sql(self, expression: exp.Not) -> str: 3237 return f"NOT {self.sql(expression, 'this')}" 3238 3239 def alias_sql(self, expression: exp.Alias) -> str: 3240 alias = self.sql(expression, "alias") 3241 alias = f" AS {alias}" if alias else "" 3242 return f"{self.sql(expression, 'this')}{alias}" 3243 3244 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3245 alias = expression.args["alias"] 3246 3247 parent = expression.parent 3248 pivot = parent and parent.parent 3249 3250 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3251 identifier_alias = isinstance(alias, exp.Identifier) 3252 literal_alias = isinstance(alias, exp.Literal) 3253 3254 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3255 alias.replace(exp.Literal.string(alias.output_name)) 3256 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3257 alias.replace(exp.to_identifier(alias.output_name)) 3258 3259 return self.alias_sql(expression) 3260 3261 def aliases_sql(self, expression: exp.Aliases) -> str: 3262 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3263 3264 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3265 this = self.sql(expression, "this") 3266 index = self.sql(expression, "expression") 3267 return f"{this} AT {index}" 3268 3269 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3270 this = self.sql(expression, "this") 3271 zone = self.sql(expression, "zone") 3272 return f"{this} AT TIME ZONE {zone}" 3273 3274 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3275 this = self.sql(expression, "this") 3276 zone = self.sql(expression, "zone") 3277 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3278 3279 def add_sql(self, expression: exp.Add) -> str: 3280 return self.binary(expression, "+") 3281 3282 def and_sql( 3283 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3284 ) -> str: 3285 return self.connector_sql(expression, "AND", stack) 3286 3287 def or_sql( 3288 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3289 ) -> str: 3290 return self.connector_sql(expression, "OR", stack) 3291 3292 def xor_sql( 3293 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3294 ) -> str: 3295 return self.connector_sql(expression, "XOR", stack) 3296 3297 def connector_sql( 3298 self, 3299 expression: exp.Connector, 3300 op: str, 3301 stack: t.Optional[t.List[str | exp.Expression]] = None, 3302 ) -> str: 3303 if stack is not None: 3304 if expression.expressions: 3305 stack.append(self.expressions(expression, sep=f" {op} ")) 3306 else: 3307 stack.append(expression.right) 3308 if expression.comments and self.comments: 3309 for comment in expression.comments: 3310 if comment: 3311 op += f" /*{self.sanitize_comment(comment)}*/" 3312 stack.extend((op, expression.left)) 3313 return op 3314 3315 stack = [expression] 3316 sqls: t.List[str] = [] 3317 ops = set() 3318 3319 while stack: 3320 node = stack.pop() 3321 if isinstance(node, exp.Connector): 3322 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3323 else: 3324 sql = self.sql(node) 3325 if sqls and sqls[-1] in ops: 3326 sqls[-1] += f" {sql}" 3327 else: 3328 sqls.append(sql) 3329 3330 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3331 return sep.join(sqls) 3332 3333 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3334 return self.binary(expression, "&") 3335 3336 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3337 return self.binary(expression, "<<") 3338 3339 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3340 return f"~{self.sql(expression, 'this')}" 3341 3342 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3343 return self.binary(expression, "|") 3344 3345 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3346 return self.binary(expression, ">>") 3347 3348 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3349 return self.binary(expression, "^") 3350 3351 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3352 format_sql = self.sql(expression, "format") 3353 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3354 to_sql = self.sql(expression, "to") 3355 to_sql = f" {to_sql}" if to_sql else "" 3356 action = self.sql(expression, "action") 3357 action = f" {action}" if action else "" 3358 default = self.sql(expression, "default") 3359 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3360 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3361 3362 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3363 zone = self.sql(expression, "this") 3364 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3365 3366 def collate_sql(self, expression: exp.Collate) -> str: 3367 if self.COLLATE_IS_FUNC: 3368 return self.function_fallback_sql(expression) 3369 return self.binary(expression, "COLLATE") 3370 3371 def command_sql(self, expression: exp.Command) -> str: 3372 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3373 3374 def comment_sql(self, expression: exp.Comment) -> str: 3375 this = self.sql(expression, "this") 3376 kind = expression.args["kind"] 3377 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3378 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3379 expression_sql = self.sql(expression, "expression") 3380 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3381 3382 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3383 this = self.sql(expression, "this") 3384 delete = " DELETE" if expression.args.get("delete") else "" 3385 recompress = self.sql(expression, "recompress") 3386 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3387 to_disk = self.sql(expression, "to_disk") 3388 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3389 to_volume = self.sql(expression, "to_volume") 3390 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3391 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3392 3393 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3394 where = self.sql(expression, "where") 3395 group = self.sql(expression, "group") 3396 aggregates = self.expressions(expression, key="aggregates") 3397 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3398 3399 if not (where or group or aggregates) and len(expression.expressions) == 1: 3400 return f"TTL {self.expressions(expression, flat=True)}" 3401 3402 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3403 3404 def transaction_sql(self, expression: exp.Transaction) -> str: 3405 return "BEGIN" 3406 3407 def commit_sql(self, expression: exp.Commit) -> str: 3408 chain = expression.args.get("chain") 3409 if chain is not None: 3410 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3411 3412 return f"COMMIT{chain or ''}" 3413 3414 def rollback_sql(self, expression: exp.Rollback) -> str: 3415 savepoint = expression.args.get("savepoint") 3416 savepoint = f" TO {savepoint}" if savepoint else "" 3417 return f"ROLLBACK{savepoint}" 3418 3419 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3420 this = self.sql(expression, "this") 3421 3422 dtype = self.sql(expression, "dtype") 3423 if dtype: 3424 collate = self.sql(expression, "collate") 3425 collate = f" COLLATE {collate}" if collate else "" 3426 using = self.sql(expression, "using") 3427 using = f" USING {using}" if using else "" 3428 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3429 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3430 3431 default = self.sql(expression, "default") 3432 if default: 3433 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3434 3435 comment = self.sql(expression, "comment") 3436 if comment: 3437 return f"ALTER COLUMN {this} COMMENT {comment}" 3438 3439 visible = expression.args.get("visible") 3440 if visible: 3441 return f"ALTER COLUMN {this} SET {visible}" 3442 3443 allow_null = expression.args.get("allow_null") 3444 drop = expression.args.get("drop") 3445 3446 if not drop and not allow_null: 3447 self.unsupported("Unsupported ALTER COLUMN syntax") 3448 3449 if allow_null is not None: 3450 keyword = "DROP" if drop else "SET" 3451 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3452 3453 return f"ALTER COLUMN {this} DROP DEFAULT" 3454 3455 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3456 this = self.sql(expression, "this") 3457 3458 visible = expression.args.get("visible") 3459 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3460 3461 return f"ALTER INDEX {this} {visible_sql}" 3462 3463 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3464 this = self.sql(expression, "this") 3465 if not isinstance(expression.this, exp.Var): 3466 this = f"KEY DISTKEY {this}" 3467 return f"ALTER DISTSTYLE {this}" 3468 3469 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3470 compound = " COMPOUND" if expression.args.get("compound") else "" 3471 this = self.sql(expression, "this") 3472 expressions = self.expressions(expression, flat=True) 3473 expressions = f"({expressions})" if expressions else "" 3474 return f"ALTER{compound} SORTKEY {this or expressions}" 3475 3476 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3477 if not self.RENAME_TABLE_WITH_DB: 3478 # Remove db from tables 3479 expression = expression.transform( 3480 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3481 ).assert_is(exp.AlterRename) 3482 this = self.sql(expression, "this") 3483 return f"RENAME TO {this}" 3484 3485 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3486 exists = " IF EXISTS" if expression.args.get("exists") else "" 3487 old_column = self.sql(expression, "this") 3488 new_column = self.sql(expression, "to") 3489 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3490 3491 def alterset_sql(self, expression: exp.AlterSet) -> str: 3492 exprs = self.expressions(expression, flat=True) 3493 if self.ALTER_SET_WRAPPED: 3494 exprs = f"({exprs})" 3495 3496 return f"SET {exprs}" 3497 3498 def alter_sql(self, expression: exp.Alter) -> str: 3499 actions = expression.args["actions"] 3500 3501 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3502 actions[0], exp.ColumnDef 3503 ): 3504 actions_sql = self.expressions(expression, key="actions", flat=True) 3505 actions_sql = f"ADD {actions_sql}" 3506 else: 3507 actions_list = [] 3508 for action in actions: 3509 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3510 action_sql = self.add_column_sql(action) 3511 else: 3512 action_sql = self.sql(action) 3513 if isinstance(action, exp.Query): 3514 action_sql = f"AS {action_sql}" 3515 3516 actions_list.append(action_sql) 3517 3518 actions_sql = self.format_args(*actions_list).lstrip("\n") 3519 3520 exists = " IF EXISTS" if expression.args.get("exists") else "" 3521 on_cluster = self.sql(expression, "cluster") 3522 on_cluster = f" {on_cluster}" if on_cluster else "" 3523 only = " ONLY" if expression.args.get("only") else "" 3524 options = self.expressions(expression, key="options") 3525 options = f", {options}" if options else "" 3526 kind = self.sql(expression, "kind") 3527 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3528 3529 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}" 3530 3531 def add_column_sql(self, expression: exp.Expression) -> str: 3532 sql = self.sql(expression) 3533 if isinstance(expression, exp.Schema): 3534 column_text = " COLUMNS" 3535 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3536 column_text = " COLUMN" 3537 else: 3538 column_text = "" 3539 3540 return f"ADD{column_text} {sql}" 3541 3542 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3543 expressions = self.expressions(expression) 3544 exists = " IF EXISTS " if expression.args.get("exists") else " " 3545 return f"DROP{exists}{expressions}" 3546 3547 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3548 return f"ADD {self.expressions(expression, indent=False)}" 3549 3550 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3551 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3552 location = self.sql(expression, "location") 3553 location = f" {location}" if location else "" 3554 return f"ADD {exists}{self.sql(expression.this)}{location}" 3555 3556 def distinct_sql(self, expression: exp.Distinct) -> str: 3557 this = self.expressions(expression, flat=True) 3558 3559 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3560 case = exp.case() 3561 for arg in expression.expressions: 3562 case = case.when(arg.is_(exp.null()), exp.null()) 3563 this = self.sql(case.else_(f"({this})")) 3564 3565 this = f" {this}" if this else "" 3566 3567 on = self.sql(expression, "on") 3568 on = f" ON {on}" if on else "" 3569 return f"DISTINCT{this}{on}" 3570 3571 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3572 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3573 3574 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3575 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3576 3577 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3578 this_sql = self.sql(expression, "this") 3579 expression_sql = self.sql(expression, "expression") 3580 kind = "MAX" if expression.args.get("max") else "MIN" 3581 return f"{this_sql} HAVING {kind} {expression_sql}" 3582 3583 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3584 return self.sql( 3585 exp.Cast( 3586 this=exp.Div(this=expression.this, expression=expression.expression), 3587 to=exp.DataType(this=exp.DataType.Type.INT), 3588 ) 3589 ) 3590 3591 def dpipe_sql(self, expression: exp.DPipe) -> str: 3592 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3593 return self.func( 3594 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3595 ) 3596 return self.binary(expression, "||") 3597 3598 def div_sql(self, expression: exp.Div) -> str: 3599 l, r = expression.left, expression.right 3600 3601 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3602 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3603 3604 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3605 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3606 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3607 3608 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3609 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3610 return self.sql( 3611 exp.cast( 3612 l / r, 3613 to=exp.DataType.Type.BIGINT, 3614 ) 3615 ) 3616 3617 return self.binary(expression, "/") 3618 3619 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3620 n = exp._wrap(expression.this, exp.Binary) 3621 d = exp._wrap(expression.expression, exp.Binary) 3622 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3623 3624 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3625 return self.binary(expression, "OVERLAPS") 3626 3627 def distance_sql(self, expression: exp.Distance) -> str: 3628 return self.binary(expression, "<->") 3629 3630 def dot_sql(self, expression: exp.Dot) -> str: 3631 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3632 3633 def eq_sql(self, expression: exp.EQ) -> str: 3634 return self.binary(expression, "=") 3635 3636 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3637 return self.binary(expression, ":=") 3638 3639 def escape_sql(self, expression: exp.Escape) -> str: 3640 return self.binary(expression, "ESCAPE") 3641 3642 def glob_sql(self, expression: exp.Glob) -> str: 3643 return self.binary(expression, "GLOB") 3644 3645 def gt_sql(self, expression: exp.GT) -> str: 3646 return self.binary(expression, ">") 3647 3648 def gte_sql(self, expression: exp.GTE) -> str: 3649 return self.binary(expression, ">=") 3650 3651 def ilike_sql(self, expression: exp.ILike) -> str: 3652 return self.binary(expression, "ILIKE") 3653 3654 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3655 return self.binary(expression, "ILIKE ANY") 3656 3657 def is_sql(self, expression: exp.Is) -> str: 3658 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3659 return self.sql( 3660 expression.this if expression.expression.this else exp.not_(expression.this) 3661 ) 3662 return self.binary(expression, "IS") 3663 3664 def like_sql(self, expression: exp.Like) -> str: 3665 return self.binary(expression, "LIKE") 3666 3667 def likeany_sql(self, expression: exp.LikeAny) -> str: 3668 return self.binary(expression, "LIKE ANY") 3669 3670 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3671 return self.binary(expression, "SIMILAR TO") 3672 3673 def lt_sql(self, expression: exp.LT) -> str: 3674 return self.binary(expression, "<") 3675 3676 def lte_sql(self, expression: exp.LTE) -> str: 3677 return self.binary(expression, "<=") 3678 3679 def mod_sql(self, expression: exp.Mod) -> str: 3680 return self.binary(expression, "%") 3681 3682 def mul_sql(self, expression: exp.Mul) -> str: 3683 return self.binary(expression, "*") 3684 3685 def neq_sql(self, expression: exp.NEQ) -> str: 3686 return self.binary(expression, "<>") 3687 3688 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3689 return self.binary(expression, "IS NOT DISTINCT FROM") 3690 3691 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3692 return self.binary(expression, "IS DISTINCT FROM") 3693 3694 def slice_sql(self, expression: exp.Slice) -> str: 3695 return self.binary(expression, ":") 3696 3697 def sub_sql(self, expression: exp.Sub) -> str: 3698 return self.binary(expression, "-") 3699 3700 def trycast_sql(self, expression: exp.TryCast) -> str: 3701 return self.cast_sql(expression, safe_prefix="TRY_") 3702 3703 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3704 return self.cast_sql(expression) 3705 3706 def try_sql(self, expression: exp.Try) -> str: 3707 if not self.TRY_SUPPORTED: 3708 self.unsupported("Unsupported TRY function") 3709 return self.sql(expression, "this") 3710 3711 return self.func("TRY", expression.this) 3712 3713 def log_sql(self, expression: exp.Log) -> str: 3714 this = expression.this 3715 expr = expression.expression 3716 3717 if self.dialect.LOG_BASE_FIRST is False: 3718 this, expr = expr, this 3719 elif self.dialect.LOG_BASE_FIRST is None and expr: 3720 if this.name in ("2", "10"): 3721 return self.func(f"LOG{this.name}", expr) 3722 3723 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3724 3725 return self.func("LOG", this, expr) 3726 3727 def use_sql(self, expression: exp.Use) -> str: 3728 kind = self.sql(expression, "kind") 3729 kind = f" {kind}" if kind else "" 3730 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3731 this = f" {this}" if this else "" 3732 return f"USE{kind}{this}" 3733 3734 def binary(self, expression: exp.Binary, op: str) -> str: 3735 sqls: t.List[str] = [] 3736 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3737 binary_type = type(expression) 3738 3739 while stack: 3740 node = stack.pop() 3741 3742 if type(node) is binary_type: 3743 op_func = node.args.get("operator") 3744 if op_func: 3745 op = f"OPERATOR({self.sql(op_func)})" 3746 3747 stack.append(node.right) 3748 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3749 stack.append(node.left) 3750 else: 3751 sqls.append(self.sql(node)) 3752 3753 return "".join(sqls) 3754 3755 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3756 to_clause = self.sql(expression, "to") 3757 if to_clause: 3758 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3759 3760 return self.function_fallback_sql(expression) 3761 3762 def function_fallback_sql(self, expression: exp.Func) -> str: 3763 args = [] 3764 3765 for key in expression.arg_types: 3766 arg_value = expression.args.get(key) 3767 3768 if isinstance(arg_value, list): 3769 for value in arg_value: 3770 args.append(value) 3771 elif arg_value is not None: 3772 args.append(arg_value) 3773 3774 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3775 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3776 else: 3777 name = expression.sql_name() 3778 3779 return self.func(name, *args) 3780 3781 def func( 3782 self, 3783 name: str, 3784 *args: t.Optional[exp.Expression | str], 3785 prefix: str = "(", 3786 suffix: str = ")", 3787 normalize: bool = True, 3788 ) -> str: 3789 name = self.normalize_func(name) if normalize else name 3790 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3791 3792 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3793 arg_sqls = tuple( 3794 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3795 ) 3796 if self.pretty and self.too_wide(arg_sqls): 3797 return self.indent( 3798 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3799 ) 3800 return sep.join(arg_sqls) 3801 3802 def too_wide(self, args: t.Iterable) -> bool: 3803 return sum(len(arg) for arg in args) > self.max_text_width 3804 3805 def format_time( 3806 self, 3807 expression: exp.Expression, 3808 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3809 inverse_time_trie: t.Optional[t.Dict] = None, 3810 ) -> t.Optional[str]: 3811 return format_time( 3812 self.sql(expression, "format"), 3813 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3814 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3815 ) 3816 3817 def expressions( 3818 self, 3819 expression: t.Optional[exp.Expression] = None, 3820 key: t.Optional[str] = None, 3821 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3822 flat: bool = False, 3823 indent: bool = True, 3824 skip_first: bool = False, 3825 skip_last: bool = False, 3826 sep: str = ", ", 3827 prefix: str = "", 3828 dynamic: bool = False, 3829 new_line: bool = False, 3830 ) -> str: 3831 expressions = expression.args.get(key or "expressions") if expression else sqls 3832 3833 if not expressions: 3834 return "" 3835 3836 if flat: 3837 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3838 3839 num_sqls = len(expressions) 3840 result_sqls = [] 3841 3842 for i, e in enumerate(expressions): 3843 sql = self.sql(e, comment=False) 3844 if not sql: 3845 continue 3846 3847 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3848 3849 if self.pretty: 3850 if self.leading_comma: 3851 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3852 else: 3853 result_sqls.append( 3854 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3855 ) 3856 else: 3857 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3858 3859 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3860 if new_line: 3861 result_sqls.insert(0, "") 3862 result_sqls.append("") 3863 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3864 else: 3865 result_sql = "".join(result_sqls) 3866 3867 return ( 3868 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3869 if indent 3870 else result_sql 3871 ) 3872 3873 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3874 flat = flat or isinstance(expression.parent, exp.Properties) 3875 expressions_sql = self.expressions(expression, flat=flat) 3876 if flat: 3877 return f"{op} {expressions_sql}" 3878 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3879 3880 def naked_property(self, expression: exp.Property) -> str: 3881 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3882 if not property_name: 3883 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3884 return f"{property_name} {self.sql(expression, 'this')}" 3885 3886 def tag_sql(self, expression: exp.Tag) -> str: 3887 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3888 3889 def token_sql(self, token_type: TokenType) -> str: 3890 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3891 3892 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3893 this = self.sql(expression, "this") 3894 expressions = self.no_identify(self.expressions, expression) 3895 expressions = ( 3896 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3897 ) 3898 return f"{this}{expressions}" if expressions.strip() != "" else this 3899 3900 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3901 this = self.sql(expression, "this") 3902 expressions = self.expressions(expression, flat=True) 3903 return f"{this}({expressions})" 3904 3905 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3906 return self.binary(expression, "=>") 3907 3908 def when_sql(self, expression: exp.When) -> str: 3909 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3910 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3911 condition = self.sql(expression, "condition") 3912 condition = f" AND {condition}" if condition else "" 3913 3914 then_expression = expression.args.get("then") 3915 if isinstance(then_expression, exp.Insert): 3916 this = self.sql(then_expression, "this") 3917 this = f"INSERT {this}" if this else "INSERT" 3918 then = self.sql(then_expression, "expression") 3919 then = f"{this} VALUES {then}" if then else this 3920 elif isinstance(then_expression, exp.Update): 3921 if isinstance(then_expression.args.get("expressions"), exp.Star): 3922 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3923 else: 3924 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3925 else: 3926 then = self.sql(then_expression) 3927 return f"WHEN {matched}{source}{condition} THEN {then}" 3928 3929 def whens_sql(self, expression: exp.Whens) -> str: 3930 return self.expressions(expression, sep=" ", indent=False) 3931 3932 def merge_sql(self, expression: exp.Merge) -> str: 3933 table = expression.this 3934 table_alias = "" 3935 3936 hints = table.args.get("hints") 3937 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3938 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3939 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3940 3941 this = self.sql(table) 3942 using = f"USING {self.sql(expression, 'using')}" 3943 on = f"ON {self.sql(expression, 'on')}" 3944 whens = self.sql(expression, "whens") 3945 3946 returning = self.sql(expression, "returning") 3947 if returning: 3948 whens = f"{whens}{returning}" 3949 3950 sep = self.sep() 3951 3952 return self.prepend_ctes( 3953 expression, 3954 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3955 ) 3956 3957 @unsupported_args("format") 3958 def tochar_sql(self, expression: exp.ToChar) -> str: 3959 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3960 3961 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3962 if not self.SUPPORTS_TO_NUMBER: 3963 self.unsupported("Unsupported TO_NUMBER function") 3964 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3965 3966 fmt = expression.args.get("format") 3967 if not fmt: 3968 self.unsupported("Conversion format is required for TO_NUMBER") 3969 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3970 3971 return self.func("TO_NUMBER", expression.this, fmt) 3972 3973 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3974 this = self.sql(expression, "this") 3975 kind = self.sql(expression, "kind") 3976 settings_sql = self.expressions(expression, key="settings", sep=" ") 3977 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3978 return f"{this}({kind}{args})" 3979 3980 def dictrange_sql(self, expression: exp.DictRange) -> str: 3981 this = self.sql(expression, "this") 3982 max = self.sql(expression, "max") 3983 min = self.sql(expression, "min") 3984 return f"{this}(MIN {min} MAX {max})" 3985 3986 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3987 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3988 3989 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3990 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3991 3992 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 3993 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 3994 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 3995 3996 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3997 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3998 expressions = self.expressions(expression, flat=True) 3999 expressions = f" {self.wrap(expressions)}" if expressions else "" 4000 buckets = self.sql(expression, "buckets") 4001 kind = self.sql(expression, "kind") 4002 buckets = f" BUCKETS {buckets}" if buckets else "" 4003 order = self.sql(expression, "order") 4004 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4005 4006 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4007 return "" 4008 4009 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4010 expressions = self.expressions(expression, key="expressions", flat=True) 4011 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4012 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4013 buckets = self.sql(expression, "buckets") 4014 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4015 4016 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4017 this = self.sql(expression, "this") 4018 having = self.sql(expression, "having") 4019 4020 if having: 4021 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4022 4023 return self.func("ANY_VALUE", this) 4024 4025 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4026 transform = self.func("TRANSFORM", *expression.expressions) 4027 row_format_before = self.sql(expression, "row_format_before") 4028 row_format_before = f" {row_format_before}" if row_format_before else "" 4029 record_writer = self.sql(expression, "record_writer") 4030 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4031 using = f" USING {self.sql(expression, 'command_script')}" 4032 schema = self.sql(expression, "schema") 4033 schema = f" AS {schema}" if schema else "" 4034 row_format_after = self.sql(expression, "row_format_after") 4035 row_format_after = f" {row_format_after}" if row_format_after else "" 4036 record_reader = self.sql(expression, "record_reader") 4037 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4038 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4039 4040 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4041 key_block_size = self.sql(expression, "key_block_size") 4042 if key_block_size: 4043 return f"KEY_BLOCK_SIZE = {key_block_size}" 4044 4045 using = self.sql(expression, "using") 4046 if using: 4047 return f"USING {using}" 4048 4049 parser = self.sql(expression, "parser") 4050 if parser: 4051 return f"WITH PARSER {parser}" 4052 4053 comment = self.sql(expression, "comment") 4054 if comment: 4055 return f"COMMENT {comment}" 4056 4057 visible = expression.args.get("visible") 4058 if visible is not None: 4059 return "VISIBLE" if visible else "INVISIBLE" 4060 4061 engine_attr = self.sql(expression, "engine_attr") 4062 if engine_attr: 4063 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4064 4065 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4066 if secondary_engine_attr: 4067 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4068 4069 self.unsupported("Unsupported index constraint option.") 4070 return "" 4071 4072 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4073 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4074 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4075 4076 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4077 kind = self.sql(expression, "kind") 4078 kind = f"{kind} INDEX" if kind else "INDEX" 4079 this = self.sql(expression, "this") 4080 this = f" {this}" if this else "" 4081 index_type = self.sql(expression, "index_type") 4082 index_type = f" USING {index_type}" if index_type else "" 4083 expressions = self.expressions(expression, flat=True) 4084 expressions = f" ({expressions})" if expressions else "" 4085 options = self.expressions(expression, key="options", sep=" ") 4086 options = f" {options}" if options else "" 4087 return f"{kind}{this}{index_type}{expressions}{options}" 4088 4089 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4090 if self.NVL2_SUPPORTED: 4091 return self.function_fallback_sql(expression) 4092 4093 case = exp.Case().when( 4094 expression.this.is_(exp.null()).not_(copy=False), 4095 expression.args["true"], 4096 copy=False, 4097 ) 4098 else_cond = expression.args.get("false") 4099 if else_cond: 4100 case.else_(else_cond, copy=False) 4101 4102 return self.sql(case) 4103 4104 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4105 this = self.sql(expression, "this") 4106 expr = self.sql(expression, "expression") 4107 iterator = self.sql(expression, "iterator") 4108 condition = self.sql(expression, "condition") 4109 condition = f" IF {condition}" if condition else "" 4110 return f"{this} FOR {expr} IN {iterator}{condition}" 4111 4112 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4113 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4114 4115 def opclass_sql(self, expression: exp.Opclass) -> str: 4116 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4117 4118 def predict_sql(self, expression: exp.Predict) -> str: 4119 model = self.sql(expression, "this") 4120 model = f"MODEL {model}" 4121 table = self.sql(expression, "expression") 4122 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4123 parameters = self.sql(expression, "params_struct") 4124 return self.func("PREDICT", model, table, parameters or None) 4125 4126 def forin_sql(self, expression: exp.ForIn) -> str: 4127 this = self.sql(expression, "this") 4128 expression_sql = self.sql(expression, "expression") 4129 return f"FOR {this} DO {expression_sql}" 4130 4131 def refresh_sql(self, expression: exp.Refresh) -> str: 4132 this = self.sql(expression, "this") 4133 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4134 return f"REFRESH {table}{this}" 4135 4136 def toarray_sql(self, expression: exp.ToArray) -> str: 4137 arg = expression.this 4138 if not arg.type: 4139 from sqlglot.optimizer.annotate_types import annotate_types 4140 4141 arg = annotate_types(arg, dialect=self.dialect) 4142 4143 if arg.is_type(exp.DataType.Type.ARRAY): 4144 return self.sql(arg) 4145 4146 cond_for_null = arg.is_(exp.null()) 4147 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4148 4149 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4150 this = expression.this 4151 time_format = self.format_time(expression) 4152 4153 if time_format: 4154 return self.sql( 4155 exp.cast( 4156 exp.StrToTime(this=this, format=expression.args["format"]), 4157 exp.DataType.Type.TIME, 4158 ) 4159 ) 4160 4161 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4162 return self.sql(this) 4163 4164 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4165 4166 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4167 this = expression.this 4168 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4169 return self.sql(this) 4170 4171 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4172 4173 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4174 this = expression.this 4175 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4176 return self.sql(this) 4177 4178 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4179 4180 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4181 this = expression.this 4182 time_format = self.format_time(expression) 4183 4184 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4185 return self.sql( 4186 exp.cast( 4187 exp.StrToTime(this=this, format=expression.args["format"]), 4188 exp.DataType.Type.DATE, 4189 ) 4190 ) 4191 4192 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4193 return self.sql(this) 4194 4195 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4196 4197 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4198 return self.sql( 4199 exp.func( 4200 "DATEDIFF", 4201 expression.this, 4202 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4203 "day", 4204 ) 4205 ) 4206 4207 def lastday_sql(self, expression: exp.LastDay) -> str: 4208 if self.LAST_DAY_SUPPORTS_DATE_PART: 4209 return self.function_fallback_sql(expression) 4210 4211 unit = expression.text("unit") 4212 if unit and unit != "MONTH": 4213 self.unsupported("Date parts are not supported in LAST_DAY.") 4214 4215 return self.func("LAST_DAY", expression.this) 4216 4217 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4218 from sqlglot.dialects.dialect import unit_to_str 4219 4220 return self.func( 4221 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4222 ) 4223 4224 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4225 if self.CAN_IMPLEMENT_ARRAY_ANY: 4226 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4227 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4228 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4229 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4230 4231 from sqlglot.dialects import Dialect 4232 4233 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4234 if self.dialect.__class__ != Dialect: 4235 self.unsupported("ARRAY_ANY is unsupported") 4236 4237 return self.function_fallback_sql(expression) 4238 4239 def struct_sql(self, expression: exp.Struct) -> str: 4240 expression.set( 4241 "expressions", 4242 [ 4243 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4244 if isinstance(e, exp.PropertyEQ) 4245 else e 4246 for e in expression.expressions 4247 ], 4248 ) 4249 4250 return self.function_fallback_sql(expression) 4251 4252 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4253 low = self.sql(expression, "this") 4254 high = self.sql(expression, "expression") 4255 4256 return f"{low} TO {high}" 4257 4258 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4259 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4260 tables = f" {self.expressions(expression)}" 4261 4262 exists = " IF EXISTS" if expression.args.get("exists") else "" 4263 4264 on_cluster = self.sql(expression, "cluster") 4265 on_cluster = f" {on_cluster}" if on_cluster else "" 4266 4267 identity = self.sql(expression, "identity") 4268 identity = f" {identity} IDENTITY" if identity else "" 4269 4270 option = self.sql(expression, "option") 4271 option = f" {option}" if option else "" 4272 4273 partition = self.sql(expression, "partition") 4274 partition = f" {partition}" if partition else "" 4275 4276 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4277 4278 # This transpiles T-SQL's CONVERT function 4279 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4280 def convert_sql(self, expression: exp.Convert) -> str: 4281 to = expression.this 4282 value = expression.expression 4283 style = expression.args.get("style") 4284 safe = expression.args.get("safe") 4285 strict = expression.args.get("strict") 4286 4287 if not to or not value: 4288 return "" 4289 4290 # Retrieve length of datatype and override to default if not specified 4291 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4292 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4293 4294 transformed: t.Optional[exp.Expression] = None 4295 cast = exp.Cast if strict else exp.TryCast 4296 4297 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4298 if isinstance(style, exp.Literal) and style.is_int: 4299 from sqlglot.dialects.tsql import TSQL 4300 4301 style_value = style.name 4302 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4303 if not converted_style: 4304 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4305 4306 fmt = exp.Literal.string(converted_style) 4307 4308 if to.this == exp.DataType.Type.DATE: 4309 transformed = exp.StrToDate(this=value, format=fmt) 4310 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4311 transformed = exp.StrToTime(this=value, format=fmt) 4312 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4313 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4314 elif to.this == exp.DataType.Type.TEXT: 4315 transformed = exp.TimeToStr(this=value, format=fmt) 4316 4317 if not transformed: 4318 transformed = cast(this=value, to=to, safe=safe) 4319 4320 return self.sql(transformed) 4321 4322 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4323 this = expression.this 4324 if isinstance(this, exp.JSONPathWildcard): 4325 this = self.json_path_part(this) 4326 return f".{this}" if this else "" 4327 4328 if exp.SAFE_IDENTIFIER_RE.match(this): 4329 return f".{this}" 4330 4331 this = self.json_path_part(this) 4332 return ( 4333 f"[{this}]" 4334 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4335 else f".{this}" 4336 ) 4337 4338 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4339 this = self.json_path_part(expression.this) 4340 return f"[{this}]" if this else "" 4341 4342 def _simplify_unless_literal(self, expression: E) -> E: 4343 if not isinstance(expression, exp.Literal): 4344 from sqlglot.optimizer.simplify import simplify 4345 4346 expression = simplify(expression, dialect=self.dialect) 4347 4348 return expression 4349 4350 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4351 this = expression.this 4352 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4353 self.unsupported( 4354 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4355 ) 4356 return self.sql(this) 4357 4358 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4359 # The first modifier here will be the one closest to the AggFunc's arg 4360 mods = sorted( 4361 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4362 key=lambda x: 0 4363 if isinstance(x, exp.HavingMax) 4364 else (1 if isinstance(x, exp.Order) else 2), 4365 ) 4366 4367 if mods: 4368 mod = mods[0] 4369 this = expression.__class__(this=mod.this.copy()) 4370 this.meta["inline"] = True 4371 mod.this.replace(this) 4372 return self.sql(expression.this) 4373 4374 agg_func = expression.find(exp.AggFunc) 4375 4376 if agg_func: 4377 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4378 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4379 4380 return f"{self.sql(expression, 'this')} {text}" 4381 4382 def _replace_line_breaks(self, string: str) -> str: 4383 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4384 if self.pretty: 4385 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4386 return string 4387 4388 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4389 option = self.sql(expression, "this") 4390 4391 if expression.expressions: 4392 upper = option.upper() 4393 4394 # Snowflake FILE_FORMAT options are separated by whitespace 4395 sep = " " if upper == "FILE_FORMAT" else ", " 4396 4397 # Databricks copy/format options do not set their list of values with EQ 4398 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4399 values = self.expressions(expression, flat=True, sep=sep) 4400 return f"{option}{op}({values})" 4401 4402 value = self.sql(expression, "expression") 4403 4404 if not value: 4405 return option 4406 4407 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4408 4409 return f"{option}{op}{value}" 4410 4411 def credentials_sql(self, expression: exp.Credentials) -> str: 4412 cred_expr = expression.args.get("credentials") 4413 if isinstance(cred_expr, exp.Literal): 4414 # Redshift case: CREDENTIALS <string> 4415 credentials = self.sql(expression, "credentials") 4416 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4417 else: 4418 # Snowflake case: CREDENTIALS = (...) 4419 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4420 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4421 4422 storage = self.sql(expression, "storage") 4423 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4424 4425 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4426 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4427 4428 iam_role = self.sql(expression, "iam_role") 4429 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4430 4431 region = self.sql(expression, "region") 4432 region = f" REGION {region}" if region else "" 4433 4434 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4435 4436 def copy_sql(self, expression: exp.Copy) -> str: 4437 this = self.sql(expression, "this") 4438 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4439 4440 credentials = self.sql(expression, "credentials") 4441 credentials = self.seg(credentials) if credentials else "" 4442 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4443 files = self.expressions(expression, key="files", flat=True) 4444 4445 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4446 params = self.expressions( 4447 expression, 4448 key="params", 4449 sep=sep, 4450 new_line=True, 4451 skip_last=True, 4452 skip_first=True, 4453 indent=self.COPY_PARAMS_ARE_WRAPPED, 4454 ) 4455 4456 if params: 4457 if self.COPY_PARAMS_ARE_WRAPPED: 4458 params = f" WITH ({params})" 4459 elif not self.pretty: 4460 params = f" {params}" 4461 4462 return f"COPY{this}{kind} {files}{credentials}{params}" 4463 4464 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4465 return "" 4466 4467 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4468 on_sql = "ON" if expression.args.get("on") else "OFF" 4469 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4470 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4471 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4472 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4473 4474 if filter_col or retention_period: 4475 on_sql = self.func("ON", filter_col, retention_period) 4476 4477 return f"DATA_DELETION={on_sql}" 4478 4479 def maskingpolicycolumnconstraint_sql( 4480 self, expression: exp.MaskingPolicyColumnConstraint 4481 ) -> str: 4482 this = self.sql(expression, "this") 4483 expressions = self.expressions(expression, flat=True) 4484 expressions = f" USING ({expressions})" if expressions else "" 4485 return f"MASKING POLICY {this}{expressions}" 4486 4487 def gapfill_sql(self, expression: exp.GapFill) -> str: 4488 this = self.sql(expression, "this") 4489 this = f"TABLE {this}" 4490 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4491 4492 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4493 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4494 4495 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4496 this = self.sql(expression, "this") 4497 expr = expression.expression 4498 4499 if isinstance(expr, exp.Func): 4500 # T-SQL's CLR functions are case sensitive 4501 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4502 else: 4503 expr = self.sql(expression, "expression") 4504 4505 return self.scope_resolution(expr, this) 4506 4507 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4508 if self.PARSE_JSON_NAME is None: 4509 return self.sql(expression.this) 4510 4511 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4512 4513 def rand_sql(self, expression: exp.Rand) -> str: 4514 lower = self.sql(expression, "lower") 4515 upper = self.sql(expression, "upper") 4516 4517 if lower and upper: 4518 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4519 return self.func("RAND", expression.this) 4520 4521 def changes_sql(self, expression: exp.Changes) -> str: 4522 information = self.sql(expression, "information") 4523 information = f"INFORMATION => {information}" 4524 at_before = self.sql(expression, "at_before") 4525 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4526 end = self.sql(expression, "end") 4527 end = f"{self.seg('')}{end}" if end else "" 4528 4529 return f"CHANGES ({information}){at_before}{end}" 4530 4531 def pad_sql(self, expression: exp.Pad) -> str: 4532 prefix = "L" if expression.args.get("is_left") else "R" 4533 4534 fill_pattern = self.sql(expression, "fill_pattern") or None 4535 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4536 fill_pattern = "' '" 4537 4538 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4539 4540 def summarize_sql(self, expression: exp.Summarize) -> str: 4541 table = " TABLE" if expression.args.get("table") else "" 4542 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4543 4544 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4545 generate_series = exp.GenerateSeries(**expression.args) 4546 4547 parent = expression.parent 4548 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4549 parent = parent.parent 4550 4551 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4552 return self.sql(exp.Unnest(expressions=[generate_series])) 4553 4554 if isinstance(parent, exp.Select): 4555 self.unsupported("GenerateSeries projection unnesting is not supported.") 4556 4557 return self.sql(generate_series) 4558 4559 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4560 exprs = expression.expressions 4561 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4562 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4563 else: 4564 rhs = self.expressions(expression) 4565 4566 return self.func(name, expression.this, rhs or None) 4567 4568 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4569 if self.SUPPORTS_CONVERT_TIMEZONE: 4570 return self.function_fallback_sql(expression) 4571 4572 source_tz = expression.args.get("source_tz") 4573 target_tz = expression.args.get("target_tz") 4574 timestamp = expression.args.get("timestamp") 4575 4576 if source_tz and timestamp: 4577 timestamp = exp.AtTimeZone( 4578 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4579 ) 4580 4581 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4582 4583 return self.sql(expr) 4584 4585 def json_sql(self, expression: exp.JSON) -> str: 4586 this = self.sql(expression, "this") 4587 this = f" {this}" if this else "" 4588 4589 _with = expression.args.get("with") 4590 4591 if _with is None: 4592 with_sql = "" 4593 elif not _with: 4594 with_sql = " WITHOUT" 4595 else: 4596 with_sql = " WITH" 4597 4598 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4599 4600 return f"JSON{this}{with_sql}{unique_sql}" 4601 4602 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4603 def _generate_on_options(arg: t.Any) -> str: 4604 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4605 4606 path = self.sql(expression, "path") 4607 returning = self.sql(expression, "returning") 4608 returning = f" RETURNING {returning}" if returning else "" 4609 4610 on_condition = self.sql(expression, "on_condition") 4611 on_condition = f" {on_condition}" if on_condition else "" 4612 4613 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4614 4615 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4616 else_ = "ELSE " if expression.args.get("else_") else "" 4617 condition = self.sql(expression, "expression") 4618 condition = f"WHEN {condition} THEN " if condition else else_ 4619 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4620 return f"{condition}{insert}" 4621 4622 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4623 kind = self.sql(expression, "kind") 4624 expressions = self.seg(self.expressions(expression, sep=" ")) 4625 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4626 return res 4627 4628 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4629 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4630 empty = expression.args.get("empty") 4631 empty = ( 4632 f"DEFAULT {empty} ON EMPTY" 4633 if isinstance(empty, exp.Expression) 4634 else self.sql(expression, "empty") 4635 ) 4636 4637 error = expression.args.get("error") 4638 error = ( 4639 f"DEFAULT {error} ON ERROR" 4640 if isinstance(error, exp.Expression) 4641 else self.sql(expression, "error") 4642 ) 4643 4644 if error and empty: 4645 error = ( 4646 f"{empty} {error}" 4647 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4648 else f"{error} {empty}" 4649 ) 4650 empty = "" 4651 4652 null = self.sql(expression, "null") 4653 4654 return f"{empty}{error}{null}" 4655 4656 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4657 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4658 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4659 4660 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4661 this = self.sql(expression, "this") 4662 path = self.sql(expression, "path") 4663 4664 passing = self.expressions(expression, "passing") 4665 passing = f" PASSING {passing}" if passing else "" 4666 4667 on_condition = self.sql(expression, "on_condition") 4668 on_condition = f" {on_condition}" if on_condition else "" 4669 4670 path = f"{path}{passing}{on_condition}" 4671 4672 return self.func("JSON_EXISTS", this, path) 4673 4674 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4675 array_agg = self.function_fallback_sql(expression) 4676 4677 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4678 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4679 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4680 parent = expression.parent 4681 if isinstance(parent, exp.Filter): 4682 parent_cond = parent.expression.this 4683 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4684 else: 4685 this = expression.this 4686 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4687 if this.find(exp.Column): 4688 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4689 this_sql = ( 4690 self.expressions(this) 4691 if isinstance(this, exp.Distinct) 4692 else self.sql(expression, "this") 4693 ) 4694 4695 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4696 4697 return array_agg 4698 4699 def apply_sql(self, expression: exp.Apply) -> str: 4700 this = self.sql(expression, "this") 4701 expr = self.sql(expression, "expression") 4702 4703 return f"{this} APPLY({expr})" 4704 4705 def grant_sql(self, expression: exp.Grant) -> str: 4706 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4707 4708 kind = self.sql(expression, "kind") 4709 kind = f" {kind}" if kind else "" 4710 4711 securable = self.sql(expression, "securable") 4712 securable = f" {securable}" if securable else "" 4713 4714 principals = self.expressions(expression, key="principals", flat=True) 4715 4716 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4717 4718 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4719 4720 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4721 this = self.sql(expression, "this") 4722 columns = self.expressions(expression, flat=True) 4723 columns = f"({columns})" if columns else "" 4724 4725 return f"{this}{columns}" 4726 4727 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4728 this = self.sql(expression, "this") 4729 4730 kind = self.sql(expression, "kind") 4731 kind = f"{kind} " if kind else "" 4732 4733 return f"{kind}{this}" 4734 4735 def columns_sql(self, expression: exp.Columns): 4736 func = self.function_fallback_sql(expression) 4737 if expression.args.get("unpack"): 4738 func = f"*{func}" 4739 4740 return func 4741 4742 def overlay_sql(self, expression: exp.Overlay): 4743 this = self.sql(expression, "this") 4744 expr = self.sql(expression, "expression") 4745 from_sql = self.sql(expression, "from") 4746 for_sql = self.sql(expression, "for") 4747 for_sql = f" FOR {for_sql}" if for_sql else "" 4748 4749 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4750 4751 @unsupported_args("format") 4752 def todouble_sql(self, expression: exp.ToDouble) -> str: 4753 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4754 4755 def string_sql(self, expression: exp.String) -> str: 4756 this = expression.this 4757 zone = expression.args.get("zone") 4758 4759 if zone: 4760 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4761 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4762 # set for source_tz to transpile the time conversion before the STRING cast 4763 this = exp.ConvertTimezone( 4764 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4765 ) 4766 4767 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4768 4769 def median_sql(self, expression: exp.Median): 4770 if not self.SUPPORTS_MEDIAN: 4771 return self.sql( 4772 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4773 ) 4774 4775 return self.function_fallback_sql(expression) 4776 4777 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4778 filler = self.sql(expression, "this") 4779 filler = f" {filler}" if filler else "" 4780 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4781 return f"TRUNCATE{filler} {with_count}" 4782 4783 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4784 if self.SUPPORTS_UNIX_SECONDS: 4785 return self.function_fallback_sql(expression) 4786 4787 start_ts = exp.cast( 4788 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4789 ) 4790 4791 return self.sql( 4792 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4793 ) 4794 4795 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4796 dim = expression.expression 4797 4798 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4799 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4800 if not (dim.is_int and dim.name == "1"): 4801 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4802 dim = None 4803 4804 # If dimension is required but not specified, default initialize it 4805 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4806 dim = exp.Literal.number(1) 4807 4808 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4809 4810 def attach_sql(self, expression: exp.Attach) -> str: 4811 this = self.sql(expression, "this") 4812 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4813 expressions = self.expressions(expression) 4814 expressions = f" ({expressions})" if expressions else "" 4815 4816 return f"ATTACH{exists_sql} {this}{expressions}" 4817 4818 def detach_sql(self, expression: exp.Detach) -> str: 4819 this = self.sql(expression, "this") 4820 # the DATABASE keyword is required if IF EXISTS is set 4821 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4822 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4823 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4824 4825 return f"DETACH{exists_sql} {this}" 4826 4827 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4828 this = self.sql(expression, "this") 4829 value = self.sql(expression, "expression") 4830 value = f" {value}" if value else "" 4831 return f"{this}{value}" 4832 4833 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4834 this_sql = self.sql(expression, "this") 4835 if isinstance(expression.this, exp.Table): 4836 this_sql = f"TABLE {this_sql}" 4837 4838 return self.func( 4839 "FEATURES_AT_TIME", 4840 this_sql, 4841 expression.args.get("time"), 4842 expression.args.get("num_rows"), 4843 expression.args.get("ignore_feature_nulls"), 4844 ) 4845 4846 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4847 return ( 4848 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4849 ) 4850 4851 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4852 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4853 encode = f"{encode} {self.sql(expression, 'this')}" 4854 4855 properties = expression.args.get("properties") 4856 if properties: 4857 encode = f"{encode} {self.properties(properties)}" 4858 4859 return encode 4860 4861 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4862 this = self.sql(expression, "this") 4863 include = f"INCLUDE {this}" 4864 4865 column_def = self.sql(expression, "column_def") 4866 if column_def: 4867 include = f"{include} {column_def}" 4868 4869 alias = self.sql(expression, "alias") 4870 if alias: 4871 include = f"{include} AS {alias}" 4872 4873 return include 4874 4875 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4876 name = f"NAME {self.sql(expression, 'this')}" 4877 return self.func("XMLELEMENT", name, *expression.expressions) 4878 4879 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 4880 this = self.sql(expression, "this") 4881 expr = self.sql(expression, "expression") 4882 expr = f"({expr})" if expr else "" 4883 return f"{this}{expr}" 4884 4885 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4886 partitions = self.expressions(expression, "partition_expressions") 4887 create = self.expressions(expression, "create_expressions") 4888 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4889 4890 def partitionbyrangepropertydynamic_sql( 4891 self, expression: exp.PartitionByRangePropertyDynamic 4892 ) -> str: 4893 start = self.sql(expression, "start") 4894 end = self.sql(expression, "end") 4895 4896 every = expression.args["every"] 4897 if isinstance(every, exp.Interval) and every.this.is_string: 4898 every.this.replace(exp.Literal.number(every.name)) 4899 4900 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4901 4902 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4903 name = self.sql(expression, "this") 4904 values = self.expressions(expression, flat=True) 4905 4906 return f"NAME {name} VALUE {values}" 4907 4908 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4909 kind = self.sql(expression, "kind") 4910 sample = self.sql(expression, "sample") 4911 return f"SAMPLE {sample} {kind}" 4912 4913 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4914 kind = self.sql(expression, "kind") 4915 option = self.sql(expression, "option") 4916 option = f" {option}" if option else "" 4917 this = self.sql(expression, "this") 4918 this = f" {this}" if this else "" 4919 columns = self.expressions(expression) 4920 columns = f" {columns}" if columns else "" 4921 return f"{kind}{option} STATISTICS{this}{columns}" 4922 4923 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4924 this = self.sql(expression, "this") 4925 columns = self.expressions(expression) 4926 inner_expression = self.sql(expression, "expression") 4927 inner_expression = f" {inner_expression}" if inner_expression else "" 4928 update_options = self.sql(expression, "update_options") 4929 update_options = f" {update_options} UPDATE" if update_options else "" 4930 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4931 4932 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4933 kind = self.sql(expression, "kind") 4934 kind = f" {kind}" if kind else "" 4935 return f"DELETE{kind} STATISTICS" 4936 4937 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4938 inner_expression = self.sql(expression, "expression") 4939 return f"LIST CHAINED ROWS{inner_expression}" 4940 4941 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4942 kind = self.sql(expression, "kind") 4943 this = self.sql(expression, "this") 4944 this = f" {this}" if this else "" 4945 inner_expression = self.sql(expression, "expression") 4946 return f"VALIDATE {kind}{this}{inner_expression}" 4947 4948 def analyze_sql(self, expression: exp.Analyze) -> str: 4949 options = self.expressions(expression, key="options", sep=" ") 4950 options = f" {options}" if options else "" 4951 kind = self.sql(expression, "kind") 4952 kind = f" {kind}" if kind else "" 4953 this = self.sql(expression, "this") 4954 this = f" {this}" if this else "" 4955 mode = self.sql(expression, "mode") 4956 mode = f" {mode}" if mode else "" 4957 properties = self.sql(expression, "properties") 4958 properties = f" {properties}" if properties else "" 4959 partition = self.sql(expression, "partition") 4960 partition = f" {partition}" if partition else "" 4961 inner_expression = self.sql(expression, "expression") 4962 inner_expression = f" {inner_expression}" if inner_expression else "" 4963 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 4964 4965 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4966 this = self.sql(expression, "this") 4967 namespaces = self.expressions(expression, key="namespaces") 4968 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4969 passing = self.expressions(expression, key="passing") 4970 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4971 columns = self.expressions(expression, key="columns") 4972 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4973 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4974 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 4975 4976 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 4977 this = self.sql(expression, "this") 4978 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 4979 4980 def export_sql(self, expression: exp.Export) -> str: 4981 this = self.sql(expression, "this") 4982 connection = self.sql(expression, "connection") 4983 connection = f"WITH CONNECTION {connection} " if connection else "" 4984 options = self.sql(expression, "options") 4985 return f"EXPORT DATA {connection}{options} AS {this}" 4986 4987 def declare_sql(self, expression: exp.Declare) -> str: 4988 return f"DECLARE {self.expressions(expression, flat=True)}" 4989 4990 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4991 variable = self.sql(expression, "this") 4992 default = self.sql(expression, "default") 4993 default = f" = {default}" if default else "" 4994 4995 kind = self.sql(expression, "kind") 4996 if isinstance(expression.args.get("kind"), exp.Schema): 4997 kind = f"TABLE {kind}" 4998 4999 return f"{variable} AS {kind}{default}" 5000 5001 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5002 kind = self.sql(expression, "kind") 5003 this = self.sql(expression, "this") 5004 set = self.sql(expression, "expression") 5005 using = self.sql(expression, "using") 5006 using = f" USING {using}" if using else "" 5007 5008 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5009 5010 return f"{kind_sql} {this} SET {set}{using}" 5011 5012 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5013 params = self.expressions(expression, key="params", flat=True) 5014 return self.func(expression.name, *expression.expressions) + f"({params})" 5015 5016 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5017 return self.func(expression.name, *expression.expressions) 5018 5019 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5020 return self.anonymousaggfunc_sql(expression) 5021 5022 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5023 return self.parameterizedagg_sql(expression) 5024 5025 def show_sql(self, expression: exp.Show) -> str: 5026 self.unsupported("Unsupported SHOW statement") 5027 return "" 5028 5029 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5030 # Snowflake GET/PUT statements: 5031 # PUT <file> <internalStage> <properties> 5032 # GET <internalStage> <file> <properties> 5033 props = expression.args.get("properties") 5034 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5035 this = self.sql(expression, "this") 5036 target = self.sql(expression, "target") 5037 5038 if isinstance(expression, exp.Put): 5039 return f"PUT {this} {target}{props_sql}" 5040 else: 5041 return f"GET {target} {this}{props_sql}" 5042 5043 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5044 this = self.sql(expression, "this") 5045 expr = self.sql(expression, "expression") 5046 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5047 return f"TRANSLATE({this} USING {expr}{with_error})" 5048 5049 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5050 if self.SUPPORTS_DECODE_CASE: 5051 return self.func("DECODE", *expression.expressions) 5052 5053 expression, *expressions = expression.expressions 5054 5055 ifs = [] 5056 for search, result in zip(expressions[::2], expressions[1::2]): 5057 if isinstance(search, exp.Literal): 5058 ifs.append(exp.If(this=expression.eq(search), true=result)) 5059 elif isinstance(search, exp.Null): 5060 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5061 else: 5062 if isinstance(search, exp.Binary): 5063 search = exp.paren(search) 5064 5065 cond = exp.or_( 5066 expression.eq(search), 5067 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5068 copy=False, 5069 ) 5070 ifs.append(exp.If(this=cond, true=result)) 5071 5072 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5073 return self.sql(case) 5074 5075 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5076 this = self.sql(expression, "this") 5077 this = self.seg(this, sep="") 5078 dimensions = self.expressions( 5079 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5080 ) 5081 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5082 metrics = self.expressions( 5083 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5084 ) 5085 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5086 where = self.sql(expression, "where") 5087 where = self.seg(f"WHERE {where}") if where else "" 5088 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE =
"Argument '{}' is not supported for expression '{}' when targeting {}."
def
unsupported_args( *args: Union[str, Tuple[str, str]]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
30def unsupported_args( 31 *args: t.Union[str, t.Tuple[str, str]], 32) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 33 """ 34 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 35 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 36 """ 37 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 38 for arg in args: 39 if isinstance(arg, str): 40 diagnostic_by_arg[arg] = None 41 else: 42 diagnostic_by_arg[arg[0]] = arg[1] 43 44 def decorator(func: GeneratorMethod) -> GeneratorMethod: 45 @wraps(func) 46 def _func(generator: G, expression: E) -> str: 47 expression_name = expression.__class__.__name__ 48 dialect_name = generator.dialect.__class__.__name__ 49 50 for arg_name, diagnostic in diagnostic_by_arg.items(): 51 if expression.args.get(arg_name): 52 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 53 arg_name, expression_name, dialect_name 54 ) 55 generator.unsupported(diagnostic) 56 57 return func(generator, expression) 58 59 return _func 60 61 return decorator
Decorator that can be used to mark certain args of an Expression
subclass as unsupported.
It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
class
Generator:
75class Generator(metaclass=_Generator): 76 """ 77 Generator converts a given syntax tree to the corresponding SQL string. 78 79 Args: 80 pretty: Whether to format the produced SQL string. 81 Default: False. 82 identify: Determines when an identifier should be quoted. Possible values are: 83 False (default): Never quote, except in cases where it's mandatory by the dialect. 84 True or 'always': Always quote. 85 'safe': Only quote identifiers that are case insensitive. 86 normalize: Whether to normalize identifiers to lowercase. 87 Default: False. 88 pad: The pad size in a formatted string. For example, this affects the indentation of 89 a projection in a query, relative to its nesting level. 90 Default: 2. 91 indent: The indentation size in a formatted string. For example, this affects the 92 indentation of subqueries and filters under a `WHERE` clause. 93 Default: 2. 94 normalize_functions: How to normalize function names. Possible values are: 95 "upper" or True (default): Convert names to uppercase. 96 "lower": Convert names to lowercase. 97 False: Disables function name normalization. 98 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 99 Default ErrorLevel.WARN. 100 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 101 This is only relevant if unsupported_level is ErrorLevel.RAISE. 102 Default: 3 103 leading_comma: Whether the comma is leading or trailing in select expressions. 104 This is only relevant when generating in pretty mode. 105 Default: False 106 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 107 The default is on the smaller end because the length only represents a segment and not the true 108 line length. 109 Default: 80 110 comments: Whether to preserve comments in the output SQL code. 111 Default: True 112 """ 113 114 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 115 **JSON_PATH_PART_TRANSFORMS, 116 exp.AllowedValuesProperty: lambda self, 117 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 118 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 119 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 120 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 121 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 122 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 123 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 124 exp.CaseSpecificColumnConstraint: lambda _, 125 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 126 exp.Ceil: lambda self, e: self.ceil_floor(e), 127 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 128 exp.CharacterSetProperty: lambda self, 129 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 130 exp.ClusteredColumnConstraint: lambda self, 131 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 132 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 133 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 134 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 135 exp.ConvertToCharset: lambda self, e: self.func( 136 "CONVERT", e.this, e.args["dest"], e.args.get("source") 137 ), 138 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 139 exp.CredentialsProperty: lambda self, 140 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 141 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 142 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 143 exp.DynamicProperty: lambda *_: "DYNAMIC", 144 exp.EmptyProperty: lambda *_: "EMPTY", 145 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 146 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 147 exp.EphemeralColumnConstraint: lambda self, 148 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 149 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 150 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 151 exp.Except: lambda self, e: self.set_operations(e), 152 exp.ExternalProperty: lambda *_: "EXTERNAL", 153 exp.Floor: lambda self, e: self.ceil_floor(e), 154 exp.Get: lambda self, e: self.get_put_sql(e), 155 exp.GlobalProperty: lambda *_: "GLOBAL", 156 exp.HeapProperty: lambda *_: "HEAP", 157 exp.IcebergProperty: lambda *_: "ICEBERG", 158 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 159 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 160 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 161 exp.Intersect: lambda self, e: self.set_operations(e), 162 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 163 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 164 exp.LanguageProperty: lambda self, e: self.naked_property(e), 165 exp.LocationProperty: lambda self, e: self.naked_property(e), 166 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 167 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 168 exp.NonClusteredColumnConstraint: lambda self, 169 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 170 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 171 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 172 exp.OnCommitProperty: lambda _, 173 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 174 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 175 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 176 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 177 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 178 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 179 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 180 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 181 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 182 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 183 exp.ProjectionPolicyColumnConstraint: lambda self, 184 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 185 exp.Put: lambda self, e: self.get_put_sql(e), 186 exp.RemoteWithConnectionModelProperty: lambda self, 187 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 188 exp.ReturnsProperty: lambda self, e: ( 189 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 190 ), 191 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 192 exp.SecureProperty: lambda *_: "SECURE", 193 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 194 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 195 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 196 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 197 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 198 exp.SqlReadWriteProperty: lambda _, e: e.name, 199 exp.SqlSecurityProperty: lambda _, 200 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 201 exp.StabilityProperty: lambda _, e: e.name, 202 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 203 exp.StreamingTableProperty: lambda *_: "STREAMING", 204 exp.StrictProperty: lambda *_: "STRICT", 205 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 206 exp.TableColumn: lambda self, e: self.sql(e.this), 207 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 208 exp.TemporaryProperty: lambda *_: "TEMPORARY", 209 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 210 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 211 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 212 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 213 exp.TransientProperty: lambda *_: "TRANSIENT", 214 exp.Union: lambda self, e: self.set_operations(e), 215 exp.UnloggedProperty: lambda *_: "UNLOGGED", 216 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 217 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 218 exp.Uuid: lambda *_: "UUID()", 219 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 220 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 221 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 222 exp.VolatileProperty: lambda *_: "VOLATILE", 223 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 224 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 225 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 226 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 227 exp.ForceProperty: lambda *_: "FORCE", 228 } 229 230 # Whether null ordering is supported in order by 231 # True: Full Support, None: No support, False: No support for certain cases 232 # such as window specifications, aggregate functions etc 233 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 234 235 # Whether ignore nulls is inside the agg or outside. 236 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 237 IGNORE_NULLS_IN_FUNC = False 238 239 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 240 LOCKING_READS_SUPPORTED = False 241 242 # Whether the EXCEPT and INTERSECT operations can return duplicates 243 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 244 245 # Wrap derived values in parens, usually standard but spark doesn't support it 246 WRAP_DERIVED_VALUES = True 247 248 # Whether create function uses an AS before the RETURN 249 CREATE_FUNCTION_RETURN_AS = True 250 251 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 252 MATCHED_BY_SOURCE = True 253 254 # Whether the INTERVAL expression works only with values like '1 day' 255 SINGLE_STRING_INTERVAL = False 256 257 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 258 INTERVAL_ALLOWS_PLURAL_FORM = True 259 260 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 261 LIMIT_FETCH = "ALL" 262 263 # Whether limit and fetch allows expresions or just limits 264 LIMIT_ONLY_LITERALS = False 265 266 # Whether a table is allowed to be renamed with a db 267 RENAME_TABLE_WITH_DB = True 268 269 # The separator for grouping sets and rollups 270 GROUPINGS_SEP = "," 271 272 # The string used for creating an index on a table 273 INDEX_ON = "ON" 274 275 # Whether join hints should be generated 276 JOIN_HINTS = True 277 278 # Whether table hints should be generated 279 TABLE_HINTS = True 280 281 # Whether query hints should be generated 282 QUERY_HINTS = True 283 284 # What kind of separator to use for query hints 285 QUERY_HINT_SEP = ", " 286 287 # Whether comparing against booleans (e.g. x IS TRUE) is supported 288 IS_BOOL_ALLOWED = True 289 290 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 291 DUPLICATE_KEY_UPDATE_WITH_SET = True 292 293 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 294 LIMIT_IS_TOP = False 295 296 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 297 RETURNING_END = True 298 299 # Whether to generate an unquoted value for EXTRACT's date part argument 300 EXTRACT_ALLOWS_QUOTES = True 301 302 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 303 TZ_TO_WITH_TIME_ZONE = False 304 305 # Whether the NVL2 function is supported 306 NVL2_SUPPORTED = True 307 308 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 309 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 310 311 # Whether VALUES statements can be used as derived tables. 312 # MySQL 5 and Redshift do not allow this, so when False, it will convert 313 # SELECT * VALUES into SELECT UNION 314 VALUES_AS_TABLE = True 315 316 # Whether the word COLUMN is included when adding a column with ALTER TABLE 317 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 318 319 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 320 UNNEST_WITH_ORDINALITY = True 321 322 # Whether FILTER (WHERE cond) can be used for conditional aggregation 323 AGGREGATE_FILTER_SUPPORTED = True 324 325 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 326 SEMI_ANTI_JOIN_WITH_SIDE = True 327 328 # Whether to include the type of a computed column in the CREATE DDL 329 COMPUTED_COLUMN_WITH_TYPE = True 330 331 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 332 SUPPORTS_TABLE_COPY = True 333 334 # Whether parentheses are required around the table sample's expression 335 TABLESAMPLE_REQUIRES_PARENS = True 336 337 # Whether a table sample clause's size needs to be followed by the ROWS keyword 338 TABLESAMPLE_SIZE_IS_ROWS = True 339 340 # The keyword(s) to use when generating a sample clause 341 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 342 343 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 344 TABLESAMPLE_WITH_METHOD = True 345 346 # The keyword to use when specifying the seed of a sample clause 347 TABLESAMPLE_SEED_KEYWORD = "SEED" 348 349 # Whether COLLATE is a function instead of a binary operator 350 COLLATE_IS_FUNC = False 351 352 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 353 DATA_TYPE_SPECIFIERS_ALLOWED = False 354 355 # Whether conditions require booleans WHERE x = 0 vs WHERE x 356 ENSURE_BOOLS = False 357 358 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 359 CTE_RECURSIVE_KEYWORD_REQUIRED = True 360 361 # Whether CONCAT requires >1 arguments 362 SUPPORTS_SINGLE_ARG_CONCAT = True 363 364 # Whether LAST_DAY function supports a date part argument 365 LAST_DAY_SUPPORTS_DATE_PART = True 366 367 # Whether named columns are allowed in table aliases 368 SUPPORTS_TABLE_ALIAS_COLUMNS = True 369 370 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 371 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 372 373 # What delimiter to use for separating JSON key/value pairs 374 JSON_KEY_VALUE_PAIR_SEP = ":" 375 376 # INSERT OVERWRITE TABLE x override 377 INSERT_OVERWRITE = " OVERWRITE TABLE" 378 379 # Whether the SELECT .. INTO syntax is used instead of CTAS 380 SUPPORTS_SELECT_INTO = False 381 382 # Whether UNLOGGED tables can be created 383 SUPPORTS_UNLOGGED_TABLES = False 384 385 # Whether the CREATE TABLE LIKE statement is supported 386 SUPPORTS_CREATE_TABLE_LIKE = True 387 388 # Whether the LikeProperty needs to be specified inside of the schema clause 389 LIKE_PROPERTY_INSIDE_SCHEMA = False 390 391 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 392 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 393 MULTI_ARG_DISTINCT = True 394 395 # Whether the JSON extraction operators expect a value of type JSON 396 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 397 398 # Whether bracketed keys like ["foo"] are supported in JSON paths 399 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 400 401 # Whether to escape keys using single quotes in JSON paths 402 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 403 404 # The JSONPathPart expressions supported by this dialect 405 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 406 407 # Whether any(f(x) for x in array) can be implemented by this dialect 408 CAN_IMPLEMENT_ARRAY_ANY = False 409 410 # Whether the function TO_NUMBER is supported 411 SUPPORTS_TO_NUMBER = True 412 413 # Whether EXCLUDE in window specification is supported 414 SUPPORTS_WINDOW_EXCLUDE = False 415 416 # Whether or not set op modifiers apply to the outer set op or select. 417 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 418 # True means limit 1 happens after the set op, False means it it happens on y. 419 SET_OP_MODIFIERS = True 420 421 # Whether parameters from COPY statement are wrapped in parentheses 422 COPY_PARAMS_ARE_WRAPPED = True 423 424 # Whether values of params are set with "=" token or empty space 425 COPY_PARAMS_EQ_REQUIRED = False 426 427 # Whether COPY statement has INTO keyword 428 COPY_HAS_INTO_KEYWORD = True 429 430 # Whether the conditional TRY(expression) function is supported 431 TRY_SUPPORTED = True 432 433 # Whether the UESCAPE syntax in unicode strings is supported 434 SUPPORTS_UESCAPE = True 435 436 # The keyword to use when generating a star projection with excluded columns 437 STAR_EXCEPT = "EXCEPT" 438 439 # The HEX function name 440 HEX_FUNC = "HEX" 441 442 # The keywords to use when prefixing & separating WITH based properties 443 WITH_PROPERTIES_PREFIX = "WITH" 444 445 # Whether to quote the generated expression of exp.JsonPath 446 QUOTE_JSON_PATH = True 447 448 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 449 PAD_FILL_PATTERN_IS_REQUIRED = False 450 451 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 452 SUPPORTS_EXPLODING_PROJECTIONS = True 453 454 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 455 ARRAY_CONCAT_IS_VAR_LEN = True 456 457 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 458 SUPPORTS_CONVERT_TIMEZONE = False 459 460 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 461 SUPPORTS_MEDIAN = True 462 463 # Whether UNIX_SECONDS(timestamp) is supported 464 SUPPORTS_UNIX_SECONDS = False 465 466 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 467 ALTER_SET_WRAPPED = False 468 469 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 470 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 471 # TODO: The normalization should be done by default once we've tested it across all dialects. 472 NORMALIZE_EXTRACT_DATE_PARTS = False 473 474 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 475 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 476 477 # The function name of the exp.ArraySize expression 478 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 479 480 # The syntax to use when altering the type of a column 481 ALTER_SET_TYPE = "SET DATA TYPE" 482 483 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 484 # None -> Doesn't support it at all 485 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 486 # True (Postgres) -> Explicitly requires it 487 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 488 489 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 490 SUPPORTS_DECODE_CASE = True 491 492 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 493 SUPPORTS_BETWEEN_FLAGS = False 494 495 TYPE_MAPPING = { 496 exp.DataType.Type.DATETIME2: "TIMESTAMP", 497 exp.DataType.Type.NCHAR: "CHAR", 498 exp.DataType.Type.NVARCHAR: "VARCHAR", 499 exp.DataType.Type.MEDIUMTEXT: "TEXT", 500 exp.DataType.Type.LONGTEXT: "TEXT", 501 exp.DataType.Type.TINYTEXT: "TEXT", 502 exp.DataType.Type.BLOB: "VARBINARY", 503 exp.DataType.Type.MEDIUMBLOB: "BLOB", 504 exp.DataType.Type.LONGBLOB: "BLOB", 505 exp.DataType.Type.TINYBLOB: "BLOB", 506 exp.DataType.Type.INET: "INET", 507 exp.DataType.Type.ROWVERSION: "VARBINARY", 508 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 509 } 510 511 TIME_PART_SINGULARS = { 512 "MICROSECONDS": "MICROSECOND", 513 "SECONDS": "SECOND", 514 "MINUTES": "MINUTE", 515 "HOURS": "HOUR", 516 "DAYS": "DAY", 517 "WEEKS": "WEEK", 518 "MONTHS": "MONTH", 519 "QUARTERS": "QUARTER", 520 "YEARS": "YEAR", 521 } 522 523 AFTER_HAVING_MODIFIER_TRANSFORMS = { 524 "cluster": lambda self, e: self.sql(e, "cluster"), 525 "distribute": lambda self, e: self.sql(e, "distribute"), 526 "sort": lambda self, e: self.sql(e, "sort"), 527 "windows": lambda self, e: ( 528 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 529 if e.args.get("windows") 530 else "" 531 ), 532 "qualify": lambda self, e: self.sql(e, "qualify"), 533 } 534 535 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 536 537 STRUCT_DELIMITER = ("<", ">") 538 539 PARAMETER_TOKEN = "@" 540 NAMED_PLACEHOLDER_TOKEN = ":" 541 542 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 543 544 PROPERTIES_LOCATION = { 545 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 546 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 547 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 548 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 549 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 550 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 551 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 552 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 553 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 555 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 556 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 557 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 558 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 559 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 560 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 561 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 562 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 563 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 564 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 565 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 569 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 572 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 573 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 574 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 575 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 576 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 577 exp.HeapProperty: exp.Properties.Location.POST_WITH, 578 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 579 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 580 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 582 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 583 exp.JournalProperty: exp.Properties.Location.POST_NAME, 584 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 589 exp.LogProperty: exp.Properties.Location.POST_NAME, 590 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 591 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 592 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 593 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 594 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 595 exp.Order: exp.Properties.Location.POST_SCHEMA, 596 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 598 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 600 exp.Property: exp.Properties.Location.POST_WITH, 601 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 609 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 611 exp.Set: exp.Properties.Location.POST_SCHEMA, 612 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.SetProperty: exp.Properties.Location.POST_CREATE, 614 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 615 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 616 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 617 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 620 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 623 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.Tags: exp.Properties.Location.POST_WITH, 625 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 626 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 628 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 629 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 630 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 631 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 632 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 633 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 634 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 635 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 636 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 637 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 639 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 640 } 641 642 # Keywords that can't be used as unquoted identifier names 643 RESERVED_KEYWORDS: t.Set[str] = set() 644 645 # Expressions whose comments are separated from them for better formatting 646 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 647 exp.Command, 648 exp.Create, 649 exp.Describe, 650 exp.Delete, 651 exp.Drop, 652 exp.From, 653 exp.Insert, 654 exp.Join, 655 exp.MultitableInserts, 656 exp.Order, 657 exp.Group, 658 exp.Having, 659 exp.Select, 660 exp.SetOperation, 661 exp.Update, 662 exp.Where, 663 exp.With, 664 ) 665 666 # Expressions that should not have their comments generated in maybe_comment 667 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 668 exp.Binary, 669 exp.SetOperation, 670 ) 671 672 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 673 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 674 exp.Column, 675 exp.Literal, 676 exp.Neg, 677 exp.Paren, 678 ) 679 680 PARAMETERIZABLE_TEXT_TYPES = { 681 exp.DataType.Type.NVARCHAR, 682 exp.DataType.Type.VARCHAR, 683 exp.DataType.Type.CHAR, 684 exp.DataType.Type.NCHAR, 685 } 686 687 # Expressions that need to have all CTEs under them bubbled up to them 688 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 689 690 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 691 692 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 693 694 __slots__ = ( 695 "pretty", 696 "identify", 697 "normalize", 698 "pad", 699 "_indent", 700 "normalize_functions", 701 "unsupported_level", 702 "max_unsupported", 703 "leading_comma", 704 "max_text_width", 705 "comments", 706 "dialect", 707 "unsupported_messages", 708 "_escaped_quote_end", 709 "_escaped_identifier_end", 710 "_next_name", 711 "_identifier_start", 712 "_identifier_end", 713 "_quote_json_path_key_using_brackets", 714 ) 715 716 def __init__( 717 self, 718 pretty: t.Optional[bool] = None, 719 identify: str | bool = False, 720 normalize: bool = False, 721 pad: int = 2, 722 indent: int = 2, 723 normalize_functions: t.Optional[str | bool] = None, 724 unsupported_level: ErrorLevel = ErrorLevel.WARN, 725 max_unsupported: int = 3, 726 leading_comma: bool = False, 727 max_text_width: int = 80, 728 comments: bool = True, 729 dialect: DialectType = None, 730 ): 731 import sqlglot 732 from sqlglot.dialects import Dialect 733 734 self.pretty = pretty if pretty is not None else sqlglot.pretty 735 self.identify = identify 736 self.normalize = normalize 737 self.pad = pad 738 self._indent = indent 739 self.unsupported_level = unsupported_level 740 self.max_unsupported = max_unsupported 741 self.leading_comma = leading_comma 742 self.max_text_width = max_text_width 743 self.comments = comments 744 self.dialect = Dialect.get_or_raise(dialect) 745 746 # This is both a Dialect property and a Generator argument, so we prioritize the latter 747 self.normalize_functions = ( 748 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 749 ) 750 751 self.unsupported_messages: t.List[str] = [] 752 self._escaped_quote_end: str = ( 753 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 754 ) 755 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 756 757 self._next_name = name_sequence("_t") 758 759 self._identifier_start = self.dialect.IDENTIFIER_START 760 self._identifier_end = self.dialect.IDENTIFIER_END 761 762 self._quote_json_path_key_using_brackets = True 763 764 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 765 """ 766 Generates the SQL string corresponding to the given syntax tree. 767 768 Args: 769 expression: The syntax tree. 770 copy: Whether to copy the expression. The generator performs mutations so 771 it is safer to copy. 772 773 Returns: 774 The SQL string corresponding to `expression`. 775 """ 776 if copy: 777 expression = expression.copy() 778 779 expression = self.preprocess(expression) 780 781 self.unsupported_messages = [] 782 sql = self.sql(expression).strip() 783 784 if self.pretty: 785 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 786 787 if self.unsupported_level == ErrorLevel.IGNORE: 788 return sql 789 790 if self.unsupported_level == ErrorLevel.WARN: 791 for msg in self.unsupported_messages: 792 logger.warning(msg) 793 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 794 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 795 796 return sql 797 798 def preprocess(self, expression: exp.Expression) -> exp.Expression: 799 """Apply generic preprocessing transformations to a given expression.""" 800 expression = self._move_ctes_to_top_level(expression) 801 802 if self.ENSURE_BOOLS: 803 from sqlglot.transforms import ensure_bools 804 805 expression = ensure_bools(expression) 806 807 return expression 808 809 def _move_ctes_to_top_level(self, expression: E) -> E: 810 if ( 811 not expression.parent 812 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 813 and any(node.parent is not expression for node in expression.find_all(exp.With)) 814 ): 815 from sqlglot.transforms import move_ctes_to_top_level 816 817 expression = move_ctes_to_top_level(expression) 818 return expression 819 820 def unsupported(self, message: str) -> None: 821 if self.unsupported_level == ErrorLevel.IMMEDIATE: 822 raise UnsupportedError(message) 823 self.unsupported_messages.append(message) 824 825 def sep(self, sep: str = " ") -> str: 826 return f"{sep.strip()}\n" if self.pretty else sep 827 828 def seg(self, sql: str, sep: str = " ") -> str: 829 return f"{self.sep(sep)}{sql}" 830 831 def sanitize_comment(self, comment: str) -> str: 832 comment = " " + comment if comment[0].strip() else comment 833 comment = comment + " " if comment[-1].strip() else comment 834 835 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 836 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 837 comment = comment.replace("*/", "* /") 838 839 return comment 840 841 def maybe_comment( 842 self, 843 sql: str, 844 expression: t.Optional[exp.Expression] = None, 845 comments: t.Optional[t.List[str]] = None, 846 separated: bool = False, 847 ) -> str: 848 comments = ( 849 ((expression and expression.comments) if comments is None else comments) # type: ignore 850 if self.comments 851 else None 852 ) 853 854 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 855 return sql 856 857 comments_sql = " ".join( 858 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 859 ) 860 861 if not comments_sql: 862 return sql 863 864 comments_sql = self._replace_line_breaks(comments_sql) 865 866 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 867 return ( 868 f"{self.sep()}{comments_sql}{sql}" 869 if not sql or sql[0].isspace() 870 else f"{comments_sql}{self.sep()}{sql}" 871 ) 872 873 return f"{sql} {comments_sql}" 874 875 def wrap(self, expression: exp.Expression | str) -> str: 876 this_sql = ( 877 self.sql(expression) 878 if isinstance(expression, exp.UNWRAPPED_QUERIES) 879 else self.sql(expression, "this") 880 ) 881 if not this_sql: 882 return "()" 883 884 this_sql = self.indent(this_sql, level=1, pad=0) 885 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 886 887 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 888 original = self.identify 889 self.identify = False 890 result = func(*args, **kwargs) 891 self.identify = original 892 return result 893 894 def normalize_func(self, name: str) -> str: 895 if self.normalize_functions == "upper" or self.normalize_functions is True: 896 return name.upper() 897 if self.normalize_functions == "lower": 898 return name.lower() 899 return name 900 901 def indent( 902 self, 903 sql: str, 904 level: int = 0, 905 pad: t.Optional[int] = None, 906 skip_first: bool = False, 907 skip_last: bool = False, 908 ) -> str: 909 if not self.pretty or not sql: 910 return sql 911 912 pad = self.pad if pad is None else pad 913 lines = sql.split("\n") 914 915 return "\n".join( 916 ( 917 line 918 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 919 else f"{' ' * (level * self._indent + pad)}{line}" 920 ) 921 for i, line in enumerate(lines) 922 ) 923 924 def sql( 925 self, 926 expression: t.Optional[str | exp.Expression], 927 key: t.Optional[str] = None, 928 comment: bool = True, 929 ) -> str: 930 if not expression: 931 return "" 932 933 if isinstance(expression, str): 934 return expression 935 936 if key: 937 value = expression.args.get(key) 938 if value: 939 return self.sql(value) 940 return "" 941 942 transform = self.TRANSFORMS.get(expression.__class__) 943 944 if callable(transform): 945 sql = transform(self, expression) 946 elif isinstance(expression, exp.Expression): 947 exp_handler_name = f"{expression.key}_sql" 948 949 if hasattr(self, exp_handler_name): 950 sql = getattr(self, exp_handler_name)(expression) 951 elif isinstance(expression, exp.Func): 952 sql = self.function_fallback_sql(expression) 953 elif isinstance(expression, exp.Property): 954 sql = self.property_sql(expression) 955 else: 956 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 957 else: 958 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 959 960 return self.maybe_comment(sql, expression) if self.comments and comment else sql 961 962 def uncache_sql(self, expression: exp.Uncache) -> str: 963 table = self.sql(expression, "this") 964 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 965 return f"UNCACHE TABLE{exists_sql} {table}" 966 967 def cache_sql(self, expression: exp.Cache) -> str: 968 lazy = " LAZY" if expression.args.get("lazy") else "" 969 table = self.sql(expression, "this") 970 options = expression.args.get("options") 971 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 972 sql = self.sql(expression, "expression") 973 sql = f" AS{self.sep()}{sql}" if sql else "" 974 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 975 return self.prepend_ctes(expression, sql) 976 977 def characterset_sql(self, expression: exp.CharacterSet) -> str: 978 if isinstance(expression.parent, exp.Cast): 979 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 980 default = "DEFAULT " if expression.args.get("default") else "" 981 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 982 983 def column_parts(self, expression: exp.Column) -> str: 984 return ".".join( 985 self.sql(part) 986 for part in ( 987 expression.args.get("catalog"), 988 expression.args.get("db"), 989 expression.args.get("table"), 990 expression.args.get("this"), 991 ) 992 if part 993 ) 994 995 def column_sql(self, expression: exp.Column) -> str: 996 join_mark = " (+)" if expression.args.get("join_mark") else "" 997 998 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 999 join_mark = "" 1000 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1001 1002 return f"{self.column_parts(expression)}{join_mark}" 1003 1004 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1005 this = self.sql(expression, "this") 1006 this = f" {this}" if this else "" 1007 position = self.sql(expression, "position") 1008 return f"{position}{this}" 1009 1010 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1011 column = self.sql(expression, "this") 1012 kind = self.sql(expression, "kind") 1013 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1014 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1015 kind = f"{sep}{kind}" if kind else "" 1016 constraints = f" {constraints}" if constraints else "" 1017 position = self.sql(expression, "position") 1018 position = f" {position}" if position else "" 1019 1020 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1021 kind = "" 1022 1023 return f"{exists}{column}{kind}{constraints}{position}" 1024 1025 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1026 this = self.sql(expression, "this") 1027 kind_sql = self.sql(expression, "kind").strip() 1028 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1029 1030 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1031 this = self.sql(expression, "this") 1032 if expression.args.get("not_null"): 1033 persisted = " PERSISTED NOT NULL" 1034 elif expression.args.get("persisted"): 1035 persisted = " PERSISTED" 1036 else: 1037 persisted = "" 1038 1039 return f"AS {this}{persisted}" 1040 1041 def autoincrementcolumnconstraint_sql(self, _) -> str: 1042 return self.token_sql(TokenType.AUTO_INCREMENT) 1043 1044 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1045 if isinstance(expression.this, list): 1046 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1047 else: 1048 this = self.sql(expression, "this") 1049 1050 return f"COMPRESS {this}" 1051 1052 def generatedasidentitycolumnconstraint_sql( 1053 self, expression: exp.GeneratedAsIdentityColumnConstraint 1054 ) -> str: 1055 this = "" 1056 if expression.this is not None: 1057 on_null = " ON NULL" if expression.args.get("on_null") else "" 1058 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1059 1060 start = expression.args.get("start") 1061 start = f"START WITH {start}" if start else "" 1062 increment = expression.args.get("increment") 1063 increment = f" INCREMENT BY {increment}" if increment else "" 1064 minvalue = expression.args.get("minvalue") 1065 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1066 maxvalue = expression.args.get("maxvalue") 1067 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1068 cycle = expression.args.get("cycle") 1069 cycle_sql = "" 1070 1071 if cycle is not None: 1072 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1073 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1074 1075 sequence_opts = "" 1076 if start or increment or cycle_sql: 1077 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1078 sequence_opts = f" ({sequence_opts.strip()})" 1079 1080 expr = self.sql(expression, "expression") 1081 expr = f"({expr})" if expr else "IDENTITY" 1082 1083 return f"GENERATED{this} AS {expr}{sequence_opts}" 1084 1085 def generatedasrowcolumnconstraint_sql( 1086 self, expression: exp.GeneratedAsRowColumnConstraint 1087 ) -> str: 1088 start = "START" if expression.args.get("start") else "END" 1089 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1090 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1091 1092 def periodforsystemtimeconstraint_sql( 1093 self, expression: exp.PeriodForSystemTimeConstraint 1094 ) -> str: 1095 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1096 1097 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1098 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1099 1100 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1101 desc = expression.args.get("desc") 1102 if desc is not None: 1103 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1104 options = self.expressions(expression, key="options", flat=True, sep=" ") 1105 options = f" {options}" if options else "" 1106 return f"PRIMARY KEY{options}" 1107 1108 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1109 this = self.sql(expression, "this") 1110 this = f" {this}" if this else "" 1111 index_type = expression.args.get("index_type") 1112 index_type = f" USING {index_type}" if index_type else "" 1113 on_conflict = self.sql(expression, "on_conflict") 1114 on_conflict = f" {on_conflict}" if on_conflict else "" 1115 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1116 options = self.expressions(expression, key="options", flat=True, sep=" ") 1117 options = f" {options}" if options else "" 1118 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1119 1120 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1121 return self.sql(expression, "this") 1122 1123 def create_sql(self, expression: exp.Create) -> str: 1124 kind = self.sql(expression, "kind") 1125 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1126 properties = expression.args.get("properties") 1127 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1128 1129 this = self.createable_sql(expression, properties_locs) 1130 1131 properties_sql = "" 1132 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1133 exp.Properties.Location.POST_WITH 1134 ): 1135 properties_sql = self.sql( 1136 exp.Properties( 1137 expressions=[ 1138 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1139 *properties_locs[exp.Properties.Location.POST_WITH], 1140 ] 1141 ) 1142 ) 1143 1144 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1145 properties_sql = self.sep() + properties_sql 1146 elif not self.pretty: 1147 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1148 properties_sql = f" {properties_sql}" 1149 1150 begin = " BEGIN" if expression.args.get("begin") else "" 1151 end = " END" if expression.args.get("end") else "" 1152 1153 expression_sql = self.sql(expression, "expression") 1154 if expression_sql: 1155 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1156 1157 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1158 postalias_props_sql = "" 1159 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1160 postalias_props_sql = self.properties( 1161 exp.Properties( 1162 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1163 ), 1164 wrapped=False, 1165 ) 1166 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1167 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1168 1169 postindex_props_sql = "" 1170 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1171 postindex_props_sql = self.properties( 1172 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1173 wrapped=False, 1174 prefix=" ", 1175 ) 1176 1177 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1178 indexes = f" {indexes}" if indexes else "" 1179 index_sql = indexes + postindex_props_sql 1180 1181 replace = " OR REPLACE" if expression.args.get("replace") else "" 1182 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1183 unique = " UNIQUE" if expression.args.get("unique") else "" 1184 1185 clustered = expression.args.get("clustered") 1186 if clustered is None: 1187 clustered_sql = "" 1188 elif clustered: 1189 clustered_sql = " CLUSTERED COLUMNSTORE" 1190 else: 1191 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1192 1193 postcreate_props_sql = "" 1194 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1195 postcreate_props_sql = self.properties( 1196 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1197 sep=" ", 1198 prefix=" ", 1199 wrapped=False, 1200 ) 1201 1202 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1203 1204 postexpression_props_sql = "" 1205 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1206 postexpression_props_sql = self.properties( 1207 exp.Properties( 1208 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1209 ), 1210 sep=" ", 1211 prefix=" ", 1212 wrapped=False, 1213 ) 1214 1215 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1216 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1217 no_schema_binding = ( 1218 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1219 ) 1220 1221 clone = self.sql(expression, "clone") 1222 clone = f" {clone}" if clone else "" 1223 1224 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1225 properties_expression = f"{expression_sql}{properties_sql}" 1226 else: 1227 properties_expression = f"{properties_sql}{expression_sql}" 1228 1229 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1230 return self.prepend_ctes(expression, expression_sql) 1231 1232 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1233 start = self.sql(expression, "start") 1234 start = f"START WITH {start}" if start else "" 1235 increment = self.sql(expression, "increment") 1236 increment = f" INCREMENT BY {increment}" if increment else "" 1237 minvalue = self.sql(expression, "minvalue") 1238 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1239 maxvalue = self.sql(expression, "maxvalue") 1240 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1241 owned = self.sql(expression, "owned") 1242 owned = f" OWNED BY {owned}" if owned else "" 1243 1244 cache = expression.args.get("cache") 1245 if cache is None: 1246 cache_str = "" 1247 elif cache is True: 1248 cache_str = " CACHE" 1249 else: 1250 cache_str = f" CACHE {cache}" 1251 1252 options = self.expressions(expression, key="options", flat=True, sep=" ") 1253 options = f" {options}" if options else "" 1254 1255 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1256 1257 def clone_sql(self, expression: exp.Clone) -> str: 1258 this = self.sql(expression, "this") 1259 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1260 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1261 return f"{shallow}{keyword} {this}" 1262 1263 def describe_sql(self, expression: exp.Describe) -> str: 1264 style = expression.args.get("style") 1265 style = f" {style}" if style else "" 1266 partition = self.sql(expression, "partition") 1267 partition = f" {partition}" if partition else "" 1268 format = self.sql(expression, "format") 1269 format = f" {format}" if format else "" 1270 1271 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1272 1273 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1274 tag = self.sql(expression, "tag") 1275 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1276 1277 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1278 with_ = self.sql(expression, "with") 1279 if with_: 1280 sql = f"{with_}{self.sep()}{sql}" 1281 return sql 1282 1283 def with_sql(self, expression: exp.With) -> str: 1284 sql = self.expressions(expression, flat=True) 1285 recursive = ( 1286 "RECURSIVE " 1287 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1288 else "" 1289 ) 1290 search = self.sql(expression, "search") 1291 search = f" {search}" if search else "" 1292 1293 return f"WITH {recursive}{sql}{search}" 1294 1295 def cte_sql(self, expression: exp.CTE) -> str: 1296 alias = expression.args.get("alias") 1297 if alias: 1298 alias.add_comments(expression.pop_comments()) 1299 1300 alias_sql = self.sql(expression, "alias") 1301 1302 materialized = expression.args.get("materialized") 1303 if materialized is False: 1304 materialized = "NOT MATERIALIZED " 1305 elif materialized: 1306 materialized = "MATERIALIZED " 1307 1308 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1309 1310 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1311 alias = self.sql(expression, "this") 1312 columns = self.expressions(expression, key="columns", flat=True) 1313 columns = f"({columns})" if columns else "" 1314 1315 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1316 columns = "" 1317 self.unsupported("Named columns are not supported in table alias.") 1318 1319 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1320 alias = self._next_name() 1321 1322 return f"{alias}{columns}" 1323 1324 def bitstring_sql(self, expression: exp.BitString) -> str: 1325 this = self.sql(expression, "this") 1326 if self.dialect.BIT_START: 1327 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1328 return f"{int(this, 2)}" 1329 1330 def hexstring_sql( 1331 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1332 ) -> str: 1333 this = self.sql(expression, "this") 1334 is_integer_type = expression.args.get("is_integer") 1335 1336 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1337 not self.dialect.HEX_START and not binary_function_repr 1338 ): 1339 # Integer representation will be returned if: 1340 # - The read dialect treats the hex value as integer literal but not the write 1341 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1342 return f"{int(this, 16)}" 1343 1344 if not is_integer_type: 1345 # Read dialect treats the hex value as BINARY/BLOB 1346 if binary_function_repr: 1347 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1348 return self.func(binary_function_repr, exp.Literal.string(this)) 1349 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1350 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1351 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1352 1353 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1354 1355 def bytestring_sql(self, expression: exp.ByteString) -> str: 1356 this = self.sql(expression, "this") 1357 if self.dialect.BYTE_START: 1358 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1359 return this 1360 1361 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1362 this = self.sql(expression, "this") 1363 escape = expression.args.get("escape") 1364 1365 if self.dialect.UNICODE_START: 1366 escape_substitute = r"\\\1" 1367 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1368 else: 1369 escape_substitute = r"\\u\1" 1370 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1371 1372 if escape: 1373 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1374 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1375 else: 1376 escape_pattern = ESCAPED_UNICODE_RE 1377 escape_sql = "" 1378 1379 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1380 this = escape_pattern.sub(escape_substitute, this) 1381 1382 return f"{left_quote}{this}{right_quote}{escape_sql}" 1383 1384 def rawstring_sql(self, expression: exp.RawString) -> str: 1385 string = expression.this 1386 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1387 string = string.replace("\\", "\\\\") 1388 1389 string = self.escape_str(string, escape_backslash=False) 1390 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1391 1392 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1393 this = self.sql(expression, "this") 1394 specifier = self.sql(expression, "expression") 1395 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1396 return f"{this}{specifier}" 1397 1398 def datatype_sql(self, expression: exp.DataType) -> str: 1399 nested = "" 1400 values = "" 1401 interior = self.expressions(expression, flat=True) 1402 1403 type_value = expression.this 1404 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1405 type_sql = self.sql(expression, "kind") 1406 else: 1407 type_sql = ( 1408 self.TYPE_MAPPING.get(type_value, type_value.value) 1409 if isinstance(type_value, exp.DataType.Type) 1410 else type_value 1411 ) 1412 1413 if interior: 1414 if expression.args.get("nested"): 1415 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1416 if expression.args.get("values") is not None: 1417 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1418 values = self.expressions(expression, key="values", flat=True) 1419 values = f"{delimiters[0]}{values}{delimiters[1]}" 1420 elif type_value == exp.DataType.Type.INTERVAL: 1421 nested = f" {interior}" 1422 else: 1423 nested = f"({interior})" 1424 1425 type_sql = f"{type_sql}{nested}{values}" 1426 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1427 exp.DataType.Type.TIMETZ, 1428 exp.DataType.Type.TIMESTAMPTZ, 1429 ): 1430 type_sql = f"{type_sql} WITH TIME ZONE" 1431 1432 return type_sql 1433 1434 def directory_sql(self, expression: exp.Directory) -> str: 1435 local = "LOCAL " if expression.args.get("local") else "" 1436 row_format = self.sql(expression, "row_format") 1437 row_format = f" {row_format}" if row_format else "" 1438 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1439 1440 def delete_sql(self, expression: exp.Delete) -> str: 1441 this = self.sql(expression, "this") 1442 this = f" FROM {this}" if this else "" 1443 using = self.sql(expression, "using") 1444 using = f" USING {using}" if using else "" 1445 cluster = self.sql(expression, "cluster") 1446 cluster = f" {cluster}" if cluster else "" 1447 where = self.sql(expression, "where") 1448 returning = self.sql(expression, "returning") 1449 limit = self.sql(expression, "limit") 1450 tables = self.expressions(expression, key="tables") 1451 tables = f" {tables}" if tables else "" 1452 if self.RETURNING_END: 1453 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1454 else: 1455 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1456 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1457 1458 def drop_sql(self, expression: exp.Drop) -> str: 1459 this = self.sql(expression, "this") 1460 expressions = self.expressions(expression, flat=True) 1461 expressions = f" ({expressions})" if expressions else "" 1462 kind = expression.args["kind"] 1463 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1464 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1465 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1466 on_cluster = self.sql(expression, "cluster") 1467 on_cluster = f" {on_cluster}" if on_cluster else "" 1468 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1469 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1470 cascade = " CASCADE" if expression.args.get("cascade") else "" 1471 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1472 purge = " PURGE" if expression.args.get("purge") else "" 1473 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1474 1475 def set_operation(self, expression: exp.SetOperation) -> str: 1476 op_type = type(expression) 1477 op_name = op_type.key.upper() 1478 1479 distinct = expression.args.get("distinct") 1480 if ( 1481 distinct is False 1482 and op_type in (exp.Except, exp.Intersect) 1483 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1484 ): 1485 self.unsupported(f"{op_name} ALL is not supported") 1486 1487 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1488 1489 if distinct is None: 1490 distinct = default_distinct 1491 if distinct is None: 1492 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1493 1494 if distinct is default_distinct: 1495 distinct_or_all = "" 1496 else: 1497 distinct_or_all = " DISTINCT" if distinct else " ALL" 1498 1499 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1500 side_kind = f"{side_kind} " if side_kind else "" 1501 1502 by_name = " BY NAME" if expression.args.get("by_name") else "" 1503 on = self.expressions(expression, key="on", flat=True) 1504 on = f" ON ({on})" if on else "" 1505 1506 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1507 1508 def set_operations(self, expression: exp.SetOperation) -> str: 1509 if not self.SET_OP_MODIFIERS: 1510 limit = expression.args.get("limit") 1511 order = expression.args.get("order") 1512 1513 if limit or order: 1514 select = self._move_ctes_to_top_level( 1515 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1516 ) 1517 1518 if limit: 1519 select = select.limit(limit.pop(), copy=False) 1520 if order: 1521 select = select.order_by(order.pop(), copy=False) 1522 return self.sql(select) 1523 1524 sqls: t.List[str] = [] 1525 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1526 1527 while stack: 1528 node = stack.pop() 1529 1530 if isinstance(node, exp.SetOperation): 1531 stack.append(node.expression) 1532 stack.append( 1533 self.maybe_comment( 1534 self.set_operation(node), comments=node.comments, separated=True 1535 ) 1536 ) 1537 stack.append(node.this) 1538 else: 1539 sqls.append(self.sql(node)) 1540 1541 this = self.sep().join(sqls) 1542 this = self.query_modifiers(expression, this) 1543 return self.prepend_ctes(expression, this) 1544 1545 def fetch_sql(self, expression: exp.Fetch) -> str: 1546 direction = expression.args.get("direction") 1547 direction = f" {direction}" if direction else "" 1548 count = self.sql(expression, "count") 1549 count = f" {count}" if count else "" 1550 limit_options = self.sql(expression, "limit_options") 1551 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1552 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1553 1554 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1555 percent = " PERCENT" if expression.args.get("percent") else "" 1556 rows = " ROWS" if expression.args.get("rows") else "" 1557 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1558 if not with_ties and rows: 1559 with_ties = " ONLY" 1560 return f"{percent}{rows}{with_ties}" 1561 1562 def filter_sql(self, expression: exp.Filter) -> str: 1563 if self.AGGREGATE_FILTER_SUPPORTED: 1564 this = self.sql(expression, "this") 1565 where = self.sql(expression, "expression").strip() 1566 return f"{this} FILTER({where})" 1567 1568 agg = expression.this 1569 agg_arg = agg.this 1570 cond = expression.expression.this 1571 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1572 return self.sql(agg) 1573 1574 def hint_sql(self, expression: exp.Hint) -> str: 1575 if not self.QUERY_HINTS: 1576 self.unsupported("Hints are not supported") 1577 return "" 1578 1579 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1580 1581 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1582 using = self.sql(expression, "using") 1583 using = f" USING {using}" if using else "" 1584 columns = self.expressions(expression, key="columns", flat=True) 1585 columns = f"({columns})" if columns else "" 1586 partition_by = self.expressions(expression, key="partition_by", flat=True) 1587 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1588 where = self.sql(expression, "where") 1589 include = self.expressions(expression, key="include", flat=True) 1590 if include: 1591 include = f" INCLUDE ({include})" 1592 with_storage = self.expressions(expression, key="with_storage", flat=True) 1593 with_storage = f" WITH ({with_storage})" if with_storage else "" 1594 tablespace = self.sql(expression, "tablespace") 1595 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1596 on = self.sql(expression, "on") 1597 on = f" ON {on}" if on else "" 1598 1599 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1600 1601 def index_sql(self, expression: exp.Index) -> str: 1602 unique = "UNIQUE " if expression.args.get("unique") else "" 1603 primary = "PRIMARY " if expression.args.get("primary") else "" 1604 amp = "AMP " if expression.args.get("amp") else "" 1605 name = self.sql(expression, "this") 1606 name = f"{name} " if name else "" 1607 table = self.sql(expression, "table") 1608 table = f"{self.INDEX_ON} {table}" if table else "" 1609 1610 index = "INDEX " if not table else "" 1611 1612 params = self.sql(expression, "params") 1613 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1614 1615 def identifier_sql(self, expression: exp.Identifier) -> str: 1616 text = expression.name 1617 lower = text.lower() 1618 text = lower if self.normalize and not expression.quoted else text 1619 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1620 if ( 1621 expression.quoted 1622 or self.dialect.can_identify(text, self.identify) 1623 or lower in self.RESERVED_KEYWORDS 1624 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1625 ): 1626 text = f"{self._identifier_start}{text}{self._identifier_end}" 1627 return text 1628 1629 def hex_sql(self, expression: exp.Hex) -> str: 1630 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1631 if self.dialect.HEX_LOWERCASE: 1632 text = self.func("LOWER", text) 1633 1634 return text 1635 1636 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1637 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1638 if not self.dialect.HEX_LOWERCASE: 1639 text = self.func("LOWER", text) 1640 return text 1641 1642 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1643 input_format = self.sql(expression, "input_format") 1644 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1645 output_format = self.sql(expression, "output_format") 1646 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1647 return self.sep().join((input_format, output_format)) 1648 1649 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1650 string = self.sql(exp.Literal.string(expression.name)) 1651 return f"{prefix}{string}" 1652 1653 def partition_sql(self, expression: exp.Partition) -> str: 1654 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1655 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1656 1657 def properties_sql(self, expression: exp.Properties) -> str: 1658 root_properties = [] 1659 with_properties = [] 1660 1661 for p in expression.expressions: 1662 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1663 if p_loc == exp.Properties.Location.POST_WITH: 1664 with_properties.append(p) 1665 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1666 root_properties.append(p) 1667 1668 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1669 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1670 1671 if root_props and with_props and not self.pretty: 1672 with_props = " " + with_props 1673 1674 return root_props + with_props 1675 1676 def root_properties(self, properties: exp.Properties) -> str: 1677 if properties.expressions: 1678 return self.expressions(properties, indent=False, sep=" ") 1679 return "" 1680 1681 def properties( 1682 self, 1683 properties: exp.Properties, 1684 prefix: str = "", 1685 sep: str = ", ", 1686 suffix: str = "", 1687 wrapped: bool = True, 1688 ) -> str: 1689 if properties.expressions: 1690 expressions = self.expressions(properties, sep=sep, indent=False) 1691 if expressions: 1692 expressions = self.wrap(expressions) if wrapped else expressions 1693 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1694 return "" 1695 1696 def with_properties(self, properties: exp.Properties) -> str: 1697 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1698 1699 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1700 properties_locs = defaultdict(list) 1701 for p in properties.expressions: 1702 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1703 if p_loc != exp.Properties.Location.UNSUPPORTED: 1704 properties_locs[p_loc].append(p) 1705 else: 1706 self.unsupported(f"Unsupported property {p.key}") 1707 1708 return properties_locs 1709 1710 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1711 if isinstance(expression.this, exp.Dot): 1712 return self.sql(expression, "this") 1713 return f"'{expression.name}'" if string_key else expression.name 1714 1715 def property_sql(self, expression: exp.Property) -> str: 1716 property_cls = expression.__class__ 1717 if property_cls == exp.Property: 1718 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1719 1720 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1721 if not property_name: 1722 self.unsupported(f"Unsupported property {expression.key}") 1723 1724 return f"{property_name}={self.sql(expression, 'this')}" 1725 1726 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1727 if self.SUPPORTS_CREATE_TABLE_LIKE: 1728 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1729 options = f" {options}" if options else "" 1730 1731 like = f"LIKE {self.sql(expression, 'this')}{options}" 1732 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1733 like = f"({like})" 1734 1735 return like 1736 1737 if expression.expressions: 1738 self.unsupported("Transpilation of LIKE property options is unsupported") 1739 1740 select = exp.select("*").from_(expression.this).limit(0) 1741 return f"AS {self.sql(select)}" 1742 1743 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1744 no = "NO " if expression.args.get("no") else "" 1745 protection = " PROTECTION" if expression.args.get("protection") else "" 1746 return f"{no}FALLBACK{protection}" 1747 1748 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1749 no = "NO " if expression.args.get("no") else "" 1750 local = expression.args.get("local") 1751 local = f"{local} " if local else "" 1752 dual = "DUAL " if expression.args.get("dual") else "" 1753 before = "BEFORE " if expression.args.get("before") else "" 1754 after = "AFTER " if expression.args.get("after") else "" 1755 return f"{no}{local}{dual}{before}{after}JOURNAL" 1756 1757 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1758 freespace = self.sql(expression, "this") 1759 percent = " PERCENT" if expression.args.get("percent") else "" 1760 return f"FREESPACE={freespace}{percent}" 1761 1762 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1763 if expression.args.get("default"): 1764 property = "DEFAULT" 1765 elif expression.args.get("on"): 1766 property = "ON" 1767 else: 1768 property = "OFF" 1769 return f"CHECKSUM={property}" 1770 1771 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1772 if expression.args.get("no"): 1773 return "NO MERGEBLOCKRATIO" 1774 if expression.args.get("default"): 1775 return "DEFAULT MERGEBLOCKRATIO" 1776 1777 percent = " PERCENT" if expression.args.get("percent") else "" 1778 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1779 1780 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1781 default = expression.args.get("default") 1782 minimum = expression.args.get("minimum") 1783 maximum = expression.args.get("maximum") 1784 if default or minimum or maximum: 1785 if default: 1786 prop = "DEFAULT" 1787 elif minimum: 1788 prop = "MINIMUM" 1789 else: 1790 prop = "MAXIMUM" 1791 return f"{prop} DATABLOCKSIZE" 1792 units = expression.args.get("units") 1793 units = f" {units}" if units else "" 1794 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1795 1796 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1797 autotemp = expression.args.get("autotemp") 1798 always = expression.args.get("always") 1799 default = expression.args.get("default") 1800 manual = expression.args.get("manual") 1801 never = expression.args.get("never") 1802 1803 if autotemp is not None: 1804 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1805 elif always: 1806 prop = "ALWAYS" 1807 elif default: 1808 prop = "DEFAULT" 1809 elif manual: 1810 prop = "MANUAL" 1811 elif never: 1812 prop = "NEVER" 1813 return f"BLOCKCOMPRESSION={prop}" 1814 1815 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1816 no = expression.args.get("no") 1817 no = " NO" if no else "" 1818 concurrent = expression.args.get("concurrent") 1819 concurrent = " CONCURRENT" if concurrent else "" 1820 target = self.sql(expression, "target") 1821 target = f" {target}" if target else "" 1822 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1823 1824 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1825 if isinstance(expression.this, list): 1826 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1827 if expression.this: 1828 modulus = self.sql(expression, "this") 1829 remainder = self.sql(expression, "expression") 1830 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1831 1832 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1833 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1834 return f"FROM ({from_expressions}) TO ({to_expressions})" 1835 1836 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1837 this = self.sql(expression, "this") 1838 1839 for_values_or_default = expression.expression 1840 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1841 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1842 else: 1843 for_values_or_default = " DEFAULT" 1844 1845 return f"PARTITION OF {this}{for_values_or_default}" 1846 1847 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1848 kind = expression.args.get("kind") 1849 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1850 for_or_in = expression.args.get("for_or_in") 1851 for_or_in = f" {for_or_in}" if for_or_in else "" 1852 lock_type = expression.args.get("lock_type") 1853 override = " OVERRIDE" if expression.args.get("override") else "" 1854 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1855 1856 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1857 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1858 statistics = expression.args.get("statistics") 1859 statistics_sql = "" 1860 if statistics is not None: 1861 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1862 return f"{data_sql}{statistics_sql}" 1863 1864 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1865 this = self.sql(expression, "this") 1866 this = f"HISTORY_TABLE={this}" if this else "" 1867 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1868 data_consistency = ( 1869 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1870 ) 1871 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1872 retention_period = ( 1873 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1874 ) 1875 1876 if this: 1877 on_sql = self.func("ON", this, data_consistency, retention_period) 1878 else: 1879 on_sql = "ON" if expression.args.get("on") else "OFF" 1880 1881 sql = f"SYSTEM_VERSIONING={on_sql}" 1882 1883 return f"WITH({sql})" if expression.args.get("with") else sql 1884 1885 def insert_sql(self, expression: exp.Insert) -> str: 1886 hint = self.sql(expression, "hint") 1887 overwrite = expression.args.get("overwrite") 1888 1889 if isinstance(expression.this, exp.Directory): 1890 this = " OVERWRITE" if overwrite else " INTO" 1891 else: 1892 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1893 1894 stored = self.sql(expression, "stored") 1895 stored = f" {stored}" if stored else "" 1896 alternative = expression.args.get("alternative") 1897 alternative = f" OR {alternative}" if alternative else "" 1898 ignore = " IGNORE" if expression.args.get("ignore") else "" 1899 is_function = expression.args.get("is_function") 1900 if is_function: 1901 this = f"{this} FUNCTION" 1902 this = f"{this} {self.sql(expression, 'this')}" 1903 1904 exists = " IF EXISTS" if expression.args.get("exists") else "" 1905 where = self.sql(expression, "where") 1906 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1907 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1908 on_conflict = self.sql(expression, "conflict") 1909 on_conflict = f" {on_conflict}" if on_conflict else "" 1910 by_name = " BY NAME" if expression.args.get("by_name") else "" 1911 returning = self.sql(expression, "returning") 1912 1913 if self.RETURNING_END: 1914 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1915 else: 1916 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1917 1918 partition_by = self.sql(expression, "partition") 1919 partition_by = f" {partition_by}" if partition_by else "" 1920 settings = self.sql(expression, "settings") 1921 settings = f" {settings}" if settings else "" 1922 1923 source = self.sql(expression, "source") 1924 source = f"TABLE {source}" if source else "" 1925 1926 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1927 return self.prepend_ctes(expression, sql) 1928 1929 def introducer_sql(self, expression: exp.Introducer) -> str: 1930 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1931 1932 def kill_sql(self, expression: exp.Kill) -> str: 1933 kind = self.sql(expression, "kind") 1934 kind = f" {kind}" if kind else "" 1935 this = self.sql(expression, "this") 1936 this = f" {this}" if this else "" 1937 return f"KILL{kind}{this}" 1938 1939 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1940 return expression.name 1941 1942 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1943 return expression.name 1944 1945 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1946 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1947 1948 constraint = self.sql(expression, "constraint") 1949 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1950 1951 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1952 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1953 action = self.sql(expression, "action") 1954 1955 expressions = self.expressions(expression, flat=True) 1956 if expressions: 1957 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1958 expressions = f" {set_keyword}{expressions}" 1959 1960 where = self.sql(expression, "where") 1961 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1962 1963 def returning_sql(self, expression: exp.Returning) -> str: 1964 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1965 1966 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1967 fields = self.sql(expression, "fields") 1968 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1969 escaped = self.sql(expression, "escaped") 1970 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1971 items = self.sql(expression, "collection_items") 1972 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1973 keys = self.sql(expression, "map_keys") 1974 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1975 lines = self.sql(expression, "lines") 1976 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1977 null = self.sql(expression, "null") 1978 null = f" NULL DEFINED AS {null}" if null else "" 1979 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1980 1981 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1982 return f"WITH ({self.expressions(expression, flat=True)})" 1983 1984 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1985 this = f"{self.sql(expression, 'this')} INDEX" 1986 target = self.sql(expression, "target") 1987 target = f" FOR {target}" if target else "" 1988 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1989 1990 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1991 this = self.sql(expression, "this") 1992 kind = self.sql(expression, "kind") 1993 expr = self.sql(expression, "expression") 1994 return f"{this} ({kind} => {expr})" 1995 1996 def table_parts(self, expression: exp.Table) -> str: 1997 return ".".join( 1998 self.sql(part) 1999 for part in ( 2000 expression.args.get("catalog"), 2001 expression.args.get("db"), 2002 expression.args.get("this"), 2003 ) 2004 if part is not None 2005 ) 2006 2007 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2008 table = self.table_parts(expression) 2009 only = "ONLY " if expression.args.get("only") else "" 2010 partition = self.sql(expression, "partition") 2011 partition = f" {partition}" if partition else "" 2012 version = self.sql(expression, "version") 2013 version = f" {version}" if version else "" 2014 alias = self.sql(expression, "alias") 2015 alias = f"{sep}{alias}" if alias else "" 2016 2017 sample = self.sql(expression, "sample") 2018 if self.dialect.ALIAS_POST_TABLESAMPLE: 2019 sample_pre_alias = sample 2020 sample_post_alias = "" 2021 else: 2022 sample_pre_alias = "" 2023 sample_post_alias = sample 2024 2025 hints = self.expressions(expression, key="hints", sep=" ") 2026 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2027 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2028 joins = self.indent( 2029 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2030 ) 2031 laterals = self.expressions(expression, key="laterals", sep="") 2032 2033 file_format = self.sql(expression, "format") 2034 if file_format: 2035 pattern = self.sql(expression, "pattern") 2036 pattern = f", PATTERN => {pattern}" if pattern else "" 2037 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2038 2039 ordinality = expression.args.get("ordinality") or "" 2040 if ordinality: 2041 ordinality = f" WITH ORDINALITY{alias}" 2042 alias = "" 2043 2044 when = self.sql(expression, "when") 2045 if when: 2046 table = f"{table} {when}" 2047 2048 changes = self.sql(expression, "changes") 2049 changes = f" {changes}" if changes else "" 2050 2051 rows_from = self.expressions(expression, key="rows_from") 2052 if rows_from: 2053 table = f"ROWS FROM {self.wrap(rows_from)}" 2054 2055 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2056 2057 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2058 table = self.func("TABLE", expression.this) 2059 alias = self.sql(expression, "alias") 2060 alias = f" AS {alias}" if alias else "" 2061 sample = self.sql(expression, "sample") 2062 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2063 joins = self.indent( 2064 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2065 ) 2066 return f"{table}{alias}{pivots}{sample}{joins}" 2067 2068 def tablesample_sql( 2069 self, 2070 expression: exp.TableSample, 2071 tablesample_keyword: t.Optional[str] = None, 2072 ) -> str: 2073 method = self.sql(expression, "method") 2074 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2075 numerator = self.sql(expression, "bucket_numerator") 2076 denominator = self.sql(expression, "bucket_denominator") 2077 field = self.sql(expression, "bucket_field") 2078 field = f" ON {field}" if field else "" 2079 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2080 seed = self.sql(expression, "seed") 2081 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2082 2083 size = self.sql(expression, "size") 2084 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2085 size = f"{size} ROWS" 2086 2087 percent = self.sql(expression, "percent") 2088 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2089 percent = f"{percent} PERCENT" 2090 2091 expr = f"{bucket}{percent}{size}" 2092 if self.TABLESAMPLE_REQUIRES_PARENS: 2093 expr = f"({expr})" 2094 2095 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2096 2097 def pivot_sql(self, expression: exp.Pivot) -> str: 2098 expressions = self.expressions(expression, flat=True) 2099 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2100 2101 group = self.sql(expression, "group") 2102 2103 if expression.this: 2104 this = self.sql(expression, "this") 2105 if not expressions: 2106 return f"UNPIVOT {this}" 2107 2108 on = f"{self.seg('ON')} {expressions}" 2109 into = self.sql(expression, "into") 2110 into = f"{self.seg('INTO')} {into}" if into else "" 2111 using = self.expressions(expression, key="using", flat=True) 2112 using = f"{self.seg('USING')} {using}" if using else "" 2113 return f"{direction} {this}{on}{into}{using}{group}" 2114 2115 alias = self.sql(expression, "alias") 2116 alias = f" AS {alias}" if alias else "" 2117 2118 fields = self.expressions( 2119 expression, 2120 "fields", 2121 sep=" ", 2122 dynamic=True, 2123 new_line=True, 2124 skip_first=True, 2125 skip_last=True, 2126 ) 2127 2128 include_nulls = expression.args.get("include_nulls") 2129 if include_nulls is not None: 2130 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2131 else: 2132 nulls = "" 2133 2134 default_on_null = self.sql(expression, "default_on_null") 2135 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2136 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2137 2138 def version_sql(self, expression: exp.Version) -> str: 2139 this = f"FOR {expression.name}" 2140 kind = expression.text("kind") 2141 expr = self.sql(expression, "expression") 2142 return f"{this} {kind} {expr}" 2143 2144 def tuple_sql(self, expression: exp.Tuple) -> str: 2145 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2146 2147 def update_sql(self, expression: exp.Update) -> str: 2148 this = self.sql(expression, "this") 2149 set_sql = self.expressions(expression, flat=True) 2150 from_sql = self.sql(expression, "from") 2151 where_sql = self.sql(expression, "where") 2152 returning = self.sql(expression, "returning") 2153 order = self.sql(expression, "order") 2154 limit = self.sql(expression, "limit") 2155 if self.RETURNING_END: 2156 expression_sql = f"{from_sql}{where_sql}{returning}" 2157 else: 2158 expression_sql = f"{returning}{from_sql}{where_sql}" 2159 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2160 return self.prepend_ctes(expression, sql) 2161 2162 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2163 values_as_table = values_as_table and self.VALUES_AS_TABLE 2164 2165 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2166 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2167 args = self.expressions(expression) 2168 alias = self.sql(expression, "alias") 2169 values = f"VALUES{self.seg('')}{args}" 2170 values = ( 2171 f"({values})" 2172 if self.WRAP_DERIVED_VALUES 2173 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2174 else values 2175 ) 2176 return f"{values} AS {alias}" if alias else values 2177 2178 # Converts `VALUES...` expression into a series of select unions. 2179 alias_node = expression.args.get("alias") 2180 column_names = alias_node and alias_node.columns 2181 2182 selects: t.List[exp.Query] = [] 2183 2184 for i, tup in enumerate(expression.expressions): 2185 row = tup.expressions 2186 2187 if i == 0 and column_names: 2188 row = [ 2189 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2190 ] 2191 2192 selects.append(exp.Select(expressions=row)) 2193 2194 if self.pretty: 2195 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2196 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2197 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2198 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2199 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2200 2201 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2202 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2203 return f"({unions}){alias}" 2204 2205 def var_sql(self, expression: exp.Var) -> str: 2206 return self.sql(expression, "this") 2207 2208 @unsupported_args("expressions") 2209 def into_sql(self, expression: exp.Into) -> str: 2210 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2211 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2212 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2213 2214 def from_sql(self, expression: exp.From) -> str: 2215 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2216 2217 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2218 grouping_sets = self.expressions(expression, indent=False) 2219 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2220 2221 def rollup_sql(self, expression: exp.Rollup) -> str: 2222 expressions = self.expressions(expression, indent=False) 2223 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2224 2225 def cube_sql(self, expression: exp.Cube) -> str: 2226 expressions = self.expressions(expression, indent=False) 2227 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2228 2229 def group_sql(self, expression: exp.Group) -> str: 2230 group_by_all = expression.args.get("all") 2231 if group_by_all is True: 2232 modifier = " ALL" 2233 elif group_by_all is False: 2234 modifier = " DISTINCT" 2235 else: 2236 modifier = "" 2237 2238 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2239 2240 grouping_sets = self.expressions(expression, key="grouping_sets") 2241 cube = self.expressions(expression, key="cube") 2242 rollup = self.expressions(expression, key="rollup") 2243 2244 groupings = csv( 2245 self.seg(grouping_sets) if grouping_sets else "", 2246 self.seg(cube) if cube else "", 2247 self.seg(rollup) if rollup else "", 2248 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2249 sep=self.GROUPINGS_SEP, 2250 ) 2251 2252 if ( 2253 expression.expressions 2254 and groupings 2255 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2256 ): 2257 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2258 2259 return f"{group_by}{groupings}" 2260 2261 def having_sql(self, expression: exp.Having) -> str: 2262 this = self.indent(self.sql(expression, "this")) 2263 return f"{self.seg('HAVING')}{self.sep()}{this}" 2264 2265 def connect_sql(self, expression: exp.Connect) -> str: 2266 start = self.sql(expression, "start") 2267 start = self.seg(f"START WITH {start}") if start else "" 2268 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2269 connect = self.sql(expression, "connect") 2270 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2271 return start + connect 2272 2273 def prior_sql(self, expression: exp.Prior) -> str: 2274 return f"PRIOR {self.sql(expression, 'this')}" 2275 2276 def join_sql(self, expression: exp.Join) -> str: 2277 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2278 side = None 2279 else: 2280 side = expression.side 2281 2282 op_sql = " ".join( 2283 op 2284 for op in ( 2285 expression.method, 2286 "GLOBAL" if expression.args.get("global") else None, 2287 side, 2288 expression.kind, 2289 expression.hint if self.JOIN_HINTS else None, 2290 ) 2291 if op 2292 ) 2293 match_cond = self.sql(expression, "match_condition") 2294 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2295 on_sql = self.sql(expression, "on") 2296 using = expression.args.get("using") 2297 2298 if not on_sql and using: 2299 on_sql = csv(*(self.sql(column) for column in using)) 2300 2301 this = expression.this 2302 this_sql = self.sql(this) 2303 2304 exprs = self.expressions(expression) 2305 if exprs: 2306 this_sql = f"{this_sql},{self.seg(exprs)}" 2307 2308 if on_sql: 2309 on_sql = self.indent(on_sql, skip_first=True) 2310 space = self.seg(" " * self.pad) if self.pretty else " " 2311 if using: 2312 on_sql = f"{space}USING ({on_sql})" 2313 else: 2314 on_sql = f"{space}ON {on_sql}" 2315 elif not op_sql: 2316 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2317 return f" {this_sql}" 2318 2319 return f", {this_sql}" 2320 2321 if op_sql != "STRAIGHT_JOIN": 2322 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2323 2324 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2325 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2326 2327 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2328 args = self.expressions(expression, flat=True) 2329 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2330 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2331 2332 def lateral_op(self, expression: exp.Lateral) -> str: 2333 cross_apply = expression.args.get("cross_apply") 2334 2335 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2336 if cross_apply is True: 2337 op = "INNER JOIN " 2338 elif cross_apply is False: 2339 op = "LEFT JOIN " 2340 else: 2341 op = "" 2342 2343 return f"{op}LATERAL" 2344 2345 def lateral_sql(self, expression: exp.Lateral) -> str: 2346 this = self.sql(expression, "this") 2347 2348 if expression.args.get("view"): 2349 alias = expression.args["alias"] 2350 columns = self.expressions(alias, key="columns", flat=True) 2351 table = f" {alias.name}" if alias.name else "" 2352 columns = f" AS {columns}" if columns else "" 2353 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2354 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2355 2356 alias = self.sql(expression, "alias") 2357 alias = f" AS {alias}" if alias else "" 2358 2359 ordinality = expression.args.get("ordinality") or "" 2360 if ordinality: 2361 ordinality = f" WITH ORDINALITY{alias}" 2362 alias = "" 2363 2364 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2365 2366 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2367 this = self.sql(expression, "this") 2368 2369 args = [ 2370 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2371 for e in (expression.args.get(k) for k in ("offset", "expression")) 2372 if e 2373 ] 2374 2375 args_sql = ", ".join(self.sql(e) for e in args) 2376 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2377 expressions = self.expressions(expression, flat=True) 2378 limit_options = self.sql(expression, "limit_options") 2379 expressions = f" BY {expressions}" if expressions else "" 2380 2381 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2382 2383 def offset_sql(self, expression: exp.Offset) -> str: 2384 this = self.sql(expression, "this") 2385 value = expression.expression 2386 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2387 expressions = self.expressions(expression, flat=True) 2388 expressions = f" BY {expressions}" if expressions else "" 2389 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2390 2391 def setitem_sql(self, expression: exp.SetItem) -> str: 2392 kind = self.sql(expression, "kind") 2393 kind = f"{kind} " if kind else "" 2394 this = self.sql(expression, "this") 2395 expressions = self.expressions(expression) 2396 collate = self.sql(expression, "collate") 2397 collate = f" COLLATE {collate}" if collate else "" 2398 global_ = "GLOBAL " if expression.args.get("global") else "" 2399 return f"{global_}{kind}{this}{expressions}{collate}" 2400 2401 def set_sql(self, expression: exp.Set) -> str: 2402 expressions = f" {self.expressions(expression, flat=True)}" 2403 tag = " TAG" if expression.args.get("tag") else "" 2404 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2405 2406 def pragma_sql(self, expression: exp.Pragma) -> str: 2407 return f"PRAGMA {self.sql(expression, 'this')}" 2408 2409 def lock_sql(self, expression: exp.Lock) -> str: 2410 if not self.LOCKING_READS_SUPPORTED: 2411 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2412 return "" 2413 2414 update = expression.args["update"] 2415 key = expression.args.get("key") 2416 if update: 2417 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2418 else: 2419 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2420 expressions = self.expressions(expression, flat=True) 2421 expressions = f" OF {expressions}" if expressions else "" 2422 wait = expression.args.get("wait") 2423 2424 if wait is not None: 2425 if isinstance(wait, exp.Literal): 2426 wait = f" WAIT {self.sql(wait)}" 2427 else: 2428 wait = " NOWAIT" if wait else " SKIP LOCKED" 2429 2430 return f"{lock_type}{expressions}{wait or ''}" 2431 2432 def literal_sql(self, expression: exp.Literal) -> str: 2433 text = expression.this or "" 2434 if expression.is_string: 2435 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2436 return text 2437 2438 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2439 if self.dialect.ESCAPED_SEQUENCES: 2440 to_escaped = self.dialect.ESCAPED_SEQUENCES 2441 text = "".join( 2442 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2443 ) 2444 2445 return self._replace_line_breaks(text).replace( 2446 self.dialect.QUOTE_END, self._escaped_quote_end 2447 ) 2448 2449 def loaddata_sql(self, expression: exp.LoadData) -> str: 2450 local = " LOCAL" if expression.args.get("local") else "" 2451 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2452 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2453 this = f" INTO TABLE {self.sql(expression, 'this')}" 2454 partition = self.sql(expression, "partition") 2455 partition = f" {partition}" if partition else "" 2456 input_format = self.sql(expression, "input_format") 2457 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2458 serde = self.sql(expression, "serde") 2459 serde = f" SERDE {serde}" if serde else "" 2460 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2461 2462 def null_sql(self, *_) -> str: 2463 return "NULL" 2464 2465 def boolean_sql(self, expression: exp.Boolean) -> str: 2466 return "TRUE" if expression.this else "FALSE" 2467 2468 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2469 this = self.sql(expression, "this") 2470 this = f"{this} " if this else this 2471 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2472 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2473 2474 def withfill_sql(self, expression: exp.WithFill) -> str: 2475 from_sql = self.sql(expression, "from") 2476 from_sql = f" FROM {from_sql}" if from_sql else "" 2477 to_sql = self.sql(expression, "to") 2478 to_sql = f" TO {to_sql}" if to_sql else "" 2479 step_sql = self.sql(expression, "step") 2480 step_sql = f" STEP {step_sql}" if step_sql else "" 2481 interpolated_values = [ 2482 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2483 if isinstance(e, exp.Alias) 2484 else self.sql(e, "this") 2485 for e in expression.args.get("interpolate") or [] 2486 ] 2487 interpolate = ( 2488 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2489 ) 2490 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2491 2492 def cluster_sql(self, expression: exp.Cluster) -> str: 2493 return self.op_expressions("CLUSTER BY", expression) 2494 2495 def distribute_sql(self, expression: exp.Distribute) -> str: 2496 return self.op_expressions("DISTRIBUTE BY", expression) 2497 2498 def sort_sql(self, expression: exp.Sort) -> str: 2499 return self.op_expressions("SORT BY", expression) 2500 2501 def ordered_sql(self, expression: exp.Ordered) -> str: 2502 desc = expression.args.get("desc") 2503 asc = not desc 2504 2505 nulls_first = expression.args.get("nulls_first") 2506 nulls_last = not nulls_first 2507 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2508 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2509 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2510 2511 this = self.sql(expression, "this") 2512 2513 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2514 nulls_sort_change = "" 2515 if nulls_first and ( 2516 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2517 ): 2518 nulls_sort_change = " NULLS FIRST" 2519 elif ( 2520 nulls_last 2521 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2522 and not nulls_are_last 2523 ): 2524 nulls_sort_change = " NULLS LAST" 2525 2526 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2527 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2528 window = expression.find_ancestor(exp.Window, exp.Select) 2529 if isinstance(window, exp.Window) and window.args.get("spec"): 2530 self.unsupported( 2531 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2532 ) 2533 nulls_sort_change = "" 2534 elif self.NULL_ORDERING_SUPPORTED is False and ( 2535 (asc and nulls_sort_change == " NULLS LAST") 2536 or (desc and nulls_sort_change == " NULLS FIRST") 2537 ): 2538 # BigQuery does not allow these ordering/nulls combinations when used under 2539 # an aggregation func or under a window containing one 2540 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2541 2542 if isinstance(ancestor, exp.Window): 2543 ancestor = ancestor.this 2544 if isinstance(ancestor, exp.AggFunc): 2545 self.unsupported( 2546 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2547 ) 2548 nulls_sort_change = "" 2549 elif self.NULL_ORDERING_SUPPORTED is None: 2550 if expression.this.is_int: 2551 self.unsupported( 2552 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2553 ) 2554 elif not isinstance(expression.this, exp.Rand): 2555 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2556 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2557 nulls_sort_change = "" 2558 2559 with_fill = self.sql(expression, "with_fill") 2560 with_fill = f" {with_fill}" if with_fill else "" 2561 2562 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2563 2564 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2565 window_frame = self.sql(expression, "window_frame") 2566 window_frame = f"{window_frame} " if window_frame else "" 2567 2568 this = self.sql(expression, "this") 2569 2570 return f"{window_frame}{this}" 2571 2572 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2573 partition = self.partition_by_sql(expression) 2574 order = self.sql(expression, "order") 2575 measures = self.expressions(expression, key="measures") 2576 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2577 rows = self.sql(expression, "rows") 2578 rows = self.seg(rows) if rows else "" 2579 after = self.sql(expression, "after") 2580 after = self.seg(after) if after else "" 2581 pattern = self.sql(expression, "pattern") 2582 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2583 definition_sqls = [ 2584 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2585 for definition in expression.args.get("define", []) 2586 ] 2587 definitions = self.expressions(sqls=definition_sqls) 2588 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2589 body = "".join( 2590 ( 2591 partition, 2592 order, 2593 measures, 2594 rows, 2595 after, 2596 pattern, 2597 define, 2598 ) 2599 ) 2600 alias = self.sql(expression, "alias") 2601 alias = f" {alias}" if alias else "" 2602 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2603 2604 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2605 limit = expression.args.get("limit") 2606 2607 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2608 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2609 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2610 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2611 2612 return csv( 2613 *sqls, 2614 *[self.sql(join) for join in expression.args.get("joins") or []], 2615 self.sql(expression, "match"), 2616 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2617 self.sql(expression, "prewhere"), 2618 self.sql(expression, "where"), 2619 self.sql(expression, "connect"), 2620 self.sql(expression, "group"), 2621 self.sql(expression, "having"), 2622 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2623 self.sql(expression, "order"), 2624 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2625 *self.after_limit_modifiers(expression), 2626 self.options_modifier(expression), 2627 self.for_modifiers(expression), 2628 sep="", 2629 ) 2630 2631 def options_modifier(self, expression: exp.Expression) -> str: 2632 options = self.expressions(expression, key="options") 2633 return f" {options}" if options else "" 2634 2635 def for_modifiers(self, expression: exp.Expression) -> str: 2636 for_modifiers = self.expressions(expression, key="for") 2637 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2638 2639 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2640 self.unsupported("Unsupported query option.") 2641 return "" 2642 2643 def offset_limit_modifiers( 2644 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2645 ) -> t.List[str]: 2646 return [ 2647 self.sql(expression, "offset") if fetch else self.sql(limit), 2648 self.sql(limit) if fetch else self.sql(expression, "offset"), 2649 ] 2650 2651 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2652 locks = self.expressions(expression, key="locks", sep=" ") 2653 locks = f" {locks}" if locks else "" 2654 return [locks, self.sql(expression, "sample")] 2655 2656 def select_sql(self, expression: exp.Select) -> str: 2657 into = expression.args.get("into") 2658 if not self.SUPPORTS_SELECT_INTO and into: 2659 into.pop() 2660 2661 hint = self.sql(expression, "hint") 2662 distinct = self.sql(expression, "distinct") 2663 distinct = f" {distinct}" if distinct else "" 2664 kind = self.sql(expression, "kind") 2665 2666 limit = expression.args.get("limit") 2667 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2668 top = self.limit_sql(limit, top=True) 2669 limit.pop() 2670 else: 2671 top = "" 2672 2673 expressions = self.expressions(expression) 2674 2675 if kind: 2676 if kind in self.SELECT_KINDS: 2677 kind = f" AS {kind}" 2678 else: 2679 if kind == "STRUCT": 2680 expressions = self.expressions( 2681 sqls=[ 2682 self.sql( 2683 exp.Struct( 2684 expressions=[ 2685 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2686 if isinstance(e, exp.Alias) 2687 else e 2688 for e in expression.expressions 2689 ] 2690 ) 2691 ) 2692 ] 2693 ) 2694 kind = "" 2695 2696 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2697 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2698 2699 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2700 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2701 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2702 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2703 sql = self.query_modifiers( 2704 expression, 2705 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2706 self.sql(expression, "into", comment=False), 2707 self.sql(expression, "from", comment=False), 2708 ) 2709 2710 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2711 if expression.args.get("with"): 2712 sql = self.maybe_comment(sql, expression) 2713 expression.pop_comments() 2714 2715 sql = self.prepend_ctes(expression, sql) 2716 2717 if not self.SUPPORTS_SELECT_INTO and into: 2718 if into.args.get("temporary"): 2719 table_kind = " TEMPORARY" 2720 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2721 table_kind = " UNLOGGED" 2722 else: 2723 table_kind = "" 2724 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2725 2726 return sql 2727 2728 def schema_sql(self, expression: exp.Schema) -> str: 2729 this = self.sql(expression, "this") 2730 sql = self.schema_columns_sql(expression) 2731 return f"{this} {sql}" if this and sql else this or sql 2732 2733 def schema_columns_sql(self, expression: exp.Schema) -> str: 2734 if expression.expressions: 2735 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2736 return "" 2737 2738 def star_sql(self, expression: exp.Star) -> str: 2739 except_ = self.expressions(expression, key="except", flat=True) 2740 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2741 replace = self.expressions(expression, key="replace", flat=True) 2742 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2743 rename = self.expressions(expression, key="rename", flat=True) 2744 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2745 return f"*{except_}{replace}{rename}" 2746 2747 def parameter_sql(self, expression: exp.Parameter) -> str: 2748 this = self.sql(expression, "this") 2749 return f"{self.PARAMETER_TOKEN}{this}" 2750 2751 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2752 this = self.sql(expression, "this") 2753 kind = expression.text("kind") 2754 if kind: 2755 kind = f"{kind}." 2756 return f"@@{kind}{this}" 2757 2758 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2759 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2760 2761 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2762 alias = self.sql(expression, "alias") 2763 alias = f"{sep}{alias}" if alias else "" 2764 sample = self.sql(expression, "sample") 2765 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2766 alias = f"{sample}{alias}" 2767 2768 # Set to None so it's not generated again by self.query_modifiers() 2769 expression.set("sample", None) 2770 2771 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2772 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2773 return self.prepend_ctes(expression, sql) 2774 2775 def qualify_sql(self, expression: exp.Qualify) -> str: 2776 this = self.indent(self.sql(expression, "this")) 2777 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2778 2779 def unnest_sql(self, expression: exp.Unnest) -> str: 2780 args = self.expressions(expression, flat=True) 2781 2782 alias = expression.args.get("alias") 2783 offset = expression.args.get("offset") 2784 2785 if self.UNNEST_WITH_ORDINALITY: 2786 if alias and isinstance(offset, exp.Expression): 2787 alias.append("columns", offset) 2788 2789 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2790 columns = alias.columns 2791 alias = self.sql(columns[0]) if columns else "" 2792 else: 2793 alias = self.sql(alias) 2794 2795 alias = f" AS {alias}" if alias else alias 2796 if self.UNNEST_WITH_ORDINALITY: 2797 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2798 else: 2799 if isinstance(offset, exp.Expression): 2800 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2801 elif offset: 2802 suffix = f"{alias} WITH OFFSET" 2803 else: 2804 suffix = alias 2805 2806 return f"UNNEST({args}){suffix}" 2807 2808 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2809 return "" 2810 2811 def where_sql(self, expression: exp.Where) -> str: 2812 this = self.indent(self.sql(expression, "this")) 2813 return f"{self.seg('WHERE')}{self.sep()}{this}" 2814 2815 def window_sql(self, expression: exp.Window) -> str: 2816 this = self.sql(expression, "this") 2817 partition = self.partition_by_sql(expression) 2818 order = expression.args.get("order") 2819 order = self.order_sql(order, flat=True) if order else "" 2820 spec = self.sql(expression, "spec") 2821 alias = self.sql(expression, "alias") 2822 over = self.sql(expression, "over") or "OVER" 2823 2824 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2825 2826 first = expression.args.get("first") 2827 if first is None: 2828 first = "" 2829 else: 2830 first = "FIRST" if first else "LAST" 2831 2832 if not partition and not order and not spec and alias: 2833 return f"{this} {alias}" 2834 2835 args = self.format_args( 2836 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2837 ) 2838 return f"{this} ({args})" 2839 2840 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2841 partition = self.expressions(expression, key="partition_by", flat=True) 2842 return f"PARTITION BY {partition}" if partition else "" 2843 2844 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2845 kind = self.sql(expression, "kind") 2846 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2847 end = ( 2848 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2849 or "CURRENT ROW" 2850 ) 2851 2852 window_spec = f"{kind} BETWEEN {start} AND {end}" 2853 2854 exclude = self.sql(expression, "exclude") 2855 if exclude: 2856 if self.SUPPORTS_WINDOW_EXCLUDE: 2857 window_spec += f" EXCLUDE {exclude}" 2858 else: 2859 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2860 2861 return window_spec 2862 2863 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2864 this = self.sql(expression, "this") 2865 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2866 return f"{this} WITHIN GROUP ({expression_sql})" 2867 2868 def between_sql(self, expression: exp.Between) -> str: 2869 this = self.sql(expression, "this") 2870 low = self.sql(expression, "low") 2871 high = self.sql(expression, "high") 2872 symmetric = expression.args.get("symmetric") 2873 2874 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2875 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2876 2877 flag = ( 2878 " SYMMETRIC" 2879 if symmetric 2880 else " ASYMMETRIC" 2881 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2882 else "" # silently drop ASYMMETRIC – semantics identical 2883 ) 2884 return f"{this} BETWEEN{flag} {low} AND {high}" 2885 2886 def bracket_offset_expressions( 2887 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2888 ) -> t.List[exp.Expression]: 2889 return apply_index_offset( 2890 expression.this, 2891 expression.expressions, 2892 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2893 dialect=self.dialect, 2894 ) 2895 2896 def bracket_sql(self, expression: exp.Bracket) -> str: 2897 expressions = self.bracket_offset_expressions(expression) 2898 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2899 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2900 2901 def all_sql(self, expression: exp.All) -> str: 2902 return f"ALL {self.wrap(expression)}" 2903 2904 def any_sql(self, expression: exp.Any) -> str: 2905 this = self.sql(expression, "this") 2906 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2907 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2908 this = self.wrap(this) 2909 return f"ANY{this}" 2910 return f"ANY {this}" 2911 2912 def exists_sql(self, expression: exp.Exists) -> str: 2913 return f"EXISTS{self.wrap(expression)}" 2914 2915 def case_sql(self, expression: exp.Case) -> str: 2916 this = self.sql(expression, "this") 2917 statements = [f"CASE {this}" if this else "CASE"] 2918 2919 for e in expression.args["ifs"]: 2920 statements.append(f"WHEN {self.sql(e, 'this')}") 2921 statements.append(f"THEN {self.sql(e, 'true')}") 2922 2923 default = self.sql(expression, "default") 2924 2925 if default: 2926 statements.append(f"ELSE {default}") 2927 2928 statements.append("END") 2929 2930 if self.pretty and self.too_wide(statements): 2931 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2932 2933 return " ".join(statements) 2934 2935 def constraint_sql(self, expression: exp.Constraint) -> str: 2936 this = self.sql(expression, "this") 2937 expressions = self.expressions(expression, flat=True) 2938 return f"CONSTRAINT {this} {expressions}" 2939 2940 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2941 order = expression.args.get("order") 2942 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2943 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2944 2945 def extract_sql(self, expression: exp.Extract) -> str: 2946 from sqlglot.dialects.dialect import map_date_part 2947 2948 this = ( 2949 map_date_part(expression.this, self.dialect) 2950 if self.NORMALIZE_EXTRACT_DATE_PARTS 2951 else expression.this 2952 ) 2953 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2954 expression_sql = self.sql(expression, "expression") 2955 2956 return f"EXTRACT({this_sql} FROM {expression_sql})" 2957 2958 def trim_sql(self, expression: exp.Trim) -> str: 2959 trim_type = self.sql(expression, "position") 2960 2961 if trim_type == "LEADING": 2962 func_name = "LTRIM" 2963 elif trim_type == "TRAILING": 2964 func_name = "RTRIM" 2965 else: 2966 func_name = "TRIM" 2967 2968 return self.func(func_name, expression.this, expression.expression) 2969 2970 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2971 args = expression.expressions 2972 if isinstance(expression, exp.ConcatWs): 2973 args = args[1:] # Skip the delimiter 2974 2975 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2976 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2977 2978 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2979 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2980 2981 return args 2982 2983 def concat_sql(self, expression: exp.Concat) -> str: 2984 expressions = self.convert_concat_args(expression) 2985 2986 # Some dialects don't allow a single-argument CONCAT call 2987 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2988 return self.sql(expressions[0]) 2989 2990 return self.func("CONCAT", *expressions) 2991 2992 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2993 return self.func( 2994 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2995 ) 2996 2997 def check_sql(self, expression: exp.Check) -> str: 2998 this = self.sql(expression, key="this") 2999 return f"CHECK ({this})" 3000 3001 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3002 expressions = self.expressions(expression, flat=True) 3003 expressions = f" ({expressions})" if expressions else "" 3004 reference = self.sql(expression, "reference") 3005 reference = f" {reference}" if reference else "" 3006 delete = self.sql(expression, "delete") 3007 delete = f" ON DELETE {delete}" if delete else "" 3008 update = self.sql(expression, "update") 3009 update = f" ON UPDATE {update}" if update else "" 3010 options = self.expressions(expression, key="options", flat=True, sep=" ") 3011 options = f" {options}" if options else "" 3012 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3013 3014 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3015 expressions = self.expressions(expression, flat=True) 3016 include = self.sql(expression, "include") 3017 options = self.expressions(expression, key="options", flat=True, sep=" ") 3018 options = f" {options}" if options else "" 3019 return f"PRIMARY KEY ({expressions}){include}{options}" 3020 3021 def if_sql(self, expression: exp.If) -> str: 3022 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3023 3024 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3025 modifier = expression.args.get("modifier") 3026 modifier = f" {modifier}" if modifier else "" 3027 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3028 3029 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3030 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3031 3032 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3033 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3034 3035 if expression.args.get("escape"): 3036 path = self.escape_str(path) 3037 3038 if self.QUOTE_JSON_PATH: 3039 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3040 3041 return path 3042 3043 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3044 if isinstance(expression, exp.JSONPathPart): 3045 transform = self.TRANSFORMS.get(expression.__class__) 3046 if not callable(transform): 3047 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3048 return "" 3049 3050 return transform(self, expression) 3051 3052 if isinstance(expression, int): 3053 return str(expression) 3054 3055 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3056 escaped = expression.replace("'", "\\'") 3057 escaped = f"\\'{expression}\\'" 3058 else: 3059 escaped = expression.replace('"', '\\"') 3060 escaped = f'"{escaped}"' 3061 3062 return escaped 3063 3064 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3065 return f"{self.sql(expression, 'this')} FORMAT JSON" 3066 3067 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3068 # Output the Teradata column FORMAT override. 3069 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3070 this = self.sql(expression, "this") 3071 fmt = self.sql(expression, "format") 3072 return f"{this} (FORMAT {fmt})" 3073 3074 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3075 null_handling = expression.args.get("null_handling") 3076 null_handling = f" {null_handling}" if null_handling else "" 3077 3078 unique_keys = expression.args.get("unique_keys") 3079 if unique_keys is not None: 3080 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3081 else: 3082 unique_keys = "" 3083 3084 return_type = self.sql(expression, "return_type") 3085 return_type = f" RETURNING {return_type}" if return_type else "" 3086 encoding = self.sql(expression, "encoding") 3087 encoding = f" ENCODING {encoding}" if encoding else "" 3088 3089 return self.func( 3090 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3091 *expression.expressions, 3092 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3093 ) 3094 3095 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3096 return self.jsonobject_sql(expression) 3097 3098 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3099 null_handling = expression.args.get("null_handling") 3100 null_handling = f" {null_handling}" if null_handling else "" 3101 return_type = self.sql(expression, "return_type") 3102 return_type = f" RETURNING {return_type}" if return_type else "" 3103 strict = " STRICT" if expression.args.get("strict") else "" 3104 return self.func( 3105 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3106 ) 3107 3108 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3109 this = self.sql(expression, "this") 3110 order = self.sql(expression, "order") 3111 null_handling = expression.args.get("null_handling") 3112 null_handling = f" {null_handling}" if null_handling else "" 3113 return_type = self.sql(expression, "return_type") 3114 return_type = f" RETURNING {return_type}" if return_type else "" 3115 strict = " STRICT" if expression.args.get("strict") else "" 3116 return self.func( 3117 "JSON_ARRAYAGG", 3118 this, 3119 suffix=f"{order}{null_handling}{return_type}{strict})", 3120 ) 3121 3122 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3123 path = self.sql(expression, "path") 3124 path = f" PATH {path}" if path else "" 3125 nested_schema = self.sql(expression, "nested_schema") 3126 3127 if nested_schema: 3128 return f"NESTED{path} {nested_schema}" 3129 3130 this = self.sql(expression, "this") 3131 kind = self.sql(expression, "kind") 3132 kind = f" {kind}" if kind else "" 3133 return f"{this}{kind}{path}" 3134 3135 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3136 return self.func("COLUMNS", *expression.expressions) 3137 3138 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3139 this = self.sql(expression, "this") 3140 path = self.sql(expression, "path") 3141 path = f", {path}" if path else "" 3142 error_handling = expression.args.get("error_handling") 3143 error_handling = f" {error_handling}" if error_handling else "" 3144 empty_handling = expression.args.get("empty_handling") 3145 empty_handling = f" {empty_handling}" if empty_handling else "" 3146 schema = self.sql(expression, "schema") 3147 return self.func( 3148 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3149 ) 3150 3151 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3152 this = self.sql(expression, "this") 3153 kind = self.sql(expression, "kind") 3154 path = self.sql(expression, "path") 3155 path = f" {path}" if path else "" 3156 as_json = " AS JSON" if expression.args.get("as_json") else "" 3157 return f"{this} {kind}{path}{as_json}" 3158 3159 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3160 this = self.sql(expression, "this") 3161 path = self.sql(expression, "path") 3162 path = f", {path}" if path else "" 3163 expressions = self.expressions(expression) 3164 with_ = ( 3165 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3166 if expressions 3167 else "" 3168 ) 3169 return f"OPENJSON({this}{path}){with_}" 3170 3171 def in_sql(self, expression: exp.In) -> str: 3172 query = expression.args.get("query") 3173 unnest = expression.args.get("unnest") 3174 field = expression.args.get("field") 3175 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3176 3177 if query: 3178 in_sql = self.sql(query) 3179 elif unnest: 3180 in_sql = self.in_unnest_op(unnest) 3181 elif field: 3182 in_sql = self.sql(field) 3183 else: 3184 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3185 3186 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3187 3188 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3189 return f"(SELECT {self.sql(unnest)})" 3190 3191 def interval_sql(self, expression: exp.Interval) -> str: 3192 unit = self.sql(expression, "unit") 3193 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3194 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3195 unit = f" {unit}" if unit else "" 3196 3197 if self.SINGLE_STRING_INTERVAL: 3198 this = expression.this.name if expression.this else "" 3199 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3200 3201 this = self.sql(expression, "this") 3202 if this: 3203 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3204 this = f" {this}" if unwrapped else f" ({this})" 3205 3206 return f"INTERVAL{this}{unit}" 3207 3208 def return_sql(self, expression: exp.Return) -> str: 3209 return f"RETURN {self.sql(expression, 'this')}" 3210 3211 def reference_sql(self, expression: exp.Reference) -> str: 3212 this = self.sql(expression, "this") 3213 expressions = self.expressions(expression, flat=True) 3214 expressions = f"({expressions})" if expressions else "" 3215 options = self.expressions(expression, key="options", flat=True, sep=" ") 3216 options = f" {options}" if options else "" 3217 return f"REFERENCES {this}{expressions}{options}" 3218 3219 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3220 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3221 parent = expression.parent 3222 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3223 return self.func( 3224 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3225 ) 3226 3227 def paren_sql(self, expression: exp.Paren) -> str: 3228 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3229 return f"({sql}{self.seg(')', sep='')}" 3230 3231 def neg_sql(self, expression: exp.Neg) -> str: 3232 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3233 this_sql = self.sql(expression, "this") 3234 sep = " " if this_sql[0] == "-" else "" 3235 return f"-{sep}{this_sql}" 3236 3237 def not_sql(self, expression: exp.Not) -> str: 3238 return f"NOT {self.sql(expression, 'this')}" 3239 3240 def alias_sql(self, expression: exp.Alias) -> str: 3241 alias = self.sql(expression, "alias") 3242 alias = f" AS {alias}" if alias else "" 3243 return f"{self.sql(expression, 'this')}{alias}" 3244 3245 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3246 alias = expression.args["alias"] 3247 3248 parent = expression.parent 3249 pivot = parent and parent.parent 3250 3251 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3252 identifier_alias = isinstance(alias, exp.Identifier) 3253 literal_alias = isinstance(alias, exp.Literal) 3254 3255 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3256 alias.replace(exp.Literal.string(alias.output_name)) 3257 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3258 alias.replace(exp.to_identifier(alias.output_name)) 3259 3260 return self.alias_sql(expression) 3261 3262 def aliases_sql(self, expression: exp.Aliases) -> str: 3263 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3264 3265 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3266 this = self.sql(expression, "this") 3267 index = self.sql(expression, "expression") 3268 return f"{this} AT {index}" 3269 3270 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3271 this = self.sql(expression, "this") 3272 zone = self.sql(expression, "zone") 3273 return f"{this} AT TIME ZONE {zone}" 3274 3275 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3276 this = self.sql(expression, "this") 3277 zone = self.sql(expression, "zone") 3278 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3279 3280 def add_sql(self, expression: exp.Add) -> str: 3281 return self.binary(expression, "+") 3282 3283 def and_sql( 3284 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3285 ) -> str: 3286 return self.connector_sql(expression, "AND", stack) 3287 3288 def or_sql( 3289 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3290 ) -> str: 3291 return self.connector_sql(expression, "OR", stack) 3292 3293 def xor_sql( 3294 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3295 ) -> str: 3296 return self.connector_sql(expression, "XOR", stack) 3297 3298 def connector_sql( 3299 self, 3300 expression: exp.Connector, 3301 op: str, 3302 stack: t.Optional[t.List[str | exp.Expression]] = None, 3303 ) -> str: 3304 if stack is not None: 3305 if expression.expressions: 3306 stack.append(self.expressions(expression, sep=f" {op} ")) 3307 else: 3308 stack.append(expression.right) 3309 if expression.comments and self.comments: 3310 for comment in expression.comments: 3311 if comment: 3312 op += f" /*{self.sanitize_comment(comment)}*/" 3313 stack.extend((op, expression.left)) 3314 return op 3315 3316 stack = [expression] 3317 sqls: t.List[str] = [] 3318 ops = set() 3319 3320 while stack: 3321 node = stack.pop() 3322 if isinstance(node, exp.Connector): 3323 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3324 else: 3325 sql = self.sql(node) 3326 if sqls and sqls[-1] in ops: 3327 sqls[-1] += f" {sql}" 3328 else: 3329 sqls.append(sql) 3330 3331 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3332 return sep.join(sqls) 3333 3334 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3335 return self.binary(expression, "&") 3336 3337 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3338 return self.binary(expression, "<<") 3339 3340 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3341 return f"~{self.sql(expression, 'this')}" 3342 3343 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3344 return self.binary(expression, "|") 3345 3346 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3347 return self.binary(expression, ">>") 3348 3349 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3350 return self.binary(expression, "^") 3351 3352 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3353 format_sql = self.sql(expression, "format") 3354 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3355 to_sql = self.sql(expression, "to") 3356 to_sql = f" {to_sql}" if to_sql else "" 3357 action = self.sql(expression, "action") 3358 action = f" {action}" if action else "" 3359 default = self.sql(expression, "default") 3360 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3361 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3362 3363 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3364 zone = self.sql(expression, "this") 3365 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3366 3367 def collate_sql(self, expression: exp.Collate) -> str: 3368 if self.COLLATE_IS_FUNC: 3369 return self.function_fallback_sql(expression) 3370 return self.binary(expression, "COLLATE") 3371 3372 def command_sql(self, expression: exp.Command) -> str: 3373 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3374 3375 def comment_sql(self, expression: exp.Comment) -> str: 3376 this = self.sql(expression, "this") 3377 kind = expression.args["kind"] 3378 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3379 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3380 expression_sql = self.sql(expression, "expression") 3381 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3382 3383 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3384 this = self.sql(expression, "this") 3385 delete = " DELETE" if expression.args.get("delete") else "" 3386 recompress = self.sql(expression, "recompress") 3387 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3388 to_disk = self.sql(expression, "to_disk") 3389 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3390 to_volume = self.sql(expression, "to_volume") 3391 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3392 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3393 3394 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3395 where = self.sql(expression, "where") 3396 group = self.sql(expression, "group") 3397 aggregates = self.expressions(expression, key="aggregates") 3398 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3399 3400 if not (where or group or aggregates) and len(expression.expressions) == 1: 3401 return f"TTL {self.expressions(expression, flat=True)}" 3402 3403 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3404 3405 def transaction_sql(self, expression: exp.Transaction) -> str: 3406 return "BEGIN" 3407 3408 def commit_sql(self, expression: exp.Commit) -> str: 3409 chain = expression.args.get("chain") 3410 if chain is not None: 3411 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3412 3413 return f"COMMIT{chain or ''}" 3414 3415 def rollback_sql(self, expression: exp.Rollback) -> str: 3416 savepoint = expression.args.get("savepoint") 3417 savepoint = f" TO {savepoint}" if savepoint else "" 3418 return f"ROLLBACK{savepoint}" 3419 3420 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3421 this = self.sql(expression, "this") 3422 3423 dtype = self.sql(expression, "dtype") 3424 if dtype: 3425 collate = self.sql(expression, "collate") 3426 collate = f" COLLATE {collate}" if collate else "" 3427 using = self.sql(expression, "using") 3428 using = f" USING {using}" if using else "" 3429 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3430 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3431 3432 default = self.sql(expression, "default") 3433 if default: 3434 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3435 3436 comment = self.sql(expression, "comment") 3437 if comment: 3438 return f"ALTER COLUMN {this} COMMENT {comment}" 3439 3440 visible = expression.args.get("visible") 3441 if visible: 3442 return f"ALTER COLUMN {this} SET {visible}" 3443 3444 allow_null = expression.args.get("allow_null") 3445 drop = expression.args.get("drop") 3446 3447 if not drop and not allow_null: 3448 self.unsupported("Unsupported ALTER COLUMN syntax") 3449 3450 if allow_null is not None: 3451 keyword = "DROP" if drop else "SET" 3452 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3453 3454 return f"ALTER COLUMN {this} DROP DEFAULT" 3455 3456 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3457 this = self.sql(expression, "this") 3458 3459 visible = expression.args.get("visible") 3460 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3461 3462 return f"ALTER INDEX {this} {visible_sql}" 3463 3464 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3465 this = self.sql(expression, "this") 3466 if not isinstance(expression.this, exp.Var): 3467 this = f"KEY DISTKEY {this}" 3468 return f"ALTER DISTSTYLE {this}" 3469 3470 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3471 compound = " COMPOUND" if expression.args.get("compound") else "" 3472 this = self.sql(expression, "this") 3473 expressions = self.expressions(expression, flat=True) 3474 expressions = f"({expressions})" if expressions else "" 3475 return f"ALTER{compound} SORTKEY {this or expressions}" 3476 3477 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3478 if not self.RENAME_TABLE_WITH_DB: 3479 # Remove db from tables 3480 expression = expression.transform( 3481 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3482 ).assert_is(exp.AlterRename) 3483 this = self.sql(expression, "this") 3484 return f"RENAME TO {this}" 3485 3486 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3487 exists = " IF EXISTS" if expression.args.get("exists") else "" 3488 old_column = self.sql(expression, "this") 3489 new_column = self.sql(expression, "to") 3490 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3491 3492 def alterset_sql(self, expression: exp.AlterSet) -> str: 3493 exprs = self.expressions(expression, flat=True) 3494 if self.ALTER_SET_WRAPPED: 3495 exprs = f"({exprs})" 3496 3497 return f"SET {exprs}" 3498 3499 def alter_sql(self, expression: exp.Alter) -> str: 3500 actions = expression.args["actions"] 3501 3502 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3503 actions[0], exp.ColumnDef 3504 ): 3505 actions_sql = self.expressions(expression, key="actions", flat=True) 3506 actions_sql = f"ADD {actions_sql}" 3507 else: 3508 actions_list = [] 3509 for action in actions: 3510 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3511 action_sql = self.add_column_sql(action) 3512 else: 3513 action_sql = self.sql(action) 3514 if isinstance(action, exp.Query): 3515 action_sql = f"AS {action_sql}" 3516 3517 actions_list.append(action_sql) 3518 3519 actions_sql = self.format_args(*actions_list).lstrip("\n") 3520 3521 exists = " IF EXISTS" if expression.args.get("exists") else "" 3522 on_cluster = self.sql(expression, "cluster") 3523 on_cluster = f" {on_cluster}" if on_cluster else "" 3524 only = " ONLY" if expression.args.get("only") else "" 3525 options = self.expressions(expression, key="options") 3526 options = f", {options}" if options else "" 3527 kind = self.sql(expression, "kind") 3528 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3529 3530 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}" 3531 3532 def add_column_sql(self, expression: exp.Expression) -> str: 3533 sql = self.sql(expression) 3534 if isinstance(expression, exp.Schema): 3535 column_text = " COLUMNS" 3536 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3537 column_text = " COLUMN" 3538 else: 3539 column_text = "" 3540 3541 return f"ADD{column_text} {sql}" 3542 3543 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3544 expressions = self.expressions(expression) 3545 exists = " IF EXISTS " if expression.args.get("exists") else " " 3546 return f"DROP{exists}{expressions}" 3547 3548 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3549 return f"ADD {self.expressions(expression, indent=False)}" 3550 3551 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3552 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3553 location = self.sql(expression, "location") 3554 location = f" {location}" if location else "" 3555 return f"ADD {exists}{self.sql(expression.this)}{location}" 3556 3557 def distinct_sql(self, expression: exp.Distinct) -> str: 3558 this = self.expressions(expression, flat=True) 3559 3560 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3561 case = exp.case() 3562 for arg in expression.expressions: 3563 case = case.when(arg.is_(exp.null()), exp.null()) 3564 this = self.sql(case.else_(f"({this})")) 3565 3566 this = f" {this}" if this else "" 3567 3568 on = self.sql(expression, "on") 3569 on = f" ON {on}" if on else "" 3570 return f"DISTINCT{this}{on}" 3571 3572 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3573 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3574 3575 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3576 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3577 3578 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3579 this_sql = self.sql(expression, "this") 3580 expression_sql = self.sql(expression, "expression") 3581 kind = "MAX" if expression.args.get("max") else "MIN" 3582 return f"{this_sql} HAVING {kind} {expression_sql}" 3583 3584 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3585 return self.sql( 3586 exp.Cast( 3587 this=exp.Div(this=expression.this, expression=expression.expression), 3588 to=exp.DataType(this=exp.DataType.Type.INT), 3589 ) 3590 ) 3591 3592 def dpipe_sql(self, expression: exp.DPipe) -> str: 3593 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3594 return self.func( 3595 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3596 ) 3597 return self.binary(expression, "||") 3598 3599 def div_sql(self, expression: exp.Div) -> str: 3600 l, r = expression.left, expression.right 3601 3602 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3603 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3604 3605 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3606 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3607 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3608 3609 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3610 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3611 return self.sql( 3612 exp.cast( 3613 l / r, 3614 to=exp.DataType.Type.BIGINT, 3615 ) 3616 ) 3617 3618 return self.binary(expression, "/") 3619 3620 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3621 n = exp._wrap(expression.this, exp.Binary) 3622 d = exp._wrap(expression.expression, exp.Binary) 3623 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3624 3625 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3626 return self.binary(expression, "OVERLAPS") 3627 3628 def distance_sql(self, expression: exp.Distance) -> str: 3629 return self.binary(expression, "<->") 3630 3631 def dot_sql(self, expression: exp.Dot) -> str: 3632 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3633 3634 def eq_sql(self, expression: exp.EQ) -> str: 3635 return self.binary(expression, "=") 3636 3637 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3638 return self.binary(expression, ":=") 3639 3640 def escape_sql(self, expression: exp.Escape) -> str: 3641 return self.binary(expression, "ESCAPE") 3642 3643 def glob_sql(self, expression: exp.Glob) -> str: 3644 return self.binary(expression, "GLOB") 3645 3646 def gt_sql(self, expression: exp.GT) -> str: 3647 return self.binary(expression, ">") 3648 3649 def gte_sql(self, expression: exp.GTE) -> str: 3650 return self.binary(expression, ">=") 3651 3652 def ilike_sql(self, expression: exp.ILike) -> str: 3653 return self.binary(expression, "ILIKE") 3654 3655 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3656 return self.binary(expression, "ILIKE ANY") 3657 3658 def is_sql(self, expression: exp.Is) -> str: 3659 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3660 return self.sql( 3661 expression.this if expression.expression.this else exp.not_(expression.this) 3662 ) 3663 return self.binary(expression, "IS") 3664 3665 def like_sql(self, expression: exp.Like) -> str: 3666 return self.binary(expression, "LIKE") 3667 3668 def likeany_sql(self, expression: exp.LikeAny) -> str: 3669 return self.binary(expression, "LIKE ANY") 3670 3671 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3672 return self.binary(expression, "SIMILAR TO") 3673 3674 def lt_sql(self, expression: exp.LT) -> str: 3675 return self.binary(expression, "<") 3676 3677 def lte_sql(self, expression: exp.LTE) -> str: 3678 return self.binary(expression, "<=") 3679 3680 def mod_sql(self, expression: exp.Mod) -> str: 3681 return self.binary(expression, "%") 3682 3683 def mul_sql(self, expression: exp.Mul) -> str: 3684 return self.binary(expression, "*") 3685 3686 def neq_sql(self, expression: exp.NEQ) -> str: 3687 return self.binary(expression, "<>") 3688 3689 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3690 return self.binary(expression, "IS NOT DISTINCT FROM") 3691 3692 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3693 return self.binary(expression, "IS DISTINCT FROM") 3694 3695 def slice_sql(self, expression: exp.Slice) -> str: 3696 return self.binary(expression, ":") 3697 3698 def sub_sql(self, expression: exp.Sub) -> str: 3699 return self.binary(expression, "-") 3700 3701 def trycast_sql(self, expression: exp.TryCast) -> str: 3702 return self.cast_sql(expression, safe_prefix="TRY_") 3703 3704 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3705 return self.cast_sql(expression) 3706 3707 def try_sql(self, expression: exp.Try) -> str: 3708 if not self.TRY_SUPPORTED: 3709 self.unsupported("Unsupported TRY function") 3710 return self.sql(expression, "this") 3711 3712 return self.func("TRY", expression.this) 3713 3714 def log_sql(self, expression: exp.Log) -> str: 3715 this = expression.this 3716 expr = expression.expression 3717 3718 if self.dialect.LOG_BASE_FIRST is False: 3719 this, expr = expr, this 3720 elif self.dialect.LOG_BASE_FIRST is None and expr: 3721 if this.name in ("2", "10"): 3722 return self.func(f"LOG{this.name}", expr) 3723 3724 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3725 3726 return self.func("LOG", this, expr) 3727 3728 def use_sql(self, expression: exp.Use) -> str: 3729 kind = self.sql(expression, "kind") 3730 kind = f" {kind}" if kind else "" 3731 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3732 this = f" {this}" if this else "" 3733 return f"USE{kind}{this}" 3734 3735 def binary(self, expression: exp.Binary, op: str) -> str: 3736 sqls: t.List[str] = [] 3737 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3738 binary_type = type(expression) 3739 3740 while stack: 3741 node = stack.pop() 3742 3743 if type(node) is binary_type: 3744 op_func = node.args.get("operator") 3745 if op_func: 3746 op = f"OPERATOR({self.sql(op_func)})" 3747 3748 stack.append(node.right) 3749 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3750 stack.append(node.left) 3751 else: 3752 sqls.append(self.sql(node)) 3753 3754 return "".join(sqls) 3755 3756 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3757 to_clause = self.sql(expression, "to") 3758 if to_clause: 3759 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3760 3761 return self.function_fallback_sql(expression) 3762 3763 def function_fallback_sql(self, expression: exp.Func) -> str: 3764 args = [] 3765 3766 for key in expression.arg_types: 3767 arg_value = expression.args.get(key) 3768 3769 if isinstance(arg_value, list): 3770 for value in arg_value: 3771 args.append(value) 3772 elif arg_value is not None: 3773 args.append(arg_value) 3774 3775 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3776 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3777 else: 3778 name = expression.sql_name() 3779 3780 return self.func(name, *args) 3781 3782 def func( 3783 self, 3784 name: str, 3785 *args: t.Optional[exp.Expression | str], 3786 prefix: str = "(", 3787 suffix: str = ")", 3788 normalize: bool = True, 3789 ) -> str: 3790 name = self.normalize_func(name) if normalize else name 3791 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3792 3793 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3794 arg_sqls = tuple( 3795 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3796 ) 3797 if self.pretty and self.too_wide(arg_sqls): 3798 return self.indent( 3799 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3800 ) 3801 return sep.join(arg_sqls) 3802 3803 def too_wide(self, args: t.Iterable) -> bool: 3804 return sum(len(arg) for arg in args) > self.max_text_width 3805 3806 def format_time( 3807 self, 3808 expression: exp.Expression, 3809 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3810 inverse_time_trie: t.Optional[t.Dict] = None, 3811 ) -> t.Optional[str]: 3812 return format_time( 3813 self.sql(expression, "format"), 3814 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3815 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3816 ) 3817 3818 def expressions( 3819 self, 3820 expression: t.Optional[exp.Expression] = None, 3821 key: t.Optional[str] = None, 3822 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3823 flat: bool = False, 3824 indent: bool = True, 3825 skip_first: bool = False, 3826 skip_last: bool = False, 3827 sep: str = ", ", 3828 prefix: str = "", 3829 dynamic: bool = False, 3830 new_line: bool = False, 3831 ) -> str: 3832 expressions = expression.args.get(key or "expressions") if expression else sqls 3833 3834 if not expressions: 3835 return "" 3836 3837 if flat: 3838 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3839 3840 num_sqls = len(expressions) 3841 result_sqls = [] 3842 3843 for i, e in enumerate(expressions): 3844 sql = self.sql(e, comment=False) 3845 if not sql: 3846 continue 3847 3848 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3849 3850 if self.pretty: 3851 if self.leading_comma: 3852 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3853 else: 3854 result_sqls.append( 3855 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3856 ) 3857 else: 3858 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3859 3860 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3861 if new_line: 3862 result_sqls.insert(0, "") 3863 result_sqls.append("") 3864 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3865 else: 3866 result_sql = "".join(result_sqls) 3867 3868 return ( 3869 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3870 if indent 3871 else result_sql 3872 ) 3873 3874 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3875 flat = flat or isinstance(expression.parent, exp.Properties) 3876 expressions_sql = self.expressions(expression, flat=flat) 3877 if flat: 3878 return f"{op} {expressions_sql}" 3879 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3880 3881 def naked_property(self, expression: exp.Property) -> str: 3882 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3883 if not property_name: 3884 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3885 return f"{property_name} {self.sql(expression, 'this')}" 3886 3887 def tag_sql(self, expression: exp.Tag) -> str: 3888 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3889 3890 def token_sql(self, token_type: TokenType) -> str: 3891 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3892 3893 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3894 this = self.sql(expression, "this") 3895 expressions = self.no_identify(self.expressions, expression) 3896 expressions = ( 3897 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3898 ) 3899 return f"{this}{expressions}" if expressions.strip() != "" else this 3900 3901 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3902 this = self.sql(expression, "this") 3903 expressions = self.expressions(expression, flat=True) 3904 return f"{this}({expressions})" 3905 3906 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3907 return self.binary(expression, "=>") 3908 3909 def when_sql(self, expression: exp.When) -> str: 3910 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3911 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3912 condition = self.sql(expression, "condition") 3913 condition = f" AND {condition}" if condition else "" 3914 3915 then_expression = expression.args.get("then") 3916 if isinstance(then_expression, exp.Insert): 3917 this = self.sql(then_expression, "this") 3918 this = f"INSERT {this}" if this else "INSERT" 3919 then = self.sql(then_expression, "expression") 3920 then = f"{this} VALUES {then}" if then else this 3921 elif isinstance(then_expression, exp.Update): 3922 if isinstance(then_expression.args.get("expressions"), exp.Star): 3923 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3924 else: 3925 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3926 else: 3927 then = self.sql(then_expression) 3928 return f"WHEN {matched}{source}{condition} THEN {then}" 3929 3930 def whens_sql(self, expression: exp.Whens) -> str: 3931 return self.expressions(expression, sep=" ", indent=False) 3932 3933 def merge_sql(self, expression: exp.Merge) -> str: 3934 table = expression.this 3935 table_alias = "" 3936 3937 hints = table.args.get("hints") 3938 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3939 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3940 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3941 3942 this = self.sql(table) 3943 using = f"USING {self.sql(expression, 'using')}" 3944 on = f"ON {self.sql(expression, 'on')}" 3945 whens = self.sql(expression, "whens") 3946 3947 returning = self.sql(expression, "returning") 3948 if returning: 3949 whens = f"{whens}{returning}" 3950 3951 sep = self.sep() 3952 3953 return self.prepend_ctes( 3954 expression, 3955 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3956 ) 3957 3958 @unsupported_args("format") 3959 def tochar_sql(self, expression: exp.ToChar) -> str: 3960 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3961 3962 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3963 if not self.SUPPORTS_TO_NUMBER: 3964 self.unsupported("Unsupported TO_NUMBER function") 3965 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3966 3967 fmt = expression.args.get("format") 3968 if not fmt: 3969 self.unsupported("Conversion format is required for TO_NUMBER") 3970 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3971 3972 return self.func("TO_NUMBER", expression.this, fmt) 3973 3974 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3975 this = self.sql(expression, "this") 3976 kind = self.sql(expression, "kind") 3977 settings_sql = self.expressions(expression, key="settings", sep=" ") 3978 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3979 return f"{this}({kind}{args})" 3980 3981 def dictrange_sql(self, expression: exp.DictRange) -> str: 3982 this = self.sql(expression, "this") 3983 max = self.sql(expression, "max") 3984 min = self.sql(expression, "min") 3985 return f"{this}(MIN {min} MAX {max})" 3986 3987 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3988 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3989 3990 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3991 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3992 3993 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 3994 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 3995 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 3996 3997 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3998 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3999 expressions = self.expressions(expression, flat=True) 4000 expressions = f" {self.wrap(expressions)}" if expressions else "" 4001 buckets = self.sql(expression, "buckets") 4002 kind = self.sql(expression, "kind") 4003 buckets = f" BUCKETS {buckets}" if buckets else "" 4004 order = self.sql(expression, "order") 4005 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4006 4007 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4008 return "" 4009 4010 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4011 expressions = self.expressions(expression, key="expressions", flat=True) 4012 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4013 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4014 buckets = self.sql(expression, "buckets") 4015 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4016 4017 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4018 this = self.sql(expression, "this") 4019 having = self.sql(expression, "having") 4020 4021 if having: 4022 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4023 4024 return self.func("ANY_VALUE", this) 4025 4026 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4027 transform = self.func("TRANSFORM", *expression.expressions) 4028 row_format_before = self.sql(expression, "row_format_before") 4029 row_format_before = f" {row_format_before}" if row_format_before else "" 4030 record_writer = self.sql(expression, "record_writer") 4031 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4032 using = f" USING {self.sql(expression, 'command_script')}" 4033 schema = self.sql(expression, "schema") 4034 schema = f" AS {schema}" if schema else "" 4035 row_format_after = self.sql(expression, "row_format_after") 4036 row_format_after = f" {row_format_after}" if row_format_after else "" 4037 record_reader = self.sql(expression, "record_reader") 4038 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4039 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4040 4041 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4042 key_block_size = self.sql(expression, "key_block_size") 4043 if key_block_size: 4044 return f"KEY_BLOCK_SIZE = {key_block_size}" 4045 4046 using = self.sql(expression, "using") 4047 if using: 4048 return f"USING {using}" 4049 4050 parser = self.sql(expression, "parser") 4051 if parser: 4052 return f"WITH PARSER {parser}" 4053 4054 comment = self.sql(expression, "comment") 4055 if comment: 4056 return f"COMMENT {comment}" 4057 4058 visible = expression.args.get("visible") 4059 if visible is not None: 4060 return "VISIBLE" if visible else "INVISIBLE" 4061 4062 engine_attr = self.sql(expression, "engine_attr") 4063 if engine_attr: 4064 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4065 4066 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4067 if secondary_engine_attr: 4068 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4069 4070 self.unsupported("Unsupported index constraint option.") 4071 return "" 4072 4073 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4074 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4075 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4076 4077 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4078 kind = self.sql(expression, "kind") 4079 kind = f"{kind} INDEX" if kind else "INDEX" 4080 this = self.sql(expression, "this") 4081 this = f" {this}" if this else "" 4082 index_type = self.sql(expression, "index_type") 4083 index_type = f" USING {index_type}" if index_type else "" 4084 expressions = self.expressions(expression, flat=True) 4085 expressions = f" ({expressions})" if expressions else "" 4086 options = self.expressions(expression, key="options", sep=" ") 4087 options = f" {options}" if options else "" 4088 return f"{kind}{this}{index_type}{expressions}{options}" 4089 4090 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4091 if self.NVL2_SUPPORTED: 4092 return self.function_fallback_sql(expression) 4093 4094 case = exp.Case().when( 4095 expression.this.is_(exp.null()).not_(copy=False), 4096 expression.args["true"], 4097 copy=False, 4098 ) 4099 else_cond = expression.args.get("false") 4100 if else_cond: 4101 case.else_(else_cond, copy=False) 4102 4103 return self.sql(case) 4104 4105 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4106 this = self.sql(expression, "this") 4107 expr = self.sql(expression, "expression") 4108 iterator = self.sql(expression, "iterator") 4109 condition = self.sql(expression, "condition") 4110 condition = f" IF {condition}" if condition else "" 4111 return f"{this} FOR {expr} IN {iterator}{condition}" 4112 4113 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4114 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4115 4116 def opclass_sql(self, expression: exp.Opclass) -> str: 4117 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4118 4119 def predict_sql(self, expression: exp.Predict) -> str: 4120 model = self.sql(expression, "this") 4121 model = f"MODEL {model}" 4122 table = self.sql(expression, "expression") 4123 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4124 parameters = self.sql(expression, "params_struct") 4125 return self.func("PREDICT", model, table, parameters or None) 4126 4127 def forin_sql(self, expression: exp.ForIn) -> str: 4128 this = self.sql(expression, "this") 4129 expression_sql = self.sql(expression, "expression") 4130 return f"FOR {this} DO {expression_sql}" 4131 4132 def refresh_sql(self, expression: exp.Refresh) -> str: 4133 this = self.sql(expression, "this") 4134 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4135 return f"REFRESH {table}{this}" 4136 4137 def toarray_sql(self, expression: exp.ToArray) -> str: 4138 arg = expression.this 4139 if not arg.type: 4140 from sqlglot.optimizer.annotate_types import annotate_types 4141 4142 arg = annotate_types(arg, dialect=self.dialect) 4143 4144 if arg.is_type(exp.DataType.Type.ARRAY): 4145 return self.sql(arg) 4146 4147 cond_for_null = arg.is_(exp.null()) 4148 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4149 4150 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4151 this = expression.this 4152 time_format = self.format_time(expression) 4153 4154 if time_format: 4155 return self.sql( 4156 exp.cast( 4157 exp.StrToTime(this=this, format=expression.args["format"]), 4158 exp.DataType.Type.TIME, 4159 ) 4160 ) 4161 4162 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4163 return self.sql(this) 4164 4165 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4166 4167 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4168 this = expression.this 4169 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4170 return self.sql(this) 4171 4172 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4173 4174 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4175 this = expression.this 4176 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4177 return self.sql(this) 4178 4179 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4180 4181 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4182 this = expression.this 4183 time_format = self.format_time(expression) 4184 4185 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4186 return self.sql( 4187 exp.cast( 4188 exp.StrToTime(this=this, format=expression.args["format"]), 4189 exp.DataType.Type.DATE, 4190 ) 4191 ) 4192 4193 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4194 return self.sql(this) 4195 4196 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4197 4198 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4199 return self.sql( 4200 exp.func( 4201 "DATEDIFF", 4202 expression.this, 4203 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4204 "day", 4205 ) 4206 ) 4207 4208 def lastday_sql(self, expression: exp.LastDay) -> str: 4209 if self.LAST_DAY_SUPPORTS_DATE_PART: 4210 return self.function_fallback_sql(expression) 4211 4212 unit = expression.text("unit") 4213 if unit and unit != "MONTH": 4214 self.unsupported("Date parts are not supported in LAST_DAY.") 4215 4216 return self.func("LAST_DAY", expression.this) 4217 4218 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4219 from sqlglot.dialects.dialect import unit_to_str 4220 4221 return self.func( 4222 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4223 ) 4224 4225 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4226 if self.CAN_IMPLEMENT_ARRAY_ANY: 4227 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4228 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4229 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4230 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4231 4232 from sqlglot.dialects import Dialect 4233 4234 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4235 if self.dialect.__class__ != Dialect: 4236 self.unsupported("ARRAY_ANY is unsupported") 4237 4238 return self.function_fallback_sql(expression) 4239 4240 def struct_sql(self, expression: exp.Struct) -> str: 4241 expression.set( 4242 "expressions", 4243 [ 4244 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4245 if isinstance(e, exp.PropertyEQ) 4246 else e 4247 for e in expression.expressions 4248 ], 4249 ) 4250 4251 return self.function_fallback_sql(expression) 4252 4253 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4254 low = self.sql(expression, "this") 4255 high = self.sql(expression, "expression") 4256 4257 return f"{low} TO {high}" 4258 4259 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4260 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4261 tables = f" {self.expressions(expression)}" 4262 4263 exists = " IF EXISTS" if expression.args.get("exists") else "" 4264 4265 on_cluster = self.sql(expression, "cluster") 4266 on_cluster = f" {on_cluster}" if on_cluster else "" 4267 4268 identity = self.sql(expression, "identity") 4269 identity = f" {identity} IDENTITY" if identity else "" 4270 4271 option = self.sql(expression, "option") 4272 option = f" {option}" if option else "" 4273 4274 partition = self.sql(expression, "partition") 4275 partition = f" {partition}" if partition else "" 4276 4277 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4278 4279 # This transpiles T-SQL's CONVERT function 4280 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4281 def convert_sql(self, expression: exp.Convert) -> str: 4282 to = expression.this 4283 value = expression.expression 4284 style = expression.args.get("style") 4285 safe = expression.args.get("safe") 4286 strict = expression.args.get("strict") 4287 4288 if not to or not value: 4289 return "" 4290 4291 # Retrieve length of datatype and override to default if not specified 4292 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4293 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4294 4295 transformed: t.Optional[exp.Expression] = None 4296 cast = exp.Cast if strict else exp.TryCast 4297 4298 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4299 if isinstance(style, exp.Literal) and style.is_int: 4300 from sqlglot.dialects.tsql import TSQL 4301 4302 style_value = style.name 4303 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4304 if not converted_style: 4305 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4306 4307 fmt = exp.Literal.string(converted_style) 4308 4309 if to.this == exp.DataType.Type.DATE: 4310 transformed = exp.StrToDate(this=value, format=fmt) 4311 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4312 transformed = exp.StrToTime(this=value, format=fmt) 4313 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4314 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4315 elif to.this == exp.DataType.Type.TEXT: 4316 transformed = exp.TimeToStr(this=value, format=fmt) 4317 4318 if not transformed: 4319 transformed = cast(this=value, to=to, safe=safe) 4320 4321 return self.sql(transformed) 4322 4323 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4324 this = expression.this 4325 if isinstance(this, exp.JSONPathWildcard): 4326 this = self.json_path_part(this) 4327 return f".{this}" if this else "" 4328 4329 if exp.SAFE_IDENTIFIER_RE.match(this): 4330 return f".{this}" 4331 4332 this = self.json_path_part(this) 4333 return ( 4334 f"[{this}]" 4335 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4336 else f".{this}" 4337 ) 4338 4339 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4340 this = self.json_path_part(expression.this) 4341 return f"[{this}]" if this else "" 4342 4343 def _simplify_unless_literal(self, expression: E) -> E: 4344 if not isinstance(expression, exp.Literal): 4345 from sqlglot.optimizer.simplify import simplify 4346 4347 expression = simplify(expression, dialect=self.dialect) 4348 4349 return expression 4350 4351 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4352 this = expression.this 4353 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4354 self.unsupported( 4355 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4356 ) 4357 return self.sql(this) 4358 4359 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4360 # The first modifier here will be the one closest to the AggFunc's arg 4361 mods = sorted( 4362 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4363 key=lambda x: 0 4364 if isinstance(x, exp.HavingMax) 4365 else (1 if isinstance(x, exp.Order) else 2), 4366 ) 4367 4368 if mods: 4369 mod = mods[0] 4370 this = expression.__class__(this=mod.this.copy()) 4371 this.meta["inline"] = True 4372 mod.this.replace(this) 4373 return self.sql(expression.this) 4374 4375 agg_func = expression.find(exp.AggFunc) 4376 4377 if agg_func: 4378 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4379 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4380 4381 return f"{self.sql(expression, 'this')} {text}" 4382 4383 def _replace_line_breaks(self, string: str) -> str: 4384 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4385 if self.pretty: 4386 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4387 return string 4388 4389 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4390 option = self.sql(expression, "this") 4391 4392 if expression.expressions: 4393 upper = option.upper() 4394 4395 # Snowflake FILE_FORMAT options are separated by whitespace 4396 sep = " " if upper == "FILE_FORMAT" else ", " 4397 4398 # Databricks copy/format options do not set their list of values with EQ 4399 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4400 values = self.expressions(expression, flat=True, sep=sep) 4401 return f"{option}{op}({values})" 4402 4403 value = self.sql(expression, "expression") 4404 4405 if not value: 4406 return option 4407 4408 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4409 4410 return f"{option}{op}{value}" 4411 4412 def credentials_sql(self, expression: exp.Credentials) -> str: 4413 cred_expr = expression.args.get("credentials") 4414 if isinstance(cred_expr, exp.Literal): 4415 # Redshift case: CREDENTIALS <string> 4416 credentials = self.sql(expression, "credentials") 4417 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4418 else: 4419 # Snowflake case: CREDENTIALS = (...) 4420 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4421 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4422 4423 storage = self.sql(expression, "storage") 4424 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4425 4426 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4427 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4428 4429 iam_role = self.sql(expression, "iam_role") 4430 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4431 4432 region = self.sql(expression, "region") 4433 region = f" REGION {region}" if region else "" 4434 4435 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4436 4437 def copy_sql(self, expression: exp.Copy) -> str: 4438 this = self.sql(expression, "this") 4439 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4440 4441 credentials = self.sql(expression, "credentials") 4442 credentials = self.seg(credentials) if credentials else "" 4443 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4444 files = self.expressions(expression, key="files", flat=True) 4445 4446 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4447 params = self.expressions( 4448 expression, 4449 key="params", 4450 sep=sep, 4451 new_line=True, 4452 skip_last=True, 4453 skip_first=True, 4454 indent=self.COPY_PARAMS_ARE_WRAPPED, 4455 ) 4456 4457 if params: 4458 if self.COPY_PARAMS_ARE_WRAPPED: 4459 params = f" WITH ({params})" 4460 elif not self.pretty: 4461 params = f" {params}" 4462 4463 return f"COPY{this}{kind} {files}{credentials}{params}" 4464 4465 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4466 return "" 4467 4468 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4469 on_sql = "ON" if expression.args.get("on") else "OFF" 4470 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4471 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4472 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4473 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4474 4475 if filter_col or retention_period: 4476 on_sql = self.func("ON", filter_col, retention_period) 4477 4478 return f"DATA_DELETION={on_sql}" 4479 4480 def maskingpolicycolumnconstraint_sql( 4481 self, expression: exp.MaskingPolicyColumnConstraint 4482 ) -> str: 4483 this = self.sql(expression, "this") 4484 expressions = self.expressions(expression, flat=True) 4485 expressions = f" USING ({expressions})" if expressions else "" 4486 return f"MASKING POLICY {this}{expressions}" 4487 4488 def gapfill_sql(self, expression: exp.GapFill) -> str: 4489 this = self.sql(expression, "this") 4490 this = f"TABLE {this}" 4491 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4492 4493 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4494 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4495 4496 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4497 this = self.sql(expression, "this") 4498 expr = expression.expression 4499 4500 if isinstance(expr, exp.Func): 4501 # T-SQL's CLR functions are case sensitive 4502 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4503 else: 4504 expr = self.sql(expression, "expression") 4505 4506 return self.scope_resolution(expr, this) 4507 4508 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4509 if self.PARSE_JSON_NAME is None: 4510 return self.sql(expression.this) 4511 4512 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4513 4514 def rand_sql(self, expression: exp.Rand) -> str: 4515 lower = self.sql(expression, "lower") 4516 upper = self.sql(expression, "upper") 4517 4518 if lower and upper: 4519 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4520 return self.func("RAND", expression.this) 4521 4522 def changes_sql(self, expression: exp.Changes) -> str: 4523 information = self.sql(expression, "information") 4524 information = f"INFORMATION => {information}" 4525 at_before = self.sql(expression, "at_before") 4526 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4527 end = self.sql(expression, "end") 4528 end = f"{self.seg('')}{end}" if end else "" 4529 4530 return f"CHANGES ({information}){at_before}{end}" 4531 4532 def pad_sql(self, expression: exp.Pad) -> str: 4533 prefix = "L" if expression.args.get("is_left") else "R" 4534 4535 fill_pattern = self.sql(expression, "fill_pattern") or None 4536 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4537 fill_pattern = "' '" 4538 4539 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4540 4541 def summarize_sql(self, expression: exp.Summarize) -> str: 4542 table = " TABLE" if expression.args.get("table") else "" 4543 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4544 4545 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4546 generate_series = exp.GenerateSeries(**expression.args) 4547 4548 parent = expression.parent 4549 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4550 parent = parent.parent 4551 4552 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4553 return self.sql(exp.Unnest(expressions=[generate_series])) 4554 4555 if isinstance(parent, exp.Select): 4556 self.unsupported("GenerateSeries projection unnesting is not supported.") 4557 4558 return self.sql(generate_series) 4559 4560 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4561 exprs = expression.expressions 4562 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4563 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4564 else: 4565 rhs = self.expressions(expression) 4566 4567 return self.func(name, expression.this, rhs or None) 4568 4569 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4570 if self.SUPPORTS_CONVERT_TIMEZONE: 4571 return self.function_fallback_sql(expression) 4572 4573 source_tz = expression.args.get("source_tz") 4574 target_tz = expression.args.get("target_tz") 4575 timestamp = expression.args.get("timestamp") 4576 4577 if source_tz and timestamp: 4578 timestamp = exp.AtTimeZone( 4579 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4580 ) 4581 4582 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4583 4584 return self.sql(expr) 4585 4586 def json_sql(self, expression: exp.JSON) -> str: 4587 this = self.sql(expression, "this") 4588 this = f" {this}" if this else "" 4589 4590 _with = expression.args.get("with") 4591 4592 if _with is None: 4593 with_sql = "" 4594 elif not _with: 4595 with_sql = " WITHOUT" 4596 else: 4597 with_sql = " WITH" 4598 4599 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4600 4601 return f"JSON{this}{with_sql}{unique_sql}" 4602 4603 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4604 def _generate_on_options(arg: t.Any) -> str: 4605 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4606 4607 path = self.sql(expression, "path") 4608 returning = self.sql(expression, "returning") 4609 returning = f" RETURNING {returning}" if returning else "" 4610 4611 on_condition = self.sql(expression, "on_condition") 4612 on_condition = f" {on_condition}" if on_condition else "" 4613 4614 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4615 4616 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4617 else_ = "ELSE " if expression.args.get("else_") else "" 4618 condition = self.sql(expression, "expression") 4619 condition = f"WHEN {condition} THEN " if condition else else_ 4620 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4621 return f"{condition}{insert}" 4622 4623 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4624 kind = self.sql(expression, "kind") 4625 expressions = self.seg(self.expressions(expression, sep=" ")) 4626 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4627 return res 4628 4629 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4630 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4631 empty = expression.args.get("empty") 4632 empty = ( 4633 f"DEFAULT {empty} ON EMPTY" 4634 if isinstance(empty, exp.Expression) 4635 else self.sql(expression, "empty") 4636 ) 4637 4638 error = expression.args.get("error") 4639 error = ( 4640 f"DEFAULT {error} ON ERROR" 4641 if isinstance(error, exp.Expression) 4642 else self.sql(expression, "error") 4643 ) 4644 4645 if error and empty: 4646 error = ( 4647 f"{empty} {error}" 4648 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4649 else f"{error} {empty}" 4650 ) 4651 empty = "" 4652 4653 null = self.sql(expression, "null") 4654 4655 return f"{empty}{error}{null}" 4656 4657 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4658 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4659 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4660 4661 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4662 this = self.sql(expression, "this") 4663 path = self.sql(expression, "path") 4664 4665 passing = self.expressions(expression, "passing") 4666 passing = f" PASSING {passing}" if passing else "" 4667 4668 on_condition = self.sql(expression, "on_condition") 4669 on_condition = f" {on_condition}" if on_condition else "" 4670 4671 path = f"{path}{passing}{on_condition}" 4672 4673 return self.func("JSON_EXISTS", this, path) 4674 4675 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4676 array_agg = self.function_fallback_sql(expression) 4677 4678 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4679 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4680 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4681 parent = expression.parent 4682 if isinstance(parent, exp.Filter): 4683 parent_cond = parent.expression.this 4684 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4685 else: 4686 this = expression.this 4687 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4688 if this.find(exp.Column): 4689 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4690 this_sql = ( 4691 self.expressions(this) 4692 if isinstance(this, exp.Distinct) 4693 else self.sql(expression, "this") 4694 ) 4695 4696 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4697 4698 return array_agg 4699 4700 def apply_sql(self, expression: exp.Apply) -> str: 4701 this = self.sql(expression, "this") 4702 expr = self.sql(expression, "expression") 4703 4704 return f"{this} APPLY({expr})" 4705 4706 def grant_sql(self, expression: exp.Grant) -> str: 4707 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4708 4709 kind = self.sql(expression, "kind") 4710 kind = f" {kind}" if kind else "" 4711 4712 securable = self.sql(expression, "securable") 4713 securable = f" {securable}" if securable else "" 4714 4715 principals = self.expressions(expression, key="principals", flat=True) 4716 4717 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4718 4719 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4720 4721 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4722 this = self.sql(expression, "this") 4723 columns = self.expressions(expression, flat=True) 4724 columns = f"({columns})" if columns else "" 4725 4726 return f"{this}{columns}" 4727 4728 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4729 this = self.sql(expression, "this") 4730 4731 kind = self.sql(expression, "kind") 4732 kind = f"{kind} " if kind else "" 4733 4734 return f"{kind}{this}" 4735 4736 def columns_sql(self, expression: exp.Columns): 4737 func = self.function_fallback_sql(expression) 4738 if expression.args.get("unpack"): 4739 func = f"*{func}" 4740 4741 return func 4742 4743 def overlay_sql(self, expression: exp.Overlay): 4744 this = self.sql(expression, "this") 4745 expr = self.sql(expression, "expression") 4746 from_sql = self.sql(expression, "from") 4747 for_sql = self.sql(expression, "for") 4748 for_sql = f" FOR {for_sql}" if for_sql else "" 4749 4750 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4751 4752 @unsupported_args("format") 4753 def todouble_sql(self, expression: exp.ToDouble) -> str: 4754 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4755 4756 def string_sql(self, expression: exp.String) -> str: 4757 this = expression.this 4758 zone = expression.args.get("zone") 4759 4760 if zone: 4761 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4762 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4763 # set for source_tz to transpile the time conversion before the STRING cast 4764 this = exp.ConvertTimezone( 4765 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4766 ) 4767 4768 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4769 4770 def median_sql(self, expression: exp.Median): 4771 if not self.SUPPORTS_MEDIAN: 4772 return self.sql( 4773 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4774 ) 4775 4776 return self.function_fallback_sql(expression) 4777 4778 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4779 filler = self.sql(expression, "this") 4780 filler = f" {filler}" if filler else "" 4781 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4782 return f"TRUNCATE{filler} {with_count}" 4783 4784 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4785 if self.SUPPORTS_UNIX_SECONDS: 4786 return self.function_fallback_sql(expression) 4787 4788 start_ts = exp.cast( 4789 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4790 ) 4791 4792 return self.sql( 4793 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4794 ) 4795 4796 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4797 dim = expression.expression 4798 4799 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4800 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4801 if not (dim.is_int and dim.name == "1"): 4802 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4803 dim = None 4804 4805 # If dimension is required but not specified, default initialize it 4806 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4807 dim = exp.Literal.number(1) 4808 4809 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4810 4811 def attach_sql(self, expression: exp.Attach) -> str: 4812 this = self.sql(expression, "this") 4813 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4814 expressions = self.expressions(expression) 4815 expressions = f" ({expressions})" if expressions else "" 4816 4817 return f"ATTACH{exists_sql} {this}{expressions}" 4818 4819 def detach_sql(self, expression: exp.Detach) -> str: 4820 this = self.sql(expression, "this") 4821 # the DATABASE keyword is required if IF EXISTS is set 4822 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4823 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4824 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4825 4826 return f"DETACH{exists_sql} {this}" 4827 4828 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4829 this = self.sql(expression, "this") 4830 value = self.sql(expression, "expression") 4831 value = f" {value}" if value else "" 4832 return f"{this}{value}" 4833 4834 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4835 this_sql = self.sql(expression, "this") 4836 if isinstance(expression.this, exp.Table): 4837 this_sql = f"TABLE {this_sql}" 4838 4839 return self.func( 4840 "FEATURES_AT_TIME", 4841 this_sql, 4842 expression.args.get("time"), 4843 expression.args.get("num_rows"), 4844 expression.args.get("ignore_feature_nulls"), 4845 ) 4846 4847 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4848 return ( 4849 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4850 ) 4851 4852 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4853 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4854 encode = f"{encode} {self.sql(expression, 'this')}" 4855 4856 properties = expression.args.get("properties") 4857 if properties: 4858 encode = f"{encode} {self.properties(properties)}" 4859 4860 return encode 4861 4862 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4863 this = self.sql(expression, "this") 4864 include = f"INCLUDE {this}" 4865 4866 column_def = self.sql(expression, "column_def") 4867 if column_def: 4868 include = f"{include} {column_def}" 4869 4870 alias = self.sql(expression, "alias") 4871 if alias: 4872 include = f"{include} AS {alias}" 4873 4874 return include 4875 4876 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4877 name = f"NAME {self.sql(expression, 'this')}" 4878 return self.func("XMLELEMENT", name, *expression.expressions) 4879 4880 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 4881 this = self.sql(expression, "this") 4882 expr = self.sql(expression, "expression") 4883 expr = f"({expr})" if expr else "" 4884 return f"{this}{expr}" 4885 4886 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4887 partitions = self.expressions(expression, "partition_expressions") 4888 create = self.expressions(expression, "create_expressions") 4889 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4890 4891 def partitionbyrangepropertydynamic_sql( 4892 self, expression: exp.PartitionByRangePropertyDynamic 4893 ) -> str: 4894 start = self.sql(expression, "start") 4895 end = self.sql(expression, "end") 4896 4897 every = expression.args["every"] 4898 if isinstance(every, exp.Interval) and every.this.is_string: 4899 every.this.replace(exp.Literal.number(every.name)) 4900 4901 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4902 4903 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4904 name = self.sql(expression, "this") 4905 values = self.expressions(expression, flat=True) 4906 4907 return f"NAME {name} VALUE {values}" 4908 4909 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4910 kind = self.sql(expression, "kind") 4911 sample = self.sql(expression, "sample") 4912 return f"SAMPLE {sample} {kind}" 4913 4914 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4915 kind = self.sql(expression, "kind") 4916 option = self.sql(expression, "option") 4917 option = f" {option}" if option else "" 4918 this = self.sql(expression, "this") 4919 this = f" {this}" if this else "" 4920 columns = self.expressions(expression) 4921 columns = f" {columns}" if columns else "" 4922 return f"{kind}{option} STATISTICS{this}{columns}" 4923 4924 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4925 this = self.sql(expression, "this") 4926 columns = self.expressions(expression) 4927 inner_expression = self.sql(expression, "expression") 4928 inner_expression = f" {inner_expression}" if inner_expression else "" 4929 update_options = self.sql(expression, "update_options") 4930 update_options = f" {update_options} UPDATE" if update_options else "" 4931 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4932 4933 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4934 kind = self.sql(expression, "kind") 4935 kind = f" {kind}" if kind else "" 4936 return f"DELETE{kind} STATISTICS" 4937 4938 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4939 inner_expression = self.sql(expression, "expression") 4940 return f"LIST CHAINED ROWS{inner_expression}" 4941 4942 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4943 kind = self.sql(expression, "kind") 4944 this = self.sql(expression, "this") 4945 this = f" {this}" if this else "" 4946 inner_expression = self.sql(expression, "expression") 4947 return f"VALIDATE {kind}{this}{inner_expression}" 4948 4949 def analyze_sql(self, expression: exp.Analyze) -> str: 4950 options = self.expressions(expression, key="options", sep=" ") 4951 options = f" {options}" if options else "" 4952 kind = self.sql(expression, "kind") 4953 kind = f" {kind}" if kind else "" 4954 this = self.sql(expression, "this") 4955 this = f" {this}" if this else "" 4956 mode = self.sql(expression, "mode") 4957 mode = f" {mode}" if mode else "" 4958 properties = self.sql(expression, "properties") 4959 properties = f" {properties}" if properties else "" 4960 partition = self.sql(expression, "partition") 4961 partition = f" {partition}" if partition else "" 4962 inner_expression = self.sql(expression, "expression") 4963 inner_expression = f" {inner_expression}" if inner_expression else "" 4964 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 4965 4966 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4967 this = self.sql(expression, "this") 4968 namespaces = self.expressions(expression, key="namespaces") 4969 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4970 passing = self.expressions(expression, key="passing") 4971 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4972 columns = self.expressions(expression, key="columns") 4973 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4974 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4975 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 4976 4977 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 4978 this = self.sql(expression, "this") 4979 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 4980 4981 def export_sql(self, expression: exp.Export) -> str: 4982 this = self.sql(expression, "this") 4983 connection = self.sql(expression, "connection") 4984 connection = f"WITH CONNECTION {connection} " if connection else "" 4985 options = self.sql(expression, "options") 4986 return f"EXPORT DATA {connection}{options} AS {this}" 4987 4988 def declare_sql(self, expression: exp.Declare) -> str: 4989 return f"DECLARE {self.expressions(expression, flat=True)}" 4990 4991 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4992 variable = self.sql(expression, "this") 4993 default = self.sql(expression, "default") 4994 default = f" = {default}" if default else "" 4995 4996 kind = self.sql(expression, "kind") 4997 if isinstance(expression.args.get("kind"), exp.Schema): 4998 kind = f"TABLE {kind}" 4999 5000 return f"{variable} AS {kind}{default}" 5001 5002 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5003 kind = self.sql(expression, "kind") 5004 this = self.sql(expression, "this") 5005 set = self.sql(expression, "expression") 5006 using = self.sql(expression, "using") 5007 using = f" USING {using}" if using else "" 5008 5009 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5010 5011 return f"{kind_sql} {this} SET {set}{using}" 5012 5013 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5014 params = self.expressions(expression, key="params", flat=True) 5015 return self.func(expression.name, *expression.expressions) + f"({params})" 5016 5017 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5018 return self.func(expression.name, *expression.expressions) 5019 5020 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5021 return self.anonymousaggfunc_sql(expression) 5022 5023 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5024 return self.parameterizedagg_sql(expression) 5025 5026 def show_sql(self, expression: exp.Show) -> str: 5027 self.unsupported("Unsupported SHOW statement") 5028 return "" 5029 5030 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5031 # Snowflake GET/PUT statements: 5032 # PUT <file> <internalStage> <properties> 5033 # GET <internalStage> <file> <properties> 5034 props = expression.args.get("properties") 5035 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5036 this = self.sql(expression, "this") 5037 target = self.sql(expression, "target") 5038 5039 if isinstance(expression, exp.Put): 5040 return f"PUT {this} {target}{props_sql}" 5041 else: 5042 return f"GET {target} {this}{props_sql}" 5043 5044 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5045 this = self.sql(expression, "this") 5046 expr = self.sql(expression, "expression") 5047 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5048 return f"TRANSLATE({this} USING {expr}{with_error})" 5049 5050 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5051 if self.SUPPORTS_DECODE_CASE: 5052 return self.func("DECODE", *expression.expressions) 5053 5054 expression, *expressions = expression.expressions 5055 5056 ifs = [] 5057 for search, result in zip(expressions[::2], expressions[1::2]): 5058 if isinstance(search, exp.Literal): 5059 ifs.append(exp.If(this=expression.eq(search), true=result)) 5060 elif isinstance(search, exp.Null): 5061 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5062 else: 5063 if isinstance(search, exp.Binary): 5064 search = exp.paren(search) 5065 5066 cond = exp.or_( 5067 expression.eq(search), 5068 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5069 copy=False, 5070 ) 5071 ifs.append(exp.If(this=cond, true=result)) 5072 5073 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5074 return self.sql(case) 5075 5076 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5077 this = self.sql(expression, "this") 5078 this = self.seg(this, sep="") 5079 dimensions = self.expressions( 5080 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5081 ) 5082 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5083 metrics = self.expressions( 5084 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5085 ) 5086 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5087 where = self.sql(expression, "where") 5088 where = self.seg(f"WHERE {where}") if where else "" 5089 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether to format the produced SQL string. Default: False.
- identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether to normalize identifiers to lowercase. Default: False.
- pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
- indent: The indentation size in a formatted string. For example, this affects the
indentation of subqueries and filters under a
WHERE
clause. Default: 2. - normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
- unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, Type[sqlglot.dialects.Dialect], NoneType] = None)
716 def __init__( 717 self, 718 pretty: t.Optional[bool] = None, 719 identify: str | bool = False, 720 normalize: bool = False, 721 pad: int = 2, 722 indent: int = 2, 723 normalize_functions: t.Optional[str | bool] = None, 724 unsupported_level: ErrorLevel = ErrorLevel.WARN, 725 max_unsupported: int = 3, 726 leading_comma: bool = False, 727 max_text_width: int = 80, 728 comments: bool = True, 729 dialect: DialectType = None, 730 ): 731 import sqlglot 732 from sqlglot.dialects import Dialect 733 734 self.pretty = pretty if pretty is not None else sqlglot.pretty 735 self.identify = identify 736 self.normalize = normalize 737 self.pad = pad 738 self._indent = indent 739 self.unsupported_level = unsupported_level 740 self.max_unsupported = max_unsupported 741 self.leading_comma = leading_comma 742 self.max_text_width = max_text_width 743 self.comments = comments 744 self.dialect = Dialect.get_or_raise(dialect) 745 746 # This is both a Dialect property and a Generator argument, so we prioritize the latter 747 self.normalize_functions = ( 748 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 749 ) 750 751 self.unsupported_messages: t.List[str] = [] 752 self._escaped_quote_end: str = ( 753 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 754 ) 755 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 756 757 self._next_name = name_sequence("_t") 758 759 self._identifier_start = self.dialect.IDENTIFIER_START 760 self._identifier_end = self.dialect.IDENTIFIER_END 761 762 self._quote_json_path_key_using_brackets = True
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] =
{<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Uuid'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.JSONPathScript'>, <class 'sqlglot.expressions.JSONPathRoot'>, <class 'sqlglot.expressions.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'>}
TYPE_MAPPING =
{<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TIME_PART_SINGULARS =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
PROPERTIES_LOCATION =
{<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Command'>, <class 'sqlglot.expressions.Create'>, <class 'sqlglot.expressions.Describe'>, <class 'sqlglot.expressions.Delete'>, <class 'sqlglot.expressions.Drop'>, <class 'sqlglot.expressions.From'>, <class 'sqlglot.expressions.Insert'>, <class 'sqlglot.expressions.Join'>, <class 'sqlglot.expressions.MultitableInserts'>, <class 'sqlglot.expressions.Order'>, <class 'sqlglot.expressions.Group'>, <class 'sqlglot.expressions.Having'>, <class 'sqlglot.expressions.Select'>, <class 'sqlglot.expressions.SetOperation'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES =
{<Type.NCHAR: 'NCHAR'>, <Type.CHAR: 'CHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.VARCHAR: 'VARCHAR'>}
764 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 765 """ 766 Generates the SQL string corresponding to the given syntax tree. 767 768 Args: 769 expression: The syntax tree. 770 copy: Whether to copy the expression. The generator performs mutations so 771 it is safer to copy. 772 773 Returns: 774 The SQL string corresponding to `expression`. 775 """ 776 if copy: 777 expression = expression.copy() 778 779 expression = self.preprocess(expression) 780 781 self.unsupported_messages = [] 782 sql = self.sql(expression).strip() 783 784 if self.pretty: 785 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 786 787 if self.unsupported_level == ErrorLevel.IGNORE: 788 return sql 789 790 if self.unsupported_level == ErrorLevel.WARN: 791 for msg in self.unsupported_messages: 792 logger.warning(msg) 793 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 794 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 795 796 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:
798 def preprocess(self, expression: exp.Expression) -> exp.Expression: 799 """Apply generic preprocessing transformations to a given expression.""" 800 expression = self._move_ctes_to_top_level(expression) 801 802 if self.ENSURE_BOOLS: 803 from sqlglot.transforms import ensure_bools 804 805 expression = ensure_bools(expression) 806 807 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
831 def sanitize_comment(self, comment: str) -> str: 832 comment = " " + comment if comment[0].strip() else comment 833 comment = comment + " " if comment[-1].strip() else comment 834 835 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 836 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 837 comment = comment.replace("*/", "* /") 838 839 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
841 def maybe_comment( 842 self, 843 sql: str, 844 expression: t.Optional[exp.Expression] = None, 845 comments: t.Optional[t.List[str]] = None, 846 separated: bool = False, 847 ) -> str: 848 comments = ( 849 ((expression and expression.comments) if comments is None else comments) # type: ignore 850 if self.comments 851 else None 852 ) 853 854 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 855 return sql 856 857 comments_sql = " ".join( 858 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 859 ) 860 861 if not comments_sql: 862 return sql 863 864 comments_sql = self._replace_line_breaks(comments_sql) 865 866 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 867 return ( 868 f"{self.sep()}{comments_sql}{sql}" 869 if not sql or sql[0].isspace() 870 else f"{comments_sql}{self.sep()}{sql}" 871 ) 872 873 return f"{sql} {comments_sql}"
875 def wrap(self, expression: exp.Expression | str) -> str: 876 this_sql = ( 877 self.sql(expression) 878 if isinstance(expression, exp.UNWRAPPED_QUERIES) 879 else self.sql(expression, "this") 880 ) 881 if not this_sql: 882 return "()" 883 884 this_sql = self.indent(this_sql, level=1, pad=0) 885 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:
901 def indent( 902 self, 903 sql: str, 904 level: int = 0, 905 pad: t.Optional[int] = None, 906 skip_first: bool = False, 907 skip_last: bool = False, 908 ) -> str: 909 if not self.pretty or not sql: 910 return sql 911 912 pad = self.pad if pad is None else pad 913 lines = sql.split("\n") 914 915 return "\n".join( 916 ( 917 line 918 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 919 else f"{' ' * (level * self._indent + pad)}{line}" 920 ) 921 for i, line in enumerate(lines) 922 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
924 def sql( 925 self, 926 expression: t.Optional[str | exp.Expression], 927 key: t.Optional[str] = None, 928 comment: bool = True, 929 ) -> str: 930 if not expression: 931 return "" 932 933 if isinstance(expression, str): 934 return expression 935 936 if key: 937 value = expression.args.get(key) 938 if value: 939 return self.sql(value) 940 return "" 941 942 transform = self.TRANSFORMS.get(expression.__class__) 943 944 if callable(transform): 945 sql = transform(self, expression) 946 elif isinstance(expression, exp.Expression): 947 exp_handler_name = f"{expression.key}_sql" 948 949 if hasattr(self, exp_handler_name): 950 sql = getattr(self, exp_handler_name)(expression) 951 elif isinstance(expression, exp.Func): 952 sql = self.function_fallback_sql(expression) 953 elif isinstance(expression, exp.Property): 954 sql = self.property_sql(expression) 955 else: 956 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 957 else: 958 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 959 960 return self.maybe_comment(sql, expression) if self.comments and comment else sql
967 def cache_sql(self, expression: exp.Cache) -> str: 968 lazy = " LAZY" if expression.args.get("lazy") else "" 969 table = self.sql(expression, "this") 970 options = expression.args.get("options") 971 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 972 sql = self.sql(expression, "expression") 973 sql = f" AS{self.sep()}{sql}" if sql else "" 974 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 975 return self.prepend_ctes(expression, sql)
977 def characterset_sql(self, expression: exp.CharacterSet) -> str: 978 if isinstance(expression.parent, exp.Cast): 979 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 980 default = "DEFAULT " if expression.args.get("default") else "" 981 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
995 def column_sql(self, expression: exp.Column) -> str: 996 join_mark = " (+)" if expression.args.get("join_mark") else "" 997 998 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 999 join_mark = "" 1000 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1001 1002 return f"{self.column_parts(expression)}{join_mark}"
1010 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1011 column = self.sql(expression, "this") 1012 kind = self.sql(expression, "kind") 1013 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1014 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1015 kind = f"{sep}{kind}" if kind else "" 1016 constraints = f" {constraints}" if constraints else "" 1017 position = self.sql(expression, "position") 1018 position = f" {position}" if position else "" 1019 1020 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1021 kind = "" 1022 1023 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1030 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1031 this = self.sql(expression, "this") 1032 if expression.args.get("not_null"): 1033 persisted = " PERSISTED NOT NULL" 1034 elif expression.args.get("persisted"): 1035 persisted = " PERSISTED" 1036 else: 1037 persisted = "" 1038 1039 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1052 def generatedasidentitycolumnconstraint_sql( 1053 self, expression: exp.GeneratedAsIdentityColumnConstraint 1054 ) -> str: 1055 this = "" 1056 if expression.this is not None: 1057 on_null = " ON NULL" if expression.args.get("on_null") else "" 1058 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1059 1060 start = expression.args.get("start") 1061 start = f"START WITH {start}" if start else "" 1062 increment = expression.args.get("increment") 1063 increment = f" INCREMENT BY {increment}" if increment else "" 1064 minvalue = expression.args.get("minvalue") 1065 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1066 maxvalue = expression.args.get("maxvalue") 1067 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1068 cycle = expression.args.get("cycle") 1069 cycle_sql = "" 1070 1071 if cycle is not None: 1072 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1073 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1074 1075 sequence_opts = "" 1076 if start or increment or cycle_sql: 1077 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1078 sequence_opts = f" ({sequence_opts.strip()})" 1079 1080 expr = self.sql(expression, "expression") 1081 expr = f"({expr})" if expr else "IDENTITY" 1082 1083 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1085 def generatedasrowcolumnconstraint_sql( 1086 self, expression: exp.GeneratedAsRowColumnConstraint 1087 ) -> str: 1088 start = "START" if expression.args.get("start") else "END" 1089 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1090 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:
1100 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1101 desc = expression.args.get("desc") 1102 if desc is not None: 1103 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1104 options = self.expressions(expression, key="options", flat=True, sep=" ") 1105 options = f" {options}" if options else "" 1106 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1108 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1109 this = self.sql(expression, "this") 1110 this = f" {this}" if this else "" 1111 index_type = expression.args.get("index_type") 1112 index_type = f" USING {index_type}" if index_type else "" 1113 on_conflict = self.sql(expression, "on_conflict") 1114 on_conflict = f" {on_conflict}" if on_conflict else "" 1115 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1116 options = self.expressions(expression, key="options", flat=True, sep=" ") 1117 options = f" {options}" if options else "" 1118 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1123 def create_sql(self, expression: exp.Create) -> str: 1124 kind = self.sql(expression, "kind") 1125 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1126 properties = expression.args.get("properties") 1127 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1128 1129 this = self.createable_sql(expression, properties_locs) 1130 1131 properties_sql = "" 1132 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1133 exp.Properties.Location.POST_WITH 1134 ): 1135 properties_sql = self.sql( 1136 exp.Properties( 1137 expressions=[ 1138 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1139 *properties_locs[exp.Properties.Location.POST_WITH], 1140 ] 1141 ) 1142 ) 1143 1144 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1145 properties_sql = self.sep() + properties_sql 1146 elif not self.pretty: 1147 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1148 properties_sql = f" {properties_sql}" 1149 1150 begin = " BEGIN" if expression.args.get("begin") else "" 1151 end = " END" if expression.args.get("end") else "" 1152 1153 expression_sql = self.sql(expression, "expression") 1154 if expression_sql: 1155 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1156 1157 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1158 postalias_props_sql = "" 1159 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1160 postalias_props_sql = self.properties( 1161 exp.Properties( 1162 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1163 ), 1164 wrapped=False, 1165 ) 1166 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1167 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1168 1169 postindex_props_sql = "" 1170 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1171 postindex_props_sql = self.properties( 1172 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1173 wrapped=False, 1174 prefix=" ", 1175 ) 1176 1177 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1178 indexes = f" {indexes}" if indexes else "" 1179 index_sql = indexes + postindex_props_sql 1180 1181 replace = " OR REPLACE" if expression.args.get("replace") else "" 1182 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1183 unique = " UNIQUE" if expression.args.get("unique") else "" 1184 1185 clustered = expression.args.get("clustered") 1186 if clustered is None: 1187 clustered_sql = "" 1188 elif clustered: 1189 clustered_sql = " CLUSTERED COLUMNSTORE" 1190 else: 1191 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1192 1193 postcreate_props_sql = "" 1194 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1195 postcreate_props_sql = self.properties( 1196 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1197 sep=" ", 1198 prefix=" ", 1199 wrapped=False, 1200 ) 1201 1202 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1203 1204 postexpression_props_sql = "" 1205 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1206 postexpression_props_sql = self.properties( 1207 exp.Properties( 1208 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1209 ), 1210 sep=" ", 1211 prefix=" ", 1212 wrapped=False, 1213 ) 1214 1215 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1216 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1217 no_schema_binding = ( 1218 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1219 ) 1220 1221 clone = self.sql(expression, "clone") 1222 clone = f" {clone}" if clone else "" 1223 1224 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1225 properties_expression = f"{expression_sql}{properties_sql}" 1226 else: 1227 properties_expression = f"{properties_sql}{expression_sql}" 1228 1229 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1230 return self.prepend_ctes(expression, expression_sql)
1232 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1233 start = self.sql(expression, "start") 1234 start = f"START WITH {start}" if start else "" 1235 increment = self.sql(expression, "increment") 1236 increment = f" INCREMENT BY {increment}" if increment else "" 1237 minvalue = self.sql(expression, "minvalue") 1238 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1239 maxvalue = self.sql(expression, "maxvalue") 1240 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1241 owned = self.sql(expression, "owned") 1242 owned = f" OWNED BY {owned}" if owned else "" 1243 1244 cache = expression.args.get("cache") 1245 if cache is None: 1246 cache_str = "" 1247 elif cache is True: 1248 cache_str = " CACHE" 1249 else: 1250 cache_str = f" CACHE {cache}" 1251 1252 options = self.expressions(expression, key="options", flat=True, sep=" ") 1253 options = f" {options}" if options else "" 1254 1255 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1257 def clone_sql(self, expression: exp.Clone) -> str: 1258 this = self.sql(expression, "this") 1259 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1260 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1261 return f"{shallow}{keyword} {this}"
1263 def describe_sql(self, expression: exp.Describe) -> str: 1264 style = expression.args.get("style") 1265 style = f" {style}" if style else "" 1266 partition = self.sql(expression, "partition") 1267 partition = f" {partition}" if partition else "" 1268 format = self.sql(expression, "format") 1269 format = f" {format}" if format else "" 1270 1271 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1283 def with_sql(self, expression: exp.With) -> str: 1284 sql = self.expressions(expression, flat=True) 1285 recursive = ( 1286 "RECURSIVE " 1287 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1288 else "" 1289 ) 1290 search = self.sql(expression, "search") 1291 search = f" {search}" if search else "" 1292 1293 return f"WITH {recursive}{sql}{search}"
1295 def cte_sql(self, expression: exp.CTE) -> str: 1296 alias = expression.args.get("alias") 1297 if alias: 1298 alias.add_comments(expression.pop_comments()) 1299 1300 alias_sql = self.sql(expression, "alias") 1301 1302 materialized = expression.args.get("materialized") 1303 if materialized is False: 1304 materialized = "NOT MATERIALIZED " 1305 elif materialized: 1306 materialized = "MATERIALIZED " 1307 1308 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1310 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1311 alias = self.sql(expression, "this") 1312 columns = self.expressions(expression, key="columns", flat=True) 1313 columns = f"({columns})" if columns else "" 1314 1315 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1316 columns = "" 1317 self.unsupported("Named columns are not supported in table alias.") 1318 1319 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1320 alias = self._next_name() 1321 1322 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1330 def hexstring_sql( 1331 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1332 ) -> str: 1333 this = self.sql(expression, "this") 1334 is_integer_type = expression.args.get("is_integer") 1335 1336 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1337 not self.dialect.HEX_START and not binary_function_repr 1338 ): 1339 # Integer representation will be returned if: 1340 # - The read dialect treats the hex value as integer literal but not the write 1341 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1342 return f"{int(this, 16)}" 1343 1344 if not is_integer_type: 1345 # Read dialect treats the hex value as BINARY/BLOB 1346 if binary_function_repr: 1347 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1348 return self.func(binary_function_repr, exp.Literal.string(this)) 1349 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1350 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1351 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1352 1353 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1361 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1362 this = self.sql(expression, "this") 1363 escape = expression.args.get("escape") 1364 1365 if self.dialect.UNICODE_START: 1366 escape_substitute = r"\\\1" 1367 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1368 else: 1369 escape_substitute = r"\\u\1" 1370 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1371 1372 if escape: 1373 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1374 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1375 else: 1376 escape_pattern = ESCAPED_UNICODE_RE 1377 escape_sql = "" 1378 1379 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1380 this = escape_pattern.sub(escape_substitute, this) 1381 1382 return f"{left_quote}{this}{right_quote}{escape_sql}"
1384 def rawstring_sql(self, expression: exp.RawString) -> str: 1385 string = expression.this 1386 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1387 string = string.replace("\\", "\\\\") 1388 1389 string = self.escape_str(string, escape_backslash=False) 1390 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1398 def datatype_sql(self, expression: exp.DataType) -> str: 1399 nested = "" 1400 values = "" 1401 interior = self.expressions(expression, flat=True) 1402 1403 type_value = expression.this 1404 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1405 type_sql = self.sql(expression, "kind") 1406 else: 1407 type_sql = ( 1408 self.TYPE_MAPPING.get(type_value, type_value.value) 1409 if isinstance(type_value, exp.DataType.Type) 1410 else type_value 1411 ) 1412 1413 if interior: 1414 if expression.args.get("nested"): 1415 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1416 if expression.args.get("values") is not None: 1417 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1418 values = self.expressions(expression, key="values", flat=True) 1419 values = f"{delimiters[0]}{values}{delimiters[1]}" 1420 elif type_value == exp.DataType.Type.INTERVAL: 1421 nested = f" {interior}" 1422 else: 1423 nested = f"({interior})" 1424 1425 type_sql = f"{type_sql}{nested}{values}" 1426 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1427 exp.DataType.Type.TIMETZ, 1428 exp.DataType.Type.TIMESTAMPTZ, 1429 ): 1430 type_sql = f"{type_sql} WITH TIME ZONE" 1431 1432 return type_sql
1434 def directory_sql(self, expression: exp.Directory) -> str: 1435 local = "LOCAL " if expression.args.get("local") else "" 1436 row_format = self.sql(expression, "row_format") 1437 row_format = f" {row_format}" if row_format else "" 1438 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1440 def delete_sql(self, expression: exp.Delete) -> str: 1441 this = self.sql(expression, "this") 1442 this = f" FROM {this}" if this else "" 1443 using = self.sql(expression, "using") 1444 using = f" USING {using}" if using else "" 1445 cluster = self.sql(expression, "cluster") 1446 cluster = f" {cluster}" if cluster else "" 1447 where = self.sql(expression, "where") 1448 returning = self.sql(expression, "returning") 1449 limit = self.sql(expression, "limit") 1450 tables = self.expressions(expression, key="tables") 1451 tables = f" {tables}" if tables else "" 1452 if self.RETURNING_END: 1453 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1454 else: 1455 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1456 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1458 def drop_sql(self, expression: exp.Drop) -> str: 1459 this = self.sql(expression, "this") 1460 expressions = self.expressions(expression, flat=True) 1461 expressions = f" ({expressions})" if expressions else "" 1462 kind = expression.args["kind"] 1463 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1464 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1465 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1466 on_cluster = self.sql(expression, "cluster") 1467 on_cluster = f" {on_cluster}" if on_cluster else "" 1468 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1469 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1470 cascade = " CASCADE" if expression.args.get("cascade") else "" 1471 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1472 purge = " PURGE" if expression.args.get("purge") else "" 1473 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1475 def set_operation(self, expression: exp.SetOperation) -> str: 1476 op_type = type(expression) 1477 op_name = op_type.key.upper() 1478 1479 distinct = expression.args.get("distinct") 1480 if ( 1481 distinct is False 1482 and op_type in (exp.Except, exp.Intersect) 1483 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1484 ): 1485 self.unsupported(f"{op_name} ALL is not supported") 1486 1487 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1488 1489 if distinct is None: 1490 distinct = default_distinct 1491 if distinct is None: 1492 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1493 1494 if distinct is default_distinct: 1495 distinct_or_all = "" 1496 else: 1497 distinct_or_all = " DISTINCT" if distinct else " ALL" 1498 1499 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1500 side_kind = f"{side_kind} " if side_kind else "" 1501 1502 by_name = " BY NAME" if expression.args.get("by_name") else "" 1503 on = self.expressions(expression, key="on", flat=True) 1504 on = f" ON ({on})" if on else "" 1505 1506 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1508 def set_operations(self, expression: exp.SetOperation) -> str: 1509 if not self.SET_OP_MODIFIERS: 1510 limit = expression.args.get("limit") 1511 order = expression.args.get("order") 1512 1513 if limit or order: 1514 select = self._move_ctes_to_top_level( 1515 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1516 ) 1517 1518 if limit: 1519 select = select.limit(limit.pop(), copy=False) 1520 if order: 1521 select = select.order_by(order.pop(), copy=False) 1522 return self.sql(select) 1523 1524 sqls: t.List[str] = [] 1525 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1526 1527 while stack: 1528 node = stack.pop() 1529 1530 if isinstance(node, exp.SetOperation): 1531 stack.append(node.expression) 1532 stack.append( 1533 self.maybe_comment( 1534 self.set_operation(node), comments=node.comments, separated=True 1535 ) 1536 ) 1537 stack.append(node.this) 1538 else: 1539 sqls.append(self.sql(node)) 1540 1541 this = self.sep().join(sqls) 1542 this = self.query_modifiers(expression, this) 1543 return self.prepend_ctes(expression, this)
1545 def fetch_sql(self, expression: exp.Fetch) -> str: 1546 direction = expression.args.get("direction") 1547 direction = f" {direction}" if direction else "" 1548 count = self.sql(expression, "count") 1549 count = f" {count}" if count else "" 1550 limit_options = self.sql(expression, "limit_options") 1551 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1552 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1554 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1555 percent = " PERCENT" if expression.args.get("percent") else "" 1556 rows = " ROWS" if expression.args.get("rows") else "" 1557 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1558 if not with_ties and rows: 1559 with_ties = " ONLY" 1560 return f"{percent}{rows}{with_ties}"
1562 def filter_sql(self, expression: exp.Filter) -> str: 1563 if self.AGGREGATE_FILTER_SUPPORTED: 1564 this = self.sql(expression, "this") 1565 where = self.sql(expression, "expression").strip() 1566 return f"{this} FILTER({where})" 1567 1568 agg = expression.this 1569 agg_arg = agg.this 1570 cond = expression.expression.this 1571 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1572 return self.sql(agg)
1581 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1582 using = self.sql(expression, "using") 1583 using = f" USING {using}" if using else "" 1584 columns = self.expressions(expression, key="columns", flat=True) 1585 columns = f"({columns})" if columns else "" 1586 partition_by = self.expressions(expression, key="partition_by", flat=True) 1587 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1588 where = self.sql(expression, "where") 1589 include = self.expressions(expression, key="include", flat=True) 1590 if include: 1591 include = f" INCLUDE ({include})" 1592 with_storage = self.expressions(expression, key="with_storage", flat=True) 1593 with_storage = f" WITH ({with_storage})" if with_storage else "" 1594 tablespace = self.sql(expression, "tablespace") 1595 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1596 on = self.sql(expression, "on") 1597 on = f" ON {on}" if on else "" 1598 1599 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1601 def index_sql(self, expression: exp.Index) -> str: 1602 unique = "UNIQUE " if expression.args.get("unique") else "" 1603 primary = "PRIMARY " if expression.args.get("primary") else "" 1604 amp = "AMP " if expression.args.get("amp") else "" 1605 name = self.sql(expression, "this") 1606 name = f"{name} " if name else "" 1607 table = self.sql(expression, "table") 1608 table = f"{self.INDEX_ON} {table}" if table else "" 1609 1610 index = "INDEX " if not table else "" 1611 1612 params = self.sql(expression, "params") 1613 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1615 def identifier_sql(self, expression: exp.Identifier) -> str: 1616 text = expression.name 1617 lower = text.lower() 1618 text = lower if self.normalize and not expression.quoted else text 1619 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1620 if ( 1621 expression.quoted 1622 or self.dialect.can_identify(text, self.identify) 1623 or lower in self.RESERVED_KEYWORDS 1624 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1625 ): 1626 text = f"{self._identifier_start}{text}{self._identifier_end}" 1627 return text
1642 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1643 input_format = self.sql(expression, "input_format") 1644 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1645 output_format = self.sql(expression, "output_format") 1646 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1647 return self.sep().join((input_format, output_format))
1657 def properties_sql(self, expression: exp.Properties) -> str: 1658 root_properties = [] 1659 with_properties = [] 1660 1661 for p in expression.expressions: 1662 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1663 if p_loc == exp.Properties.Location.POST_WITH: 1664 with_properties.append(p) 1665 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1666 root_properties.append(p) 1667 1668 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1669 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1670 1671 if root_props and with_props and not self.pretty: 1672 with_props = " " + with_props 1673 1674 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1681 def properties( 1682 self, 1683 properties: exp.Properties, 1684 prefix: str = "", 1685 sep: str = ", ", 1686 suffix: str = "", 1687 wrapped: bool = True, 1688 ) -> str: 1689 if properties.expressions: 1690 expressions = self.expressions(properties, sep=sep, indent=False) 1691 if expressions: 1692 expressions = self.wrap(expressions) if wrapped else expressions 1693 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1694 return ""
1699 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1700 properties_locs = defaultdict(list) 1701 for p in properties.expressions: 1702 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1703 if p_loc != exp.Properties.Location.UNSUPPORTED: 1704 properties_locs[p_loc].append(p) 1705 else: 1706 self.unsupported(f"Unsupported property {p.key}") 1707 1708 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1715 def property_sql(self, expression: exp.Property) -> str: 1716 property_cls = expression.__class__ 1717 if property_cls == exp.Property: 1718 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1719 1720 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1721 if not property_name: 1722 self.unsupported(f"Unsupported property {expression.key}") 1723 1724 return f"{property_name}={self.sql(expression, 'this')}"
1726 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1727 if self.SUPPORTS_CREATE_TABLE_LIKE: 1728 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1729 options = f" {options}" if options else "" 1730 1731 like = f"LIKE {self.sql(expression, 'this')}{options}" 1732 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1733 like = f"({like})" 1734 1735 return like 1736 1737 if expression.expressions: 1738 self.unsupported("Transpilation of LIKE property options is unsupported") 1739 1740 select = exp.select("*").from_(expression.this).limit(0) 1741 return f"AS {self.sql(select)}"
1748 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1749 no = "NO " if expression.args.get("no") else "" 1750 local = expression.args.get("local") 1751 local = f"{local} " if local else "" 1752 dual = "DUAL " if expression.args.get("dual") else "" 1753 before = "BEFORE " if expression.args.get("before") else "" 1754 after = "AFTER " if expression.args.get("after") else "" 1755 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1771 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1772 if expression.args.get("no"): 1773 return "NO MERGEBLOCKRATIO" 1774 if expression.args.get("default"): 1775 return "DEFAULT MERGEBLOCKRATIO" 1776 1777 percent = " PERCENT" if expression.args.get("percent") else "" 1778 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1780 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1781 default = expression.args.get("default") 1782 minimum = expression.args.get("minimum") 1783 maximum = expression.args.get("maximum") 1784 if default or minimum or maximum: 1785 if default: 1786 prop = "DEFAULT" 1787 elif minimum: 1788 prop = "MINIMUM" 1789 else: 1790 prop = "MAXIMUM" 1791 return f"{prop} DATABLOCKSIZE" 1792 units = expression.args.get("units") 1793 units = f" {units}" if units else "" 1794 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1796 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1797 autotemp = expression.args.get("autotemp") 1798 always = expression.args.get("always") 1799 default = expression.args.get("default") 1800 manual = expression.args.get("manual") 1801 never = expression.args.get("never") 1802 1803 if autotemp is not None: 1804 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1805 elif always: 1806 prop = "ALWAYS" 1807 elif default: 1808 prop = "DEFAULT" 1809 elif manual: 1810 prop = "MANUAL" 1811 elif never: 1812 prop = "NEVER" 1813 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1815 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1816 no = expression.args.get("no") 1817 no = " NO" if no else "" 1818 concurrent = expression.args.get("concurrent") 1819 concurrent = " CONCURRENT" if concurrent else "" 1820 target = self.sql(expression, "target") 1821 target = f" {target}" if target else "" 1822 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1824 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1825 if isinstance(expression.this, list): 1826 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1827 if expression.this: 1828 modulus = self.sql(expression, "this") 1829 remainder = self.sql(expression, "expression") 1830 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1831 1832 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1833 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1834 return f"FROM ({from_expressions}) TO ({to_expressions})"
1836 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1837 this = self.sql(expression, "this") 1838 1839 for_values_or_default = expression.expression 1840 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1841 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1842 else: 1843 for_values_or_default = " DEFAULT" 1844 1845 return f"PARTITION OF {this}{for_values_or_default}"
1847 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1848 kind = expression.args.get("kind") 1849 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1850 for_or_in = expression.args.get("for_or_in") 1851 for_or_in = f" {for_or_in}" if for_or_in else "" 1852 lock_type = expression.args.get("lock_type") 1853 override = " OVERRIDE" if expression.args.get("override") else "" 1854 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1856 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1857 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1858 statistics = expression.args.get("statistics") 1859 statistics_sql = "" 1860 if statistics is not None: 1861 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1862 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1864 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1865 this = self.sql(expression, "this") 1866 this = f"HISTORY_TABLE={this}" if this else "" 1867 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1868 data_consistency = ( 1869 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1870 ) 1871 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1872 retention_period = ( 1873 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1874 ) 1875 1876 if this: 1877 on_sql = self.func("ON", this, data_consistency, retention_period) 1878 else: 1879 on_sql = "ON" if expression.args.get("on") else "OFF" 1880 1881 sql = f"SYSTEM_VERSIONING={on_sql}" 1882 1883 return f"WITH({sql})" if expression.args.get("with") else sql
1885 def insert_sql(self, expression: exp.Insert) -> str: 1886 hint = self.sql(expression, "hint") 1887 overwrite = expression.args.get("overwrite") 1888 1889 if isinstance(expression.this, exp.Directory): 1890 this = " OVERWRITE" if overwrite else " INTO" 1891 else: 1892 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1893 1894 stored = self.sql(expression, "stored") 1895 stored = f" {stored}" if stored else "" 1896 alternative = expression.args.get("alternative") 1897 alternative = f" OR {alternative}" if alternative else "" 1898 ignore = " IGNORE" if expression.args.get("ignore") else "" 1899 is_function = expression.args.get("is_function") 1900 if is_function: 1901 this = f"{this} FUNCTION" 1902 this = f"{this} {self.sql(expression, 'this')}" 1903 1904 exists = " IF EXISTS" if expression.args.get("exists") else "" 1905 where = self.sql(expression, "where") 1906 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1907 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1908 on_conflict = self.sql(expression, "conflict") 1909 on_conflict = f" {on_conflict}" if on_conflict else "" 1910 by_name = " BY NAME" if expression.args.get("by_name") else "" 1911 returning = self.sql(expression, "returning") 1912 1913 if self.RETURNING_END: 1914 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1915 else: 1916 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1917 1918 partition_by = self.sql(expression, "partition") 1919 partition_by = f" {partition_by}" if partition_by else "" 1920 settings = self.sql(expression, "settings") 1921 settings = f" {settings}" if settings else "" 1922 1923 source = self.sql(expression, "source") 1924 source = f"TABLE {source}" if source else "" 1925 1926 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1927 return self.prepend_ctes(expression, sql)
1945 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1946 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1947 1948 constraint = self.sql(expression, "constraint") 1949 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1950 1951 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1952 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1953 action = self.sql(expression, "action") 1954 1955 expressions = self.expressions(expression, flat=True) 1956 if expressions: 1957 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1958 expressions = f" {set_keyword}{expressions}" 1959 1960 where = self.sql(expression, "where") 1961 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1966 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1967 fields = self.sql(expression, "fields") 1968 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1969 escaped = self.sql(expression, "escaped") 1970 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1971 items = self.sql(expression, "collection_items") 1972 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1973 keys = self.sql(expression, "map_keys") 1974 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1975 lines = self.sql(expression, "lines") 1976 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1977 null = self.sql(expression, "null") 1978 null = f" NULL DEFINED AS {null}" if null else "" 1979 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2007 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2008 table = self.table_parts(expression) 2009 only = "ONLY " if expression.args.get("only") else "" 2010 partition = self.sql(expression, "partition") 2011 partition = f" {partition}" if partition else "" 2012 version = self.sql(expression, "version") 2013 version = f" {version}" if version else "" 2014 alias = self.sql(expression, "alias") 2015 alias = f"{sep}{alias}" if alias else "" 2016 2017 sample = self.sql(expression, "sample") 2018 if self.dialect.ALIAS_POST_TABLESAMPLE: 2019 sample_pre_alias = sample 2020 sample_post_alias = "" 2021 else: 2022 sample_pre_alias = "" 2023 sample_post_alias = sample 2024 2025 hints = self.expressions(expression, key="hints", sep=" ") 2026 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2027 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2028 joins = self.indent( 2029 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2030 ) 2031 laterals = self.expressions(expression, key="laterals", sep="") 2032 2033 file_format = self.sql(expression, "format") 2034 if file_format: 2035 pattern = self.sql(expression, "pattern") 2036 pattern = f", PATTERN => {pattern}" if pattern else "" 2037 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2038 2039 ordinality = expression.args.get("ordinality") or "" 2040 if ordinality: 2041 ordinality = f" WITH ORDINALITY{alias}" 2042 alias = "" 2043 2044 when = self.sql(expression, "when") 2045 if when: 2046 table = f"{table} {when}" 2047 2048 changes = self.sql(expression, "changes") 2049 changes = f" {changes}" if changes else "" 2050 2051 rows_from = self.expressions(expression, key="rows_from") 2052 if rows_from: 2053 table = f"ROWS FROM {self.wrap(rows_from)}" 2054 2055 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2057 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2058 table = self.func("TABLE", expression.this) 2059 alias = self.sql(expression, "alias") 2060 alias = f" AS {alias}" if alias else "" 2061 sample = self.sql(expression, "sample") 2062 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2063 joins = self.indent( 2064 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2065 ) 2066 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2068 def tablesample_sql( 2069 self, 2070 expression: exp.TableSample, 2071 tablesample_keyword: t.Optional[str] = None, 2072 ) -> str: 2073 method = self.sql(expression, "method") 2074 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2075 numerator = self.sql(expression, "bucket_numerator") 2076 denominator = self.sql(expression, "bucket_denominator") 2077 field = self.sql(expression, "bucket_field") 2078 field = f" ON {field}" if field else "" 2079 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2080 seed = self.sql(expression, "seed") 2081 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2082 2083 size = self.sql(expression, "size") 2084 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2085 size = f"{size} ROWS" 2086 2087 percent = self.sql(expression, "percent") 2088 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2089 percent = f"{percent} PERCENT" 2090 2091 expr = f"{bucket}{percent}{size}" 2092 if self.TABLESAMPLE_REQUIRES_PARENS: 2093 expr = f"({expr})" 2094 2095 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2097 def pivot_sql(self, expression: exp.Pivot) -> str: 2098 expressions = self.expressions(expression, flat=True) 2099 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2100 2101 group = self.sql(expression, "group") 2102 2103 if expression.this: 2104 this = self.sql(expression, "this") 2105 if not expressions: 2106 return f"UNPIVOT {this}" 2107 2108 on = f"{self.seg('ON')} {expressions}" 2109 into = self.sql(expression, "into") 2110 into = f"{self.seg('INTO')} {into}" if into else "" 2111 using = self.expressions(expression, key="using", flat=True) 2112 using = f"{self.seg('USING')} {using}" if using else "" 2113 return f"{direction} {this}{on}{into}{using}{group}" 2114 2115 alias = self.sql(expression, "alias") 2116 alias = f" AS {alias}" if alias else "" 2117 2118 fields = self.expressions( 2119 expression, 2120 "fields", 2121 sep=" ", 2122 dynamic=True, 2123 new_line=True, 2124 skip_first=True, 2125 skip_last=True, 2126 ) 2127 2128 include_nulls = expression.args.get("include_nulls") 2129 if include_nulls is not None: 2130 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2131 else: 2132 nulls = "" 2133 2134 default_on_null = self.sql(expression, "default_on_null") 2135 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2136 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2147 def update_sql(self, expression: exp.Update) -> str: 2148 this = self.sql(expression, "this") 2149 set_sql = self.expressions(expression, flat=True) 2150 from_sql = self.sql(expression, "from") 2151 where_sql = self.sql(expression, "where") 2152 returning = self.sql(expression, "returning") 2153 order = self.sql(expression, "order") 2154 limit = self.sql(expression, "limit") 2155 if self.RETURNING_END: 2156 expression_sql = f"{from_sql}{where_sql}{returning}" 2157 else: 2158 expression_sql = f"{returning}{from_sql}{where_sql}" 2159 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2160 return self.prepend_ctes(expression, sql)
2162 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2163 values_as_table = values_as_table and self.VALUES_AS_TABLE 2164 2165 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2166 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2167 args = self.expressions(expression) 2168 alias = self.sql(expression, "alias") 2169 values = f"VALUES{self.seg('')}{args}" 2170 values = ( 2171 f"({values})" 2172 if self.WRAP_DERIVED_VALUES 2173 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2174 else values 2175 ) 2176 return f"{values} AS {alias}" if alias else values 2177 2178 # Converts `VALUES...` expression into a series of select unions. 2179 alias_node = expression.args.get("alias") 2180 column_names = alias_node and alias_node.columns 2181 2182 selects: t.List[exp.Query] = [] 2183 2184 for i, tup in enumerate(expression.expressions): 2185 row = tup.expressions 2186 2187 if i == 0 and column_names: 2188 row = [ 2189 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2190 ] 2191 2192 selects.append(exp.Select(expressions=row)) 2193 2194 if self.pretty: 2195 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2196 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2197 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2198 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2199 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2200 2201 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2202 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2203 return f"({unions}){alias}"
2208 @unsupported_args("expressions") 2209 def into_sql(self, expression: exp.Into) -> str: 2210 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2211 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2212 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2229 def group_sql(self, expression: exp.Group) -> str: 2230 group_by_all = expression.args.get("all") 2231 if group_by_all is True: 2232 modifier = " ALL" 2233 elif group_by_all is False: 2234 modifier = " DISTINCT" 2235 else: 2236 modifier = "" 2237 2238 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2239 2240 grouping_sets = self.expressions(expression, key="grouping_sets") 2241 cube = self.expressions(expression, key="cube") 2242 rollup = self.expressions(expression, key="rollup") 2243 2244 groupings = csv( 2245 self.seg(grouping_sets) if grouping_sets else "", 2246 self.seg(cube) if cube else "", 2247 self.seg(rollup) if rollup else "", 2248 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2249 sep=self.GROUPINGS_SEP, 2250 ) 2251 2252 if ( 2253 expression.expressions 2254 and groupings 2255 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2256 ): 2257 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2258 2259 return f"{group_by}{groupings}"
2265 def connect_sql(self, expression: exp.Connect) -> str: 2266 start = self.sql(expression, "start") 2267 start = self.seg(f"START WITH {start}") if start else "" 2268 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2269 connect = self.sql(expression, "connect") 2270 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2271 return start + connect
2276 def join_sql(self, expression: exp.Join) -> str: 2277 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2278 side = None 2279 else: 2280 side = expression.side 2281 2282 op_sql = " ".join( 2283 op 2284 for op in ( 2285 expression.method, 2286 "GLOBAL" if expression.args.get("global") else None, 2287 side, 2288 expression.kind, 2289 expression.hint if self.JOIN_HINTS else None, 2290 ) 2291 if op 2292 ) 2293 match_cond = self.sql(expression, "match_condition") 2294 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2295 on_sql = self.sql(expression, "on") 2296 using = expression.args.get("using") 2297 2298 if not on_sql and using: 2299 on_sql = csv(*(self.sql(column) for column in using)) 2300 2301 this = expression.this 2302 this_sql = self.sql(this) 2303 2304 exprs = self.expressions(expression) 2305 if exprs: 2306 this_sql = f"{this_sql},{self.seg(exprs)}" 2307 2308 if on_sql: 2309 on_sql = self.indent(on_sql, skip_first=True) 2310 space = self.seg(" " * self.pad) if self.pretty else " " 2311 if using: 2312 on_sql = f"{space}USING ({on_sql})" 2313 else: 2314 on_sql = f"{space}ON {on_sql}" 2315 elif not op_sql: 2316 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2317 return f" {this_sql}" 2318 2319 return f", {this_sql}" 2320 2321 if op_sql != "STRAIGHT_JOIN": 2322 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2323 2324 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2325 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:
2332 def lateral_op(self, expression: exp.Lateral) -> str: 2333 cross_apply = expression.args.get("cross_apply") 2334 2335 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2336 if cross_apply is True: 2337 op = "INNER JOIN " 2338 elif cross_apply is False: 2339 op = "LEFT JOIN " 2340 else: 2341 op = "" 2342 2343 return f"{op}LATERAL"
2345 def lateral_sql(self, expression: exp.Lateral) -> str: 2346 this = self.sql(expression, "this") 2347 2348 if expression.args.get("view"): 2349 alias = expression.args["alias"] 2350 columns = self.expressions(alias, key="columns", flat=True) 2351 table = f" {alias.name}" if alias.name else "" 2352 columns = f" AS {columns}" if columns else "" 2353 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2354 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2355 2356 alias = self.sql(expression, "alias") 2357 alias = f" AS {alias}" if alias else "" 2358 2359 ordinality = expression.args.get("ordinality") or "" 2360 if ordinality: 2361 ordinality = f" WITH ORDINALITY{alias}" 2362 alias = "" 2363 2364 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2366 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2367 this = self.sql(expression, "this") 2368 2369 args = [ 2370 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2371 for e in (expression.args.get(k) for k in ("offset", "expression")) 2372 if e 2373 ] 2374 2375 args_sql = ", ".join(self.sql(e) for e in args) 2376 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2377 expressions = self.expressions(expression, flat=True) 2378 limit_options = self.sql(expression, "limit_options") 2379 expressions = f" BY {expressions}" if expressions else "" 2380 2381 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2383 def offset_sql(self, expression: exp.Offset) -> str: 2384 this = self.sql(expression, "this") 2385 value = expression.expression 2386 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2387 expressions = self.expressions(expression, flat=True) 2388 expressions = f" BY {expressions}" if expressions else "" 2389 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2391 def setitem_sql(self, expression: exp.SetItem) -> str: 2392 kind = self.sql(expression, "kind") 2393 kind = f"{kind} " if kind else "" 2394 this = self.sql(expression, "this") 2395 expressions = self.expressions(expression) 2396 collate = self.sql(expression, "collate") 2397 collate = f" COLLATE {collate}" if collate else "" 2398 global_ = "GLOBAL " if expression.args.get("global") else "" 2399 return f"{global_}{kind}{this}{expressions}{collate}"
2409 def lock_sql(self, expression: exp.Lock) -> str: 2410 if not self.LOCKING_READS_SUPPORTED: 2411 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2412 return "" 2413 2414 update = expression.args["update"] 2415 key = expression.args.get("key") 2416 if update: 2417 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2418 else: 2419 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2420 expressions = self.expressions(expression, flat=True) 2421 expressions = f" OF {expressions}" if expressions else "" 2422 wait = expression.args.get("wait") 2423 2424 if wait is not None: 2425 if isinstance(wait, exp.Literal): 2426 wait = f" WAIT {self.sql(wait)}" 2427 else: 2428 wait = " NOWAIT" if wait else " SKIP LOCKED" 2429 2430 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2438 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2439 if self.dialect.ESCAPED_SEQUENCES: 2440 to_escaped = self.dialect.ESCAPED_SEQUENCES 2441 text = "".join( 2442 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2443 ) 2444 2445 return self._replace_line_breaks(text).replace( 2446 self.dialect.QUOTE_END, self._escaped_quote_end 2447 )
2449 def loaddata_sql(self, expression: exp.LoadData) -> str: 2450 local = " LOCAL" if expression.args.get("local") else "" 2451 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2452 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2453 this = f" INTO TABLE {self.sql(expression, 'this')}" 2454 partition = self.sql(expression, "partition") 2455 partition = f" {partition}" if partition else "" 2456 input_format = self.sql(expression, "input_format") 2457 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2458 serde = self.sql(expression, "serde") 2459 serde = f" SERDE {serde}" if serde else "" 2460 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2468 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2469 this = self.sql(expression, "this") 2470 this = f"{this} " if this else this 2471 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2472 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2474 def withfill_sql(self, expression: exp.WithFill) -> str: 2475 from_sql = self.sql(expression, "from") 2476 from_sql = f" FROM {from_sql}" if from_sql else "" 2477 to_sql = self.sql(expression, "to") 2478 to_sql = f" TO {to_sql}" if to_sql else "" 2479 step_sql = self.sql(expression, "step") 2480 step_sql = f" STEP {step_sql}" if step_sql else "" 2481 interpolated_values = [ 2482 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2483 if isinstance(e, exp.Alias) 2484 else self.sql(e, "this") 2485 for e in expression.args.get("interpolate") or [] 2486 ] 2487 interpolate = ( 2488 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2489 ) 2490 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2501 def ordered_sql(self, expression: exp.Ordered) -> str: 2502 desc = expression.args.get("desc") 2503 asc = not desc 2504 2505 nulls_first = expression.args.get("nulls_first") 2506 nulls_last = not nulls_first 2507 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2508 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2509 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2510 2511 this = self.sql(expression, "this") 2512 2513 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2514 nulls_sort_change = "" 2515 if nulls_first and ( 2516 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2517 ): 2518 nulls_sort_change = " NULLS FIRST" 2519 elif ( 2520 nulls_last 2521 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2522 and not nulls_are_last 2523 ): 2524 nulls_sort_change = " NULLS LAST" 2525 2526 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2527 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2528 window = expression.find_ancestor(exp.Window, exp.Select) 2529 if isinstance(window, exp.Window) and window.args.get("spec"): 2530 self.unsupported( 2531 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2532 ) 2533 nulls_sort_change = "" 2534 elif self.NULL_ORDERING_SUPPORTED is False and ( 2535 (asc and nulls_sort_change == " NULLS LAST") 2536 or (desc and nulls_sort_change == " NULLS FIRST") 2537 ): 2538 # BigQuery does not allow these ordering/nulls combinations when used under 2539 # an aggregation func or under a window containing one 2540 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2541 2542 if isinstance(ancestor, exp.Window): 2543 ancestor = ancestor.this 2544 if isinstance(ancestor, exp.AggFunc): 2545 self.unsupported( 2546 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2547 ) 2548 nulls_sort_change = "" 2549 elif self.NULL_ORDERING_SUPPORTED is None: 2550 if expression.this.is_int: 2551 self.unsupported( 2552 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2553 ) 2554 elif not isinstance(expression.this, exp.Rand): 2555 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2556 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2557 nulls_sort_change = "" 2558 2559 with_fill = self.sql(expression, "with_fill") 2560 with_fill = f" {with_fill}" if with_fill else "" 2561 2562 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2572 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2573 partition = self.partition_by_sql(expression) 2574 order = self.sql(expression, "order") 2575 measures = self.expressions(expression, key="measures") 2576 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2577 rows = self.sql(expression, "rows") 2578 rows = self.seg(rows) if rows else "" 2579 after = self.sql(expression, "after") 2580 after = self.seg(after) if after else "" 2581 pattern = self.sql(expression, "pattern") 2582 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2583 definition_sqls = [ 2584 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2585 for definition in expression.args.get("define", []) 2586 ] 2587 definitions = self.expressions(sqls=definition_sqls) 2588 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2589 body = "".join( 2590 ( 2591 partition, 2592 order, 2593 measures, 2594 rows, 2595 after, 2596 pattern, 2597 define, 2598 ) 2599 ) 2600 alias = self.sql(expression, "alias") 2601 alias = f" {alias}" if alias else "" 2602 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2604 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2605 limit = expression.args.get("limit") 2606 2607 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2608 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2609 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2610 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2611 2612 return csv( 2613 *sqls, 2614 *[self.sql(join) for join in expression.args.get("joins") or []], 2615 self.sql(expression, "match"), 2616 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2617 self.sql(expression, "prewhere"), 2618 self.sql(expression, "where"), 2619 self.sql(expression, "connect"), 2620 self.sql(expression, "group"), 2621 self.sql(expression, "having"), 2622 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2623 self.sql(expression, "order"), 2624 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2625 *self.after_limit_modifiers(expression), 2626 self.options_modifier(expression), 2627 self.for_modifiers(expression), 2628 sep="", 2629 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2643 def offset_limit_modifiers( 2644 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2645 ) -> t.List[str]: 2646 return [ 2647 self.sql(expression, "offset") if fetch else self.sql(limit), 2648 self.sql(limit) if fetch else self.sql(expression, "offset"), 2649 ]
2656 def select_sql(self, expression: exp.Select) -> str: 2657 into = expression.args.get("into") 2658 if not self.SUPPORTS_SELECT_INTO and into: 2659 into.pop() 2660 2661 hint = self.sql(expression, "hint") 2662 distinct = self.sql(expression, "distinct") 2663 distinct = f" {distinct}" if distinct else "" 2664 kind = self.sql(expression, "kind") 2665 2666 limit = expression.args.get("limit") 2667 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2668 top = self.limit_sql(limit, top=True) 2669 limit.pop() 2670 else: 2671 top = "" 2672 2673 expressions = self.expressions(expression) 2674 2675 if kind: 2676 if kind in self.SELECT_KINDS: 2677 kind = f" AS {kind}" 2678 else: 2679 if kind == "STRUCT": 2680 expressions = self.expressions( 2681 sqls=[ 2682 self.sql( 2683 exp.Struct( 2684 expressions=[ 2685 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2686 if isinstance(e, exp.Alias) 2687 else e 2688 for e in expression.expressions 2689 ] 2690 ) 2691 ) 2692 ] 2693 ) 2694 kind = "" 2695 2696 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2697 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2698 2699 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2700 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2701 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2702 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2703 sql = self.query_modifiers( 2704 expression, 2705 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2706 self.sql(expression, "into", comment=False), 2707 self.sql(expression, "from", comment=False), 2708 ) 2709 2710 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2711 if expression.args.get("with"): 2712 sql = self.maybe_comment(sql, expression) 2713 expression.pop_comments() 2714 2715 sql = self.prepend_ctes(expression, sql) 2716 2717 if not self.SUPPORTS_SELECT_INTO and into: 2718 if into.args.get("temporary"): 2719 table_kind = " TEMPORARY" 2720 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2721 table_kind = " UNLOGGED" 2722 else: 2723 table_kind = "" 2724 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2725 2726 return sql
2738 def star_sql(self, expression: exp.Star) -> str: 2739 except_ = self.expressions(expression, key="except", flat=True) 2740 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2741 replace = self.expressions(expression, key="replace", flat=True) 2742 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2743 rename = self.expressions(expression, key="rename", flat=True) 2744 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2745 return f"*{except_}{replace}{rename}"
2761 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2762 alias = self.sql(expression, "alias") 2763 alias = f"{sep}{alias}" if alias else "" 2764 sample = self.sql(expression, "sample") 2765 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2766 alias = f"{sample}{alias}" 2767 2768 # Set to None so it's not generated again by self.query_modifiers() 2769 expression.set("sample", None) 2770 2771 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2772 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2773 return self.prepend_ctes(expression, sql)
2779 def unnest_sql(self, expression: exp.Unnest) -> str: 2780 args = self.expressions(expression, flat=True) 2781 2782 alias = expression.args.get("alias") 2783 offset = expression.args.get("offset") 2784 2785 if self.UNNEST_WITH_ORDINALITY: 2786 if alias and isinstance(offset, exp.Expression): 2787 alias.append("columns", offset) 2788 2789 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2790 columns = alias.columns 2791 alias = self.sql(columns[0]) if columns else "" 2792 else: 2793 alias = self.sql(alias) 2794 2795 alias = f" AS {alias}" if alias else alias 2796 if self.UNNEST_WITH_ORDINALITY: 2797 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2798 else: 2799 if isinstance(offset, exp.Expression): 2800 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2801 elif offset: 2802 suffix = f"{alias} WITH OFFSET" 2803 else: 2804 suffix = alias 2805 2806 return f"UNNEST({args}){suffix}"
2815 def window_sql(self, expression: exp.Window) -> str: 2816 this = self.sql(expression, "this") 2817 partition = self.partition_by_sql(expression) 2818 order = expression.args.get("order") 2819 order = self.order_sql(order, flat=True) if order else "" 2820 spec = self.sql(expression, "spec") 2821 alias = self.sql(expression, "alias") 2822 over = self.sql(expression, "over") or "OVER" 2823 2824 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2825 2826 first = expression.args.get("first") 2827 if first is None: 2828 first = "" 2829 else: 2830 first = "FIRST" if first else "LAST" 2831 2832 if not partition and not order and not spec and alias: 2833 return f"{this} {alias}" 2834 2835 args = self.format_args( 2836 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2837 ) 2838 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2844 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2845 kind = self.sql(expression, "kind") 2846 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2847 end = ( 2848 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2849 or "CURRENT ROW" 2850 ) 2851 2852 window_spec = f"{kind} BETWEEN {start} AND {end}" 2853 2854 exclude = self.sql(expression, "exclude") 2855 if exclude: 2856 if self.SUPPORTS_WINDOW_EXCLUDE: 2857 window_spec += f" EXCLUDE {exclude}" 2858 else: 2859 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2860 2861 return window_spec
2868 def between_sql(self, expression: exp.Between) -> str: 2869 this = self.sql(expression, "this") 2870 low = self.sql(expression, "low") 2871 high = self.sql(expression, "high") 2872 symmetric = expression.args.get("symmetric") 2873 2874 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2875 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2876 2877 flag = ( 2878 " SYMMETRIC" 2879 if symmetric 2880 else " ASYMMETRIC" 2881 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2882 else "" # silently drop ASYMMETRIC – semantics identical 2883 ) 2884 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]:
2886 def bracket_offset_expressions( 2887 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2888 ) -> t.List[exp.Expression]: 2889 return apply_index_offset( 2890 expression.this, 2891 expression.expressions, 2892 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2893 dialect=self.dialect, 2894 )
2904 def any_sql(self, expression: exp.Any) -> str: 2905 this = self.sql(expression, "this") 2906 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2907 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2908 this = self.wrap(this) 2909 return f"ANY{this}" 2910 return f"ANY {this}"
2915 def case_sql(self, expression: exp.Case) -> str: 2916 this = self.sql(expression, "this") 2917 statements = [f"CASE {this}" if this else "CASE"] 2918 2919 for e in expression.args["ifs"]: 2920 statements.append(f"WHEN {self.sql(e, 'this')}") 2921 statements.append(f"THEN {self.sql(e, 'true')}") 2922 2923 default = self.sql(expression, "default") 2924 2925 if default: 2926 statements.append(f"ELSE {default}") 2927 2928 statements.append("END") 2929 2930 if self.pretty and self.too_wide(statements): 2931 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2932 2933 return " ".join(statements)
2945 def extract_sql(self, expression: exp.Extract) -> str: 2946 from sqlglot.dialects.dialect import map_date_part 2947 2948 this = ( 2949 map_date_part(expression.this, self.dialect) 2950 if self.NORMALIZE_EXTRACT_DATE_PARTS 2951 else expression.this 2952 ) 2953 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2954 expression_sql = self.sql(expression, "expression") 2955 2956 return f"EXTRACT({this_sql} FROM {expression_sql})"
2958 def trim_sql(self, expression: exp.Trim) -> str: 2959 trim_type = self.sql(expression, "position") 2960 2961 if trim_type == "LEADING": 2962 func_name = "LTRIM" 2963 elif trim_type == "TRAILING": 2964 func_name = "RTRIM" 2965 else: 2966 func_name = "TRIM" 2967 2968 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]:
2970 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2971 args = expression.expressions 2972 if isinstance(expression, exp.ConcatWs): 2973 args = args[1:] # Skip the delimiter 2974 2975 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2976 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2977 2978 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2979 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2980 2981 return args
2983 def concat_sql(self, expression: exp.Concat) -> str: 2984 expressions = self.convert_concat_args(expression) 2985 2986 # Some dialects don't allow a single-argument CONCAT call 2987 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2988 return self.sql(expressions[0]) 2989 2990 return self.func("CONCAT", *expressions)
3001 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3002 expressions = self.expressions(expression, flat=True) 3003 expressions = f" ({expressions})" if expressions else "" 3004 reference = self.sql(expression, "reference") 3005 reference = f" {reference}" if reference else "" 3006 delete = self.sql(expression, "delete") 3007 delete = f" ON DELETE {delete}" if delete else "" 3008 update = self.sql(expression, "update") 3009 update = f" ON UPDATE {update}" if update else "" 3010 options = self.expressions(expression, key="options", flat=True, sep=" ") 3011 options = f" {options}" if options else "" 3012 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3014 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3015 expressions = self.expressions(expression, flat=True) 3016 include = self.sql(expression, "include") 3017 options = self.expressions(expression, key="options", flat=True, sep=" ") 3018 options = f" {options}" if options else "" 3019 return f"PRIMARY KEY ({expressions}){include}{options}"
3032 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3033 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3034 3035 if expression.args.get("escape"): 3036 path = self.escape_str(path) 3037 3038 if self.QUOTE_JSON_PATH: 3039 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3040 3041 return path
3043 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3044 if isinstance(expression, exp.JSONPathPart): 3045 transform = self.TRANSFORMS.get(expression.__class__) 3046 if not callable(transform): 3047 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3048 return "" 3049 3050 return transform(self, expression) 3051 3052 if isinstance(expression, int): 3053 return str(expression) 3054 3055 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3056 escaped = expression.replace("'", "\\'") 3057 escaped = f"\\'{expression}\\'" 3058 else: 3059 escaped = expression.replace('"', '\\"') 3060 escaped = f'"{escaped}"' 3061 3062 return escaped
3067 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3068 # Output the Teradata column FORMAT override. 3069 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3070 this = self.sql(expression, "this") 3071 fmt = self.sql(expression, "format") 3072 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3074 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3075 null_handling = expression.args.get("null_handling") 3076 null_handling = f" {null_handling}" if null_handling else "" 3077 3078 unique_keys = expression.args.get("unique_keys") 3079 if unique_keys is not None: 3080 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3081 else: 3082 unique_keys = "" 3083 3084 return_type = self.sql(expression, "return_type") 3085 return_type = f" RETURNING {return_type}" if return_type else "" 3086 encoding = self.sql(expression, "encoding") 3087 encoding = f" ENCODING {encoding}" if encoding else "" 3088 3089 return self.func( 3090 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3091 *expression.expressions, 3092 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3093 )
3098 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3099 null_handling = expression.args.get("null_handling") 3100 null_handling = f" {null_handling}" if null_handling else "" 3101 return_type = self.sql(expression, "return_type") 3102 return_type = f" RETURNING {return_type}" if return_type else "" 3103 strict = " STRICT" if expression.args.get("strict") else "" 3104 return self.func( 3105 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3106 )
3108 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3109 this = self.sql(expression, "this") 3110 order = self.sql(expression, "order") 3111 null_handling = expression.args.get("null_handling") 3112 null_handling = f" {null_handling}" if null_handling else "" 3113 return_type = self.sql(expression, "return_type") 3114 return_type = f" RETURNING {return_type}" if return_type else "" 3115 strict = " STRICT" if expression.args.get("strict") else "" 3116 return self.func( 3117 "JSON_ARRAYAGG", 3118 this, 3119 suffix=f"{order}{null_handling}{return_type}{strict})", 3120 )
3122 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3123 path = self.sql(expression, "path") 3124 path = f" PATH {path}" if path else "" 3125 nested_schema = self.sql(expression, "nested_schema") 3126 3127 if nested_schema: 3128 return f"NESTED{path} {nested_schema}" 3129 3130 this = self.sql(expression, "this") 3131 kind = self.sql(expression, "kind") 3132 kind = f" {kind}" if kind else "" 3133 return f"{this}{kind}{path}"
3138 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3139 this = self.sql(expression, "this") 3140 path = self.sql(expression, "path") 3141 path = f", {path}" if path else "" 3142 error_handling = expression.args.get("error_handling") 3143 error_handling = f" {error_handling}" if error_handling else "" 3144 empty_handling = expression.args.get("empty_handling") 3145 empty_handling = f" {empty_handling}" if empty_handling else "" 3146 schema = self.sql(expression, "schema") 3147 return self.func( 3148 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3149 )
3151 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3152 this = self.sql(expression, "this") 3153 kind = self.sql(expression, "kind") 3154 path = self.sql(expression, "path") 3155 path = f" {path}" if path else "" 3156 as_json = " AS JSON" if expression.args.get("as_json") else "" 3157 return f"{this} {kind}{path}{as_json}"
3159 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3160 this = self.sql(expression, "this") 3161 path = self.sql(expression, "path") 3162 path = f", {path}" if path else "" 3163 expressions = self.expressions(expression) 3164 with_ = ( 3165 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3166 if expressions 3167 else "" 3168 ) 3169 return f"OPENJSON({this}{path}){with_}"
3171 def in_sql(self, expression: exp.In) -> str: 3172 query = expression.args.get("query") 3173 unnest = expression.args.get("unnest") 3174 field = expression.args.get("field") 3175 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3176 3177 if query: 3178 in_sql = self.sql(query) 3179 elif unnest: 3180 in_sql = self.in_unnest_op(unnest) 3181 elif field: 3182 in_sql = self.sql(field) 3183 else: 3184 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3185 3186 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3191 def interval_sql(self, expression: exp.Interval) -> str: 3192 unit = self.sql(expression, "unit") 3193 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3194 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3195 unit = f" {unit}" if unit else "" 3196 3197 if self.SINGLE_STRING_INTERVAL: 3198 this = expression.this.name if expression.this else "" 3199 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3200 3201 this = self.sql(expression, "this") 3202 if this: 3203 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3204 this = f" {this}" if unwrapped else f" ({this})" 3205 3206 return f"INTERVAL{this}{unit}"
3211 def reference_sql(self, expression: exp.Reference) -> str: 3212 this = self.sql(expression, "this") 3213 expressions = self.expressions(expression, flat=True) 3214 expressions = f"({expressions})" if expressions else "" 3215 options = self.expressions(expression, key="options", flat=True, sep=" ") 3216 options = f" {options}" if options else "" 3217 return f"REFERENCES {this}{expressions}{options}"
3219 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3220 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3221 parent = expression.parent 3222 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3223 return self.func( 3224 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3225 )
3245 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3246 alias = expression.args["alias"] 3247 3248 parent = expression.parent 3249 pivot = parent and parent.parent 3250 3251 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3252 identifier_alias = isinstance(alias, exp.Identifier) 3253 literal_alias = isinstance(alias, exp.Literal) 3254 3255 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3256 alias.replace(exp.Literal.string(alias.output_name)) 3257 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3258 alias.replace(exp.to_identifier(alias.output_name)) 3259 3260 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:
3298 def connector_sql( 3299 self, 3300 expression: exp.Connector, 3301 op: str, 3302 stack: t.Optional[t.List[str | exp.Expression]] = None, 3303 ) -> str: 3304 if stack is not None: 3305 if expression.expressions: 3306 stack.append(self.expressions(expression, sep=f" {op} ")) 3307 else: 3308 stack.append(expression.right) 3309 if expression.comments and self.comments: 3310 for comment in expression.comments: 3311 if comment: 3312 op += f" /*{self.sanitize_comment(comment)}*/" 3313 stack.extend((op, expression.left)) 3314 return op 3315 3316 stack = [expression] 3317 sqls: t.List[str] = [] 3318 ops = set() 3319 3320 while stack: 3321 node = stack.pop() 3322 if isinstance(node, exp.Connector): 3323 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3324 else: 3325 sql = self.sql(node) 3326 if sqls and sqls[-1] in ops: 3327 sqls[-1] += f" {sql}" 3328 else: 3329 sqls.append(sql) 3330 3331 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3332 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3352 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3353 format_sql = self.sql(expression, "format") 3354 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3355 to_sql = self.sql(expression, "to") 3356 to_sql = f" {to_sql}" if to_sql else "" 3357 action = self.sql(expression, "action") 3358 action = f" {action}" if action else "" 3359 default = self.sql(expression, "default") 3360 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3361 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3375 def comment_sql(self, expression: exp.Comment) -> str: 3376 this = self.sql(expression, "this") 3377 kind = expression.args["kind"] 3378 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3379 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3380 expression_sql = self.sql(expression, "expression") 3381 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3383 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3384 this = self.sql(expression, "this") 3385 delete = " DELETE" if expression.args.get("delete") else "" 3386 recompress = self.sql(expression, "recompress") 3387 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3388 to_disk = self.sql(expression, "to_disk") 3389 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3390 to_volume = self.sql(expression, "to_volume") 3391 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3392 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3394 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3395 where = self.sql(expression, "where") 3396 group = self.sql(expression, "group") 3397 aggregates = self.expressions(expression, key="aggregates") 3398 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3399 3400 if not (where or group or aggregates) and len(expression.expressions) == 1: 3401 return f"TTL {self.expressions(expression, flat=True)}" 3402 3403 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3420 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3421 this = self.sql(expression, "this") 3422 3423 dtype = self.sql(expression, "dtype") 3424 if dtype: 3425 collate = self.sql(expression, "collate") 3426 collate = f" COLLATE {collate}" if collate else "" 3427 using = self.sql(expression, "using") 3428 using = f" USING {using}" if using else "" 3429 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3430 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3431 3432 default = self.sql(expression, "default") 3433 if default: 3434 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3435 3436 comment = self.sql(expression, "comment") 3437 if comment: 3438 return f"ALTER COLUMN {this} COMMENT {comment}" 3439 3440 visible = expression.args.get("visible") 3441 if visible: 3442 return f"ALTER COLUMN {this} SET {visible}" 3443 3444 allow_null = expression.args.get("allow_null") 3445 drop = expression.args.get("drop") 3446 3447 if not drop and not allow_null: 3448 self.unsupported("Unsupported ALTER COLUMN syntax") 3449 3450 if allow_null is not None: 3451 keyword = "DROP" if drop else "SET" 3452 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3453 3454 return f"ALTER COLUMN {this} DROP DEFAULT"
3470 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3471 compound = " COMPOUND" if expression.args.get("compound") else "" 3472 this = self.sql(expression, "this") 3473 expressions = self.expressions(expression, flat=True) 3474 expressions = f"({expressions})" if expressions else "" 3475 return f"ALTER{compound} SORTKEY {this or expressions}"
3477 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3478 if not self.RENAME_TABLE_WITH_DB: 3479 # Remove db from tables 3480 expression = expression.transform( 3481 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3482 ).assert_is(exp.AlterRename) 3483 this = self.sql(expression, "this") 3484 return f"RENAME TO {this}"
3499 def alter_sql(self, expression: exp.Alter) -> str: 3500 actions = expression.args["actions"] 3501 3502 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3503 actions[0], exp.ColumnDef 3504 ): 3505 actions_sql = self.expressions(expression, key="actions", flat=True) 3506 actions_sql = f"ADD {actions_sql}" 3507 else: 3508 actions_list = [] 3509 for action in actions: 3510 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3511 action_sql = self.add_column_sql(action) 3512 else: 3513 action_sql = self.sql(action) 3514 if isinstance(action, exp.Query): 3515 action_sql = f"AS {action_sql}" 3516 3517 actions_list.append(action_sql) 3518 3519 actions_sql = self.format_args(*actions_list).lstrip("\n") 3520 3521 exists = " IF EXISTS" if expression.args.get("exists") else "" 3522 on_cluster = self.sql(expression, "cluster") 3523 on_cluster = f" {on_cluster}" if on_cluster else "" 3524 only = " ONLY" if expression.args.get("only") else "" 3525 options = self.expressions(expression, key="options") 3526 options = f", {options}" if options else "" 3527 kind = self.sql(expression, "kind") 3528 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3529 3530 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3532 def add_column_sql(self, expression: exp.Expression) -> str: 3533 sql = self.sql(expression) 3534 if isinstance(expression, exp.Schema): 3535 column_text = " COLUMNS" 3536 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3537 column_text = " COLUMN" 3538 else: 3539 column_text = "" 3540 3541 return f"ADD{column_text} {sql}"
3551 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3552 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3553 location = self.sql(expression, "location") 3554 location = f" {location}" if location else "" 3555 return f"ADD {exists}{self.sql(expression.this)}{location}"
3557 def distinct_sql(self, expression: exp.Distinct) -> str: 3558 this = self.expressions(expression, flat=True) 3559 3560 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3561 case = exp.case() 3562 for arg in expression.expressions: 3563 case = case.when(arg.is_(exp.null()), exp.null()) 3564 this = self.sql(case.else_(f"({this})")) 3565 3566 this = f" {this}" if this else "" 3567 3568 on = self.sql(expression, "on") 3569 on = f" ON {on}" if on else "" 3570 return f"DISTINCT{this}{on}"
3599 def div_sql(self, expression: exp.Div) -> str: 3600 l, r = expression.left, expression.right 3601 3602 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3603 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3604 3605 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3606 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3607 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3608 3609 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3610 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3611 return self.sql( 3612 exp.cast( 3613 l / r, 3614 to=exp.DataType.Type.BIGINT, 3615 ) 3616 ) 3617 3618 return self.binary(expression, "/")
3714 def log_sql(self, expression: exp.Log) -> str: 3715 this = expression.this 3716 expr = expression.expression 3717 3718 if self.dialect.LOG_BASE_FIRST is False: 3719 this, expr = expr, this 3720 elif self.dialect.LOG_BASE_FIRST is None and expr: 3721 if this.name in ("2", "10"): 3722 return self.func(f"LOG{this.name}", expr) 3723 3724 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3725 3726 return self.func("LOG", this, expr)
3735 def binary(self, expression: exp.Binary, op: str) -> str: 3736 sqls: t.List[str] = [] 3737 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3738 binary_type = type(expression) 3739 3740 while stack: 3741 node = stack.pop() 3742 3743 if type(node) is binary_type: 3744 op_func = node.args.get("operator") 3745 if op_func: 3746 op = f"OPERATOR({self.sql(op_func)})" 3747 3748 stack.append(node.right) 3749 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3750 stack.append(node.left) 3751 else: 3752 sqls.append(self.sql(node)) 3753 3754 return "".join(sqls)
3763 def function_fallback_sql(self, expression: exp.Func) -> str: 3764 args = [] 3765 3766 for key in expression.arg_types: 3767 arg_value = expression.args.get(key) 3768 3769 if isinstance(arg_value, list): 3770 for value in arg_value: 3771 args.append(value) 3772 elif arg_value is not None: 3773 args.append(arg_value) 3774 3775 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3776 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3777 else: 3778 name = expression.sql_name() 3779 3780 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:
3782 def func( 3783 self, 3784 name: str, 3785 *args: t.Optional[exp.Expression | str], 3786 prefix: str = "(", 3787 suffix: str = ")", 3788 normalize: bool = True, 3789 ) -> str: 3790 name = self.normalize_func(name) if normalize else name 3791 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3793 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3794 arg_sqls = tuple( 3795 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3796 ) 3797 if self.pretty and self.too_wide(arg_sqls): 3798 return self.indent( 3799 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3800 ) 3801 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]:
3806 def format_time( 3807 self, 3808 expression: exp.Expression, 3809 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3810 inverse_time_trie: t.Optional[t.Dict] = None, 3811 ) -> t.Optional[str]: 3812 return format_time( 3813 self.sql(expression, "format"), 3814 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3815 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3816 )
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:
3818 def expressions( 3819 self, 3820 expression: t.Optional[exp.Expression] = None, 3821 key: t.Optional[str] = None, 3822 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3823 flat: bool = False, 3824 indent: bool = True, 3825 skip_first: bool = False, 3826 skip_last: bool = False, 3827 sep: str = ", ", 3828 prefix: str = "", 3829 dynamic: bool = False, 3830 new_line: bool = False, 3831 ) -> str: 3832 expressions = expression.args.get(key or "expressions") if expression else sqls 3833 3834 if not expressions: 3835 return "" 3836 3837 if flat: 3838 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3839 3840 num_sqls = len(expressions) 3841 result_sqls = [] 3842 3843 for i, e in enumerate(expressions): 3844 sql = self.sql(e, comment=False) 3845 if not sql: 3846 continue 3847 3848 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3849 3850 if self.pretty: 3851 if self.leading_comma: 3852 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3853 else: 3854 result_sqls.append( 3855 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3856 ) 3857 else: 3858 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3859 3860 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3861 if new_line: 3862 result_sqls.insert(0, "") 3863 result_sqls.append("") 3864 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3865 else: 3866 result_sql = "".join(result_sqls) 3867 3868 return ( 3869 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3870 if indent 3871 else result_sql 3872 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3874 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3875 flat = flat or isinstance(expression.parent, exp.Properties) 3876 expressions_sql = self.expressions(expression, flat=flat) 3877 if flat: 3878 return f"{op} {expressions_sql}" 3879 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3881 def naked_property(self, expression: exp.Property) -> str: 3882 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3883 if not property_name: 3884 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3885 return f"{property_name} {self.sql(expression, 'this')}"
3893 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3894 this = self.sql(expression, "this") 3895 expressions = self.no_identify(self.expressions, expression) 3896 expressions = ( 3897 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3898 ) 3899 return f"{this}{expressions}" if expressions.strip() != "" else this
3909 def when_sql(self, expression: exp.When) -> str: 3910 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3911 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3912 condition = self.sql(expression, "condition") 3913 condition = f" AND {condition}" if condition else "" 3914 3915 then_expression = expression.args.get("then") 3916 if isinstance(then_expression, exp.Insert): 3917 this = self.sql(then_expression, "this") 3918 this = f"INSERT {this}" if this else "INSERT" 3919 then = self.sql(then_expression, "expression") 3920 then = f"{this} VALUES {then}" if then else this 3921 elif isinstance(then_expression, exp.Update): 3922 if isinstance(then_expression.args.get("expressions"), exp.Star): 3923 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3924 else: 3925 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3926 else: 3927 then = self.sql(then_expression) 3928 return f"WHEN {matched}{source}{condition} THEN {then}"
3933 def merge_sql(self, expression: exp.Merge) -> str: 3934 table = expression.this 3935 table_alias = "" 3936 3937 hints = table.args.get("hints") 3938 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3939 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3940 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3941 3942 this = self.sql(table) 3943 using = f"USING {self.sql(expression, 'using')}" 3944 on = f"ON {self.sql(expression, 'on')}" 3945 whens = self.sql(expression, "whens") 3946 3947 returning = self.sql(expression, "returning") 3948 if returning: 3949 whens = f"{whens}{returning}" 3950 3951 sep = self.sep() 3952 3953 return self.prepend_ctes( 3954 expression, 3955 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3956 )
3962 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3963 if not self.SUPPORTS_TO_NUMBER: 3964 self.unsupported("Unsupported TO_NUMBER function") 3965 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3966 3967 fmt = expression.args.get("format") 3968 if not fmt: 3969 self.unsupported("Conversion format is required for TO_NUMBER") 3970 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3971 3972 return self.func("TO_NUMBER", expression.this, fmt)
3974 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3975 this = self.sql(expression, "this") 3976 kind = self.sql(expression, "kind") 3977 settings_sql = self.expressions(expression, key="settings", sep=" ") 3978 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3979 return f"{this}({kind}{args})"
3998 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3999 expressions = self.expressions(expression, flat=True) 4000 expressions = f" {self.wrap(expressions)}" if expressions else "" 4001 buckets = self.sql(expression, "buckets") 4002 kind = self.sql(expression, "kind") 4003 buckets = f" BUCKETS {buckets}" if buckets else "" 4004 order = self.sql(expression, "order") 4005 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4010 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4011 expressions = self.expressions(expression, key="expressions", flat=True) 4012 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4013 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4014 buckets = self.sql(expression, "buckets") 4015 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4017 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4018 this = self.sql(expression, "this") 4019 having = self.sql(expression, "having") 4020 4021 if having: 4022 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4023 4024 return self.func("ANY_VALUE", this)
4026 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4027 transform = self.func("TRANSFORM", *expression.expressions) 4028 row_format_before = self.sql(expression, "row_format_before") 4029 row_format_before = f" {row_format_before}" if row_format_before else "" 4030 record_writer = self.sql(expression, "record_writer") 4031 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4032 using = f" USING {self.sql(expression, 'command_script')}" 4033 schema = self.sql(expression, "schema") 4034 schema = f" AS {schema}" if schema else "" 4035 row_format_after = self.sql(expression, "row_format_after") 4036 row_format_after = f" {row_format_after}" if row_format_after else "" 4037 record_reader = self.sql(expression, "record_reader") 4038 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4039 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4041 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4042 key_block_size = self.sql(expression, "key_block_size") 4043 if key_block_size: 4044 return f"KEY_BLOCK_SIZE = {key_block_size}" 4045 4046 using = self.sql(expression, "using") 4047 if using: 4048 return f"USING {using}" 4049 4050 parser = self.sql(expression, "parser") 4051 if parser: 4052 return f"WITH PARSER {parser}" 4053 4054 comment = self.sql(expression, "comment") 4055 if comment: 4056 return f"COMMENT {comment}" 4057 4058 visible = expression.args.get("visible") 4059 if visible is not None: 4060 return "VISIBLE" if visible else "INVISIBLE" 4061 4062 engine_attr = self.sql(expression, "engine_attr") 4063 if engine_attr: 4064 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4065 4066 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4067 if secondary_engine_attr: 4068 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4069 4070 self.unsupported("Unsupported index constraint option.") 4071 return ""
4077 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4078 kind = self.sql(expression, "kind") 4079 kind = f"{kind} INDEX" if kind else "INDEX" 4080 this = self.sql(expression, "this") 4081 this = f" {this}" if this else "" 4082 index_type = self.sql(expression, "index_type") 4083 index_type = f" USING {index_type}" if index_type else "" 4084 expressions = self.expressions(expression, flat=True) 4085 expressions = f" ({expressions})" if expressions else "" 4086 options = self.expressions(expression, key="options", sep=" ") 4087 options = f" {options}" if options else "" 4088 return f"{kind}{this}{index_type}{expressions}{options}"
4090 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4091 if self.NVL2_SUPPORTED: 4092 return self.function_fallback_sql(expression) 4093 4094 case = exp.Case().when( 4095 expression.this.is_(exp.null()).not_(copy=False), 4096 expression.args["true"], 4097 copy=False, 4098 ) 4099 else_cond = expression.args.get("false") 4100 if else_cond: 4101 case.else_(else_cond, copy=False) 4102 4103 return self.sql(case)
4105 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4106 this = self.sql(expression, "this") 4107 expr = self.sql(expression, "expression") 4108 iterator = self.sql(expression, "iterator") 4109 condition = self.sql(expression, "condition") 4110 condition = f" IF {condition}" if condition else "" 4111 return f"{this} FOR {expr} IN {iterator}{condition}"
4119 def predict_sql(self, expression: exp.Predict) -> str: 4120 model = self.sql(expression, "this") 4121 model = f"MODEL {model}" 4122 table = self.sql(expression, "expression") 4123 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4124 parameters = self.sql(expression, "params_struct") 4125 return self.func("PREDICT", model, table, parameters or None)
4137 def toarray_sql(self, expression: exp.ToArray) -> str: 4138 arg = expression.this 4139 if not arg.type: 4140 from sqlglot.optimizer.annotate_types import annotate_types 4141 4142 arg = annotate_types(arg, dialect=self.dialect) 4143 4144 if arg.is_type(exp.DataType.Type.ARRAY): 4145 return self.sql(arg) 4146 4147 cond_for_null = arg.is_(exp.null()) 4148 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4150 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4151 this = expression.this 4152 time_format = self.format_time(expression) 4153 4154 if time_format: 4155 return self.sql( 4156 exp.cast( 4157 exp.StrToTime(this=this, format=expression.args["format"]), 4158 exp.DataType.Type.TIME, 4159 ) 4160 ) 4161 4162 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4163 return self.sql(this) 4164 4165 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4167 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4168 this = expression.this 4169 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4170 return self.sql(this) 4171 4172 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4174 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4175 this = expression.this 4176 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4177 return self.sql(this) 4178 4179 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4181 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4182 this = expression.this 4183 time_format = self.format_time(expression) 4184 4185 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4186 return self.sql( 4187 exp.cast( 4188 exp.StrToTime(this=this, format=expression.args["format"]), 4189 exp.DataType.Type.DATE, 4190 ) 4191 ) 4192 4193 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4194 return self.sql(this) 4195 4196 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4208 def lastday_sql(self, expression: exp.LastDay) -> str: 4209 if self.LAST_DAY_SUPPORTS_DATE_PART: 4210 return self.function_fallback_sql(expression) 4211 4212 unit = expression.text("unit") 4213 if unit and unit != "MONTH": 4214 self.unsupported("Date parts are not supported in LAST_DAY.") 4215 4216 return self.func("LAST_DAY", expression.this)
4225 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4226 if self.CAN_IMPLEMENT_ARRAY_ANY: 4227 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4228 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4229 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4230 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4231 4232 from sqlglot.dialects import Dialect 4233 4234 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4235 if self.dialect.__class__ != Dialect: 4236 self.unsupported("ARRAY_ANY is unsupported") 4237 4238 return self.function_fallback_sql(expression)
4240 def struct_sql(self, expression: exp.Struct) -> str: 4241 expression.set( 4242 "expressions", 4243 [ 4244 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4245 if isinstance(e, exp.PropertyEQ) 4246 else e 4247 for e in expression.expressions 4248 ], 4249 ) 4250 4251 return self.function_fallback_sql(expression)
4259 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4260 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4261 tables = f" {self.expressions(expression)}" 4262 4263 exists = " IF EXISTS" if expression.args.get("exists") else "" 4264 4265 on_cluster = self.sql(expression, "cluster") 4266 on_cluster = f" {on_cluster}" if on_cluster else "" 4267 4268 identity = self.sql(expression, "identity") 4269 identity = f" {identity} IDENTITY" if identity else "" 4270 4271 option = self.sql(expression, "option") 4272 option = f" {option}" if option else "" 4273 4274 partition = self.sql(expression, "partition") 4275 partition = f" {partition}" if partition else "" 4276 4277 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4281 def convert_sql(self, expression: exp.Convert) -> str: 4282 to = expression.this 4283 value = expression.expression 4284 style = expression.args.get("style") 4285 safe = expression.args.get("safe") 4286 strict = expression.args.get("strict") 4287 4288 if not to or not value: 4289 return "" 4290 4291 # Retrieve length of datatype and override to default if not specified 4292 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4293 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4294 4295 transformed: t.Optional[exp.Expression] = None 4296 cast = exp.Cast if strict else exp.TryCast 4297 4298 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4299 if isinstance(style, exp.Literal) and style.is_int: 4300 from sqlglot.dialects.tsql import TSQL 4301 4302 style_value = style.name 4303 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4304 if not converted_style: 4305 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4306 4307 fmt = exp.Literal.string(converted_style) 4308 4309 if to.this == exp.DataType.Type.DATE: 4310 transformed = exp.StrToDate(this=value, format=fmt) 4311 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4312 transformed = exp.StrToTime(this=value, format=fmt) 4313 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4314 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4315 elif to.this == exp.DataType.Type.TEXT: 4316 transformed = exp.TimeToStr(this=value, format=fmt) 4317 4318 if not transformed: 4319 transformed = cast(this=value, to=to, safe=safe) 4320 4321 return self.sql(transformed)
4389 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4390 option = self.sql(expression, "this") 4391 4392 if expression.expressions: 4393 upper = option.upper() 4394 4395 # Snowflake FILE_FORMAT options are separated by whitespace 4396 sep = " " if upper == "FILE_FORMAT" else ", " 4397 4398 # Databricks copy/format options do not set their list of values with EQ 4399 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4400 values = self.expressions(expression, flat=True, sep=sep) 4401 return f"{option}{op}({values})" 4402 4403 value = self.sql(expression, "expression") 4404 4405 if not value: 4406 return option 4407 4408 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4409 4410 return f"{option}{op}{value}"
4412 def credentials_sql(self, expression: exp.Credentials) -> str: 4413 cred_expr = expression.args.get("credentials") 4414 if isinstance(cred_expr, exp.Literal): 4415 # Redshift case: CREDENTIALS <string> 4416 credentials = self.sql(expression, "credentials") 4417 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4418 else: 4419 # Snowflake case: CREDENTIALS = (...) 4420 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4421 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4422 4423 storage = self.sql(expression, "storage") 4424 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4425 4426 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4427 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4428 4429 iam_role = self.sql(expression, "iam_role") 4430 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4431 4432 region = self.sql(expression, "region") 4433 region = f" REGION {region}" if region else "" 4434 4435 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4437 def copy_sql(self, expression: exp.Copy) -> str: 4438 this = self.sql(expression, "this") 4439 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4440 4441 credentials = self.sql(expression, "credentials") 4442 credentials = self.seg(credentials) if credentials else "" 4443 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4444 files = self.expressions(expression, key="files", flat=True) 4445 4446 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4447 params = self.expressions( 4448 expression, 4449 key="params", 4450 sep=sep, 4451 new_line=True, 4452 skip_last=True, 4453 skip_first=True, 4454 indent=self.COPY_PARAMS_ARE_WRAPPED, 4455 ) 4456 4457 if params: 4458 if self.COPY_PARAMS_ARE_WRAPPED: 4459 params = f" WITH ({params})" 4460 elif not self.pretty: 4461 params = f" {params}" 4462 4463 return f"COPY{this}{kind} {files}{credentials}{params}"
4468 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4469 on_sql = "ON" if expression.args.get("on") else "OFF" 4470 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4471 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4472 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4473 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4474 4475 if filter_col or retention_period: 4476 on_sql = self.func("ON", filter_col, retention_period) 4477 4478 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4480 def maskingpolicycolumnconstraint_sql( 4481 self, expression: exp.MaskingPolicyColumnConstraint 4482 ) -> str: 4483 this = self.sql(expression, "this") 4484 expressions = self.expressions(expression, flat=True) 4485 expressions = f" USING ({expressions})" if expressions else "" 4486 return f"MASKING POLICY {this}{expressions}"
4496 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4497 this = self.sql(expression, "this") 4498 expr = expression.expression 4499 4500 if isinstance(expr, exp.Func): 4501 # T-SQL's CLR functions are case sensitive 4502 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4503 else: 4504 expr = self.sql(expression, "expression") 4505 4506 return self.scope_resolution(expr, this)
4514 def rand_sql(self, expression: exp.Rand) -> str: 4515 lower = self.sql(expression, "lower") 4516 upper = self.sql(expression, "upper") 4517 4518 if lower and upper: 4519 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4520 return self.func("RAND", expression.this)
4522 def changes_sql(self, expression: exp.Changes) -> str: 4523 information = self.sql(expression, "information") 4524 information = f"INFORMATION => {information}" 4525 at_before = self.sql(expression, "at_before") 4526 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4527 end = self.sql(expression, "end") 4528 end = f"{self.seg('')}{end}" if end else "" 4529 4530 return f"CHANGES ({information}){at_before}{end}"
4532 def pad_sql(self, expression: exp.Pad) -> str: 4533 prefix = "L" if expression.args.get("is_left") else "R" 4534 4535 fill_pattern = self.sql(expression, "fill_pattern") or None 4536 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4537 fill_pattern = "' '" 4538 4539 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4545 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4546 generate_series = exp.GenerateSeries(**expression.args) 4547 4548 parent = expression.parent 4549 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4550 parent = parent.parent 4551 4552 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4553 return self.sql(exp.Unnest(expressions=[generate_series])) 4554 4555 if isinstance(parent, exp.Select): 4556 self.unsupported("GenerateSeries projection unnesting is not supported.") 4557 4558 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4560 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4561 exprs = expression.expressions 4562 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4563 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4564 else: 4565 rhs = self.expressions(expression) 4566 4567 return self.func(name, expression.this, rhs or None)
4569 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4570 if self.SUPPORTS_CONVERT_TIMEZONE: 4571 return self.function_fallback_sql(expression) 4572 4573 source_tz = expression.args.get("source_tz") 4574 target_tz = expression.args.get("target_tz") 4575 timestamp = expression.args.get("timestamp") 4576 4577 if source_tz and timestamp: 4578 timestamp = exp.AtTimeZone( 4579 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4580 ) 4581 4582 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4583 4584 return self.sql(expr)
4586 def json_sql(self, expression: exp.JSON) -> str: 4587 this = self.sql(expression, "this") 4588 this = f" {this}" if this else "" 4589 4590 _with = expression.args.get("with") 4591 4592 if _with is None: 4593 with_sql = "" 4594 elif not _with: 4595 with_sql = " WITHOUT" 4596 else: 4597 with_sql = " WITH" 4598 4599 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4600 4601 return f"JSON{this}{with_sql}{unique_sql}"
4603 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4604 def _generate_on_options(arg: t.Any) -> str: 4605 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4606 4607 path = self.sql(expression, "path") 4608 returning = self.sql(expression, "returning") 4609 returning = f" RETURNING {returning}" if returning else "" 4610 4611 on_condition = self.sql(expression, "on_condition") 4612 on_condition = f" {on_condition}" if on_condition else "" 4613 4614 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4616 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4617 else_ = "ELSE " if expression.args.get("else_") else "" 4618 condition = self.sql(expression, "expression") 4619 condition = f"WHEN {condition} THEN " if condition else else_ 4620 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4621 return f"{condition}{insert}"
4629 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4630 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4631 empty = expression.args.get("empty") 4632 empty = ( 4633 f"DEFAULT {empty} ON EMPTY" 4634 if isinstance(empty, exp.Expression) 4635 else self.sql(expression, "empty") 4636 ) 4637 4638 error = expression.args.get("error") 4639 error = ( 4640 f"DEFAULT {error} ON ERROR" 4641 if isinstance(error, exp.Expression) 4642 else self.sql(expression, "error") 4643 ) 4644 4645 if error and empty: 4646 error = ( 4647 f"{empty} {error}" 4648 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4649 else f"{error} {empty}" 4650 ) 4651 empty = "" 4652 4653 null = self.sql(expression, "null") 4654 4655 return f"{empty}{error}{null}"
4661 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4662 this = self.sql(expression, "this") 4663 path = self.sql(expression, "path") 4664 4665 passing = self.expressions(expression, "passing") 4666 passing = f" PASSING {passing}" if passing else "" 4667 4668 on_condition = self.sql(expression, "on_condition") 4669 on_condition = f" {on_condition}" if on_condition else "" 4670 4671 path = f"{path}{passing}{on_condition}" 4672 4673 return self.func("JSON_EXISTS", this, path)
4675 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4676 array_agg = self.function_fallback_sql(expression) 4677 4678 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4679 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4680 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4681 parent = expression.parent 4682 if isinstance(parent, exp.Filter): 4683 parent_cond = parent.expression.this 4684 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4685 else: 4686 this = expression.this 4687 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4688 if this.find(exp.Column): 4689 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4690 this_sql = ( 4691 self.expressions(this) 4692 if isinstance(this, exp.Distinct) 4693 else self.sql(expression, "this") 4694 ) 4695 4696 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4697 4698 return array_agg
4706 def grant_sql(self, expression: exp.Grant) -> str: 4707 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4708 4709 kind = self.sql(expression, "kind") 4710 kind = f" {kind}" if kind else "" 4711 4712 securable = self.sql(expression, "securable") 4713 securable = f" {securable}" if securable else "" 4714 4715 principals = self.expressions(expression, key="principals", flat=True) 4716 4717 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4718 4719 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4743 def overlay_sql(self, expression: exp.Overlay): 4744 this = self.sql(expression, "this") 4745 expr = self.sql(expression, "expression") 4746 from_sql = self.sql(expression, "from") 4747 for_sql = self.sql(expression, "for") 4748 for_sql = f" FOR {for_sql}" if for_sql else "" 4749 4750 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4756 def string_sql(self, expression: exp.String) -> str: 4757 this = expression.this 4758 zone = expression.args.get("zone") 4759 4760 if zone: 4761 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4762 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4763 # set for source_tz to transpile the time conversion before the STRING cast 4764 this = exp.ConvertTimezone( 4765 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4766 ) 4767 4768 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4778 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4779 filler = self.sql(expression, "this") 4780 filler = f" {filler}" if filler else "" 4781 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4782 return f"TRUNCATE{filler} {with_count}"
4784 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4785 if self.SUPPORTS_UNIX_SECONDS: 4786 return self.function_fallback_sql(expression) 4787 4788 start_ts = exp.cast( 4789 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4790 ) 4791 4792 return self.sql( 4793 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4794 )
4796 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4797 dim = expression.expression 4798 4799 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4800 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4801 if not (dim.is_int and dim.name == "1"): 4802 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4803 dim = None 4804 4805 # If dimension is required but not specified, default initialize it 4806 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4807 dim = exp.Literal.number(1) 4808 4809 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4811 def attach_sql(self, expression: exp.Attach) -> str: 4812 this = self.sql(expression, "this") 4813 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4814 expressions = self.expressions(expression) 4815 expressions = f" ({expressions})" if expressions else "" 4816 4817 return f"ATTACH{exists_sql} {this}{expressions}"
4819 def detach_sql(self, expression: exp.Detach) -> str: 4820 this = self.sql(expression, "this") 4821 # the DATABASE keyword is required if IF EXISTS is set 4822 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4823 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4824 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4825 4826 return f"DETACH{exists_sql} {this}"
4834 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4835 this_sql = self.sql(expression, "this") 4836 if isinstance(expression.this, exp.Table): 4837 this_sql = f"TABLE {this_sql}" 4838 4839 return self.func( 4840 "FEATURES_AT_TIME", 4841 this_sql, 4842 expression.args.get("time"), 4843 expression.args.get("num_rows"), 4844 expression.args.get("ignore_feature_nulls"), 4845 )
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
4852 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4853 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4854 encode = f"{encode} {self.sql(expression, 'this')}" 4855 4856 properties = expression.args.get("properties") 4857 if properties: 4858 encode = f"{encode} {self.properties(properties)}" 4859 4860 return encode
4862 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4863 this = self.sql(expression, "this") 4864 include = f"INCLUDE {this}" 4865 4866 column_def = self.sql(expression, "column_def") 4867 if column_def: 4868 include = f"{include} {column_def}" 4869 4870 alias = self.sql(expression, "alias") 4871 if alias: 4872 include = f"{include} AS {alias}" 4873 4874 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
4886 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4887 partitions = self.expressions(expression, "partition_expressions") 4888 create = self.expressions(expression, "create_expressions") 4889 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
4891 def partitionbyrangepropertydynamic_sql( 4892 self, expression: exp.PartitionByRangePropertyDynamic 4893 ) -> str: 4894 start = self.sql(expression, "start") 4895 end = self.sql(expression, "end") 4896 4897 every = expression.args["every"] 4898 if isinstance(every, exp.Interval) and every.this.is_string: 4899 every.this.replace(exp.Literal.number(every.name)) 4900 4901 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4914 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4915 kind = self.sql(expression, "kind") 4916 option = self.sql(expression, "option") 4917 option = f" {option}" if option else "" 4918 this = self.sql(expression, "this") 4919 this = f" {this}" if this else "" 4920 columns = self.expressions(expression) 4921 columns = f" {columns}" if columns else "" 4922 return f"{kind}{option} STATISTICS{this}{columns}"
4924 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4925 this = self.sql(expression, "this") 4926 columns = self.expressions(expression) 4927 inner_expression = self.sql(expression, "expression") 4928 inner_expression = f" {inner_expression}" if inner_expression else "" 4929 update_options = self.sql(expression, "update_options") 4930 update_options = f" {update_options} UPDATE" if update_options else "" 4931 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
4942 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4943 kind = self.sql(expression, "kind") 4944 this = self.sql(expression, "this") 4945 this = f" {this}" if this else "" 4946 inner_expression = self.sql(expression, "expression") 4947 return f"VALIDATE {kind}{this}{inner_expression}"
4949 def analyze_sql(self, expression: exp.Analyze) -> str: 4950 options = self.expressions(expression, key="options", sep=" ") 4951 options = f" {options}" if options else "" 4952 kind = self.sql(expression, "kind") 4953 kind = f" {kind}" if kind else "" 4954 this = self.sql(expression, "this") 4955 this = f" {this}" if this else "" 4956 mode = self.sql(expression, "mode") 4957 mode = f" {mode}" if mode else "" 4958 properties = self.sql(expression, "properties") 4959 properties = f" {properties}" if properties else "" 4960 partition = self.sql(expression, "partition") 4961 partition = f" {partition}" if partition else "" 4962 inner_expression = self.sql(expression, "expression") 4963 inner_expression = f" {inner_expression}" if inner_expression else "" 4964 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
4966 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4967 this = self.sql(expression, "this") 4968 namespaces = self.expressions(expression, key="namespaces") 4969 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4970 passing = self.expressions(expression, key="passing") 4971 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4972 columns = self.expressions(expression, key="columns") 4973 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4974 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4975 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4981 def export_sql(self, expression: exp.Export) -> str: 4982 this = self.sql(expression, "this") 4983 connection = self.sql(expression, "connection") 4984 connection = f"WITH CONNECTION {connection} " if connection else "" 4985 options = self.sql(expression, "options") 4986 return f"EXPORT DATA {connection}{options} AS {this}"
4991 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4992 variable = self.sql(expression, "this") 4993 default = self.sql(expression, "default") 4994 default = f" = {default}" if default else "" 4995 4996 kind = self.sql(expression, "kind") 4997 if isinstance(expression.args.get("kind"), exp.Schema): 4998 kind = f"TABLE {kind}" 4999 5000 return f"{variable} AS {kind}{default}"
5002 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5003 kind = self.sql(expression, "kind") 5004 this = self.sql(expression, "this") 5005 set = self.sql(expression, "expression") 5006 using = self.sql(expression, "using") 5007 using = f" USING {using}" if using else "" 5008 5009 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5010 5011 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5030 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5031 # Snowflake GET/PUT statements: 5032 # PUT <file> <internalStage> <properties> 5033 # GET <internalStage> <file> <properties> 5034 props = expression.args.get("properties") 5035 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5036 this = self.sql(expression, "this") 5037 target = self.sql(expression, "target") 5038 5039 if isinstance(expression, exp.Put): 5040 return f"PUT {this} {target}{props_sql}" 5041 else: 5042 return f"GET {target} {this}{props_sql}"
5050 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5051 if self.SUPPORTS_DECODE_CASE: 5052 return self.func("DECODE", *expression.expressions) 5053 5054 expression, *expressions = expression.expressions 5055 5056 ifs = [] 5057 for search, result in zip(expressions[::2], expressions[1::2]): 5058 if isinstance(search, exp.Literal): 5059 ifs.append(exp.If(this=expression.eq(search), true=result)) 5060 elif isinstance(search, exp.Null): 5061 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5062 else: 5063 if isinstance(search, exp.Binary): 5064 search = exp.paren(search) 5065 5066 cond = exp.or_( 5067 expression.eq(search), 5068 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5069 copy=False, 5070 ) 5071 ifs.append(exp.If(this=cond, true=result)) 5072 5073 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5074 return self.sql(case)
5076 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5077 this = self.sql(expression, "this") 5078 this = self.seg(this, sep="") 5079 dimensions = self.expressions( 5080 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5081 ) 5082 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5083 metrics = self.expressions( 5084 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5085 ) 5086 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5087 where = self.sql(expression, "where") 5088 where = self.seg(f"WHERE {where}") if where else "" 5089 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"