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.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 118 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 119 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 120 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 121 exp.CaseSpecificColumnConstraint: lambda _, 122 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 123 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 124 exp.CharacterSetProperty: lambda self, 125 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 126 exp.ClusteredColumnConstraint: lambda self, 127 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 128 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 129 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 130 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 131 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 132 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 133 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 134 exp.DynamicProperty: lambda *_: "DYNAMIC", 135 exp.EmptyProperty: lambda *_: "EMPTY", 136 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 137 exp.EphemeralColumnConstraint: lambda self, 138 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 139 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 140 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 141 exp.Except: lambda self, e: self.set_operations(e), 142 exp.ExternalProperty: lambda *_: "EXTERNAL", 143 exp.GlobalProperty: lambda *_: "GLOBAL", 144 exp.HeapProperty: lambda *_: "HEAP", 145 exp.IcebergProperty: lambda *_: "ICEBERG", 146 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 147 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 148 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 149 exp.Intersect: lambda self, e: self.set_operations(e), 150 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 151 exp.LanguageProperty: lambda self, e: self.naked_property(e), 152 exp.LocationProperty: lambda self, e: self.naked_property(e), 153 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 154 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 155 exp.NonClusteredColumnConstraint: lambda self, 156 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 157 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 158 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 159 exp.OnCommitProperty: lambda _, 160 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 161 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 162 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 163 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 164 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 165 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 166 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 167 exp.ProjectionPolicyColumnConstraint: lambda self, 168 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 169 exp.RemoteWithConnectionModelProperty: lambda self, 170 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 171 exp.ReturnsProperty: lambda self, e: ( 172 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 173 ), 174 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 175 exp.SecureProperty: lambda *_: "SECURE", 176 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 177 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 178 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 179 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 180 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 181 exp.SqlReadWriteProperty: lambda _, e: e.name, 182 exp.SqlSecurityProperty: lambda _, 183 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 184 exp.StabilityProperty: lambda _, e: e.name, 185 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 186 exp.StreamingTableProperty: lambda *_: "STREAMING", 187 exp.StrictProperty: lambda *_: "STRICT", 188 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 189 exp.TemporaryProperty: lambda *_: "TEMPORARY", 190 exp.TagColumnConstraint: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 191 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 192 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 193 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 194 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 195 exp.TransientProperty: lambda *_: "TRANSIENT", 196 exp.Union: lambda self, e: self.set_operations(e), 197 exp.UnloggedProperty: lambda *_: "UNLOGGED", 198 exp.Uuid: lambda *_: "UUID()", 199 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 200 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 201 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 202 exp.VolatileProperty: lambda *_: "VOLATILE", 203 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 204 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 205 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 206 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 207 } 208 209 # Whether null ordering is supported in order by 210 # True: Full Support, None: No support, False: No support for certain cases 211 # such as window specifications, aggregate functions etc 212 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 213 214 # Whether ignore nulls is inside the agg or outside. 215 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 216 IGNORE_NULLS_IN_FUNC = False 217 218 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 219 LOCKING_READS_SUPPORTED = False 220 221 # Whether the EXCEPT and INTERSECT operations can return duplicates 222 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 223 224 # Wrap derived values in parens, usually standard but spark doesn't support it 225 WRAP_DERIVED_VALUES = True 226 227 # Whether create function uses an AS before the RETURN 228 CREATE_FUNCTION_RETURN_AS = True 229 230 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 231 MATCHED_BY_SOURCE = True 232 233 # Whether the INTERVAL expression works only with values like '1 day' 234 SINGLE_STRING_INTERVAL = False 235 236 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 237 INTERVAL_ALLOWS_PLURAL_FORM = True 238 239 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 240 LIMIT_FETCH = "ALL" 241 242 # Whether limit and fetch allows expresions or just limits 243 LIMIT_ONLY_LITERALS = False 244 245 # Whether a table is allowed to be renamed with a db 246 RENAME_TABLE_WITH_DB = True 247 248 # The separator for grouping sets and rollups 249 GROUPINGS_SEP = "," 250 251 # The string used for creating an index on a table 252 INDEX_ON = "ON" 253 254 # Whether join hints should be generated 255 JOIN_HINTS = True 256 257 # Whether table hints should be generated 258 TABLE_HINTS = True 259 260 # Whether query hints should be generated 261 QUERY_HINTS = True 262 263 # What kind of separator to use for query hints 264 QUERY_HINT_SEP = ", " 265 266 # Whether comparing against booleans (e.g. x IS TRUE) is supported 267 IS_BOOL_ALLOWED = True 268 269 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 270 DUPLICATE_KEY_UPDATE_WITH_SET = True 271 272 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 273 LIMIT_IS_TOP = False 274 275 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 276 RETURNING_END = True 277 278 # Whether to generate an unquoted value for EXTRACT's date part argument 279 EXTRACT_ALLOWS_QUOTES = True 280 281 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 282 TZ_TO_WITH_TIME_ZONE = False 283 284 # Whether the NVL2 function is supported 285 NVL2_SUPPORTED = True 286 287 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 288 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 289 290 # Whether VALUES statements can be used as derived tables. 291 # MySQL 5 and Redshift do not allow this, so when False, it will convert 292 # SELECT * VALUES into SELECT UNION 293 VALUES_AS_TABLE = True 294 295 # Whether the word COLUMN is included when adding a column with ALTER TABLE 296 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 297 298 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 299 UNNEST_WITH_ORDINALITY = True 300 301 # Whether FILTER (WHERE cond) can be used for conditional aggregation 302 AGGREGATE_FILTER_SUPPORTED = True 303 304 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 305 SEMI_ANTI_JOIN_WITH_SIDE = True 306 307 # Whether to include the type of a computed column in the CREATE DDL 308 COMPUTED_COLUMN_WITH_TYPE = True 309 310 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 311 SUPPORTS_TABLE_COPY = True 312 313 # Whether parentheses are required around the table sample's expression 314 TABLESAMPLE_REQUIRES_PARENS = True 315 316 # Whether a table sample clause's size needs to be followed by the ROWS keyword 317 TABLESAMPLE_SIZE_IS_ROWS = True 318 319 # The keyword(s) to use when generating a sample clause 320 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 321 322 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 323 TABLESAMPLE_WITH_METHOD = True 324 325 # The keyword to use when specifying the seed of a sample clause 326 TABLESAMPLE_SEED_KEYWORD = "SEED" 327 328 # Whether COLLATE is a function instead of a binary operator 329 COLLATE_IS_FUNC = False 330 331 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 332 DATA_TYPE_SPECIFIERS_ALLOWED = False 333 334 # Whether conditions require booleans WHERE x = 0 vs WHERE x 335 ENSURE_BOOLS = False 336 337 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 338 CTE_RECURSIVE_KEYWORD_REQUIRED = True 339 340 # Whether CONCAT requires >1 arguments 341 SUPPORTS_SINGLE_ARG_CONCAT = True 342 343 # Whether LAST_DAY function supports a date part argument 344 LAST_DAY_SUPPORTS_DATE_PART = True 345 346 # Whether named columns are allowed in table aliases 347 SUPPORTS_TABLE_ALIAS_COLUMNS = True 348 349 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 350 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 351 352 # What delimiter to use for separating JSON key/value pairs 353 JSON_KEY_VALUE_PAIR_SEP = ":" 354 355 # INSERT OVERWRITE TABLE x override 356 INSERT_OVERWRITE = " OVERWRITE TABLE" 357 358 # Whether the SELECT .. INTO syntax is used instead of CTAS 359 SUPPORTS_SELECT_INTO = False 360 361 # Whether UNLOGGED tables can be created 362 SUPPORTS_UNLOGGED_TABLES = False 363 364 # Whether the CREATE TABLE LIKE statement is supported 365 SUPPORTS_CREATE_TABLE_LIKE = True 366 367 # Whether the LikeProperty needs to be specified inside of the schema clause 368 LIKE_PROPERTY_INSIDE_SCHEMA = False 369 370 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 371 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 372 MULTI_ARG_DISTINCT = True 373 374 # Whether the JSON extraction operators expect a value of type JSON 375 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 376 377 # Whether bracketed keys like ["foo"] are supported in JSON paths 378 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 379 380 # Whether to escape keys using single quotes in JSON paths 381 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 382 383 # The JSONPathPart expressions supported by this dialect 384 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 385 386 # Whether any(f(x) for x in array) can be implemented by this dialect 387 CAN_IMPLEMENT_ARRAY_ANY = False 388 389 # Whether the function TO_NUMBER is supported 390 SUPPORTS_TO_NUMBER = True 391 392 # Whether or not set op modifiers apply to the outer set op or select. 393 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 394 # True means limit 1 happens after the set op, False means it it happens on y. 395 SET_OP_MODIFIERS = True 396 397 # Whether parameters from COPY statement are wrapped in parentheses 398 COPY_PARAMS_ARE_WRAPPED = True 399 400 # Whether values of params are set with "=" token or empty space 401 COPY_PARAMS_EQ_REQUIRED = False 402 403 # Whether COPY statement has INTO keyword 404 COPY_HAS_INTO_KEYWORD = True 405 406 # Whether the conditional TRY(expression) function is supported 407 TRY_SUPPORTED = True 408 409 # Whether the UESCAPE syntax in unicode strings is supported 410 SUPPORTS_UESCAPE = True 411 412 # The keyword to use when generating a star projection with excluded columns 413 STAR_EXCEPT = "EXCEPT" 414 415 # The HEX function name 416 HEX_FUNC = "HEX" 417 418 # The keywords to use when prefixing & separating WITH based properties 419 WITH_PROPERTIES_PREFIX = "WITH" 420 421 # Whether to quote the generated expression of exp.JsonPath 422 QUOTE_JSON_PATH = True 423 424 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 425 PAD_FILL_PATTERN_IS_REQUIRED = False 426 427 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 428 SUPPORTS_EXPLODING_PROJECTIONS = True 429 430 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 431 ARRAY_CONCAT_IS_VAR_LEN = True 432 433 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 434 SUPPORTS_CONVERT_TIMEZONE = False 435 436 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 437 SUPPORTS_MEDIAN = True 438 439 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 440 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 441 442 TYPE_MAPPING = { 443 exp.DataType.Type.NCHAR: "CHAR", 444 exp.DataType.Type.NVARCHAR: "VARCHAR", 445 exp.DataType.Type.MEDIUMTEXT: "TEXT", 446 exp.DataType.Type.LONGTEXT: "TEXT", 447 exp.DataType.Type.TINYTEXT: "TEXT", 448 exp.DataType.Type.MEDIUMBLOB: "BLOB", 449 exp.DataType.Type.LONGBLOB: "BLOB", 450 exp.DataType.Type.TINYBLOB: "BLOB", 451 exp.DataType.Type.INET: "INET", 452 exp.DataType.Type.ROWVERSION: "VARBINARY", 453 } 454 455 TIME_PART_SINGULARS = { 456 "MICROSECONDS": "MICROSECOND", 457 "SECONDS": "SECOND", 458 "MINUTES": "MINUTE", 459 "HOURS": "HOUR", 460 "DAYS": "DAY", 461 "WEEKS": "WEEK", 462 "MONTHS": "MONTH", 463 "QUARTERS": "QUARTER", 464 "YEARS": "YEAR", 465 } 466 467 AFTER_HAVING_MODIFIER_TRANSFORMS = { 468 "cluster": lambda self, e: self.sql(e, "cluster"), 469 "distribute": lambda self, e: self.sql(e, "distribute"), 470 "sort": lambda self, e: self.sql(e, "sort"), 471 "windows": lambda self, e: ( 472 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 473 if e.args.get("windows") 474 else "" 475 ), 476 "qualify": lambda self, e: self.sql(e, "qualify"), 477 } 478 479 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 480 481 STRUCT_DELIMITER = ("<", ">") 482 483 PARAMETER_TOKEN = "@" 484 NAMED_PLACEHOLDER_TOKEN = ":" 485 486 PROPERTIES_LOCATION = { 487 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 488 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 489 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 490 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 491 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 492 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 493 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 494 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 495 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 496 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 497 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 498 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 499 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 500 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 501 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 502 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 503 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 504 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 505 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 506 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 507 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 508 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 509 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 510 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 511 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 512 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 513 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 514 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 515 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 516 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 517 exp.HeapProperty: exp.Properties.Location.POST_WITH, 518 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 519 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 520 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 521 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 522 exp.JournalProperty: exp.Properties.Location.POST_NAME, 523 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 524 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 525 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 526 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 527 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 528 exp.LogProperty: exp.Properties.Location.POST_NAME, 529 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 530 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 531 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 532 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 533 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 534 exp.Order: exp.Properties.Location.POST_SCHEMA, 535 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 536 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 537 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 538 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 539 exp.Property: exp.Properties.Location.POST_WITH, 540 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 541 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 542 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 543 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 544 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 545 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 546 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 547 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 548 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 549 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 550 exp.Set: exp.Properties.Location.POST_SCHEMA, 551 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 552 exp.SetProperty: exp.Properties.Location.POST_CREATE, 553 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 555 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 556 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 557 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 558 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 559 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 560 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 561 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 562 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 563 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 564 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 565 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 567 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 568 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 570 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 571 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 572 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 573 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 575 } 576 577 # Keywords that can't be used as unquoted identifier names 578 RESERVED_KEYWORDS: t.Set[str] = set() 579 580 # Expressions whose comments are separated from them for better formatting 581 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 582 exp.Command, 583 exp.Create, 584 exp.Delete, 585 exp.Drop, 586 exp.From, 587 exp.Insert, 588 exp.Join, 589 exp.MultitableInserts, 590 exp.Select, 591 exp.SetOperation, 592 exp.Update, 593 exp.Where, 594 exp.With, 595 ) 596 597 # Expressions that should not have their comments generated in maybe_comment 598 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 599 exp.Binary, 600 exp.SetOperation, 601 ) 602 603 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 604 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 605 exp.Column, 606 exp.Literal, 607 exp.Neg, 608 exp.Paren, 609 ) 610 611 PARAMETERIZABLE_TEXT_TYPES = { 612 exp.DataType.Type.NVARCHAR, 613 exp.DataType.Type.VARCHAR, 614 exp.DataType.Type.CHAR, 615 exp.DataType.Type.NCHAR, 616 } 617 618 # Expressions that need to have all CTEs under them bubbled up to them 619 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 620 621 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 622 623 __slots__ = ( 624 "pretty", 625 "identify", 626 "normalize", 627 "pad", 628 "_indent", 629 "normalize_functions", 630 "unsupported_level", 631 "max_unsupported", 632 "leading_comma", 633 "max_text_width", 634 "comments", 635 "dialect", 636 "unsupported_messages", 637 "_escaped_quote_end", 638 "_escaped_identifier_end", 639 "_next_name", 640 "_identifier_start", 641 "_identifier_end", 642 ) 643 644 def __init__( 645 self, 646 pretty: t.Optional[bool] = None, 647 identify: str | bool = False, 648 normalize: bool = False, 649 pad: int = 2, 650 indent: int = 2, 651 normalize_functions: t.Optional[str | bool] = None, 652 unsupported_level: ErrorLevel = ErrorLevel.WARN, 653 max_unsupported: int = 3, 654 leading_comma: bool = False, 655 max_text_width: int = 80, 656 comments: bool = True, 657 dialect: DialectType = None, 658 ): 659 import sqlglot 660 from sqlglot.dialects import Dialect 661 662 self.pretty = pretty if pretty is not None else sqlglot.pretty 663 self.identify = identify 664 self.normalize = normalize 665 self.pad = pad 666 self._indent = indent 667 self.unsupported_level = unsupported_level 668 self.max_unsupported = max_unsupported 669 self.leading_comma = leading_comma 670 self.max_text_width = max_text_width 671 self.comments = comments 672 self.dialect = Dialect.get_or_raise(dialect) 673 674 # This is both a Dialect property and a Generator argument, so we prioritize the latter 675 self.normalize_functions = ( 676 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 677 ) 678 679 self.unsupported_messages: t.List[str] = [] 680 self._escaped_quote_end: str = ( 681 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 682 ) 683 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 684 685 self._next_name = name_sequence("_t") 686 687 self._identifier_start = self.dialect.IDENTIFIER_START 688 self._identifier_end = self.dialect.IDENTIFIER_END 689 690 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 691 """ 692 Generates the SQL string corresponding to the given syntax tree. 693 694 Args: 695 expression: The syntax tree. 696 copy: Whether to copy the expression. The generator performs mutations so 697 it is safer to copy. 698 699 Returns: 700 The SQL string corresponding to `expression`. 701 """ 702 if copy: 703 expression = expression.copy() 704 705 expression = self.preprocess(expression) 706 707 self.unsupported_messages = [] 708 sql = self.sql(expression).strip() 709 710 if self.pretty: 711 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 712 713 if self.unsupported_level == ErrorLevel.IGNORE: 714 return sql 715 716 if self.unsupported_level == ErrorLevel.WARN: 717 for msg in self.unsupported_messages: 718 logger.warning(msg) 719 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 720 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 721 722 return sql 723 724 def preprocess(self, expression: exp.Expression) -> exp.Expression: 725 """Apply generic preprocessing transformations to a given expression.""" 726 expression = self._move_ctes_to_top_level(expression) 727 728 if self.ENSURE_BOOLS: 729 from sqlglot.transforms import ensure_bools 730 731 expression = ensure_bools(expression) 732 733 return expression 734 735 def _move_ctes_to_top_level(self, expression: E) -> E: 736 if ( 737 not expression.parent 738 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 739 and any(node.parent is not expression for node in expression.find_all(exp.With)) 740 ): 741 from sqlglot.transforms import move_ctes_to_top_level 742 743 expression = move_ctes_to_top_level(expression) 744 return expression 745 746 def unsupported(self, message: str) -> None: 747 if self.unsupported_level == ErrorLevel.IMMEDIATE: 748 raise UnsupportedError(message) 749 self.unsupported_messages.append(message) 750 751 def sep(self, sep: str = " ") -> str: 752 return f"{sep.strip()}\n" if self.pretty else sep 753 754 def seg(self, sql: str, sep: str = " ") -> str: 755 return f"{self.sep(sep)}{sql}" 756 757 def pad_comment(self, comment: str) -> str: 758 comment = " " + comment if comment[0].strip() else comment 759 comment = comment + " " if comment[-1].strip() else comment 760 return comment 761 762 def maybe_comment( 763 self, 764 sql: str, 765 expression: t.Optional[exp.Expression] = None, 766 comments: t.Optional[t.List[str]] = None, 767 separated: bool = False, 768 ) -> str: 769 comments = ( 770 ((expression and expression.comments) if comments is None else comments) # type: ignore 771 if self.comments 772 else None 773 ) 774 775 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 776 return sql 777 778 comments_sql = " ".join( 779 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 780 ) 781 782 if not comments_sql: 783 return sql 784 785 comments_sql = self._replace_line_breaks(comments_sql) 786 787 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 788 return ( 789 f"{self.sep()}{comments_sql}{sql}" 790 if not sql or sql[0].isspace() 791 else f"{comments_sql}{self.sep()}{sql}" 792 ) 793 794 return f"{sql} {comments_sql}" 795 796 def wrap(self, expression: exp.Expression | str) -> str: 797 this_sql = ( 798 self.sql(expression) 799 if isinstance(expression, exp.UNWRAPPED_QUERIES) 800 else self.sql(expression, "this") 801 ) 802 if not this_sql: 803 return "()" 804 805 this_sql = self.indent(this_sql, level=1, pad=0) 806 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 807 808 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 809 original = self.identify 810 self.identify = False 811 result = func(*args, **kwargs) 812 self.identify = original 813 return result 814 815 def normalize_func(self, name: str) -> str: 816 if self.normalize_functions == "upper" or self.normalize_functions is True: 817 return name.upper() 818 if self.normalize_functions == "lower": 819 return name.lower() 820 return name 821 822 def indent( 823 self, 824 sql: str, 825 level: int = 0, 826 pad: t.Optional[int] = None, 827 skip_first: bool = False, 828 skip_last: bool = False, 829 ) -> str: 830 if not self.pretty or not sql: 831 return sql 832 833 pad = self.pad if pad is None else pad 834 lines = sql.split("\n") 835 836 return "\n".join( 837 ( 838 line 839 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 840 else f"{' ' * (level * self._indent + pad)}{line}" 841 ) 842 for i, line in enumerate(lines) 843 ) 844 845 def sql( 846 self, 847 expression: t.Optional[str | exp.Expression], 848 key: t.Optional[str] = None, 849 comment: bool = True, 850 ) -> str: 851 if not expression: 852 return "" 853 854 if isinstance(expression, str): 855 return expression 856 857 if key: 858 value = expression.args.get(key) 859 if value: 860 return self.sql(value) 861 return "" 862 863 transform = self.TRANSFORMS.get(expression.__class__) 864 865 if callable(transform): 866 sql = transform(self, expression) 867 elif isinstance(expression, exp.Expression): 868 exp_handler_name = f"{expression.key}_sql" 869 870 if hasattr(self, exp_handler_name): 871 sql = getattr(self, exp_handler_name)(expression) 872 elif isinstance(expression, exp.Func): 873 sql = self.function_fallback_sql(expression) 874 elif isinstance(expression, exp.Property): 875 sql = self.property_sql(expression) 876 else: 877 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 878 else: 879 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 880 881 return self.maybe_comment(sql, expression) if self.comments and comment else sql 882 883 def uncache_sql(self, expression: exp.Uncache) -> str: 884 table = self.sql(expression, "this") 885 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 886 return f"UNCACHE TABLE{exists_sql} {table}" 887 888 def cache_sql(self, expression: exp.Cache) -> str: 889 lazy = " LAZY" if expression.args.get("lazy") else "" 890 table = self.sql(expression, "this") 891 options = expression.args.get("options") 892 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 893 sql = self.sql(expression, "expression") 894 sql = f" AS{self.sep()}{sql}" if sql else "" 895 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 896 return self.prepend_ctes(expression, sql) 897 898 def characterset_sql(self, expression: exp.CharacterSet) -> str: 899 if isinstance(expression.parent, exp.Cast): 900 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 901 default = "DEFAULT " if expression.args.get("default") else "" 902 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 903 904 def column_parts(self, expression: exp.Column) -> str: 905 return ".".join( 906 self.sql(part) 907 for part in ( 908 expression.args.get("catalog"), 909 expression.args.get("db"), 910 expression.args.get("table"), 911 expression.args.get("this"), 912 ) 913 if part 914 ) 915 916 def column_sql(self, expression: exp.Column) -> str: 917 join_mark = " (+)" if expression.args.get("join_mark") else "" 918 919 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 920 join_mark = "" 921 self.unsupported("Outer join syntax using the (+) operator is not supported.") 922 923 return f"{self.column_parts(expression)}{join_mark}" 924 925 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 926 this = self.sql(expression, "this") 927 this = f" {this}" if this else "" 928 position = self.sql(expression, "position") 929 return f"{position}{this}" 930 931 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 932 column = self.sql(expression, "this") 933 kind = self.sql(expression, "kind") 934 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 935 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 936 kind = f"{sep}{kind}" if kind else "" 937 constraints = f" {constraints}" if constraints else "" 938 position = self.sql(expression, "position") 939 position = f" {position}" if position else "" 940 941 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 942 kind = "" 943 944 return f"{exists}{column}{kind}{constraints}{position}" 945 946 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 947 this = self.sql(expression, "this") 948 kind_sql = self.sql(expression, "kind").strip() 949 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 950 951 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 952 this = self.sql(expression, "this") 953 if expression.args.get("not_null"): 954 persisted = " PERSISTED NOT NULL" 955 elif expression.args.get("persisted"): 956 persisted = " PERSISTED" 957 else: 958 persisted = "" 959 return f"AS {this}{persisted}" 960 961 def autoincrementcolumnconstraint_sql(self, _) -> str: 962 return self.token_sql(TokenType.AUTO_INCREMENT) 963 964 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 965 if isinstance(expression.this, list): 966 this = self.wrap(self.expressions(expression, key="this", flat=True)) 967 else: 968 this = self.sql(expression, "this") 969 970 return f"COMPRESS {this}" 971 972 def generatedasidentitycolumnconstraint_sql( 973 self, expression: exp.GeneratedAsIdentityColumnConstraint 974 ) -> str: 975 this = "" 976 if expression.this is not None: 977 on_null = " ON NULL" if expression.args.get("on_null") else "" 978 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 979 980 start = expression.args.get("start") 981 start = f"START WITH {start}" if start else "" 982 increment = expression.args.get("increment") 983 increment = f" INCREMENT BY {increment}" if increment else "" 984 minvalue = expression.args.get("minvalue") 985 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 986 maxvalue = expression.args.get("maxvalue") 987 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 988 cycle = expression.args.get("cycle") 989 cycle_sql = "" 990 991 if cycle is not None: 992 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 993 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 994 995 sequence_opts = "" 996 if start or increment or cycle_sql: 997 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 998 sequence_opts = f" ({sequence_opts.strip()})" 999 1000 expr = self.sql(expression, "expression") 1001 expr = f"({expr})" if expr else "IDENTITY" 1002 1003 return f"GENERATED{this} AS {expr}{sequence_opts}" 1004 1005 def generatedasrowcolumnconstraint_sql( 1006 self, expression: exp.GeneratedAsRowColumnConstraint 1007 ) -> str: 1008 start = "START" if expression.args.get("start") else "END" 1009 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1010 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1011 1012 def periodforsystemtimeconstraint_sql( 1013 self, expression: exp.PeriodForSystemTimeConstraint 1014 ) -> str: 1015 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1016 1017 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1018 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1019 1020 def transformcolumnconstraint_sql(self, expression: exp.TransformColumnConstraint) -> str: 1021 return f"AS {self.sql(expression, 'this')}" 1022 1023 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1024 desc = expression.args.get("desc") 1025 if desc is not None: 1026 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1027 return "PRIMARY KEY" 1028 1029 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1030 this = self.sql(expression, "this") 1031 this = f" {this}" if this else "" 1032 index_type = expression.args.get("index_type") 1033 index_type = f" USING {index_type}" if index_type else "" 1034 on_conflict = self.sql(expression, "on_conflict") 1035 on_conflict = f" {on_conflict}" if on_conflict else "" 1036 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1037 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}" 1038 1039 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1040 return self.sql(expression, "this") 1041 1042 def create_sql(self, expression: exp.Create) -> str: 1043 kind = self.sql(expression, "kind") 1044 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1045 properties = expression.args.get("properties") 1046 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1047 1048 this = self.createable_sql(expression, properties_locs) 1049 1050 properties_sql = "" 1051 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1052 exp.Properties.Location.POST_WITH 1053 ): 1054 properties_sql = self.sql( 1055 exp.Properties( 1056 expressions=[ 1057 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1058 *properties_locs[exp.Properties.Location.POST_WITH], 1059 ] 1060 ) 1061 ) 1062 1063 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1064 properties_sql = self.sep() + properties_sql 1065 elif not self.pretty: 1066 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1067 properties_sql = f" {properties_sql}" 1068 1069 begin = " BEGIN" if expression.args.get("begin") else "" 1070 end = " END" if expression.args.get("end") else "" 1071 1072 expression_sql = self.sql(expression, "expression") 1073 if expression_sql: 1074 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1075 1076 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1077 postalias_props_sql = "" 1078 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1079 postalias_props_sql = self.properties( 1080 exp.Properties( 1081 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1082 ), 1083 wrapped=False, 1084 ) 1085 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1086 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1087 1088 postindex_props_sql = "" 1089 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1090 postindex_props_sql = self.properties( 1091 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1092 wrapped=False, 1093 prefix=" ", 1094 ) 1095 1096 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1097 indexes = f" {indexes}" if indexes else "" 1098 index_sql = indexes + postindex_props_sql 1099 1100 replace = " OR REPLACE" if expression.args.get("replace") else "" 1101 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1102 unique = " UNIQUE" if expression.args.get("unique") else "" 1103 1104 clustered = expression.args.get("clustered") 1105 if clustered is None: 1106 clustered_sql = "" 1107 elif clustered: 1108 clustered_sql = " CLUSTERED COLUMNSTORE" 1109 else: 1110 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1111 1112 postcreate_props_sql = "" 1113 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1114 postcreate_props_sql = self.properties( 1115 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1116 sep=" ", 1117 prefix=" ", 1118 wrapped=False, 1119 ) 1120 1121 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1122 1123 postexpression_props_sql = "" 1124 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1125 postexpression_props_sql = self.properties( 1126 exp.Properties( 1127 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1128 ), 1129 sep=" ", 1130 prefix=" ", 1131 wrapped=False, 1132 ) 1133 1134 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1135 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1136 no_schema_binding = ( 1137 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1138 ) 1139 1140 clone = self.sql(expression, "clone") 1141 clone = f" {clone}" if clone else "" 1142 1143 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1144 return self.prepend_ctes(expression, expression_sql) 1145 1146 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1147 start = self.sql(expression, "start") 1148 start = f"START WITH {start}" if start else "" 1149 increment = self.sql(expression, "increment") 1150 increment = f" INCREMENT BY {increment}" if increment else "" 1151 minvalue = self.sql(expression, "minvalue") 1152 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1153 maxvalue = self.sql(expression, "maxvalue") 1154 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1155 owned = self.sql(expression, "owned") 1156 owned = f" OWNED BY {owned}" if owned else "" 1157 1158 cache = expression.args.get("cache") 1159 if cache is None: 1160 cache_str = "" 1161 elif cache is True: 1162 cache_str = " CACHE" 1163 else: 1164 cache_str = f" CACHE {cache}" 1165 1166 options = self.expressions(expression, key="options", flat=True, sep=" ") 1167 options = f" {options}" if options else "" 1168 1169 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1170 1171 def clone_sql(self, expression: exp.Clone) -> str: 1172 this = self.sql(expression, "this") 1173 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1174 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1175 return f"{shallow}{keyword} {this}" 1176 1177 def describe_sql(self, expression: exp.Describe) -> str: 1178 style = expression.args.get("style") 1179 style = f" {style}" if style else "" 1180 partition = self.sql(expression, "partition") 1181 partition = f" {partition}" if partition else "" 1182 return f"DESCRIBE{style} {self.sql(expression, 'this')}{partition}" 1183 1184 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1185 tag = self.sql(expression, "tag") 1186 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1187 1188 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1189 with_ = self.sql(expression, "with") 1190 if with_: 1191 sql = f"{with_}{self.sep()}{sql}" 1192 return sql 1193 1194 def with_sql(self, expression: exp.With) -> str: 1195 sql = self.expressions(expression, flat=True) 1196 recursive = ( 1197 "RECURSIVE " 1198 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1199 else "" 1200 ) 1201 1202 return f"WITH {recursive}{sql}" 1203 1204 def cte_sql(self, expression: exp.CTE) -> str: 1205 alias = expression.args.get("alias") 1206 if alias: 1207 alias.add_comments(expression.pop_comments()) 1208 1209 alias_sql = self.sql(expression, "alias") 1210 1211 materialized = expression.args.get("materialized") 1212 if materialized is False: 1213 materialized = "NOT MATERIALIZED " 1214 elif materialized: 1215 materialized = "MATERIALIZED " 1216 1217 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1218 1219 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1220 alias = self.sql(expression, "this") 1221 columns = self.expressions(expression, key="columns", flat=True) 1222 columns = f"({columns})" if columns else "" 1223 1224 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1225 columns = "" 1226 self.unsupported("Named columns are not supported in table alias.") 1227 1228 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1229 alias = self._next_name() 1230 1231 return f"{alias}{columns}" 1232 1233 def bitstring_sql(self, expression: exp.BitString) -> str: 1234 this = self.sql(expression, "this") 1235 if self.dialect.BIT_START: 1236 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1237 return f"{int(this, 2)}" 1238 1239 def hexstring_sql(self, expression: exp.HexString) -> str: 1240 this = self.sql(expression, "this") 1241 if self.dialect.HEX_START: 1242 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1243 return f"{int(this, 16)}" 1244 1245 def bytestring_sql(self, expression: exp.ByteString) -> str: 1246 this = self.sql(expression, "this") 1247 if self.dialect.BYTE_START: 1248 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1249 return this 1250 1251 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1252 this = self.sql(expression, "this") 1253 escape = expression.args.get("escape") 1254 1255 if self.dialect.UNICODE_START: 1256 escape_substitute = r"\\\1" 1257 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1258 else: 1259 escape_substitute = r"\\u\1" 1260 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1261 1262 if escape: 1263 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1264 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1265 else: 1266 escape_pattern = ESCAPED_UNICODE_RE 1267 escape_sql = "" 1268 1269 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1270 this = escape_pattern.sub(escape_substitute, this) 1271 1272 return f"{left_quote}{this}{right_quote}{escape_sql}" 1273 1274 def rawstring_sql(self, expression: exp.RawString) -> str: 1275 string = self.escape_str(expression.this.replace("\\", "\\\\"), escape_backslash=False) 1276 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1277 1278 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1279 this = self.sql(expression, "this") 1280 specifier = self.sql(expression, "expression") 1281 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1282 return f"{this}{specifier}" 1283 1284 def datatype_sql(self, expression: exp.DataType) -> str: 1285 nested = "" 1286 values = "" 1287 interior = self.expressions(expression, flat=True) 1288 1289 type_value = expression.this 1290 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1291 type_sql = self.sql(expression, "kind") 1292 else: 1293 type_sql = ( 1294 self.TYPE_MAPPING.get(type_value, type_value.value) 1295 if isinstance(type_value, exp.DataType.Type) 1296 else type_value 1297 ) 1298 1299 if interior: 1300 if expression.args.get("nested"): 1301 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1302 if expression.args.get("values") is not None: 1303 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1304 values = self.expressions(expression, key="values", flat=True) 1305 values = f"{delimiters[0]}{values}{delimiters[1]}" 1306 elif type_value == exp.DataType.Type.INTERVAL: 1307 nested = f" {interior}" 1308 else: 1309 nested = f"({interior})" 1310 1311 type_sql = f"{type_sql}{nested}{values}" 1312 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1313 exp.DataType.Type.TIMETZ, 1314 exp.DataType.Type.TIMESTAMPTZ, 1315 ): 1316 type_sql = f"{type_sql} WITH TIME ZONE" 1317 1318 return type_sql 1319 1320 def directory_sql(self, expression: exp.Directory) -> str: 1321 local = "LOCAL " if expression.args.get("local") else "" 1322 row_format = self.sql(expression, "row_format") 1323 row_format = f" {row_format}" if row_format else "" 1324 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1325 1326 def delete_sql(self, expression: exp.Delete) -> str: 1327 this = self.sql(expression, "this") 1328 this = f" FROM {this}" if this else "" 1329 using = self.sql(expression, "using") 1330 using = f" USING {using}" if using else "" 1331 cluster = self.sql(expression, "cluster") 1332 cluster = f" {cluster}" if cluster else "" 1333 where = self.sql(expression, "where") 1334 returning = self.sql(expression, "returning") 1335 limit = self.sql(expression, "limit") 1336 tables = self.expressions(expression, key="tables") 1337 tables = f" {tables}" if tables else "" 1338 if self.RETURNING_END: 1339 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1340 else: 1341 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1342 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1343 1344 def drop_sql(self, expression: exp.Drop) -> str: 1345 this = self.sql(expression, "this") 1346 expressions = self.expressions(expression, flat=True) 1347 expressions = f" ({expressions})" if expressions else "" 1348 kind = expression.args["kind"] 1349 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1350 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1351 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1352 on_cluster = self.sql(expression, "cluster") 1353 on_cluster = f" {on_cluster}" if on_cluster else "" 1354 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1355 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1356 cascade = " CASCADE" if expression.args.get("cascade") else "" 1357 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1358 purge = " PURGE" if expression.args.get("purge") else "" 1359 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1360 1361 def set_operation(self, expression: exp.SetOperation) -> str: 1362 op_type = type(expression) 1363 op_name = op_type.key.upper() 1364 1365 distinct = expression.args.get("distinct") 1366 if ( 1367 distinct is False 1368 and op_type in (exp.Except, exp.Intersect) 1369 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1370 ): 1371 self.unsupported(f"{op_name} ALL is not supported") 1372 1373 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1374 1375 if distinct is None: 1376 distinct = default_distinct 1377 if distinct is None: 1378 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1379 1380 if distinct is default_distinct: 1381 kind = "" 1382 else: 1383 kind = " DISTINCT" if distinct else " ALL" 1384 1385 by_name = " BY NAME" if expression.args.get("by_name") else "" 1386 return f"{op_name}{kind}{by_name}" 1387 1388 def set_operations(self, expression: exp.SetOperation) -> str: 1389 if not self.SET_OP_MODIFIERS: 1390 limit = expression.args.get("limit") 1391 order = expression.args.get("order") 1392 1393 if limit or order: 1394 select = self._move_ctes_to_top_level( 1395 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1396 ) 1397 1398 if limit: 1399 select = select.limit(limit.pop(), copy=False) 1400 if order: 1401 select = select.order_by(order.pop(), copy=False) 1402 return self.sql(select) 1403 1404 sqls: t.List[str] = [] 1405 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1406 1407 while stack: 1408 node = stack.pop() 1409 1410 if isinstance(node, exp.SetOperation): 1411 stack.append(node.expression) 1412 stack.append( 1413 self.maybe_comment( 1414 self.set_operation(node), comments=node.comments, separated=True 1415 ) 1416 ) 1417 stack.append(node.this) 1418 else: 1419 sqls.append(self.sql(node)) 1420 1421 this = self.sep().join(sqls) 1422 this = self.query_modifiers(expression, this) 1423 return self.prepend_ctes(expression, this) 1424 1425 def fetch_sql(self, expression: exp.Fetch) -> str: 1426 direction = expression.args.get("direction") 1427 direction = f" {direction}" if direction else "" 1428 count = self.sql(expression, "count") 1429 count = f" {count}" if count else "" 1430 if expression.args.get("percent"): 1431 count = f"{count} PERCENT" 1432 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY" 1433 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}" 1434 1435 def filter_sql(self, expression: exp.Filter) -> str: 1436 if self.AGGREGATE_FILTER_SUPPORTED: 1437 this = self.sql(expression, "this") 1438 where = self.sql(expression, "expression").strip() 1439 return f"{this} FILTER({where})" 1440 1441 agg = expression.this 1442 agg_arg = agg.this 1443 cond = expression.expression.this 1444 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1445 return self.sql(agg) 1446 1447 def hint_sql(self, expression: exp.Hint) -> str: 1448 if not self.QUERY_HINTS: 1449 self.unsupported("Hints are not supported") 1450 return "" 1451 1452 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1453 1454 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1455 using = self.sql(expression, "using") 1456 using = f" USING {using}" if using else "" 1457 columns = self.expressions(expression, key="columns", flat=True) 1458 columns = f"({columns})" if columns else "" 1459 partition_by = self.expressions(expression, key="partition_by", flat=True) 1460 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1461 where = self.sql(expression, "where") 1462 include = self.expressions(expression, key="include", flat=True) 1463 if include: 1464 include = f" INCLUDE ({include})" 1465 with_storage = self.expressions(expression, key="with_storage", flat=True) 1466 with_storage = f" WITH ({with_storage})" if with_storage else "" 1467 tablespace = self.sql(expression, "tablespace") 1468 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1469 on = self.sql(expression, "on") 1470 on = f" ON {on}" if on else "" 1471 1472 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1473 1474 def index_sql(self, expression: exp.Index) -> str: 1475 unique = "UNIQUE " if expression.args.get("unique") else "" 1476 primary = "PRIMARY " if expression.args.get("primary") else "" 1477 amp = "AMP " if expression.args.get("amp") else "" 1478 name = self.sql(expression, "this") 1479 name = f"{name} " if name else "" 1480 table = self.sql(expression, "table") 1481 table = f"{self.INDEX_ON} {table}" if table else "" 1482 1483 index = "INDEX " if not table else "" 1484 1485 params = self.sql(expression, "params") 1486 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1487 1488 def identifier_sql(self, expression: exp.Identifier) -> str: 1489 text = expression.name 1490 lower = text.lower() 1491 text = lower if self.normalize and not expression.quoted else text 1492 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1493 if ( 1494 expression.quoted 1495 or self.dialect.can_identify(text, self.identify) 1496 or lower in self.RESERVED_KEYWORDS 1497 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1498 ): 1499 text = f"{self._identifier_start}{text}{self._identifier_end}" 1500 return text 1501 1502 def hex_sql(self, expression: exp.Hex) -> str: 1503 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1504 if self.dialect.HEX_LOWERCASE: 1505 text = self.func("LOWER", text) 1506 1507 return text 1508 1509 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1510 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1511 if not self.dialect.HEX_LOWERCASE: 1512 text = self.func("LOWER", text) 1513 return text 1514 1515 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1516 input_format = self.sql(expression, "input_format") 1517 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1518 output_format = self.sql(expression, "output_format") 1519 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1520 return self.sep().join((input_format, output_format)) 1521 1522 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1523 string = self.sql(exp.Literal.string(expression.name)) 1524 return f"{prefix}{string}" 1525 1526 def partition_sql(self, expression: exp.Partition) -> str: 1527 return f"PARTITION({self.expressions(expression, flat=True)})" 1528 1529 def properties_sql(self, expression: exp.Properties) -> str: 1530 root_properties = [] 1531 with_properties = [] 1532 1533 for p in expression.expressions: 1534 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1535 if p_loc == exp.Properties.Location.POST_WITH: 1536 with_properties.append(p) 1537 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1538 root_properties.append(p) 1539 1540 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1541 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1542 1543 if root_props and with_props and not self.pretty: 1544 with_props = " " + with_props 1545 1546 return root_props + with_props 1547 1548 def root_properties(self, properties: exp.Properties) -> str: 1549 if properties.expressions: 1550 return self.expressions(properties, indent=False, sep=" ") 1551 return "" 1552 1553 def properties( 1554 self, 1555 properties: exp.Properties, 1556 prefix: str = "", 1557 sep: str = ", ", 1558 suffix: str = "", 1559 wrapped: bool = True, 1560 ) -> str: 1561 if properties.expressions: 1562 expressions = self.expressions(properties, sep=sep, indent=False) 1563 if expressions: 1564 expressions = self.wrap(expressions) if wrapped else expressions 1565 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1566 return "" 1567 1568 def with_properties(self, properties: exp.Properties) -> str: 1569 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1570 1571 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1572 properties_locs = defaultdict(list) 1573 for p in properties.expressions: 1574 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1575 if p_loc != exp.Properties.Location.UNSUPPORTED: 1576 properties_locs[p_loc].append(p) 1577 else: 1578 self.unsupported(f"Unsupported property {p.key}") 1579 1580 return properties_locs 1581 1582 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1583 if isinstance(expression.this, exp.Dot): 1584 return self.sql(expression, "this") 1585 return f"'{expression.name}'" if string_key else expression.name 1586 1587 def property_sql(self, expression: exp.Property) -> str: 1588 property_cls = expression.__class__ 1589 if property_cls == exp.Property: 1590 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1591 1592 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1593 if not property_name: 1594 self.unsupported(f"Unsupported property {expression.key}") 1595 1596 return f"{property_name}={self.sql(expression, 'this')}" 1597 1598 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1599 if self.SUPPORTS_CREATE_TABLE_LIKE: 1600 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1601 options = f" {options}" if options else "" 1602 1603 like = f"LIKE {self.sql(expression, 'this')}{options}" 1604 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1605 like = f"({like})" 1606 1607 return like 1608 1609 if expression.expressions: 1610 self.unsupported("Transpilation of LIKE property options is unsupported") 1611 1612 select = exp.select("*").from_(expression.this).limit(0) 1613 return f"AS {self.sql(select)}" 1614 1615 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1616 no = "NO " if expression.args.get("no") else "" 1617 protection = " PROTECTION" if expression.args.get("protection") else "" 1618 return f"{no}FALLBACK{protection}" 1619 1620 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1621 no = "NO " if expression.args.get("no") else "" 1622 local = expression.args.get("local") 1623 local = f"{local} " if local else "" 1624 dual = "DUAL " if expression.args.get("dual") else "" 1625 before = "BEFORE " if expression.args.get("before") else "" 1626 after = "AFTER " if expression.args.get("after") else "" 1627 return f"{no}{local}{dual}{before}{after}JOURNAL" 1628 1629 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1630 freespace = self.sql(expression, "this") 1631 percent = " PERCENT" if expression.args.get("percent") else "" 1632 return f"FREESPACE={freespace}{percent}" 1633 1634 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1635 if expression.args.get("default"): 1636 property = "DEFAULT" 1637 elif expression.args.get("on"): 1638 property = "ON" 1639 else: 1640 property = "OFF" 1641 return f"CHECKSUM={property}" 1642 1643 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1644 if expression.args.get("no"): 1645 return "NO MERGEBLOCKRATIO" 1646 if expression.args.get("default"): 1647 return "DEFAULT MERGEBLOCKRATIO" 1648 1649 percent = " PERCENT" if expression.args.get("percent") else "" 1650 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1651 1652 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1653 default = expression.args.get("default") 1654 minimum = expression.args.get("minimum") 1655 maximum = expression.args.get("maximum") 1656 if default or minimum or maximum: 1657 if default: 1658 prop = "DEFAULT" 1659 elif minimum: 1660 prop = "MINIMUM" 1661 else: 1662 prop = "MAXIMUM" 1663 return f"{prop} DATABLOCKSIZE" 1664 units = expression.args.get("units") 1665 units = f" {units}" if units else "" 1666 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1667 1668 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1669 autotemp = expression.args.get("autotemp") 1670 always = expression.args.get("always") 1671 default = expression.args.get("default") 1672 manual = expression.args.get("manual") 1673 never = expression.args.get("never") 1674 1675 if autotemp is not None: 1676 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1677 elif always: 1678 prop = "ALWAYS" 1679 elif default: 1680 prop = "DEFAULT" 1681 elif manual: 1682 prop = "MANUAL" 1683 elif never: 1684 prop = "NEVER" 1685 return f"BLOCKCOMPRESSION={prop}" 1686 1687 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1688 no = expression.args.get("no") 1689 no = " NO" if no else "" 1690 concurrent = expression.args.get("concurrent") 1691 concurrent = " CONCURRENT" if concurrent else "" 1692 target = self.sql(expression, "target") 1693 target = f" {target}" if target else "" 1694 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1695 1696 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1697 if isinstance(expression.this, list): 1698 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1699 if expression.this: 1700 modulus = self.sql(expression, "this") 1701 remainder = self.sql(expression, "expression") 1702 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1703 1704 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1705 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1706 return f"FROM ({from_expressions}) TO ({to_expressions})" 1707 1708 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1709 this = self.sql(expression, "this") 1710 1711 for_values_or_default = expression.expression 1712 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1713 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1714 else: 1715 for_values_or_default = " DEFAULT" 1716 1717 return f"PARTITION OF {this}{for_values_or_default}" 1718 1719 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1720 kind = expression.args.get("kind") 1721 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1722 for_or_in = expression.args.get("for_or_in") 1723 for_or_in = f" {for_or_in}" if for_or_in else "" 1724 lock_type = expression.args.get("lock_type") 1725 override = " OVERRIDE" if expression.args.get("override") else "" 1726 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1727 1728 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1729 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1730 statistics = expression.args.get("statistics") 1731 statistics_sql = "" 1732 if statistics is not None: 1733 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1734 return f"{data_sql}{statistics_sql}" 1735 1736 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1737 this = self.sql(expression, "this") 1738 this = f"HISTORY_TABLE={this}" if this else "" 1739 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1740 data_consistency = ( 1741 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1742 ) 1743 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1744 retention_period = ( 1745 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1746 ) 1747 1748 if this: 1749 on_sql = self.func("ON", this, data_consistency, retention_period) 1750 else: 1751 on_sql = "ON" if expression.args.get("on") else "OFF" 1752 1753 sql = f"SYSTEM_VERSIONING={on_sql}" 1754 1755 return f"WITH({sql})" if expression.args.get("with") else sql 1756 1757 def insert_sql(self, expression: exp.Insert) -> str: 1758 hint = self.sql(expression, "hint") 1759 overwrite = expression.args.get("overwrite") 1760 1761 if isinstance(expression.this, exp.Directory): 1762 this = " OVERWRITE" if overwrite else " INTO" 1763 else: 1764 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1765 1766 stored = self.sql(expression, "stored") 1767 stored = f" {stored}" if stored else "" 1768 alternative = expression.args.get("alternative") 1769 alternative = f" OR {alternative}" if alternative else "" 1770 ignore = " IGNORE" if expression.args.get("ignore") else "" 1771 is_function = expression.args.get("is_function") 1772 if is_function: 1773 this = f"{this} FUNCTION" 1774 this = f"{this} {self.sql(expression, 'this')}" 1775 1776 exists = " IF EXISTS" if expression.args.get("exists") else "" 1777 where = self.sql(expression, "where") 1778 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1779 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1780 on_conflict = self.sql(expression, "conflict") 1781 on_conflict = f" {on_conflict}" if on_conflict else "" 1782 by_name = " BY NAME" if expression.args.get("by_name") else "" 1783 returning = self.sql(expression, "returning") 1784 1785 if self.RETURNING_END: 1786 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1787 else: 1788 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1789 1790 partition_by = self.sql(expression, "partition") 1791 partition_by = f" {partition_by}" if partition_by else "" 1792 settings = self.sql(expression, "settings") 1793 settings = f" {settings}" if settings else "" 1794 1795 source = self.sql(expression, "source") 1796 source = f"TABLE {source}" if source else "" 1797 1798 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1799 return self.prepend_ctes(expression, sql) 1800 1801 def introducer_sql(self, expression: exp.Introducer) -> str: 1802 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1803 1804 def kill_sql(self, expression: exp.Kill) -> str: 1805 kind = self.sql(expression, "kind") 1806 kind = f" {kind}" if kind else "" 1807 this = self.sql(expression, "this") 1808 this = f" {this}" if this else "" 1809 return f"KILL{kind}{this}" 1810 1811 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1812 return expression.name 1813 1814 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1815 return expression.name 1816 1817 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1818 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1819 1820 constraint = self.sql(expression, "constraint") 1821 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1822 1823 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1824 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1825 action = self.sql(expression, "action") 1826 1827 expressions = self.expressions(expression, flat=True) 1828 if expressions: 1829 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1830 expressions = f" {set_keyword}{expressions}" 1831 1832 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}" 1833 1834 def returning_sql(self, expression: exp.Returning) -> str: 1835 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1836 1837 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1838 fields = self.sql(expression, "fields") 1839 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1840 escaped = self.sql(expression, "escaped") 1841 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1842 items = self.sql(expression, "collection_items") 1843 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1844 keys = self.sql(expression, "map_keys") 1845 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1846 lines = self.sql(expression, "lines") 1847 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1848 null = self.sql(expression, "null") 1849 null = f" NULL DEFINED AS {null}" if null else "" 1850 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1851 1852 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1853 return f"WITH ({self.expressions(expression, flat=True)})" 1854 1855 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1856 this = f"{self.sql(expression, 'this')} INDEX" 1857 target = self.sql(expression, "target") 1858 target = f" FOR {target}" if target else "" 1859 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1860 1861 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1862 this = self.sql(expression, "this") 1863 kind = self.sql(expression, "kind") 1864 expr = self.sql(expression, "expression") 1865 return f"{this} ({kind} => {expr})" 1866 1867 def table_parts(self, expression: exp.Table) -> str: 1868 return ".".join( 1869 self.sql(part) 1870 for part in ( 1871 expression.args.get("catalog"), 1872 expression.args.get("db"), 1873 expression.args.get("this"), 1874 ) 1875 if part is not None 1876 ) 1877 1878 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1879 table = self.table_parts(expression) 1880 only = "ONLY " if expression.args.get("only") else "" 1881 partition = self.sql(expression, "partition") 1882 partition = f" {partition}" if partition else "" 1883 version = self.sql(expression, "version") 1884 version = f" {version}" if version else "" 1885 alias = self.sql(expression, "alias") 1886 alias = f"{sep}{alias}" if alias else "" 1887 1888 sample = self.sql(expression, "sample") 1889 if self.dialect.ALIAS_POST_TABLESAMPLE: 1890 sample_pre_alias = sample 1891 sample_post_alias = "" 1892 else: 1893 sample_pre_alias = "" 1894 sample_post_alias = sample 1895 1896 hints = self.expressions(expression, key="hints", sep=" ") 1897 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1898 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 1899 joins = self.indent( 1900 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 1901 ) 1902 laterals = self.expressions(expression, key="laterals", sep="") 1903 1904 file_format = self.sql(expression, "format") 1905 if file_format: 1906 pattern = self.sql(expression, "pattern") 1907 pattern = f", PATTERN => {pattern}" if pattern else "" 1908 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 1909 1910 ordinality = expression.args.get("ordinality") or "" 1911 if ordinality: 1912 ordinality = f" WITH ORDINALITY{alias}" 1913 alias = "" 1914 1915 when = self.sql(expression, "when") 1916 if when: 1917 table = f"{table} {when}" 1918 1919 changes = self.sql(expression, "changes") 1920 changes = f" {changes}" if changes else "" 1921 1922 rows_from = self.expressions(expression, key="rows_from") 1923 if rows_from: 1924 table = f"ROWS FROM {self.wrap(rows_from)}" 1925 1926 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 1927 1928 def tablesample_sql( 1929 self, 1930 expression: exp.TableSample, 1931 tablesample_keyword: t.Optional[str] = None, 1932 ) -> str: 1933 method = self.sql(expression, "method") 1934 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 1935 numerator = self.sql(expression, "bucket_numerator") 1936 denominator = self.sql(expression, "bucket_denominator") 1937 field = self.sql(expression, "bucket_field") 1938 field = f" ON {field}" if field else "" 1939 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 1940 seed = self.sql(expression, "seed") 1941 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 1942 1943 size = self.sql(expression, "size") 1944 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 1945 size = f"{size} ROWS" 1946 1947 percent = self.sql(expression, "percent") 1948 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 1949 percent = f"{percent} PERCENT" 1950 1951 expr = f"{bucket}{percent}{size}" 1952 if self.TABLESAMPLE_REQUIRES_PARENS: 1953 expr = f"({expr})" 1954 1955 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 1956 1957 def pivot_sql(self, expression: exp.Pivot) -> str: 1958 expressions = self.expressions(expression, flat=True) 1959 1960 if expression.this: 1961 this = self.sql(expression, "this") 1962 if not expressions: 1963 return f"UNPIVOT {this}" 1964 1965 on = f"{self.seg('ON')} {expressions}" 1966 using = self.expressions(expression, key="using", flat=True) 1967 using = f"{self.seg('USING')} {using}" if using else "" 1968 group = self.sql(expression, "group") 1969 return f"PIVOT {this}{on}{using}{group}" 1970 1971 alias = self.sql(expression, "alias") 1972 alias = f" AS {alias}" if alias else "" 1973 direction = self.seg("UNPIVOT" if expression.unpivot else "PIVOT") 1974 1975 field = self.sql(expression, "field") 1976 1977 include_nulls = expression.args.get("include_nulls") 1978 if include_nulls is not None: 1979 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 1980 else: 1981 nulls = "" 1982 1983 default_on_null = self.sql(expression, "default_on_null") 1984 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 1985 return f"{direction}{nulls}({expressions} FOR {field}{default_on_null}){alias}" 1986 1987 def version_sql(self, expression: exp.Version) -> str: 1988 this = f"FOR {expression.name}" 1989 kind = expression.text("kind") 1990 expr = self.sql(expression, "expression") 1991 return f"{this} {kind} {expr}" 1992 1993 def tuple_sql(self, expression: exp.Tuple) -> str: 1994 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 1995 1996 def update_sql(self, expression: exp.Update) -> str: 1997 this = self.sql(expression, "this") 1998 set_sql = self.expressions(expression, flat=True) 1999 from_sql = self.sql(expression, "from") 2000 where_sql = self.sql(expression, "where") 2001 returning = self.sql(expression, "returning") 2002 order = self.sql(expression, "order") 2003 limit = self.sql(expression, "limit") 2004 if self.RETURNING_END: 2005 expression_sql = f"{from_sql}{where_sql}{returning}" 2006 else: 2007 expression_sql = f"{returning}{from_sql}{where_sql}" 2008 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2009 return self.prepend_ctes(expression, sql) 2010 2011 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2012 values_as_table = values_as_table and self.VALUES_AS_TABLE 2013 2014 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2015 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2016 args = self.expressions(expression) 2017 alias = self.sql(expression, "alias") 2018 values = f"VALUES{self.seg('')}{args}" 2019 values = ( 2020 f"({values})" 2021 if self.WRAP_DERIVED_VALUES 2022 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2023 else values 2024 ) 2025 return f"{values} AS {alias}" if alias else values 2026 2027 # Converts `VALUES...` expression into a series of select unions. 2028 alias_node = expression.args.get("alias") 2029 column_names = alias_node and alias_node.columns 2030 2031 selects: t.List[exp.Query] = [] 2032 2033 for i, tup in enumerate(expression.expressions): 2034 row = tup.expressions 2035 2036 if i == 0 and column_names: 2037 row = [ 2038 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2039 ] 2040 2041 selects.append(exp.Select(expressions=row)) 2042 2043 if self.pretty: 2044 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2045 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2046 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2047 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2048 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2049 2050 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2051 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2052 return f"({unions}){alias}" 2053 2054 def var_sql(self, expression: exp.Var) -> str: 2055 return self.sql(expression, "this") 2056 2057 @unsupported_args("expressions") 2058 def into_sql(self, expression: exp.Into) -> str: 2059 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2060 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2061 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2062 2063 def from_sql(self, expression: exp.From) -> str: 2064 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2065 2066 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2067 grouping_sets = self.expressions(expression, indent=False) 2068 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2069 2070 def rollup_sql(self, expression: exp.Rollup) -> str: 2071 expressions = self.expressions(expression, indent=False) 2072 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2073 2074 def cube_sql(self, expression: exp.Cube) -> str: 2075 expressions = self.expressions(expression, indent=False) 2076 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2077 2078 def group_sql(self, expression: exp.Group) -> str: 2079 group_by_all = expression.args.get("all") 2080 if group_by_all is True: 2081 modifier = " ALL" 2082 elif group_by_all is False: 2083 modifier = " DISTINCT" 2084 else: 2085 modifier = "" 2086 2087 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2088 2089 grouping_sets = self.expressions(expression, key="grouping_sets") 2090 cube = self.expressions(expression, key="cube") 2091 rollup = self.expressions(expression, key="rollup") 2092 2093 groupings = csv( 2094 self.seg(grouping_sets) if grouping_sets else "", 2095 self.seg(cube) if cube else "", 2096 self.seg(rollup) if rollup else "", 2097 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2098 sep=self.GROUPINGS_SEP, 2099 ) 2100 2101 if ( 2102 expression.expressions 2103 and groupings 2104 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2105 ): 2106 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2107 2108 return f"{group_by}{groupings}" 2109 2110 def having_sql(self, expression: exp.Having) -> str: 2111 this = self.indent(self.sql(expression, "this")) 2112 return f"{self.seg('HAVING')}{self.sep()}{this}" 2113 2114 def connect_sql(self, expression: exp.Connect) -> str: 2115 start = self.sql(expression, "start") 2116 start = self.seg(f"START WITH {start}") if start else "" 2117 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2118 connect = self.sql(expression, "connect") 2119 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2120 return start + connect 2121 2122 def prior_sql(self, expression: exp.Prior) -> str: 2123 return f"PRIOR {self.sql(expression, 'this')}" 2124 2125 def join_sql(self, expression: exp.Join) -> str: 2126 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2127 side = None 2128 else: 2129 side = expression.side 2130 2131 op_sql = " ".join( 2132 op 2133 for op in ( 2134 expression.method, 2135 "GLOBAL" if expression.args.get("global") else None, 2136 side, 2137 expression.kind, 2138 expression.hint if self.JOIN_HINTS else None, 2139 ) 2140 if op 2141 ) 2142 match_cond = self.sql(expression, "match_condition") 2143 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2144 on_sql = self.sql(expression, "on") 2145 using = expression.args.get("using") 2146 2147 if not on_sql and using: 2148 on_sql = csv(*(self.sql(column) for column in using)) 2149 2150 this = expression.this 2151 this_sql = self.sql(this) 2152 2153 exprs = self.expressions(expression) 2154 if exprs: 2155 this_sql = f"{this_sql},{self.seg(exprs)}" 2156 2157 if on_sql: 2158 on_sql = self.indent(on_sql, skip_first=True) 2159 space = self.seg(" " * self.pad) if self.pretty else " " 2160 if using: 2161 on_sql = f"{space}USING ({on_sql})" 2162 else: 2163 on_sql = f"{space}ON {on_sql}" 2164 elif not op_sql: 2165 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2166 return f" {this_sql}" 2167 2168 return f", {this_sql}" 2169 2170 if op_sql != "STRAIGHT_JOIN": 2171 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2172 2173 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}" 2174 2175 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 2176 args = self.expressions(expression, flat=True) 2177 args = f"({args})" if len(args.split(",")) > 1 else args 2178 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2179 2180 def lateral_op(self, expression: exp.Lateral) -> str: 2181 cross_apply = expression.args.get("cross_apply") 2182 2183 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2184 if cross_apply is True: 2185 op = "INNER JOIN " 2186 elif cross_apply is False: 2187 op = "LEFT JOIN " 2188 else: 2189 op = "" 2190 2191 return f"{op}LATERAL" 2192 2193 def lateral_sql(self, expression: exp.Lateral) -> str: 2194 this = self.sql(expression, "this") 2195 2196 if expression.args.get("view"): 2197 alias = expression.args["alias"] 2198 columns = self.expressions(alias, key="columns", flat=True) 2199 table = f" {alias.name}" if alias.name else "" 2200 columns = f" AS {columns}" if columns else "" 2201 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2202 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2203 2204 alias = self.sql(expression, "alias") 2205 alias = f" AS {alias}" if alias else "" 2206 return f"{self.lateral_op(expression)} {this}{alias}" 2207 2208 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2209 this = self.sql(expression, "this") 2210 2211 args = [ 2212 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2213 for e in (expression.args.get(k) for k in ("offset", "expression")) 2214 if e 2215 ] 2216 2217 args_sql = ", ".join(self.sql(e) for e in args) 2218 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2219 expressions = self.expressions(expression, flat=True) 2220 expressions = f" BY {expressions}" if expressions else "" 2221 2222 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{expressions}" 2223 2224 def offset_sql(self, expression: exp.Offset) -> str: 2225 this = self.sql(expression, "this") 2226 value = expression.expression 2227 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2228 expressions = self.expressions(expression, flat=True) 2229 expressions = f" BY {expressions}" if expressions else "" 2230 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2231 2232 def setitem_sql(self, expression: exp.SetItem) -> str: 2233 kind = self.sql(expression, "kind") 2234 kind = f"{kind} " if kind else "" 2235 this = self.sql(expression, "this") 2236 expressions = self.expressions(expression) 2237 collate = self.sql(expression, "collate") 2238 collate = f" COLLATE {collate}" if collate else "" 2239 global_ = "GLOBAL " if expression.args.get("global") else "" 2240 return f"{global_}{kind}{this}{expressions}{collate}" 2241 2242 def set_sql(self, expression: exp.Set) -> str: 2243 expressions = ( 2244 f" {self.expressions(expression, flat=True)}" if expression.expressions else "" 2245 ) 2246 tag = " TAG" if expression.args.get("tag") else "" 2247 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2248 2249 def pragma_sql(self, expression: exp.Pragma) -> str: 2250 return f"PRAGMA {self.sql(expression, 'this')}" 2251 2252 def lock_sql(self, expression: exp.Lock) -> str: 2253 if not self.LOCKING_READS_SUPPORTED: 2254 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2255 return "" 2256 2257 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2258 expressions = self.expressions(expression, flat=True) 2259 expressions = f" OF {expressions}" if expressions else "" 2260 wait = expression.args.get("wait") 2261 2262 if wait is not None: 2263 if isinstance(wait, exp.Literal): 2264 wait = f" WAIT {self.sql(wait)}" 2265 else: 2266 wait = " NOWAIT" if wait else " SKIP LOCKED" 2267 2268 return f"{lock_type}{expressions}{wait or ''}" 2269 2270 def literal_sql(self, expression: exp.Literal) -> str: 2271 text = expression.this or "" 2272 if expression.is_string: 2273 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2274 return text 2275 2276 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2277 if self.dialect.ESCAPED_SEQUENCES: 2278 to_escaped = self.dialect.ESCAPED_SEQUENCES 2279 text = "".join( 2280 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2281 ) 2282 2283 return self._replace_line_breaks(text).replace( 2284 self.dialect.QUOTE_END, self._escaped_quote_end 2285 ) 2286 2287 def loaddata_sql(self, expression: exp.LoadData) -> str: 2288 local = " LOCAL" if expression.args.get("local") else "" 2289 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2290 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2291 this = f" INTO TABLE {self.sql(expression, 'this')}" 2292 partition = self.sql(expression, "partition") 2293 partition = f" {partition}" if partition else "" 2294 input_format = self.sql(expression, "input_format") 2295 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2296 serde = self.sql(expression, "serde") 2297 serde = f" SERDE {serde}" if serde else "" 2298 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2299 2300 def null_sql(self, *_) -> str: 2301 return "NULL" 2302 2303 def boolean_sql(self, expression: exp.Boolean) -> str: 2304 return "TRUE" if expression.this else "FALSE" 2305 2306 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2307 this = self.sql(expression, "this") 2308 this = f"{this} " if this else this 2309 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2310 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2311 2312 def withfill_sql(self, expression: exp.WithFill) -> str: 2313 from_sql = self.sql(expression, "from") 2314 from_sql = f" FROM {from_sql}" if from_sql else "" 2315 to_sql = self.sql(expression, "to") 2316 to_sql = f" TO {to_sql}" if to_sql else "" 2317 step_sql = self.sql(expression, "step") 2318 step_sql = f" STEP {step_sql}" if step_sql else "" 2319 interpolated_values = [ 2320 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2321 if isinstance(e, exp.Alias) 2322 else self.sql(e, "this") 2323 for e in expression.args.get("interpolate") or [] 2324 ] 2325 interpolate = ( 2326 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2327 ) 2328 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2329 2330 def cluster_sql(self, expression: exp.Cluster) -> str: 2331 return self.op_expressions("CLUSTER BY", expression) 2332 2333 def distribute_sql(self, expression: exp.Distribute) -> str: 2334 return self.op_expressions("DISTRIBUTE BY", expression) 2335 2336 def sort_sql(self, expression: exp.Sort) -> str: 2337 return self.op_expressions("SORT BY", expression) 2338 2339 def ordered_sql(self, expression: exp.Ordered) -> str: 2340 desc = expression.args.get("desc") 2341 asc = not desc 2342 2343 nulls_first = expression.args.get("nulls_first") 2344 nulls_last = not nulls_first 2345 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2346 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2347 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2348 2349 this = self.sql(expression, "this") 2350 2351 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2352 nulls_sort_change = "" 2353 if nulls_first and ( 2354 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2355 ): 2356 nulls_sort_change = " NULLS FIRST" 2357 elif ( 2358 nulls_last 2359 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2360 and not nulls_are_last 2361 ): 2362 nulls_sort_change = " NULLS LAST" 2363 2364 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2365 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2366 window = expression.find_ancestor(exp.Window, exp.Select) 2367 if isinstance(window, exp.Window) and window.args.get("spec"): 2368 self.unsupported( 2369 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2370 ) 2371 nulls_sort_change = "" 2372 elif ( 2373 self.NULL_ORDERING_SUPPORTED is False 2374 and (isinstance(expression.find_ancestor(exp.AggFunc, exp.Select), exp.AggFunc)) 2375 and ( 2376 (asc and nulls_sort_change == " NULLS LAST") 2377 or (desc and nulls_sort_change == " NULLS FIRST") 2378 ) 2379 ): 2380 self.unsupported( 2381 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2382 ) 2383 nulls_sort_change = "" 2384 elif self.NULL_ORDERING_SUPPORTED is None: 2385 if expression.this.is_int: 2386 self.unsupported( 2387 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2388 ) 2389 elif not isinstance(expression.this, exp.Rand): 2390 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2391 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2392 nulls_sort_change = "" 2393 2394 with_fill = self.sql(expression, "with_fill") 2395 with_fill = f" {with_fill}" if with_fill else "" 2396 2397 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2398 2399 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2400 window_frame = self.sql(expression, "window_frame") 2401 window_frame = f"{window_frame} " if window_frame else "" 2402 2403 this = self.sql(expression, "this") 2404 2405 return f"{window_frame}{this}" 2406 2407 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2408 partition = self.partition_by_sql(expression) 2409 order = self.sql(expression, "order") 2410 measures = self.expressions(expression, key="measures") 2411 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2412 rows = self.sql(expression, "rows") 2413 rows = self.seg(rows) if rows else "" 2414 after = self.sql(expression, "after") 2415 after = self.seg(after) if after else "" 2416 pattern = self.sql(expression, "pattern") 2417 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2418 definition_sqls = [ 2419 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2420 for definition in expression.args.get("define", []) 2421 ] 2422 definitions = self.expressions(sqls=definition_sqls) 2423 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2424 body = "".join( 2425 ( 2426 partition, 2427 order, 2428 measures, 2429 rows, 2430 after, 2431 pattern, 2432 define, 2433 ) 2434 ) 2435 alias = self.sql(expression, "alias") 2436 alias = f" {alias}" if alias else "" 2437 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2438 2439 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2440 limit = expression.args.get("limit") 2441 2442 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2443 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2444 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2445 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2446 2447 return csv( 2448 *sqls, 2449 *[self.sql(join) for join in expression.args.get("joins") or []], 2450 self.sql(expression, "connect"), 2451 self.sql(expression, "match"), 2452 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2453 self.sql(expression, "prewhere"), 2454 self.sql(expression, "where"), 2455 self.sql(expression, "group"), 2456 self.sql(expression, "having"), 2457 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2458 self.sql(expression, "order"), 2459 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2460 *self.after_limit_modifiers(expression), 2461 self.options_modifier(expression), 2462 sep="", 2463 ) 2464 2465 def options_modifier(self, expression: exp.Expression) -> str: 2466 options = self.expressions(expression, key="options") 2467 return f" {options}" if options else "" 2468 2469 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2470 return "" 2471 2472 def offset_limit_modifiers( 2473 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2474 ) -> t.List[str]: 2475 return [ 2476 self.sql(expression, "offset") if fetch else self.sql(limit), 2477 self.sql(limit) if fetch else self.sql(expression, "offset"), 2478 ] 2479 2480 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2481 locks = self.expressions(expression, key="locks", sep=" ") 2482 locks = f" {locks}" if locks else "" 2483 return [locks, self.sql(expression, "sample")] 2484 2485 def select_sql(self, expression: exp.Select) -> str: 2486 into = expression.args.get("into") 2487 if not self.SUPPORTS_SELECT_INTO and into: 2488 into.pop() 2489 2490 hint = self.sql(expression, "hint") 2491 distinct = self.sql(expression, "distinct") 2492 distinct = f" {distinct}" if distinct else "" 2493 kind = self.sql(expression, "kind") 2494 2495 limit = expression.args.get("limit") 2496 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2497 top = self.limit_sql(limit, top=True) 2498 limit.pop() 2499 else: 2500 top = "" 2501 2502 expressions = self.expressions(expression) 2503 2504 if kind: 2505 if kind in self.SELECT_KINDS: 2506 kind = f" AS {kind}" 2507 else: 2508 if kind == "STRUCT": 2509 expressions = self.expressions( 2510 sqls=[ 2511 self.sql( 2512 exp.Struct( 2513 expressions=[ 2514 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2515 if isinstance(e, exp.Alias) 2516 else e 2517 for e in expression.expressions 2518 ] 2519 ) 2520 ) 2521 ] 2522 ) 2523 kind = "" 2524 2525 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2526 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2527 2528 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2529 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2530 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2531 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2532 sql = self.query_modifiers( 2533 expression, 2534 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2535 self.sql(expression, "into", comment=False), 2536 self.sql(expression, "from", comment=False), 2537 ) 2538 2539 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2540 if expression.args.get("with"): 2541 sql = self.maybe_comment(sql, expression) 2542 expression.pop_comments() 2543 2544 sql = self.prepend_ctes(expression, sql) 2545 2546 if not self.SUPPORTS_SELECT_INTO and into: 2547 if into.args.get("temporary"): 2548 table_kind = " TEMPORARY" 2549 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2550 table_kind = " UNLOGGED" 2551 else: 2552 table_kind = "" 2553 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2554 2555 return sql 2556 2557 def schema_sql(self, expression: exp.Schema) -> str: 2558 this = self.sql(expression, "this") 2559 sql = self.schema_columns_sql(expression) 2560 return f"{this} {sql}" if this and sql else this or sql 2561 2562 def schema_columns_sql(self, expression: exp.Schema) -> str: 2563 if expression.expressions: 2564 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2565 return "" 2566 2567 def star_sql(self, expression: exp.Star) -> str: 2568 except_ = self.expressions(expression, key="except", flat=True) 2569 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2570 replace = self.expressions(expression, key="replace", flat=True) 2571 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2572 rename = self.expressions(expression, key="rename", flat=True) 2573 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2574 return f"*{except_}{replace}{rename}" 2575 2576 def parameter_sql(self, expression: exp.Parameter) -> str: 2577 this = self.sql(expression, "this") 2578 return f"{self.PARAMETER_TOKEN}{this}" 2579 2580 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2581 this = self.sql(expression, "this") 2582 kind = expression.text("kind") 2583 if kind: 2584 kind = f"{kind}." 2585 return f"@@{kind}{this}" 2586 2587 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2588 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2589 2590 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2591 alias = self.sql(expression, "alias") 2592 alias = f"{sep}{alias}" if alias else "" 2593 sample = self.sql(expression, "sample") 2594 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2595 alias = f"{sample}{alias}" 2596 2597 # Set to None so it's not generated again by self.query_modifiers() 2598 expression.set("sample", None) 2599 2600 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2601 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2602 return self.prepend_ctes(expression, sql) 2603 2604 def qualify_sql(self, expression: exp.Qualify) -> str: 2605 this = self.indent(self.sql(expression, "this")) 2606 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2607 2608 def unnest_sql(self, expression: exp.Unnest) -> str: 2609 args = self.expressions(expression, flat=True) 2610 2611 alias = expression.args.get("alias") 2612 offset = expression.args.get("offset") 2613 2614 if self.UNNEST_WITH_ORDINALITY: 2615 if alias and isinstance(offset, exp.Expression): 2616 alias.append("columns", offset) 2617 2618 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2619 columns = alias.columns 2620 alias = self.sql(columns[0]) if columns else "" 2621 else: 2622 alias = self.sql(alias) 2623 2624 alias = f" AS {alias}" if alias else alias 2625 if self.UNNEST_WITH_ORDINALITY: 2626 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2627 else: 2628 if isinstance(offset, exp.Expression): 2629 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2630 elif offset: 2631 suffix = f"{alias} WITH OFFSET" 2632 else: 2633 suffix = alias 2634 2635 return f"UNNEST({args}){suffix}" 2636 2637 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2638 return "" 2639 2640 def where_sql(self, expression: exp.Where) -> str: 2641 this = self.indent(self.sql(expression, "this")) 2642 return f"{self.seg('WHERE')}{self.sep()}{this}" 2643 2644 def window_sql(self, expression: exp.Window) -> str: 2645 this = self.sql(expression, "this") 2646 partition = self.partition_by_sql(expression) 2647 order = expression.args.get("order") 2648 order = self.order_sql(order, flat=True) if order else "" 2649 spec = self.sql(expression, "spec") 2650 alias = self.sql(expression, "alias") 2651 over = self.sql(expression, "over") or "OVER" 2652 2653 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2654 2655 first = expression.args.get("first") 2656 if first is None: 2657 first = "" 2658 else: 2659 first = "FIRST" if first else "LAST" 2660 2661 if not partition and not order and not spec and alias: 2662 return f"{this} {alias}" 2663 2664 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 2665 return f"{this} ({args})" 2666 2667 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2668 partition = self.expressions(expression, key="partition_by", flat=True) 2669 return f"PARTITION BY {partition}" if partition else "" 2670 2671 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2672 kind = self.sql(expression, "kind") 2673 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2674 end = ( 2675 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2676 or "CURRENT ROW" 2677 ) 2678 return f"{kind} BETWEEN {start} AND {end}" 2679 2680 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2681 this = self.sql(expression, "this") 2682 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2683 return f"{this} WITHIN GROUP ({expression_sql})" 2684 2685 def between_sql(self, expression: exp.Between) -> str: 2686 this = self.sql(expression, "this") 2687 low = self.sql(expression, "low") 2688 high = self.sql(expression, "high") 2689 return f"{this} BETWEEN {low} AND {high}" 2690 2691 def bracket_offset_expressions( 2692 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2693 ) -> t.List[exp.Expression]: 2694 return apply_index_offset( 2695 expression.this, 2696 expression.expressions, 2697 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2698 ) 2699 2700 def bracket_sql(self, expression: exp.Bracket) -> str: 2701 expressions = self.bracket_offset_expressions(expression) 2702 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2703 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2704 2705 def all_sql(self, expression: exp.All) -> str: 2706 return f"ALL {self.wrap(expression)}" 2707 2708 def any_sql(self, expression: exp.Any) -> str: 2709 this = self.sql(expression, "this") 2710 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2711 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2712 this = self.wrap(this) 2713 return f"ANY{this}" 2714 return f"ANY {this}" 2715 2716 def exists_sql(self, expression: exp.Exists) -> str: 2717 return f"EXISTS{self.wrap(expression)}" 2718 2719 def case_sql(self, expression: exp.Case) -> str: 2720 this = self.sql(expression, "this") 2721 statements = [f"CASE {this}" if this else "CASE"] 2722 2723 for e in expression.args["ifs"]: 2724 statements.append(f"WHEN {self.sql(e, 'this')}") 2725 statements.append(f"THEN {self.sql(e, 'true')}") 2726 2727 default = self.sql(expression, "default") 2728 2729 if default: 2730 statements.append(f"ELSE {default}") 2731 2732 statements.append("END") 2733 2734 if self.pretty and self.too_wide(statements): 2735 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2736 2737 return " ".join(statements) 2738 2739 def constraint_sql(self, expression: exp.Constraint) -> str: 2740 this = self.sql(expression, "this") 2741 expressions = self.expressions(expression, flat=True) 2742 return f"CONSTRAINT {this} {expressions}" 2743 2744 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2745 order = expression.args.get("order") 2746 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2747 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2748 2749 def extract_sql(self, expression: exp.Extract) -> str: 2750 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name 2751 expression_sql = self.sql(expression, "expression") 2752 return f"EXTRACT({this} FROM {expression_sql})" 2753 2754 def trim_sql(self, expression: exp.Trim) -> str: 2755 trim_type = self.sql(expression, "position") 2756 2757 if trim_type == "LEADING": 2758 func_name = "LTRIM" 2759 elif trim_type == "TRAILING": 2760 func_name = "RTRIM" 2761 else: 2762 func_name = "TRIM" 2763 2764 return self.func(func_name, expression.this, expression.expression) 2765 2766 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2767 args = expression.expressions 2768 if isinstance(expression, exp.ConcatWs): 2769 args = args[1:] # Skip the delimiter 2770 2771 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2772 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2773 2774 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2775 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2776 2777 return args 2778 2779 def concat_sql(self, expression: exp.Concat) -> str: 2780 expressions = self.convert_concat_args(expression) 2781 2782 # Some dialects don't allow a single-argument CONCAT call 2783 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2784 return self.sql(expressions[0]) 2785 2786 return self.func("CONCAT", *expressions) 2787 2788 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2789 return self.func( 2790 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2791 ) 2792 2793 def check_sql(self, expression: exp.Check) -> str: 2794 this = self.sql(expression, key="this") 2795 return f"CHECK ({this})" 2796 2797 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2798 expressions = self.expressions(expression, flat=True) 2799 reference = self.sql(expression, "reference") 2800 reference = f" {reference}" if reference else "" 2801 delete = self.sql(expression, "delete") 2802 delete = f" ON DELETE {delete}" if delete else "" 2803 update = self.sql(expression, "update") 2804 update = f" ON UPDATE {update}" if update else "" 2805 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}" 2806 2807 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2808 expressions = self.expressions(expression, flat=True) 2809 options = self.expressions(expression, key="options", flat=True, sep=" ") 2810 options = f" {options}" if options else "" 2811 return f"PRIMARY KEY ({expressions}){options}" 2812 2813 def if_sql(self, expression: exp.If) -> str: 2814 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 2815 2816 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 2817 modifier = expression.args.get("modifier") 2818 modifier = f" {modifier}" if modifier else "" 2819 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 2820 2821 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 2822 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 2823 2824 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2825 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2826 2827 if expression.args.get("escape"): 2828 path = self.escape_str(path) 2829 2830 if self.QUOTE_JSON_PATH: 2831 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2832 2833 return path 2834 2835 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2836 if isinstance(expression, exp.JSONPathPart): 2837 transform = self.TRANSFORMS.get(expression.__class__) 2838 if not callable(transform): 2839 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2840 return "" 2841 2842 return transform(self, expression) 2843 2844 if isinstance(expression, int): 2845 return str(expression) 2846 2847 if self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 2848 escaped = expression.replace("'", "\\'") 2849 escaped = f"\\'{expression}\\'" 2850 else: 2851 escaped = expression.replace('"', '\\"') 2852 escaped = f'"{escaped}"' 2853 2854 return escaped 2855 2856 def formatjson_sql(self, expression: exp.FormatJson) -> str: 2857 return f"{self.sql(expression, 'this')} FORMAT JSON" 2858 2859 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 2860 null_handling = expression.args.get("null_handling") 2861 null_handling = f" {null_handling}" if null_handling else "" 2862 2863 unique_keys = expression.args.get("unique_keys") 2864 if unique_keys is not None: 2865 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2866 else: 2867 unique_keys = "" 2868 2869 return_type = self.sql(expression, "return_type") 2870 return_type = f" RETURNING {return_type}" if return_type else "" 2871 encoding = self.sql(expression, "encoding") 2872 encoding = f" ENCODING {encoding}" if encoding else "" 2873 2874 return self.func( 2875 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 2876 *expression.expressions, 2877 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2878 ) 2879 2880 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 2881 return self.jsonobject_sql(expression) 2882 2883 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2884 null_handling = expression.args.get("null_handling") 2885 null_handling = f" {null_handling}" if null_handling else "" 2886 return_type = self.sql(expression, "return_type") 2887 return_type = f" RETURNING {return_type}" if return_type else "" 2888 strict = " STRICT" if expression.args.get("strict") else "" 2889 return self.func( 2890 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 2891 ) 2892 2893 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 2894 this = self.sql(expression, "this") 2895 order = self.sql(expression, "order") 2896 null_handling = expression.args.get("null_handling") 2897 null_handling = f" {null_handling}" if null_handling else "" 2898 return_type = self.sql(expression, "return_type") 2899 return_type = f" RETURNING {return_type}" if return_type else "" 2900 strict = " STRICT" if expression.args.get("strict") else "" 2901 return self.func( 2902 "JSON_ARRAYAGG", 2903 this, 2904 suffix=f"{order}{null_handling}{return_type}{strict})", 2905 ) 2906 2907 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 2908 path = self.sql(expression, "path") 2909 path = f" PATH {path}" if path else "" 2910 nested_schema = self.sql(expression, "nested_schema") 2911 2912 if nested_schema: 2913 return f"NESTED{path} {nested_schema}" 2914 2915 this = self.sql(expression, "this") 2916 kind = self.sql(expression, "kind") 2917 kind = f" {kind}" if kind else "" 2918 return f"{this}{kind}{path}" 2919 2920 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 2921 return self.func("COLUMNS", *expression.expressions) 2922 2923 def jsontable_sql(self, expression: exp.JSONTable) -> str: 2924 this = self.sql(expression, "this") 2925 path = self.sql(expression, "path") 2926 path = f", {path}" if path else "" 2927 error_handling = expression.args.get("error_handling") 2928 error_handling = f" {error_handling}" if error_handling else "" 2929 empty_handling = expression.args.get("empty_handling") 2930 empty_handling = f" {empty_handling}" if empty_handling else "" 2931 schema = self.sql(expression, "schema") 2932 return self.func( 2933 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 2934 ) 2935 2936 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 2937 this = self.sql(expression, "this") 2938 kind = self.sql(expression, "kind") 2939 path = self.sql(expression, "path") 2940 path = f" {path}" if path else "" 2941 as_json = " AS JSON" if expression.args.get("as_json") else "" 2942 return f"{this} {kind}{path}{as_json}" 2943 2944 def openjson_sql(self, expression: exp.OpenJSON) -> str: 2945 this = self.sql(expression, "this") 2946 path = self.sql(expression, "path") 2947 path = f", {path}" if path else "" 2948 expressions = self.expressions(expression) 2949 with_ = ( 2950 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 2951 if expressions 2952 else "" 2953 ) 2954 return f"OPENJSON({this}{path}){with_}" 2955 2956 def in_sql(self, expression: exp.In) -> str: 2957 query = expression.args.get("query") 2958 unnest = expression.args.get("unnest") 2959 field = expression.args.get("field") 2960 is_global = " GLOBAL" if expression.args.get("is_global") else "" 2961 2962 if query: 2963 in_sql = self.sql(query) 2964 elif unnest: 2965 in_sql = self.in_unnest_op(unnest) 2966 elif field: 2967 in_sql = self.sql(field) 2968 else: 2969 in_sql = f"({self.expressions(expression, flat=True)})" 2970 2971 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 2972 2973 def in_unnest_op(self, unnest: exp.Unnest) -> str: 2974 return f"(SELECT {self.sql(unnest)})" 2975 2976 def interval_sql(self, expression: exp.Interval) -> str: 2977 unit = self.sql(expression, "unit") 2978 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 2979 unit = self.TIME_PART_SINGULARS.get(unit, unit) 2980 unit = f" {unit}" if unit else "" 2981 2982 if self.SINGLE_STRING_INTERVAL: 2983 this = expression.this.name if expression.this else "" 2984 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 2985 2986 this = self.sql(expression, "this") 2987 if this: 2988 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 2989 this = f" {this}" if unwrapped else f" ({this})" 2990 2991 return f"INTERVAL{this}{unit}" 2992 2993 def return_sql(self, expression: exp.Return) -> str: 2994 return f"RETURN {self.sql(expression, 'this')}" 2995 2996 def reference_sql(self, expression: exp.Reference) -> str: 2997 this = self.sql(expression, "this") 2998 expressions = self.expressions(expression, flat=True) 2999 expressions = f"({expressions})" if expressions else "" 3000 options = self.expressions(expression, key="options", flat=True, sep=" ") 3001 options = f" {options}" if options else "" 3002 return f"REFERENCES {this}{expressions}{options}" 3003 3004 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3005 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3006 parent = expression.parent 3007 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3008 return self.func( 3009 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3010 ) 3011 3012 def paren_sql(self, expression: exp.Paren) -> str: 3013 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3014 return f"({sql}{self.seg(')', sep='')}" 3015 3016 def neg_sql(self, expression: exp.Neg) -> str: 3017 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3018 this_sql = self.sql(expression, "this") 3019 sep = " " if this_sql[0] == "-" else "" 3020 return f"-{sep}{this_sql}" 3021 3022 def not_sql(self, expression: exp.Not) -> str: 3023 return f"NOT {self.sql(expression, 'this')}" 3024 3025 def alias_sql(self, expression: exp.Alias) -> str: 3026 alias = self.sql(expression, "alias") 3027 alias = f" AS {alias}" if alias else "" 3028 return f"{self.sql(expression, 'this')}{alias}" 3029 3030 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3031 alias = expression.args["alias"] 3032 3033 identifier_alias = isinstance(alias, exp.Identifier) 3034 literal_alias = isinstance(alias, exp.Literal) 3035 3036 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3037 alias.replace(exp.Literal.string(alias.output_name)) 3038 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3039 alias.replace(exp.to_identifier(alias.output_name)) 3040 3041 return self.alias_sql(expression) 3042 3043 def aliases_sql(self, expression: exp.Aliases) -> str: 3044 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3045 3046 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3047 this = self.sql(expression, "this") 3048 index = self.sql(expression, "expression") 3049 return f"{this} AT {index}" 3050 3051 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3052 this = self.sql(expression, "this") 3053 zone = self.sql(expression, "zone") 3054 return f"{this} AT TIME ZONE {zone}" 3055 3056 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3057 this = self.sql(expression, "this") 3058 zone = self.sql(expression, "zone") 3059 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3060 3061 def add_sql(self, expression: exp.Add) -> str: 3062 return self.binary(expression, "+") 3063 3064 def and_sql( 3065 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3066 ) -> str: 3067 return self.connector_sql(expression, "AND", stack) 3068 3069 def or_sql( 3070 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3071 ) -> str: 3072 return self.connector_sql(expression, "OR", stack) 3073 3074 def xor_sql( 3075 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3076 ) -> str: 3077 return self.connector_sql(expression, "XOR", stack) 3078 3079 def connector_sql( 3080 self, 3081 expression: exp.Connector, 3082 op: str, 3083 stack: t.Optional[t.List[str | exp.Expression]] = None, 3084 ) -> str: 3085 if stack is not None: 3086 if expression.expressions: 3087 stack.append(self.expressions(expression, sep=f" {op} ")) 3088 else: 3089 stack.append(expression.right) 3090 if expression.comments and self.comments: 3091 for comment in expression.comments: 3092 if comment: 3093 op += f" /*{self.pad_comment(comment)}*/" 3094 stack.extend((op, expression.left)) 3095 return op 3096 3097 stack = [expression] 3098 sqls: t.List[str] = [] 3099 ops = set() 3100 3101 while stack: 3102 node = stack.pop() 3103 if isinstance(node, exp.Connector): 3104 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3105 else: 3106 sql = self.sql(node) 3107 if sqls and sqls[-1] in ops: 3108 sqls[-1] += f" {sql}" 3109 else: 3110 sqls.append(sql) 3111 3112 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3113 return sep.join(sqls) 3114 3115 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3116 return self.binary(expression, "&") 3117 3118 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3119 return self.binary(expression, "<<") 3120 3121 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3122 return f"~{self.sql(expression, 'this')}" 3123 3124 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3125 return self.binary(expression, "|") 3126 3127 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3128 return self.binary(expression, ">>") 3129 3130 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3131 return self.binary(expression, "^") 3132 3133 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3134 format_sql = self.sql(expression, "format") 3135 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3136 to_sql = self.sql(expression, "to") 3137 to_sql = f" {to_sql}" if to_sql else "" 3138 action = self.sql(expression, "action") 3139 action = f" {action}" if action else "" 3140 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{format_sql}{action})" 3141 3142 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3143 zone = self.sql(expression, "this") 3144 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3145 3146 def collate_sql(self, expression: exp.Collate) -> str: 3147 if self.COLLATE_IS_FUNC: 3148 return self.function_fallback_sql(expression) 3149 return self.binary(expression, "COLLATE") 3150 3151 def command_sql(self, expression: exp.Command) -> str: 3152 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3153 3154 def comment_sql(self, expression: exp.Comment) -> str: 3155 this = self.sql(expression, "this") 3156 kind = expression.args["kind"] 3157 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3158 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3159 expression_sql = self.sql(expression, "expression") 3160 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3161 3162 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3163 this = self.sql(expression, "this") 3164 delete = " DELETE" if expression.args.get("delete") else "" 3165 recompress = self.sql(expression, "recompress") 3166 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3167 to_disk = self.sql(expression, "to_disk") 3168 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3169 to_volume = self.sql(expression, "to_volume") 3170 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3171 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3172 3173 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3174 where = self.sql(expression, "where") 3175 group = self.sql(expression, "group") 3176 aggregates = self.expressions(expression, key="aggregates") 3177 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3178 3179 if not (where or group or aggregates) and len(expression.expressions) == 1: 3180 return f"TTL {self.expressions(expression, flat=True)}" 3181 3182 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3183 3184 def transaction_sql(self, expression: exp.Transaction) -> str: 3185 return "BEGIN" 3186 3187 def commit_sql(self, expression: exp.Commit) -> str: 3188 chain = expression.args.get("chain") 3189 if chain is not None: 3190 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3191 3192 return f"COMMIT{chain or ''}" 3193 3194 def rollback_sql(self, expression: exp.Rollback) -> str: 3195 savepoint = expression.args.get("savepoint") 3196 savepoint = f" TO {savepoint}" if savepoint else "" 3197 return f"ROLLBACK{savepoint}" 3198 3199 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3200 this = self.sql(expression, "this") 3201 3202 dtype = self.sql(expression, "dtype") 3203 if dtype: 3204 collate = self.sql(expression, "collate") 3205 collate = f" COLLATE {collate}" if collate else "" 3206 using = self.sql(expression, "using") 3207 using = f" USING {using}" if using else "" 3208 return f"ALTER COLUMN {this} SET DATA TYPE {dtype}{collate}{using}" 3209 3210 default = self.sql(expression, "default") 3211 if default: 3212 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3213 3214 comment = self.sql(expression, "comment") 3215 if comment: 3216 return f"ALTER COLUMN {this} COMMENT {comment}" 3217 3218 allow_null = expression.args.get("allow_null") 3219 drop = expression.args.get("drop") 3220 3221 if not drop and not allow_null: 3222 self.unsupported("Unsupported ALTER COLUMN syntax") 3223 3224 if allow_null is not None: 3225 keyword = "DROP" if drop else "SET" 3226 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3227 3228 return f"ALTER COLUMN {this} DROP DEFAULT" 3229 3230 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3231 this = self.sql(expression, "this") 3232 if not isinstance(expression.this, exp.Var): 3233 this = f"KEY DISTKEY {this}" 3234 return f"ALTER DISTSTYLE {this}" 3235 3236 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3237 compound = " COMPOUND" if expression.args.get("compound") else "" 3238 this = self.sql(expression, "this") 3239 expressions = self.expressions(expression, flat=True) 3240 expressions = f"({expressions})" if expressions else "" 3241 return f"ALTER{compound} SORTKEY {this or expressions}" 3242 3243 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3244 if not self.RENAME_TABLE_WITH_DB: 3245 # Remove db from tables 3246 expression = expression.transform( 3247 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3248 ).assert_is(exp.AlterRename) 3249 this = self.sql(expression, "this") 3250 return f"RENAME TO {this}" 3251 3252 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3253 exists = " IF EXISTS" if expression.args.get("exists") else "" 3254 old_column = self.sql(expression, "this") 3255 new_column = self.sql(expression, "to") 3256 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3257 3258 def alterset_sql(self, expression: exp.AlterSet) -> str: 3259 exprs = self.expressions(expression, flat=True) 3260 return f"SET {exprs}" 3261 3262 def alter_sql(self, expression: exp.Alter) -> str: 3263 actions = expression.args["actions"] 3264 3265 if isinstance(actions[0], exp.ColumnDef): 3266 actions = self.add_column_sql(expression) 3267 elif isinstance(actions[0], exp.Schema): 3268 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3269 elif isinstance(actions[0], exp.Delete): 3270 actions = self.expressions(expression, key="actions", flat=True) 3271 elif isinstance(actions[0], exp.Query): 3272 actions = "AS " + self.expressions(expression, key="actions") 3273 else: 3274 actions = self.expressions(expression, key="actions", flat=True) 3275 3276 exists = " IF EXISTS" if expression.args.get("exists") else "" 3277 on_cluster = self.sql(expression, "cluster") 3278 on_cluster = f" {on_cluster}" if on_cluster else "" 3279 only = " ONLY" if expression.args.get("only") else "" 3280 options = self.expressions(expression, key="options") 3281 options = f", {options}" if options else "" 3282 kind = self.sql(expression, "kind") 3283 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3284 3285 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{not_valid}{options}" 3286 3287 def add_column_sql(self, expression: exp.Alter) -> str: 3288 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3289 return self.expressions( 3290 expression, 3291 key="actions", 3292 prefix="ADD COLUMN ", 3293 skip_first=True, 3294 ) 3295 return f"ADD {self.expressions(expression, key='actions', flat=True)}" 3296 3297 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3298 expressions = self.expressions(expression) 3299 exists = " IF EXISTS " if expression.args.get("exists") else " " 3300 return f"DROP{exists}{expressions}" 3301 3302 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3303 return f"ADD {self.expressions(expression)}" 3304 3305 def distinct_sql(self, expression: exp.Distinct) -> str: 3306 this = self.expressions(expression, flat=True) 3307 3308 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3309 case = exp.case() 3310 for arg in expression.expressions: 3311 case = case.when(arg.is_(exp.null()), exp.null()) 3312 this = self.sql(case.else_(f"({this})")) 3313 3314 this = f" {this}" if this else "" 3315 3316 on = self.sql(expression, "on") 3317 on = f" ON {on}" if on else "" 3318 return f"DISTINCT{this}{on}" 3319 3320 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3321 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3322 3323 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3324 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3325 3326 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3327 this_sql = self.sql(expression, "this") 3328 expression_sql = self.sql(expression, "expression") 3329 kind = "MAX" if expression.args.get("max") else "MIN" 3330 return f"{this_sql} HAVING {kind} {expression_sql}" 3331 3332 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3333 return self.sql( 3334 exp.Cast( 3335 this=exp.Div(this=expression.this, expression=expression.expression), 3336 to=exp.DataType(this=exp.DataType.Type.INT), 3337 ) 3338 ) 3339 3340 def dpipe_sql(self, expression: exp.DPipe) -> str: 3341 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3342 return self.func( 3343 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3344 ) 3345 return self.binary(expression, "||") 3346 3347 def div_sql(self, expression: exp.Div) -> str: 3348 l, r = expression.left, expression.right 3349 3350 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3351 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3352 3353 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3354 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3355 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3356 3357 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3358 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3359 return self.sql( 3360 exp.cast( 3361 l / r, 3362 to=exp.DataType.Type.BIGINT, 3363 ) 3364 ) 3365 3366 return self.binary(expression, "/") 3367 3368 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3369 return self.binary(expression, "OVERLAPS") 3370 3371 def distance_sql(self, expression: exp.Distance) -> str: 3372 return self.binary(expression, "<->") 3373 3374 def dot_sql(self, expression: exp.Dot) -> str: 3375 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3376 3377 def eq_sql(self, expression: exp.EQ) -> str: 3378 return self.binary(expression, "=") 3379 3380 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3381 return self.binary(expression, ":=") 3382 3383 def escape_sql(self, expression: exp.Escape) -> str: 3384 return self.binary(expression, "ESCAPE") 3385 3386 def glob_sql(self, expression: exp.Glob) -> str: 3387 return self.binary(expression, "GLOB") 3388 3389 def gt_sql(self, expression: exp.GT) -> str: 3390 return self.binary(expression, ">") 3391 3392 def gte_sql(self, expression: exp.GTE) -> str: 3393 return self.binary(expression, ">=") 3394 3395 def ilike_sql(self, expression: exp.ILike) -> str: 3396 return self.binary(expression, "ILIKE") 3397 3398 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3399 return self.binary(expression, "ILIKE ANY") 3400 3401 def is_sql(self, expression: exp.Is) -> str: 3402 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3403 return self.sql( 3404 expression.this if expression.expression.this else exp.not_(expression.this) 3405 ) 3406 return self.binary(expression, "IS") 3407 3408 def like_sql(self, expression: exp.Like) -> str: 3409 return self.binary(expression, "LIKE") 3410 3411 def likeany_sql(self, expression: exp.LikeAny) -> str: 3412 return self.binary(expression, "LIKE ANY") 3413 3414 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3415 return self.binary(expression, "SIMILAR TO") 3416 3417 def lt_sql(self, expression: exp.LT) -> str: 3418 return self.binary(expression, "<") 3419 3420 def lte_sql(self, expression: exp.LTE) -> str: 3421 return self.binary(expression, "<=") 3422 3423 def mod_sql(self, expression: exp.Mod) -> str: 3424 return self.binary(expression, "%") 3425 3426 def mul_sql(self, expression: exp.Mul) -> str: 3427 return self.binary(expression, "*") 3428 3429 def neq_sql(self, expression: exp.NEQ) -> str: 3430 return self.binary(expression, "<>") 3431 3432 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3433 return self.binary(expression, "IS NOT DISTINCT FROM") 3434 3435 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3436 return self.binary(expression, "IS DISTINCT FROM") 3437 3438 def slice_sql(self, expression: exp.Slice) -> str: 3439 return self.binary(expression, ":") 3440 3441 def sub_sql(self, expression: exp.Sub) -> str: 3442 return self.binary(expression, "-") 3443 3444 def trycast_sql(self, expression: exp.TryCast) -> str: 3445 return self.cast_sql(expression, safe_prefix="TRY_") 3446 3447 def try_sql(self, expression: exp.Try) -> str: 3448 if not self.TRY_SUPPORTED: 3449 self.unsupported("Unsupported TRY function") 3450 return self.sql(expression, "this") 3451 3452 return self.func("TRY", expression.this) 3453 3454 def log_sql(self, expression: exp.Log) -> str: 3455 this = expression.this 3456 expr = expression.expression 3457 3458 if self.dialect.LOG_BASE_FIRST is False: 3459 this, expr = expr, this 3460 elif self.dialect.LOG_BASE_FIRST is None and expr: 3461 if this.name in ("2", "10"): 3462 return self.func(f"LOG{this.name}", expr) 3463 3464 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3465 3466 return self.func("LOG", this, expr) 3467 3468 def use_sql(self, expression: exp.Use) -> str: 3469 kind = self.sql(expression, "kind") 3470 kind = f" {kind}" if kind else "" 3471 this = self.sql(expression, "this") 3472 this = f" {this}" if this else "" 3473 return f"USE{kind}{this}" 3474 3475 def binary(self, expression: exp.Binary, op: str) -> str: 3476 sqls: t.List[str] = [] 3477 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3478 binary_type = type(expression) 3479 3480 while stack: 3481 node = stack.pop() 3482 3483 if type(node) is binary_type: 3484 op_func = node.args.get("operator") 3485 if op_func: 3486 op = f"OPERATOR({self.sql(op_func)})" 3487 3488 stack.append(node.right) 3489 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3490 stack.append(node.left) 3491 else: 3492 sqls.append(self.sql(node)) 3493 3494 return "".join(sqls) 3495 3496 def function_fallback_sql(self, expression: exp.Func) -> str: 3497 args = [] 3498 3499 for key in expression.arg_types: 3500 arg_value = expression.args.get(key) 3501 3502 if isinstance(arg_value, list): 3503 for value in arg_value: 3504 args.append(value) 3505 elif arg_value is not None: 3506 args.append(arg_value) 3507 3508 if self.normalize_functions: 3509 name = expression.sql_name() 3510 else: 3511 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3512 3513 return self.func(name, *args) 3514 3515 def func( 3516 self, 3517 name: str, 3518 *args: t.Optional[exp.Expression | str], 3519 prefix: str = "(", 3520 suffix: str = ")", 3521 normalize: bool = True, 3522 ) -> str: 3523 name = self.normalize_func(name) if normalize else name 3524 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3525 3526 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3527 arg_sqls = tuple( 3528 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3529 ) 3530 if self.pretty and self.too_wide(arg_sqls): 3531 return self.indent( 3532 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3533 ) 3534 return sep.join(arg_sqls) 3535 3536 def too_wide(self, args: t.Iterable) -> bool: 3537 return sum(len(arg) for arg in args) > self.max_text_width 3538 3539 def format_time( 3540 self, 3541 expression: exp.Expression, 3542 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3543 inverse_time_trie: t.Optional[t.Dict] = None, 3544 ) -> t.Optional[str]: 3545 return format_time( 3546 self.sql(expression, "format"), 3547 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3548 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3549 ) 3550 3551 def expressions( 3552 self, 3553 expression: t.Optional[exp.Expression] = None, 3554 key: t.Optional[str] = None, 3555 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3556 flat: bool = False, 3557 indent: bool = True, 3558 skip_first: bool = False, 3559 skip_last: bool = False, 3560 sep: str = ", ", 3561 prefix: str = "", 3562 dynamic: bool = False, 3563 new_line: bool = False, 3564 ) -> str: 3565 expressions = expression.args.get(key or "expressions") if expression else sqls 3566 3567 if not expressions: 3568 return "" 3569 3570 if flat: 3571 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3572 3573 num_sqls = len(expressions) 3574 result_sqls = [] 3575 3576 for i, e in enumerate(expressions): 3577 sql = self.sql(e, comment=False) 3578 if not sql: 3579 continue 3580 3581 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3582 3583 if self.pretty: 3584 if self.leading_comma: 3585 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3586 else: 3587 result_sqls.append( 3588 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3589 ) 3590 else: 3591 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3592 3593 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3594 if new_line: 3595 result_sqls.insert(0, "") 3596 result_sqls.append("") 3597 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3598 else: 3599 result_sql = "".join(result_sqls) 3600 3601 return ( 3602 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3603 if indent 3604 else result_sql 3605 ) 3606 3607 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3608 flat = flat or isinstance(expression.parent, exp.Properties) 3609 expressions_sql = self.expressions(expression, flat=flat) 3610 if flat: 3611 return f"{op} {expressions_sql}" 3612 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3613 3614 def naked_property(self, expression: exp.Property) -> str: 3615 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3616 if not property_name: 3617 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3618 return f"{property_name} {self.sql(expression, 'this')}" 3619 3620 def tag_sql(self, expression: exp.Tag) -> str: 3621 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3622 3623 def token_sql(self, token_type: TokenType) -> str: 3624 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3625 3626 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3627 this = self.sql(expression, "this") 3628 expressions = self.no_identify(self.expressions, expression) 3629 expressions = ( 3630 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3631 ) 3632 return f"{this}{expressions}" if expressions.strip() != "" else this 3633 3634 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3635 this = self.sql(expression, "this") 3636 expressions = self.expressions(expression, flat=True) 3637 return f"{this}({expressions})" 3638 3639 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3640 return self.binary(expression, "=>") 3641 3642 def when_sql(self, expression: exp.When) -> str: 3643 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3644 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3645 condition = self.sql(expression, "condition") 3646 condition = f" AND {condition}" if condition else "" 3647 3648 then_expression = expression.args.get("then") 3649 if isinstance(then_expression, exp.Insert): 3650 this = self.sql(then_expression, "this") 3651 this = f"INSERT {this}" if this else "INSERT" 3652 then = self.sql(then_expression, "expression") 3653 then = f"{this} VALUES {then}" if then else this 3654 elif isinstance(then_expression, exp.Update): 3655 if isinstance(then_expression.args.get("expressions"), exp.Star): 3656 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3657 else: 3658 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 3659 else: 3660 then = self.sql(then_expression) 3661 return f"WHEN {matched}{source}{condition} THEN {then}" 3662 3663 def merge_sql(self, expression: exp.Merge) -> str: 3664 table = expression.this 3665 table_alias = "" 3666 3667 hints = table.args.get("hints") 3668 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3669 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3670 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3671 3672 this = self.sql(table) 3673 using = f"USING {self.sql(expression, 'using')}" 3674 on = f"ON {self.sql(expression, 'on')}" 3675 expressions = self.expressions(expression, sep=" ", indent=False) 3676 returning = self.sql(expression, "returning") 3677 if returning: 3678 expressions = f"{expressions}{returning}" 3679 3680 sep = self.sep() 3681 3682 return self.prepend_ctes( 3683 expression, 3684 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{expressions}", 3685 ) 3686 3687 @unsupported_args("format") 3688 def tochar_sql(self, expression: exp.ToChar) -> str: 3689 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3690 3691 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3692 if not self.SUPPORTS_TO_NUMBER: 3693 self.unsupported("Unsupported TO_NUMBER function") 3694 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3695 3696 fmt = expression.args.get("format") 3697 if not fmt: 3698 self.unsupported("Conversion format is required for TO_NUMBER") 3699 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3700 3701 return self.func("TO_NUMBER", expression.this, fmt) 3702 3703 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3704 this = self.sql(expression, "this") 3705 kind = self.sql(expression, "kind") 3706 settings_sql = self.expressions(expression, key="settings", sep=" ") 3707 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3708 return f"{this}({kind}{args})" 3709 3710 def dictrange_sql(self, expression: exp.DictRange) -> str: 3711 this = self.sql(expression, "this") 3712 max = self.sql(expression, "max") 3713 min = self.sql(expression, "min") 3714 return f"{this}(MIN {min} MAX {max})" 3715 3716 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3717 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3718 3719 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3720 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3721 3722 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3723 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3724 expressions = self.expressions(expression, flat=True) 3725 expressions = f" {self.wrap(expressions)}" if expressions else "" 3726 buckets = self.sql(expression, "buckets") 3727 kind = self.sql(expression, "kind") 3728 buckets = f" BUCKETS {buckets}" if buckets else "" 3729 order = self.sql(expression, "order") 3730 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 3731 3732 def oncluster_sql(self, expression: exp.OnCluster) -> str: 3733 return "" 3734 3735 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3736 expressions = self.expressions(expression, key="expressions", flat=True) 3737 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3738 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3739 buckets = self.sql(expression, "buckets") 3740 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 3741 3742 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3743 this = self.sql(expression, "this") 3744 having = self.sql(expression, "having") 3745 3746 if having: 3747 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3748 3749 return self.func("ANY_VALUE", this) 3750 3751 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3752 transform = self.func("TRANSFORM", *expression.expressions) 3753 row_format_before = self.sql(expression, "row_format_before") 3754 row_format_before = f" {row_format_before}" if row_format_before else "" 3755 record_writer = self.sql(expression, "record_writer") 3756 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3757 using = f" USING {self.sql(expression, 'command_script')}" 3758 schema = self.sql(expression, "schema") 3759 schema = f" AS {schema}" if schema else "" 3760 row_format_after = self.sql(expression, "row_format_after") 3761 row_format_after = f" {row_format_after}" if row_format_after else "" 3762 record_reader = self.sql(expression, "record_reader") 3763 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3764 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 3765 3766 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3767 key_block_size = self.sql(expression, "key_block_size") 3768 if key_block_size: 3769 return f"KEY_BLOCK_SIZE = {key_block_size}" 3770 3771 using = self.sql(expression, "using") 3772 if using: 3773 return f"USING {using}" 3774 3775 parser = self.sql(expression, "parser") 3776 if parser: 3777 return f"WITH PARSER {parser}" 3778 3779 comment = self.sql(expression, "comment") 3780 if comment: 3781 return f"COMMENT {comment}" 3782 3783 visible = expression.args.get("visible") 3784 if visible is not None: 3785 return "VISIBLE" if visible else "INVISIBLE" 3786 3787 engine_attr = self.sql(expression, "engine_attr") 3788 if engine_attr: 3789 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3790 3791 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3792 if secondary_engine_attr: 3793 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3794 3795 self.unsupported("Unsupported index constraint option.") 3796 return "" 3797 3798 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 3799 enforced = " ENFORCED" if expression.args.get("enforced") else "" 3800 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 3801 3802 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3803 kind = self.sql(expression, "kind") 3804 kind = f"{kind} INDEX" if kind else "INDEX" 3805 this = self.sql(expression, "this") 3806 this = f" {this}" if this else "" 3807 index_type = self.sql(expression, "index_type") 3808 index_type = f" USING {index_type}" if index_type else "" 3809 expressions = self.expressions(expression, flat=True) 3810 expressions = f" ({expressions})" if expressions else "" 3811 options = self.expressions(expression, key="options", sep=" ") 3812 options = f" {options}" if options else "" 3813 return f"{kind}{this}{index_type}{expressions}{options}" 3814 3815 def nvl2_sql(self, expression: exp.Nvl2) -> str: 3816 if self.NVL2_SUPPORTED: 3817 return self.function_fallback_sql(expression) 3818 3819 case = exp.Case().when( 3820 expression.this.is_(exp.null()).not_(copy=False), 3821 expression.args["true"], 3822 copy=False, 3823 ) 3824 else_cond = expression.args.get("false") 3825 if else_cond: 3826 case.else_(else_cond, copy=False) 3827 3828 return self.sql(case) 3829 3830 def comprehension_sql(self, expression: exp.Comprehension) -> str: 3831 this = self.sql(expression, "this") 3832 expr = self.sql(expression, "expression") 3833 iterator = self.sql(expression, "iterator") 3834 condition = self.sql(expression, "condition") 3835 condition = f" IF {condition}" if condition else "" 3836 return f"{this} FOR {expr} IN {iterator}{condition}" 3837 3838 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 3839 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 3840 3841 def opclass_sql(self, expression: exp.Opclass) -> str: 3842 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 3843 3844 def predict_sql(self, expression: exp.Predict) -> str: 3845 model = self.sql(expression, "this") 3846 model = f"MODEL {model}" 3847 table = self.sql(expression, "expression") 3848 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 3849 parameters = self.sql(expression, "params_struct") 3850 return self.func("PREDICT", model, table, parameters or None) 3851 3852 def forin_sql(self, expression: exp.ForIn) -> str: 3853 this = self.sql(expression, "this") 3854 expression_sql = self.sql(expression, "expression") 3855 return f"FOR {this} DO {expression_sql}" 3856 3857 def refresh_sql(self, expression: exp.Refresh) -> str: 3858 this = self.sql(expression, "this") 3859 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 3860 return f"REFRESH {table}{this}" 3861 3862 def toarray_sql(self, expression: exp.ToArray) -> str: 3863 arg = expression.this 3864 if not arg.type: 3865 from sqlglot.optimizer.annotate_types import annotate_types 3866 3867 arg = annotate_types(arg) 3868 3869 if arg.is_type(exp.DataType.Type.ARRAY): 3870 return self.sql(arg) 3871 3872 cond_for_null = arg.is_(exp.null()) 3873 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 3874 3875 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 3876 this = expression.this 3877 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 3878 return self.sql(this) 3879 3880 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 3881 3882 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 3883 this = expression.this 3884 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 3885 return self.sql(this) 3886 3887 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP)) 3888 3889 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 3890 this = expression.this 3891 time_format = self.format_time(expression) 3892 3893 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 3894 return self.sql( 3895 exp.cast( 3896 exp.StrToTime(this=this, format=expression.args["format"]), 3897 exp.DataType.Type.DATE, 3898 ) 3899 ) 3900 3901 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 3902 return self.sql(this) 3903 3904 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 3905 3906 def unixdate_sql(self, expression: exp.UnixDate) -> str: 3907 return self.sql( 3908 exp.func( 3909 "DATEDIFF", 3910 expression.this, 3911 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 3912 "day", 3913 ) 3914 ) 3915 3916 def lastday_sql(self, expression: exp.LastDay) -> str: 3917 if self.LAST_DAY_SUPPORTS_DATE_PART: 3918 return self.function_fallback_sql(expression) 3919 3920 unit = expression.text("unit") 3921 if unit and unit != "MONTH": 3922 self.unsupported("Date parts are not supported in LAST_DAY.") 3923 3924 return self.func("LAST_DAY", expression.this) 3925 3926 def dateadd_sql(self, expression: exp.DateAdd) -> str: 3927 from sqlglot.dialects.dialect import unit_to_str 3928 3929 return self.func( 3930 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 3931 ) 3932 3933 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 3934 if self.CAN_IMPLEMENT_ARRAY_ANY: 3935 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 3936 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 3937 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 3938 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 3939 3940 from sqlglot.dialects import Dialect 3941 3942 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 3943 if self.dialect.__class__ != Dialect: 3944 self.unsupported("ARRAY_ANY is unsupported") 3945 3946 return self.function_fallback_sql(expression) 3947 3948 def struct_sql(self, expression: exp.Struct) -> str: 3949 expression.set( 3950 "expressions", 3951 [ 3952 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 3953 if isinstance(e, exp.PropertyEQ) 3954 else e 3955 for e in expression.expressions 3956 ], 3957 ) 3958 3959 return self.function_fallback_sql(expression) 3960 3961 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 3962 low = self.sql(expression, "this") 3963 high = self.sql(expression, "expression") 3964 3965 return f"{low} TO {high}" 3966 3967 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 3968 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 3969 tables = f" {self.expressions(expression)}" 3970 3971 exists = " IF EXISTS" if expression.args.get("exists") else "" 3972 3973 on_cluster = self.sql(expression, "cluster") 3974 on_cluster = f" {on_cluster}" if on_cluster else "" 3975 3976 identity = self.sql(expression, "identity") 3977 identity = f" {identity} IDENTITY" if identity else "" 3978 3979 option = self.sql(expression, "option") 3980 option = f" {option}" if option else "" 3981 3982 partition = self.sql(expression, "partition") 3983 partition = f" {partition}" if partition else "" 3984 3985 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 3986 3987 # This transpiles T-SQL's CONVERT function 3988 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 3989 def convert_sql(self, expression: exp.Convert) -> str: 3990 to = expression.this 3991 value = expression.expression 3992 style = expression.args.get("style") 3993 safe = expression.args.get("safe") 3994 strict = expression.args.get("strict") 3995 3996 if not to or not value: 3997 return "" 3998 3999 # Retrieve length of datatype and override to default if not specified 4000 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4001 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4002 4003 transformed: t.Optional[exp.Expression] = None 4004 cast = exp.Cast if strict else exp.TryCast 4005 4006 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4007 if isinstance(style, exp.Literal) and style.is_int: 4008 from sqlglot.dialects.tsql import TSQL 4009 4010 style_value = style.name 4011 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4012 if not converted_style: 4013 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4014 4015 fmt = exp.Literal.string(converted_style) 4016 4017 if to.this == exp.DataType.Type.DATE: 4018 transformed = exp.StrToDate(this=value, format=fmt) 4019 elif to.this == exp.DataType.Type.DATETIME: 4020 transformed = exp.StrToTime(this=value, format=fmt) 4021 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4022 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4023 elif to.this == exp.DataType.Type.TEXT: 4024 transformed = exp.TimeToStr(this=value, format=fmt) 4025 4026 if not transformed: 4027 transformed = cast(this=value, to=to, safe=safe) 4028 4029 return self.sql(transformed) 4030 4031 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4032 this = expression.this 4033 if isinstance(this, exp.JSONPathWildcard): 4034 this = self.json_path_part(this) 4035 return f".{this}" if this else "" 4036 4037 if exp.SAFE_IDENTIFIER_RE.match(this): 4038 return f".{this}" 4039 4040 this = self.json_path_part(this) 4041 return f"[{this}]" if self.JSON_PATH_BRACKETED_KEY_SUPPORTED else f".{this}" 4042 4043 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4044 this = self.json_path_part(expression.this) 4045 return f"[{this}]" if this else "" 4046 4047 def _simplify_unless_literal(self, expression: E) -> E: 4048 if not isinstance(expression, exp.Literal): 4049 from sqlglot.optimizer.simplify import simplify 4050 4051 expression = simplify(expression, dialect=self.dialect) 4052 4053 return expression 4054 4055 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4056 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4057 # The first modifier here will be the one closest to the AggFunc's arg 4058 mods = sorted( 4059 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4060 key=lambda x: 0 4061 if isinstance(x, exp.HavingMax) 4062 else (1 if isinstance(x, exp.Order) else 2), 4063 ) 4064 4065 if mods: 4066 mod = mods[0] 4067 this = expression.__class__(this=mod.this.copy()) 4068 this.meta["inline"] = True 4069 mod.this.replace(this) 4070 return self.sql(expression.this) 4071 4072 agg_func = expression.find(exp.AggFunc) 4073 4074 if agg_func: 4075 return self.sql(agg_func)[:-1] + f" {text})" 4076 4077 return f"{self.sql(expression, 'this')} {text}" 4078 4079 def _replace_line_breaks(self, string: str) -> str: 4080 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4081 if self.pretty: 4082 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4083 return string 4084 4085 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4086 option = self.sql(expression, "this") 4087 4088 if expression.expressions: 4089 upper = option.upper() 4090 4091 # Snowflake FILE_FORMAT options are separated by whitespace 4092 sep = " " if upper == "FILE_FORMAT" else ", " 4093 4094 # Databricks copy/format options do not set their list of values with EQ 4095 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4096 values = self.expressions(expression, flat=True, sep=sep) 4097 return f"{option}{op}({values})" 4098 4099 value = self.sql(expression, "expression") 4100 4101 if not value: 4102 return option 4103 4104 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4105 4106 return f"{option}{op}{value}" 4107 4108 def credentials_sql(self, expression: exp.Credentials) -> str: 4109 cred_expr = expression.args.get("credentials") 4110 if isinstance(cred_expr, exp.Literal): 4111 # Redshift case: CREDENTIALS <string> 4112 credentials = self.sql(expression, "credentials") 4113 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4114 else: 4115 # Snowflake case: CREDENTIALS = (...) 4116 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4117 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4118 4119 storage = self.sql(expression, "storage") 4120 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4121 4122 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4123 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4124 4125 iam_role = self.sql(expression, "iam_role") 4126 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4127 4128 region = self.sql(expression, "region") 4129 region = f" REGION {region}" if region else "" 4130 4131 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4132 4133 def copy_sql(self, expression: exp.Copy) -> str: 4134 this = self.sql(expression, "this") 4135 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4136 4137 credentials = self.sql(expression, "credentials") 4138 credentials = self.seg(credentials) if credentials else "" 4139 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4140 files = self.expressions(expression, key="files", flat=True) 4141 4142 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4143 params = self.expressions( 4144 expression, 4145 key="params", 4146 sep=sep, 4147 new_line=True, 4148 skip_last=True, 4149 skip_first=True, 4150 indent=self.COPY_PARAMS_ARE_WRAPPED, 4151 ) 4152 4153 if params: 4154 if self.COPY_PARAMS_ARE_WRAPPED: 4155 params = f" WITH ({params})" 4156 elif not self.pretty: 4157 params = f" {params}" 4158 4159 return f"COPY{this}{kind} {files}{credentials}{params}" 4160 4161 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4162 return "" 4163 4164 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4165 on_sql = "ON" if expression.args.get("on") else "OFF" 4166 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4167 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4168 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4169 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4170 4171 if filter_col or retention_period: 4172 on_sql = self.func("ON", filter_col, retention_period) 4173 4174 return f"DATA_DELETION={on_sql}" 4175 4176 def maskingpolicycolumnconstraint_sql( 4177 self, expression: exp.MaskingPolicyColumnConstraint 4178 ) -> str: 4179 this = self.sql(expression, "this") 4180 expressions = self.expressions(expression, flat=True) 4181 expressions = f" USING ({expressions})" if expressions else "" 4182 return f"MASKING POLICY {this}{expressions}" 4183 4184 def gapfill_sql(self, expression: exp.GapFill) -> str: 4185 this = self.sql(expression, "this") 4186 this = f"TABLE {this}" 4187 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4188 4189 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4190 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4191 4192 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4193 this = self.sql(expression, "this") 4194 expr = expression.expression 4195 4196 if isinstance(expr, exp.Func): 4197 # T-SQL's CLR functions are case sensitive 4198 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4199 else: 4200 expr = self.sql(expression, "expression") 4201 4202 return self.scope_resolution(expr, this) 4203 4204 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4205 if self.PARSE_JSON_NAME is None: 4206 return self.sql(expression.this) 4207 4208 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4209 4210 def rand_sql(self, expression: exp.Rand) -> str: 4211 lower = self.sql(expression, "lower") 4212 upper = self.sql(expression, "upper") 4213 4214 if lower and upper: 4215 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4216 return self.func("RAND", expression.this) 4217 4218 def changes_sql(self, expression: exp.Changes) -> str: 4219 information = self.sql(expression, "information") 4220 information = f"INFORMATION => {information}" 4221 at_before = self.sql(expression, "at_before") 4222 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4223 end = self.sql(expression, "end") 4224 end = f"{self.seg('')}{end}" if end else "" 4225 4226 return f"CHANGES ({information}){at_before}{end}" 4227 4228 def pad_sql(self, expression: exp.Pad) -> str: 4229 prefix = "L" if expression.args.get("is_left") else "R" 4230 4231 fill_pattern = self.sql(expression, "fill_pattern") or None 4232 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4233 fill_pattern = "' '" 4234 4235 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4236 4237 def summarize_sql(self, expression: exp.Summarize) -> str: 4238 table = " TABLE" if expression.args.get("table") else "" 4239 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4240 4241 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4242 generate_series = exp.GenerateSeries(**expression.args) 4243 4244 parent = expression.parent 4245 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4246 parent = parent.parent 4247 4248 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4249 return self.sql(exp.Unnest(expressions=[generate_series])) 4250 4251 if isinstance(parent, exp.Select): 4252 self.unsupported("GenerateSeries projection unnesting is not supported.") 4253 4254 return self.sql(generate_series) 4255 4256 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4257 exprs = expression.expressions 4258 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4259 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4260 else: 4261 rhs = self.expressions(expression) 4262 4263 return self.func(name, expression.this, rhs or None) 4264 4265 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4266 if self.SUPPORTS_CONVERT_TIMEZONE: 4267 return self.function_fallback_sql(expression) 4268 4269 source_tz = expression.args.get("source_tz") 4270 target_tz = expression.args.get("target_tz") 4271 timestamp = expression.args.get("timestamp") 4272 4273 if source_tz and timestamp: 4274 timestamp = exp.AtTimeZone( 4275 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4276 ) 4277 4278 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4279 4280 return self.sql(expr) 4281 4282 def json_sql(self, expression: exp.JSON) -> str: 4283 this = self.sql(expression, "this") 4284 this = f" {this}" if this else "" 4285 4286 _with = expression.args.get("with") 4287 4288 if _with is None: 4289 with_sql = "" 4290 elif not _with: 4291 with_sql = " WITHOUT" 4292 else: 4293 with_sql = " WITH" 4294 4295 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4296 4297 return f"JSON{this}{with_sql}{unique_sql}" 4298 4299 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4300 def _generate_on_options(arg: t.Any) -> str: 4301 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4302 4303 path = self.sql(expression, "path") 4304 returning = self.sql(expression, "returning") 4305 returning = f" RETURNING {returning}" if returning else "" 4306 4307 on_condition = self.sql(expression, "on_condition") 4308 on_condition = f" {on_condition}" if on_condition else "" 4309 4310 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4311 4312 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4313 else_ = "ELSE " if expression.args.get("else_") else "" 4314 condition = self.sql(expression, "expression") 4315 condition = f"WHEN {condition} THEN " if condition else else_ 4316 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4317 return f"{condition}{insert}" 4318 4319 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4320 kind = self.sql(expression, "kind") 4321 expressions = self.seg(self.expressions(expression, sep=" ")) 4322 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4323 return res 4324 4325 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4326 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4327 empty = expression.args.get("empty") 4328 empty = ( 4329 f"DEFAULT {empty} ON EMPTY" 4330 if isinstance(empty, exp.Expression) 4331 else self.sql(expression, "empty") 4332 ) 4333 4334 error = expression.args.get("error") 4335 error = ( 4336 f"DEFAULT {error} ON ERROR" 4337 if isinstance(error, exp.Expression) 4338 else self.sql(expression, "error") 4339 ) 4340 4341 if error and empty: 4342 error = ( 4343 f"{empty} {error}" 4344 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4345 else f"{error} {empty}" 4346 ) 4347 empty = "" 4348 4349 null = self.sql(expression, "null") 4350 4351 return f"{empty}{error}{null}" 4352 4353 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4354 this = self.sql(expression, "this") 4355 path = self.sql(expression, "path") 4356 4357 passing = self.expressions(expression, "passing") 4358 passing = f" PASSING {passing}" if passing else "" 4359 4360 on_condition = self.sql(expression, "on_condition") 4361 on_condition = f" {on_condition}" if on_condition else "" 4362 4363 path = f"{path}{passing}{on_condition}" 4364 4365 return self.func("JSON_EXISTS", this, path) 4366 4367 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4368 array_agg = self.function_fallback_sql(expression) 4369 4370 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4371 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4372 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4373 parent = expression.parent 4374 if isinstance(parent, exp.Filter): 4375 parent_cond = parent.expression.this 4376 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4377 else: 4378 this = expression.this 4379 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4380 if this.find(exp.Column): 4381 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4382 this_sql = ( 4383 self.expressions(this) 4384 if isinstance(this, exp.Distinct) 4385 else self.sql(expression, "this") 4386 ) 4387 4388 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4389 4390 return array_agg 4391 4392 def apply_sql(self, expression: exp.Apply) -> str: 4393 this = self.sql(expression, "this") 4394 expr = self.sql(expression, "expression") 4395 4396 return f"{this} APPLY({expr})" 4397 4398 def grant_sql(self, expression: exp.Grant) -> str: 4399 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4400 4401 kind = self.sql(expression, "kind") 4402 kind = f" {kind}" if kind else "" 4403 4404 securable = self.sql(expression, "securable") 4405 securable = f" {securable}" if securable else "" 4406 4407 principals = self.expressions(expression, key="principals", flat=True) 4408 4409 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4410 4411 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4412 4413 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4414 this = self.sql(expression, "this") 4415 columns = self.expressions(expression, flat=True) 4416 columns = f"({columns})" if columns else "" 4417 4418 return f"{this}{columns}" 4419 4420 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4421 this = self.sql(expression, "this") 4422 4423 kind = self.sql(expression, "kind") 4424 kind = f"{kind} " if kind else "" 4425 4426 return f"{kind}{this}" 4427 4428 def columns_sql(self, expression: exp.Columns): 4429 func = self.function_fallback_sql(expression) 4430 if expression.args.get("unpack"): 4431 func = f"*{func}" 4432 4433 return func 4434 4435 def overlay_sql(self, expression: exp.Overlay): 4436 this = self.sql(expression, "this") 4437 expr = self.sql(expression, "expression") 4438 from_sql = self.sql(expression, "from") 4439 for_sql = self.sql(expression, "for") 4440 for_sql = f" FOR {for_sql}" if for_sql else "" 4441 4442 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4443 4444 @unsupported_args("format") 4445 def todouble_sql(self, expression: exp.ToDouble) -> str: 4446 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4447 4448 def string_sql(self, expression: exp.String) -> str: 4449 this = expression.this 4450 zone = expression.args.get("zone") 4451 4452 if zone: 4453 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4454 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4455 # set for source_tz to transpile the time conversion before the STRING cast 4456 this = exp.ConvertTimezone( 4457 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4458 ) 4459 4460 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4461 4462 def median_sql(self, expression: exp.Median): 4463 if not self.SUPPORTS_MEDIAN: 4464 return self.sql( 4465 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4466 ) 4467 4468 return self.function_fallback_sql(expression) 4469 4470 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4471 filler = self.sql(expression, "this") 4472 filler = f" {filler}" if filler else "" 4473 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4474 return f"TRUNCATE{filler} {with_count}"
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.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 119 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 120 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 121 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 122 exp.CaseSpecificColumnConstraint: lambda _, 123 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 124 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 125 exp.CharacterSetProperty: lambda self, 126 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 127 exp.ClusteredColumnConstraint: lambda self, 128 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 129 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 130 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 131 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 132 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 133 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 134 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 135 exp.DynamicProperty: lambda *_: "DYNAMIC", 136 exp.EmptyProperty: lambda *_: "EMPTY", 137 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 138 exp.EphemeralColumnConstraint: lambda self, 139 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 140 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 141 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 142 exp.Except: lambda self, e: self.set_operations(e), 143 exp.ExternalProperty: lambda *_: "EXTERNAL", 144 exp.GlobalProperty: lambda *_: "GLOBAL", 145 exp.HeapProperty: lambda *_: "HEAP", 146 exp.IcebergProperty: lambda *_: "ICEBERG", 147 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 148 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 149 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 150 exp.Intersect: lambda self, e: self.set_operations(e), 151 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 152 exp.LanguageProperty: lambda self, e: self.naked_property(e), 153 exp.LocationProperty: lambda self, e: self.naked_property(e), 154 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 155 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 156 exp.NonClusteredColumnConstraint: lambda self, 157 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 158 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 159 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 160 exp.OnCommitProperty: lambda _, 161 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 162 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 163 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 164 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 165 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 166 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 167 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 168 exp.ProjectionPolicyColumnConstraint: lambda self, 169 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 170 exp.RemoteWithConnectionModelProperty: lambda self, 171 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 172 exp.ReturnsProperty: lambda self, e: ( 173 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 174 ), 175 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 176 exp.SecureProperty: lambda *_: "SECURE", 177 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 178 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 179 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 180 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 181 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 182 exp.SqlReadWriteProperty: lambda _, e: e.name, 183 exp.SqlSecurityProperty: lambda _, 184 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 185 exp.StabilityProperty: lambda _, e: e.name, 186 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 187 exp.StreamingTableProperty: lambda *_: "STREAMING", 188 exp.StrictProperty: lambda *_: "STRICT", 189 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 190 exp.TemporaryProperty: lambda *_: "TEMPORARY", 191 exp.TagColumnConstraint: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 192 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 193 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 194 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 195 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 196 exp.TransientProperty: lambda *_: "TRANSIENT", 197 exp.Union: lambda self, e: self.set_operations(e), 198 exp.UnloggedProperty: lambda *_: "UNLOGGED", 199 exp.Uuid: lambda *_: "UUID()", 200 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 201 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 202 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 203 exp.VolatileProperty: lambda *_: "VOLATILE", 204 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 205 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 206 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 207 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 208 } 209 210 # Whether null ordering is supported in order by 211 # True: Full Support, None: No support, False: No support for certain cases 212 # such as window specifications, aggregate functions etc 213 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 214 215 # Whether ignore nulls is inside the agg or outside. 216 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 217 IGNORE_NULLS_IN_FUNC = False 218 219 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 220 LOCKING_READS_SUPPORTED = False 221 222 # Whether the EXCEPT and INTERSECT operations can return duplicates 223 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 224 225 # Wrap derived values in parens, usually standard but spark doesn't support it 226 WRAP_DERIVED_VALUES = True 227 228 # Whether create function uses an AS before the RETURN 229 CREATE_FUNCTION_RETURN_AS = True 230 231 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 232 MATCHED_BY_SOURCE = True 233 234 # Whether the INTERVAL expression works only with values like '1 day' 235 SINGLE_STRING_INTERVAL = False 236 237 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 238 INTERVAL_ALLOWS_PLURAL_FORM = True 239 240 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 241 LIMIT_FETCH = "ALL" 242 243 # Whether limit and fetch allows expresions or just limits 244 LIMIT_ONLY_LITERALS = False 245 246 # Whether a table is allowed to be renamed with a db 247 RENAME_TABLE_WITH_DB = True 248 249 # The separator for grouping sets and rollups 250 GROUPINGS_SEP = "," 251 252 # The string used for creating an index on a table 253 INDEX_ON = "ON" 254 255 # Whether join hints should be generated 256 JOIN_HINTS = True 257 258 # Whether table hints should be generated 259 TABLE_HINTS = True 260 261 # Whether query hints should be generated 262 QUERY_HINTS = True 263 264 # What kind of separator to use for query hints 265 QUERY_HINT_SEP = ", " 266 267 # Whether comparing against booleans (e.g. x IS TRUE) is supported 268 IS_BOOL_ALLOWED = True 269 270 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 271 DUPLICATE_KEY_UPDATE_WITH_SET = True 272 273 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 274 LIMIT_IS_TOP = False 275 276 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 277 RETURNING_END = True 278 279 # Whether to generate an unquoted value for EXTRACT's date part argument 280 EXTRACT_ALLOWS_QUOTES = True 281 282 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 283 TZ_TO_WITH_TIME_ZONE = False 284 285 # Whether the NVL2 function is supported 286 NVL2_SUPPORTED = True 287 288 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 289 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 290 291 # Whether VALUES statements can be used as derived tables. 292 # MySQL 5 and Redshift do not allow this, so when False, it will convert 293 # SELECT * VALUES into SELECT UNION 294 VALUES_AS_TABLE = True 295 296 # Whether the word COLUMN is included when adding a column with ALTER TABLE 297 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 298 299 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 300 UNNEST_WITH_ORDINALITY = True 301 302 # Whether FILTER (WHERE cond) can be used for conditional aggregation 303 AGGREGATE_FILTER_SUPPORTED = True 304 305 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 306 SEMI_ANTI_JOIN_WITH_SIDE = True 307 308 # Whether to include the type of a computed column in the CREATE DDL 309 COMPUTED_COLUMN_WITH_TYPE = True 310 311 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 312 SUPPORTS_TABLE_COPY = True 313 314 # Whether parentheses are required around the table sample's expression 315 TABLESAMPLE_REQUIRES_PARENS = True 316 317 # Whether a table sample clause's size needs to be followed by the ROWS keyword 318 TABLESAMPLE_SIZE_IS_ROWS = True 319 320 # The keyword(s) to use when generating a sample clause 321 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 322 323 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 324 TABLESAMPLE_WITH_METHOD = True 325 326 # The keyword to use when specifying the seed of a sample clause 327 TABLESAMPLE_SEED_KEYWORD = "SEED" 328 329 # Whether COLLATE is a function instead of a binary operator 330 COLLATE_IS_FUNC = False 331 332 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 333 DATA_TYPE_SPECIFIERS_ALLOWED = False 334 335 # Whether conditions require booleans WHERE x = 0 vs WHERE x 336 ENSURE_BOOLS = False 337 338 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 339 CTE_RECURSIVE_KEYWORD_REQUIRED = True 340 341 # Whether CONCAT requires >1 arguments 342 SUPPORTS_SINGLE_ARG_CONCAT = True 343 344 # Whether LAST_DAY function supports a date part argument 345 LAST_DAY_SUPPORTS_DATE_PART = True 346 347 # Whether named columns are allowed in table aliases 348 SUPPORTS_TABLE_ALIAS_COLUMNS = True 349 350 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 351 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 352 353 # What delimiter to use for separating JSON key/value pairs 354 JSON_KEY_VALUE_PAIR_SEP = ":" 355 356 # INSERT OVERWRITE TABLE x override 357 INSERT_OVERWRITE = " OVERWRITE TABLE" 358 359 # Whether the SELECT .. INTO syntax is used instead of CTAS 360 SUPPORTS_SELECT_INTO = False 361 362 # Whether UNLOGGED tables can be created 363 SUPPORTS_UNLOGGED_TABLES = False 364 365 # Whether the CREATE TABLE LIKE statement is supported 366 SUPPORTS_CREATE_TABLE_LIKE = True 367 368 # Whether the LikeProperty needs to be specified inside of the schema clause 369 LIKE_PROPERTY_INSIDE_SCHEMA = False 370 371 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 372 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 373 MULTI_ARG_DISTINCT = True 374 375 # Whether the JSON extraction operators expect a value of type JSON 376 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 377 378 # Whether bracketed keys like ["foo"] are supported in JSON paths 379 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 380 381 # Whether to escape keys using single quotes in JSON paths 382 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 383 384 # The JSONPathPart expressions supported by this dialect 385 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 386 387 # Whether any(f(x) for x in array) can be implemented by this dialect 388 CAN_IMPLEMENT_ARRAY_ANY = False 389 390 # Whether the function TO_NUMBER is supported 391 SUPPORTS_TO_NUMBER = True 392 393 # Whether or not set op modifiers apply to the outer set op or select. 394 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 395 # True means limit 1 happens after the set op, False means it it happens on y. 396 SET_OP_MODIFIERS = True 397 398 # Whether parameters from COPY statement are wrapped in parentheses 399 COPY_PARAMS_ARE_WRAPPED = True 400 401 # Whether values of params are set with "=" token or empty space 402 COPY_PARAMS_EQ_REQUIRED = False 403 404 # Whether COPY statement has INTO keyword 405 COPY_HAS_INTO_KEYWORD = True 406 407 # Whether the conditional TRY(expression) function is supported 408 TRY_SUPPORTED = True 409 410 # Whether the UESCAPE syntax in unicode strings is supported 411 SUPPORTS_UESCAPE = True 412 413 # The keyword to use when generating a star projection with excluded columns 414 STAR_EXCEPT = "EXCEPT" 415 416 # The HEX function name 417 HEX_FUNC = "HEX" 418 419 # The keywords to use when prefixing & separating WITH based properties 420 WITH_PROPERTIES_PREFIX = "WITH" 421 422 # Whether to quote the generated expression of exp.JsonPath 423 QUOTE_JSON_PATH = True 424 425 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 426 PAD_FILL_PATTERN_IS_REQUIRED = False 427 428 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 429 SUPPORTS_EXPLODING_PROJECTIONS = True 430 431 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 432 ARRAY_CONCAT_IS_VAR_LEN = True 433 434 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 435 SUPPORTS_CONVERT_TIMEZONE = False 436 437 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 438 SUPPORTS_MEDIAN = True 439 440 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 441 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 442 443 TYPE_MAPPING = { 444 exp.DataType.Type.NCHAR: "CHAR", 445 exp.DataType.Type.NVARCHAR: "VARCHAR", 446 exp.DataType.Type.MEDIUMTEXT: "TEXT", 447 exp.DataType.Type.LONGTEXT: "TEXT", 448 exp.DataType.Type.TINYTEXT: "TEXT", 449 exp.DataType.Type.MEDIUMBLOB: "BLOB", 450 exp.DataType.Type.LONGBLOB: "BLOB", 451 exp.DataType.Type.TINYBLOB: "BLOB", 452 exp.DataType.Type.INET: "INET", 453 exp.DataType.Type.ROWVERSION: "VARBINARY", 454 } 455 456 TIME_PART_SINGULARS = { 457 "MICROSECONDS": "MICROSECOND", 458 "SECONDS": "SECOND", 459 "MINUTES": "MINUTE", 460 "HOURS": "HOUR", 461 "DAYS": "DAY", 462 "WEEKS": "WEEK", 463 "MONTHS": "MONTH", 464 "QUARTERS": "QUARTER", 465 "YEARS": "YEAR", 466 } 467 468 AFTER_HAVING_MODIFIER_TRANSFORMS = { 469 "cluster": lambda self, e: self.sql(e, "cluster"), 470 "distribute": lambda self, e: self.sql(e, "distribute"), 471 "sort": lambda self, e: self.sql(e, "sort"), 472 "windows": lambda self, e: ( 473 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 474 if e.args.get("windows") 475 else "" 476 ), 477 "qualify": lambda self, e: self.sql(e, "qualify"), 478 } 479 480 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 481 482 STRUCT_DELIMITER = ("<", ">") 483 484 PARAMETER_TOKEN = "@" 485 NAMED_PLACEHOLDER_TOKEN = ":" 486 487 PROPERTIES_LOCATION = { 488 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 489 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 490 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 491 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 492 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 493 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 494 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 495 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 496 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 497 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 498 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 499 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 500 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 501 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 502 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 503 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 504 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 505 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 506 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 507 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 508 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 509 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 510 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 511 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 512 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 513 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 514 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 515 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 516 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 517 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 518 exp.HeapProperty: exp.Properties.Location.POST_WITH, 519 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 520 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 521 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 522 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 523 exp.JournalProperty: exp.Properties.Location.POST_NAME, 524 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 525 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 526 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 527 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 528 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 529 exp.LogProperty: exp.Properties.Location.POST_NAME, 530 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 531 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 532 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 533 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 534 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 535 exp.Order: exp.Properties.Location.POST_SCHEMA, 536 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 537 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 538 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 539 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 540 exp.Property: exp.Properties.Location.POST_WITH, 541 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 542 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 543 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 544 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 545 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 546 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 547 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 548 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 549 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 550 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 551 exp.Set: exp.Properties.Location.POST_SCHEMA, 552 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 553 exp.SetProperty: exp.Properties.Location.POST_CREATE, 554 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 555 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 556 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 557 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 558 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 559 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 560 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 561 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 562 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 563 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 564 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 566 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 568 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 569 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 571 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 572 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 573 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 574 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 575 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 576 } 577 578 # Keywords that can't be used as unquoted identifier names 579 RESERVED_KEYWORDS: t.Set[str] = set() 580 581 # Expressions whose comments are separated from them for better formatting 582 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 583 exp.Command, 584 exp.Create, 585 exp.Delete, 586 exp.Drop, 587 exp.From, 588 exp.Insert, 589 exp.Join, 590 exp.MultitableInserts, 591 exp.Select, 592 exp.SetOperation, 593 exp.Update, 594 exp.Where, 595 exp.With, 596 ) 597 598 # Expressions that should not have their comments generated in maybe_comment 599 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 600 exp.Binary, 601 exp.SetOperation, 602 ) 603 604 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 605 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 606 exp.Column, 607 exp.Literal, 608 exp.Neg, 609 exp.Paren, 610 ) 611 612 PARAMETERIZABLE_TEXT_TYPES = { 613 exp.DataType.Type.NVARCHAR, 614 exp.DataType.Type.VARCHAR, 615 exp.DataType.Type.CHAR, 616 exp.DataType.Type.NCHAR, 617 } 618 619 # Expressions that need to have all CTEs under them bubbled up to them 620 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 621 622 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 623 624 __slots__ = ( 625 "pretty", 626 "identify", 627 "normalize", 628 "pad", 629 "_indent", 630 "normalize_functions", 631 "unsupported_level", 632 "max_unsupported", 633 "leading_comma", 634 "max_text_width", 635 "comments", 636 "dialect", 637 "unsupported_messages", 638 "_escaped_quote_end", 639 "_escaped_identifier_end", 640 "_next_name", 641 "_identifier_start", 642 "_identifier_end", 643 ) 644 645 def __init__( 646 self, 647 pretty: t.Optional[bool] = None, 648 identify: str | bool = False, 649 normalize: bool = False, 650 pad: int = 2, 651 indent: int = 2, 652 normalize_functions: t.Optional[str | bool] = None, 653 unsupported_level: ErrorLevel = ErrorLevel.WARN, 654 max_unsupported: int = 3, 655 leading_comma: bool = False, 656 max_text_width: int = 80, 657 comments: bool = True, 658 dialect: DialectType = None, 659 ): 660 import sqlglot 661 from sqlglot.dialects import Dialect 662 663 self.pretty = pretty if pretty is not None else sqlglot.pretty 664 self.identify = identify 665 self.normalize = normalize 666 self.pad = pad 667 self._indent = indent 668 self.unsupported_level = unsupported_level 669 self.max_unsupported = max_unsupported 670 self.leading_comma = leading_comma 671 self.max_text_width = max_text_width 672 self.comments = comments 673 self.dialect = Dialect.get_or_raise(dialect) 674 675 # This is both a Dialect property and a Generator argument, so we prioritize the latter 676 self.normalize_functions = ( 677 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 678 ) 679 680 self.unsupported_messages: t.List[str] = [] 681 self._escaped_quote_end: str = ( 682 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 683 ) 684 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 685 686 self._next_name = name_sequence("_t") 687 688 self._identifier_start = self.dialect.IDENTIFIER_START 689 self._identifier_end = self.dialect.IDENTIFIER_END 690 691 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 692 """ 693 Generates the SQL string corresponding to the given syntax tree. 694 695 Args: 696 expression: The syntax tree. 697 copy: Whether to copy the expression. The generator performs mutations so 698 it is safer to copy. 699 700 Returns: 701 The SQL string corresponding to `expression`. 702 """ 703 if copy: 704 expression = expression.copy() 705 706 expression = self.preprocess(expression) 707 708 self.unsupported_messages = [] 709 sql = self.sql(expression).strip() 710 711 if self.pretty: 712 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 713 714 if self.unsupported_level == ErrorLevel.IGNORE: 715 return sql 716 717 if self.unsupported_level == ErrorLevel.WARN: 718 for msg in self.unsupported_messages: 719 logger.warning(msg) 720 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 721 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 722 723 return sql 724 725 def preprocess(self, expression: exp.Expression) -> exp.Expression: 726 """Apply generic preprocessing transformations to a given expression.""" 727 expression = self._move_ctes_to_top_level(expression) 728 729 if self.ENSURE_BOOLS: 730 from sqlglot.transforms import ensure_bools 731 732 expression = ensure_bools(expression) 733 734 return expression 735 736 def _move_ctes_to_top_level(self, expression: E) -> E: 737 if ( 738 not expression.parent 739 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 740 and any(node.parent is not expression for node in expression.find_all(exp.With)) 741 ): 742 from sqlglot.transforms import move_ctes_to_top_level 743 744 expression = move_ctes_to_top_level(expression) 745 return expression 746 747 def unsupported(self, message: str) -> None: 748 if self.unsupported_level == ErrorLevel.IMMEDIATE: 749 raise UnsupportedError(message) 750 self.unsupported_messages.append(message) 751 752 def sep(self, sep: str = " ") -> str: 753 return f"{sep.strip()}\n" if self.pretty else sep 754 755 def seg(self, sql: str, sep: str = " ") -> str: 756 return f"{self.sep(sep)}{sql}" 757 758 def pad_comment(self, comment: str) -> str: 759 comment = " " + comment if comment[0].strip() else comment 760 comment = comment + " " if comment[-1].strip() else comment 761 return comment 762 763 def maybe_comment( 764 self, 765 sql: str, 766 expression: t.Optional[exp.Expression] = None, 767 comments: t.Optional[t.List[str]] = None, 768 separated: bool = False, 769 ) -> str: 770 comments = ( 771 ((expression and expression.comments) if comments is None else comments) # type: ignore 772 if self.comments 773 else None 774 ) 775 776 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 777 return sql 778 779 comments_sql = " ".join( 780 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 781 ) 782 783 if not comments_sql: 784 return sql 785 786 comments_sql = self._replace_line_breaks(comments_sql) 787 788 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 789 return ( 790 f"{self.sep()}{comments_sql}{sql}" 791 if not sql or sql[0].isspace() 792 else f"{comments_sql}{self.sep()}{sql}" 793 ) 794 795 return f"{sql} {comments_sql}" 796 797 def wrap(self, expression: exp.Expression | str) -> str: 798 this_sql = ( 799 self.sql(expression) 800 if isinstance(expression, exp.UNWRAPPED_QUERIES) 801 else self.sql(expression, "this") 802 ) 803 if not this_sql: 804 return "()" 805 806 this_sql = self.indent(this_sql, level=1, pad=0) 807 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 808 809 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 810 original = self.identify 811 self.identify = False 812 result = func(*args, **kwargs) 813 self.identify = original 814 return result 815 816 def normalize_func(self, name: str) -> str: 817 if self.normalize_functions == "upper" or self.normalize_functions is True: 818 return name.upper() 819 if self.normalize_functions == "lower": 820 return name.lower() 821 return name 822 823 def indent( 824 self, 825 sql: str, 826 level: int = 0, 827 pad: t.Optional[int] = None, 828 skip_first: bool = False, 829 skip_last: bool = False, 830 ) -> str: 831 if not self.pretty or not sql: 832 return sql 833 834 pad = self.pad if pad is None else pad 835 lines = sql.split("\n") 836 837 return "\n".join( 838 ( 839 line 840 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 841 else f"{' ' * (level * self._indent + pad)}{line}" 842 ) 843 for i, line in enumerate(lines) 844 ) 845 846 def sql( 847 self, 848 expression: t.Optional[str | exp.Expression], 849 key: t.Optional[str] = None, 850 comment: bool = True, 851 ) -> str: 852 if not expression: 853 return "" 854 855 if isinstance(expression, str): 856 return expression 857 858 if key: 859 value = expression.args.get(key) 860 if value: 861 return self.sql(value) 862 return "" 863 864 transform = self.TRANSFORMS.get(expression.__class__) 865 866 if callable(transform): 867 sql = transform(self, expression) 868 elif isinstance(expression, exp.Expression): 869 exp_handler_name = f"{expression.key}_sql" 870 871 if hasattr(self, exp_handler_name): 872 sql = getattr(self, exp_handler_name)(expression) 873 elif isinstance(expression, exp.Func): 874 sql = self.function_fallback_sql(expression) 875 elif isinstance(expression, exp.Property): 876 sql = self.property_sql(expression) 877 else: 878 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 879 else: 880 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 881 882 return self.maybe_comment(sql, expression) if self.comments and comment else sql 883 884 def uncache_sql(self, expression: exp.Uncache) -> str: 885 table = self.sql(expression, "this") 886 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 887 return f"UNCACHE TABLE{exists_sql} {table}" 888 889 def cache_sql(self, expression: exp.Cache) -> str: 890 lazy = " LAZY" if expression.args.get("lazy") else "" 891 table = self.sql(expression, "this") 892 options = expression.args.get("options") 893 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 894 sql = self.sql(expression, "expression") 895 sql = f" AS{self.sep()}{sql}" if sql else "" 896 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 897 return self.prepend_ctes(expression, sql) 898 899 def characterset_sql(self, expression: exp.CharacterSet) -> str: 900 if isinstance(expression.parent, exp.Cast): 901 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 902 default = "DEFAULT " if expression.args.get("default") else "" 903 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 904 905 def column_parts(self, expression: exp.Column) -> str: 906 return ".".join( 907 self.sql(part) 908 for part in ( 909 expression.args.get("catalog"), 910 expression.args.get("db"), 911 expression.args.get("table"), 912 expression.args.get("this"), 913 ) 914 if part 915 ) 916 917 def column_sql(self, expression: exp.Column) -> str: 918 join_mark = " (+)" if expression.args.get("join_mark") else "" 919 920 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 921 join_mark = "" 922 self.unsupported("Outer join syntax using the (+) operator is not supported.") 923 924 return f"{self.column_parts(expression)}{join_mark}" 925 926 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 927 this = self.sql(expression, "this") 928 this = f" {this}" if this else "" 929 position = self.sql(expression, "position") 930 return f"{position}{this}" 931 932 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 933 column = self.sql(expression, "this") 934 kind = self.sql(expression, "kind") 935 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 936 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 937 kind = f"{sep}{kind}" if kind else "" 938 constraints = f" {constraints}" if constraints else "" 939 position = self.sql(expression, "position") 940 position = f" {position}" if position else "" 941 942 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 943 kind = "" 944 945 return f"{exists}{column}{kind}{constraints}{position}" 946 947 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 948 this = self.sql(expression, "this") 949 kind_sql = self.sql(expression, "kind").strip() 950 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 951 952 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 953 this = self.sql(expression, "this") 954 if expression.args.get("not_null"): 955 persisted = " PERSISTED NOT NULL" 956 elif expression.args.get("persisted"): 957 persisted = " PERSISTED" 958 else: 959 persisted = "" 960 return f"AS {this}{persisted}" 961 962 def autoincrementcolumnconstraint_sql(self, _) -> str: 963 return self.token_sql(TokenType.AUTO_INCREMENT) 964 965 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 966 if isinstance(expression.this, list): 967 this = self.wrap(self.expressions(expression, key="this", flat=True)) 968 else: 969 this = self.sql(expression, "this") 970 971 return f"COMPRESS {this}" 972 973 def generatedasidentitycolumnconstraint_sql( 974 self, expression: exp.GeneratedAsIdentityColumnConstraint 975 ) -> str: 976 this = "" 977 if expression.this is not None: 978 on_null = " ON NULL" if expression.args.get("on_null") else "" 979 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 980 981 start = expression.args.get("start") 982 start = f"START WITH {start}" if start else "" 983 increment = expression.args.get("increment") 984 increment = f" INCREMENT BY {increment}" if increment else "" 985 minvalue = expression.args.get("minvalue") 986 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 987 maxvalue = expression.args.get("maxvalue") 988 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 989 cycle = expression.args.get("cycle") 990 cycle_sql = "" 991 992 if cycle is not None: 993 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 994 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 995 996 sequence_opts = "" 997 if start or increment or cycle_sql: 998 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 999 sequence_opts = f" ({sequence_opts.strip()})" 1000 1001 expr = self.sql(expression, "expression") 1002 expr = f"({expr})" if expr else "IDENTITY" 1003 1004 return f"GENERATED{this} AS {expr}{sequence_opts}" 1005 1006 def generatedasrowcolumnconstraint_sql( 1007 self, expression: exp.GeneratedAsRowColumnConstraint 1008 ) -> str: 1009 start = "START" if expression.args.get("start") else "END" 1010 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1011 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1012 1013 def periodforsystemtimeconstraint_sql( 1014 self, expression: exp.PeriodForSystemTimeConstraint 1015 ) -> str: 1016 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1017 1018 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1019 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1020 1021 def transformcolumnconstraint_sql(self, expression: exp.TransformColumnConstraint) -> str: 1022 return f"AS {self.sql(expression, 'this')}" 1023 1024 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1025 desc = expression.args.get("desc") 1026 if desc is not None: 1027 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1028 return "PRIMARY KEY" 1029 1030 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1031 this = self.sql(expression, "this") 1032 this = f" {this}" if this else "" 1033 index_type = expression.args.get("index_type") 1034 index_type = f" USING {index_type}" if index_type else "" 1035 on_conflict = self.sql(expression, "on_conflict") 1036 on_conflict = f" {on_conflict}" if on_conflict else "" 1037 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1038 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}" 1039 1040 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1041 return self.sql(expression, "this") 1042 1043 def create_sql(self, expression: exp.Create) -> str: 1044 kind = self.sql(expression, "kind") 1045 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1046 properties = expression.args.get("properties") 1047 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1048 1049 this = self.createable_sql(expression, properties_locs) 1050 1051 properties_sql = "" 1052 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1053 exp.Properties.Location.POST_WITH 1054 ): 1055 properties_sql = self.sql( 1056 exp.Properties( 1057 expressions=[ 1058 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1059 *properties_locs[exp.Properties.Location.POST_WITH], 1060 ] 1061 ) 1062 ) 1063 1064 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1065 properties_sql = self.sep() + properties_sql 1066 elif not self.pretty: 1067 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1068 properties_sql = f" {properties_sql}" 1069 1070 begin = " BEGIN" if expression.args.get("begin") else "" 1071 end = " END" if expression.args.get("end") else "" 1072 1073 expression_sql = self.sql(expression, "expression") 1074 if expression_sql: 1075 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1076 1077 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1078 postalias_props_sql = "" 1079 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1080 postalias_props_sql = self.properties( 1081 exp.Properties( 1082 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1083 ), 1084 wrapped=False, 1085 ) 1086 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1087 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1088 1089 postindex_props_sql = "" 1090 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1091 postindex_props_sql = self.properties( 1092 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1093 wrapped=False, 1094 prefix=" ", 1095 ) 1096 1097 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1098 indexes = f" {indexes}" if indexes else "" 1099 index_sql = indexes + postindex_props_sql 1100 1101 replace = " OR REPLACE" if expression.args.get("replace") else "" 1102 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1103 unique = " UNIQUE" if expression.args.get("unique") else "" 1104 1105 clustered = expression.args.get("clustered") 1106 if clustered is None: 1107 clustered_sql = "" 1108 elif clustered: 1109 clustered_sql = " CLUSTERED COLUMNSTORE" 1110 else: 1111 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1112 1113 postcreate_props_sql = "" 1114 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1115 postcreate_props_sql = self.properties( 1116 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1117 sep=" ", 1118 prefix=" ", 1119 wrapped=False, 1120 ) 1121 1122 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1123 1124 postexpression_props_sql = "" 1125 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1126 postexpression_props_sql = self.properties( 1127 exp.Properties( 1128 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1129 ), 1130 sep=" ", 1131 prefix=" ", 1132 wrapped=False, 1133 ) 1134 1135 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1136 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1137 no_schema_binding = ( 1138 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1139 ) 1140 1141 clone = self.sql(expression, "clone") 1142 clone = f" {clone}" if clone else "" 1143 1144 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1145 return self.prepend_ctes(expression, expression_sql) 1146 1147 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1148 start = self.sql(expression, "start") 1149 start = f"START WITH {start}" if start else "" 1150 increment = self.sql(expression, "increment") 1151 increment = f" INCREMENT BY {increment}" if increment else "" 1152 minvalue = self.sql(expression, "minvalue") 1153 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1154 maxvalue = self.sql(expression, "maxvalue") 1155 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1156 owned = self.sql(expression, "owned") 1157 owned = f" OWNED BY {owned}" if owned else "" 1158 1159 cache = expression.args.get("cache") 1160 if cache is None: 1161 cache_str = "" 1162 elif cache is True: 1163 cache_str = " CACHE" 1164 else: 1165 cache_str = f" CACHE {cache}" 1166 1167 options = self.expressions(expression, key="options", flat=True, sep=" ") 1168 options = f" {options}" if options else "" 1169 1170 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1171 1172 def clone_sql(self, expression: exp.Clone) -> str: 1173 this = self.sql(expression, "this") 1174 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1175 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1176 return f"{shallow}{keyword} {this}" 1177 1178 def describe_sql(self, expression: exp.Describe) -> str: 1179 style = expression.args.get("style") 1180 style = f" {style}" if style else "" 1181 partition = self.sql(expression, "partition") 1182 partition = f" {partition}" if partition else "" 1183 return f"DESCRIBE{style} {self.sql(expression, 'this')}{partition}" 1184 1185 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1186 tag = self.sql(expression, "tag") 1187 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1188 1189 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1190 with_ = self.sql(expression, "with") 1191 if with_: 1192 sql = f"{with_}{self.sep()}{sql}" 1193 return sql 1194 1195 def with_sql(self, expression: exp.With) -> str: 1196 sql = self.expressions(expression, flat=True) 1197 recursive = ( 1198 "RECURSIVE " 1199 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1200 else "" 1201 ) 1202 1203 return f"WITH {recursive}{sql}" 1204 1205 def cte_sql(self, expression: exp.CTE) -> str: 1206 alias = expression.args.get("alias") 1207 if alias: 1208 alias.add_comments(expression.pop_comments()) 1209 1210 alias_sql = self.sql(expression, "alias") 1211 1212 materialized = expression.args.get("materialized") 1213 if materialized is False: 1214 materialized = "NOT MATERIALIZED " 1215 elif materialized: 1216 materialized = "MATERIALIZED " 1217 1218 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1219 1220 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1221 alias = self.sql(expression, "this") 1222 columns = self.expressions(expression, key="columns", flat=True) 1223 columns = f"({columns})" if columns else "" 1224 1225 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1226 columns = "" 1227 self.unsupported("Named columns are not supported in table alias.") 1228 1229 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1230 alias = self._next_name() 1231 1232 return f"{alias}{columns}" 1233 1234 def bitstring_sql(self, expression: exp.BitString) -> str: 1235 this = self.sql(expression, "this") 1236 if self.dialect.BIT_START: 1237 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1238 return f"{int(this, 2)}" 1239 1240 def hexstring_sql(self, expression: exp.HexString) -> str: 1241 this = self.sql(expression, "this") 1242 if self.dialect.HEX_START: 1243 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1244 return f"{int(this, 16)}" 1245 1246 def bytestring_sql(self, expression: exp.ByteString) -> str: 1247 this = self.sql(expression, "this") 1248 if self.dialect.BYTE_START: 1249 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1250 return this 1251 1252 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1253 this = self.sql(expression, "this") 1254 escape = expression.args.get("escape") 1255 1256 if self.dialect.UNICODE_START: 1257 escape_substitute = r"\\\1" 1258 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1259 else: 1260 escape_substitute = r"\\u\1" 1261 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1262 1263 if escape: 1264 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1265 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1266 else: 1267 escape_pattern = ESCAPED_UNICODE_RE 1268 escape_sql = "" 1269 1270 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1271 this = escape_pattern.sub(escape_substitute, this) 1272 1273 return f"{left_quote}{this}{right_quote}{escape_sql}" 1274 1275 def rawstring_sql(self, expression: exp.RawString) -> str: 1276 string = self.escape_str(expression.this.replace("\\", "\\\\"), escape_backslash=False) 1277 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1278 1279 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1280 this = self.sql(expression, "this") 1281 specifier = self.sql(expression, "expression") 1282 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1283 return f"{this}{specifier}" 1284 1285 def datatype_sql(self, expression: exp.DataType) -> str: 1286 nested = "" 1287 values = "" 1288 interior = self.expressions(expression, flat=True) 1289 1290 type_value = expression.this 1291 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1292 type_sql = self.sql(expression, "kind") 1293 else: 1294 type_sql = ( 1295 self.TYPE_MAPPING.get(type_value, type_value.value) 1296 if isinstance(type_value, exp.DataType.Type) 1297 else type_value 1298 ) 1299 1300 if interior: 1301 if expression.args.get("nested"): 1302 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1303 if expression.args.get("values") is not None: 1304 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1305 values = self.expressions(expression, key="values", flat=True) 1306 values = f"{delimiters[0]}{values}{delimiters[1]}" 1307 elif type_value == exp.DataType.Type.INTERVAL: 1308 nested = f" {interior}" 1309 else: 1310 nested = f"({interior})" 1311 1312 type_sql = f"{type_sql}{nested}{values}" 1313 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1314 exp.DataType.Type.TIMETZ, 1315 exp.DataType.Type.TIMESTAMPTZ, 1316 ): 1317 type_sql = f"{type_sql} WITH TIME ZONE" 1318 1319 return type_sql 1320 1321 def directory_sql(self, expression: exp.Directory) -> str: 1322 local = "LOCAL " if expression.args.get("local") else "" 1323 row_format = self.sql(expression, "row_format") 1324 row_format = f" {row_format}" if row_format else "" 1325 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1326 1327 def delete_sql(self, expression: exp.Delete) -> str: 1328 this = self.sql(expression, "this") 1329 this = f" FROM {this}" if this else "" 1330 using = self.sql(expression, "using") 1331 using = f" USING {using}" if using else "" 1332 cluster = self.sql(expression, "cluster") 1333 cluster = f" {cluster}" if cluster else "" 1334 where = self.sql(expression, "where") 1335 returning = self.sql(expression, "returning") 1336 limit = self.sql(expression, "limit") 1337 tables = self.expressions(expression, key="tables") 1338 tables = f" {tables}" if tables else "" 1339 if self.RETURNING_END: 1340 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1341 else: 1342 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1343 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1344 1345 def drop_sql(self, expression: exp.Drop) -> str: 1346 this = self.sql(expression, "this") 1347 expressions = self.expressions(expression, flat=True) 1348 expressions = f" ({expressions})" if expressions else "" 1349 kind = expression.args["kind"] 1350 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1351 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1352 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1353 on_cluster = self.sql(expression, "cluster") 1354 on_cluster = f" {on_cluster}" if on_cluster else "" 1355 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1356 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1357 cascade = " CASCADE" if expression.args.get("cascade") else "" 1358 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1359 purge = " PURGE" if expression.args.get("purge") else "" 1360 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1361 1362 def set_operation(self, expression: exp.SetOperation) -> str: 1363 op_type = type(expression) 1364 op_name = op_type.key.upper() 1365 1366 distinct = expression.args.get("distinct") 1367 if ( 1368 distinct is False 1369 and op_type in (exp.Except, exp.Intersect) 1370 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1371 ): 1372 self.unsupported(f"{op_name} ALL is not supported") 1373 1374 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1375 1376 if distinct is None: 1377 distinct = default_distinct 1378 if distinct is None: 1379 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1380 1381 if distinct is default_distinct: 1382 kind = "" 1383 else: 1384 kind = " DISTINCT" if distinct else " ALL" 1385 1386 by_name = " BY NAME" if expression.args.get("by_name") else "" 1387 return f"{op_name}{kind}{by_name}" 1388 1389 def set_operations(self, expression: exp.SetOperation) -> str: 1390 if not self.SET_OP_MODIFIERS: 1391 limit = expression.args.get("limit") 1392 order = expression.args.get("order") 1393 1394 if limit or order: 1395 select = self._move_ctes_to_top_level( 1396 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1397 ) 1398 1399 if limit: 1400 select = select.limit(limit.pop(), copy=False) 1401 if order: 1402 select = select.order_by(order.pop(), copy=False) 1403 return self.sql(select) 1404 1405 sqls: t.List[str] = [] 1406 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1407 1408 while stack: 1409 node = stack.pop() 1410 1411 if isinstance(node, exp.SetOperation): 1412 stack.append(node.expression) 1413 stack.append( 1414 self.maybe_comment( 1415 self.set_operation(node), comments=node.comments, separated=True 1416 ) 1417 ) 1418 stack.append(node.this) 1419 else: 1420 sqls.append(self.sql(node)) 1421 1422 this = self.sep().join(sqls) 1423 this = self.query_modifiers(expression, this) 1424 return self.prepend_ctes(expression, this) 1425 1426 def fetch_sql(self, expression: exp.Fetch) -> str: 1427 direction = expression.args.get("direction") 1428 direction = f" {direction}" if direction else "" 1429 count = self.sql(expression, "count") 1430 count = f" {count}" if count else "" 1431 if expression.args.get("percent"): 1432 count = f"{count} PERCENT" 1433 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY" 1434 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}" 1435 1436 def filter_sql(self, expression: exp.Filter) -> str: 1437 if self.AGGREGATE_FILTER_SUPPORTED: 1438 this = self.sql(expression, "this") 1439 where = self.sql(expression, "expression").strip() 1440 return f"{this} FILTER({where})" 1441 1442 agg = expression.this 1443 agg_arg = agg.this 1444 cond = expression.expression.this 1445 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1446 return self.sql(agg) 1447 1448 def hint_sql(self, expression: exp.Hint) -> str: 1449 if not self.QUERY_HINTS: 1450 self.unsupported("Hints are not supported") 1451 return "" 1452 1453 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1454 1455 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1456 using = self.sql(expression, "using") 1457 using = f" USING {using}" if using else "" 1458 columns = self.expressions(expression, key="columns", flat=True) 1459 columns = f"({columns})" if columns else "" 1460 partition_by = self.expressions(expression, key="partition_by", flat=True) 1461 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1462 where = self.sql(expression, "where") 1463 include = self.expressions(expression, key="include", flat=True) 1464 if include: 1465 include = f" INCLUDE ({include})" 1466 with_storage = self.expressions(expression, key="with_storage", flat=True) 1467 with_storage = f" WITH ({with_storage})" if with_storage else "" 1468 tablespace = self.sql(expression, "tablespace") 1469 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1470 on = self.sql(expression, "on") 1471 on = f" ON {on}" if on else "" 1472 1473 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1474 1475 def index_sql(self, expression: exp.Index) -> str: 1476 unique = "UNIQUE " if expression.args.get("unique") else "" 1477 primary = "PRIMARY " if expression.args.get("primary") else "" 1478 amp = "AMP " if expression.args.get("amp") else "" 1479 name = self.sql(expression, "this") 1480 name = f"{name} " if name else "" 1481 table = self.sql(expression, "table") 1482 table = f"{self.INDEX_ON} {table}" if table else "" 1483 1484 index = "INDEX " if not table else "" 1485 1486 params = self.sql(expression, "params") 1487 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1488 1489 def identifier_sql(self, expression: exp.Identifier) -> str: 1490 text = expression.name 1491 lower = text.lower() 1492 text = lower if self.normalize and not expression.quoted else text 1493 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1494 if ( 1495 expression.quoted 1496 or self.dialect.can_identify(text, self.identify) 1497 or lower in self.RESERVED_KEYWORDS 1498 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1499 ): 1500 text = f"{self._identifier_start}{text}{self._identifier_end}" 1501 return text 1502 1503 def hex_sql(self, expression: exp.Hex) -> str: 1504 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1505 if self.dialect.HEX_LOWERCASE: 1506 text = self.func("LOWER", text) 1507 1508 return text 1509 1510 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1511 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1512 if not self.dialect.HEX_LOWERCASE: 1513 text = self.func("LOWER", text) 1514 return text 1515 1516 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1517 input_format = self.sql(expression, "input_format") 1518 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1519 output_format = self.sql(expression, "output_format") 1520 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1521 return self.sep().join((input_format, output_format)) 1522 1523 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1524 string = self.sql(exp.Literal.string(expression.name)) 1525 return f"{prefix}{string}" 1526 1527 def partition_sql(self, expression: exp.Partition) -> str: 1528 return f"PARTITION({self.expressions(expression, flat=True)})" 1529 1530 def properties_sql(self, expression: exp.Properties) -> str: 1531 root_properties = [] 1532 with_properties = [] 1533 1534 for p in expression.expressions: 1535 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1536 if p_loc == exp.Properties.Location.POST_WITH: 1537 with_properties.append(p) 1538 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1539 root_properties.append(p) 1540 1541 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1542 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1543 1544 if root_props and with_props and not self.pretty: 1545 with_props = " " + with_props 1546 1547 return root_props + with_props 1548 1549 def root_properties(self, properties: exp.Properties) -> str: 1550 if properties.expressions: 1551 return self.expressions(properties, indent=False, sep=" ") 1552 return "" 1553 1554 def properties( 1555 self, 1556 properties: exp.Properties, 1557 prefix: str = "", 1558 sep: str = ", ", 1559 suffix: str = "", 1560 wrapped: bool = True, 1561 ) -> str: 1562 if properties.expressions: 1563 expressions = self.expressions(properties, sep=sep, indent=False) 1564 if expressions: 1565 expressions = self.wrap(expressions) if wrapped else expressions 1566 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1567 return "" 1568 1569 def with_properties(self, properties: exp.Properties) -> str: 1570 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1571 1572 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1573 properties_locs = defaultdict(list) 1574 for p in properties.expressions: 1575 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1576 if p_loc != exp.Properties.Location.UNSUPPORTED: 1577 properties_locs[p_loc].append(p) 1578 else: 1579 self.unsupported(f"Unsupported property {p.key}") 1580 1581 return properties_locs 1582 1583 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1584 if isinstance(expression.this, exp.Dot): 1585 return self.sql(expression, "this") 1586 return f"'{expression.name}'" if string_key else expression.name 1587 1588 def property_sql(self, expression: exp.Property) -> str: 1589 property_cls = expression.__class__ 1590 if property_cls == exp.Property: 1591 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1592 1593 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1594 if not property_name: 1595 self.unsupported(f"Unsupported property {expression.key}") 1596 1597 return f"{property_name}={self.sql(expression, 'this')}" 1598 1599 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1600 if self.SUPPORTS_CREATE_TABLE_LIKE: 1601 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1602 options = f" {options}" if options else "" 1603 1604 like = f"LIKE {self.sql(expression, 'this')}{options}" 1605 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1606 like = f"({like})" 1607 1608 return like 1609 1610 if expression.expressions: 1611 self.unsupported("Transpilation of LIKE property options is unsupported") 1612 1613 select = exp.select("*").from_(expression.this).limit(0) 1614 return f"AS {self.sql(select)}" 1615 1616 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1617 no = "NO " if expression.args.get("no") else "" 1618 protection = " PROTECTION" if expression.args.get("protection") else "" 1619 return f"{no}FALLBACK{protection}" 1620 1621 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1622 no = "NO " if expression.args.get("no") else "" 1623 local = expression.args.get("local") 1624 local = f"{local} " if local else "" 1625 dual = "DUAL " if expression.args.get("dual") else "" 1626 before = "BEFORE " if expression.args.get("before") else "" 1627 after = "AFTER " if expression.args.get("after") else "" 1628 return f"{no}{local}{dual}{before}{after}JOURNAL" 1629 1630 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1631 freespace = self.sql(expression, "this") 1632 percent = " PERCENT" if expression.args.get("percent") else "" 1633 return f"FREESPACE={freespace}{percent}" 1634 1635 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1636 if expression.args.get("default"): 1637 property = "DEFAULT" 1638 elif expression.args.get("on"): 1639 property = "ON" 1640 else: 1641 property = "OFF" 1642 return f"CHECKSUM={property}" 1643 1644 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1645 if expression.args.get("no"): 1646 return "NO MERGEBLOCKRATIO" 1647 if expression.args.get("default"): 1648 return "DEFAULT MERGEBLOCKRATIO" 1649 1650 percent = " PERCENT" if expression.args.get("percent") else "" 1651 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1652 1653 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1654 default = expression.args.get("default") 1655 minimum = expression.args.get("minimum") 1656 maximum = expression.args.get("maximum") 1657 if default or minimum or maximum: 1658 if default: 1659 prop = "DEFAULT" 1660 elif minimum: 1661 prop = "MINIMUM" 1662 else: 1663 prop = "MAXIMUM" 1664 return f"{prop} DATABLOCKSIZE" 1665 units = expression.args.get("units") 1666 units = f" {units}" if units else "" 1667 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1668 1669 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1670 autotemp = expression.args.get("autotemp") 1671 always = expression.args.get("always") 1672 default = expression.args.get("default") 1673 manual = expression.args.get("manual") 1674 never = expression.args.get("never") 1675 1676 if autotemp is not None: 1677 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1678 elif always: 1679 prop = "ALWAYS" 1680 elif default: 1681 prop = "DEFAULT" 1682 elif manual: 1683 prop = "MANUAL" 1684 elif never: 1685 prop = "NEVER" 1686 return f"BLOCKCOMPRESSION={prop}" 1687 1688 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1689 no = expression.args.get("no") 1690 no = " NO" if no else "" 1691 concurrent = expression.args.get("concurrent") 1692 concurrent = " CONCURRENT" if concurrent else "" 1693 target = self.sql(expression, "target") 1694 target = f" {target}" if target else "" 1695 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1696 1697 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1698 if isinstance(expression.this, list): 1699 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1700 if expression.this: 1701 modulus = self.sql(expression, "this") 1702 remainder = self.sql(expression, "expression") 1703 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1704 1705 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1706 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1707 return f"FROM ({from_expressions}) TO ({to_expressions})" 1708 1709 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1710 this = self.sql(expression, "this") 1711 1712 for_values_or_default = expression.expression 1713 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1714 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1715 else: 1716 for_values_or_default = " DEFAULT" 1717 1718 return f"PARTITION OF {this}{for_values_or_default}" 1719 1720 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1721 kind = expression.args.get("kind") 1722 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1723 for_or_in = expression.args.get("for_or_in") 1724 for_or_in = f" {for_or_in}" if for_or_in else "" 1725 lock_type = expression.args.get("lock_type") 1726 override = " OVERRIDE" if expression.args.get("override") else "" 1727 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1728 1729 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1730 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1731 statistics = expression.args.get("statistics") 1732 statistics_sql = "" 1733 if statistics is not None: 1734 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1735 return f"{data_sql}{statistics_sql}" 1736 1737 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1738 this = self.sql(expression, "this") 1739 this = f"HISTORY_TABLE={this}" if this else "" 1740 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1741 data_consistency = ( 1742 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1743 ) 1744 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1745 retention_period = ( 1746 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1747 ) 1748 1749 if this: 1750 on_sql = self.func("ON", this, data_consistency, retention_period) 1751 else: 1752 on_sql = "ON" if expression.args.get("on") else "OFF" 1753 1754 sql = f"SYSTEM_VERSIONING={on_sql}" 1755 1756 return f"WITH({sql})" if expression.args.get("with") else sql 1757 1758 def insert_sql(self, expression: exp.Insert) -> str: 1759 hint = self.sql(expression, "hint") 1760 overwrite = expression.args.get("overwrite") 1761 1762 if isinstance(expression.this, exp.Directory): 1763 this = " OVERWRITE" if overwrite else " INTO" 1764 else: 1765 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1766 1767 stored = self.sql(expression, "stored") 1768 stored = f" {stored}" if stored else "" 1769 alternative = expression.args.get("alternative") 1770 alternative = f" OR {alternative}" if alternative else "" 1771 ignore = " IGNORE" if expression.args.get("ignore") else "" 1772 is_function = expression.args.get("is_function") 1773 if is_function: 1774 this = f"{this} FUNCTION" 1775 this = f"{this} {self.sql(expression, 'this')}" 1776 1777 exists = " IF EXISTS" if expression.args.get("exists") else "" 1778 where = self.sql(expression, "where") 1779 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1780 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1781 on_conflict = self.sql(expression, "conflict") 1782 on_conflict = f" {on_conflict}" if on_conflict else "" 1783 by_name = " BY NAME" if expression.args.get("by_name") else "" 1784 returning = self.sql(expression, "returning") 1785 1786 if self.RETURNING_END: 1787 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1788 else: 1789 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1790 1791 partition_by = self.sql(expression, "partition") 1792 partition_by = f" {partition_by}" if partition_by else "" 1793 settings = self.sql(expression, "settings") 1794 settings = f" {settings}" if settings else "" 1795 1796 source = self.sql(expression, "source") 1797 source = f"TABLE {source}" if source else "" 1798 1799 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1800 return self.prepend_ctes(expression, sql) 1801 1802 def introducer_sql(self, expression: exp.Introducer) -> str: 1803 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1804 1805 def kill_sql(self, expression: exp.Kill) -> str: 1806 kind = self.sql(expression, "kind") 1807 kind = f" {kind}" if kind else "" 1808 this = self.sql(expression, "this") 1809 this = f" {this}" if this else "" 1810 return f"KILL{kind}{this}" 1811 1812 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1813 return expression.name 1814 1815 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1816 return expression.name 1817 1818 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1819 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1820 1821 constraint = self.sql(expression, "constraint") 1822 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1823 1824 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1825 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1826 action = self.sql(expression, "action") 1827 1828 expressions = self.expressions(expression, flat=True) 1829 if expressions: 1830 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1831 expressions = f" {set_keyword}{expressions}" 1832 1833 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}" 1834 1835 def returning_sql(self, expression: exp.Returning) -> str: 1836 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1837 1838 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1839 fields = self.sql(expression, "fields") 1840 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1841 escaped = self.sql(expression, "escaped") 1842 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1843 items = self.sql(expression, "collection_items") 1844 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1845 keys = self.sql(expression, "map_keys") 1846 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1847 lines = self.sql(expression, "lines") 1848 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1849 null = self.sql(expression, "null") 1850 null = f" NULL DEFINED AS {null}" if null else "" 1851 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1852 1853 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1854 return f"WITH ({self.expressions(expression, flat=True)})" 1855 1856 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1857 this = f"{self.sql(expression, 'this')} INDEX" 1858 target = self.sql(expression, "target") 1859 target = f" FOR {target}" if target else "" 1860 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1861 1862 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1863 this = self.sql(expression, "this") 1864 kind = self.sql(expression, "kind") 1865 expr = self.sql(expression, "expression") 1866 return f"{this} ({kind} => {expr})" 1867 1868 def table_parts(self, expression: exp.Table) -> str: 1869 return ".".join( 1870 self.sql(part) 1871 for part in ( 1872 expression.args.get("catalog"), 1873 expression.args.get("db"), 1874 expression.args.get("this"), 1875 ) 1876 if part is not None 1877 ) 1878 1879 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1880 table = self.table_parts(expression) 1881 only = "ONLY " if expression.args.get("only") else "" 1882 partition = self.sql(expression, "partition") 1883 partition = f" {partition}" if partition else "" 1884 version = self.sql(expression, "version") 1885 version = f" {version}" if version else "" 1886 alias = self.sql(expression, "alias") 1887 alias = f"{sep}{alias}" if alias else "" 1888 1889 sample = self.sql(expression, "sample") 1890 if self.dialect.ALIAS_POST_TABLESAMPLE: 1891 sample_pre_alias = sample 1892 sample_post_alias = "" 1893 else: 1894 sample_pre_alias = "" 1895 sample_post_alias = sample 1896 1897 hints = self.expressions(expression, key="hints", sep=" ") 1898 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1899 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 1900 joins = self.indent( 1901 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 1902 ) 1903 laterals = self.expressions(expression, key="laterals", sep="") 1904 1905 file_format = self.sql(expression, "format") 1906 if file_format: 1907 pattern = self.sql(expression, "pattern") 1908 pattern = f", PATTERN => {pattern}" if pattern else "" 1909 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 1910 1911 ordinality = expression.args.get("ordinality") or "" 1912 if ordinality: 1913 ordinality = f" WITH ORDINALITY{alias}" 1914 alias = "" 1915 1916 when = self.sql(expression, "when") 1917 if when: 1918 table = f"{table} {when}" 1919 1920 changes = self.sql(expression, "changes") 1921 changes = f" {changes}" if changes else "" 1922 1923 rows_from = self.expressions(expression, key="rows_from") 1924 if rows_from: 1925 table = f"ROWS FROM {self.wrap(rows_from)}" 1926 1927 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 1928 1929 def tablesample_sql( 1930 self, 1931 expression: exp.TableSample, 1932 tablesample_keyword: t.Optional[str] = None, 1933 ) -> str: 1934 method = self.sql(expression, "method") 1935 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 1936 numerator = self.sql(expression, "bucket_numerator") 1937 denominator = self.sql(expression, "bucket_denominator") 1938 field = self.sql(expression, "bucket_field") 1939 field = f" ON {field}" if field else "" 1940 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 1941 seed = self.sql(expression, "seed") 1942 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 1943 1944 size = self.sql(expression, "size") 1945 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 1946 size = f"{size} ROWS" 1947 1948 percent = self.sql(expression, "percent") 1949 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 1950 percent = f"{percent} PERCENT" 1951 1952 expr = f"{bucket}{percent}{size}" 1953 if self.TABLESAMPLE_REQUIRES_PARENS: 1954 expr = f"({expr})" 1955 1956 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 1957 1958 def pivot_sql(self, expression: exp.Pivot) -> str: 1959 expressions = self.expressions(expression, flat=True) 1960 1961 if expression.this: 1962 this = self.sql(expression, "this") 1963 if not expressions: 1964 return f"UNPIVOT {this}" 1965 1966 on = f"{self.seg('ON')} {expressions}" 1967 using = self.expressions(expression, key="using", flat=True) 1968 using = f"{self.seg('USING')} {using}" if using else "" 1969 group = self.sql(expression, "group") 1970 return f"PIVOT {this}{on}{using}{group}" 1971 1972 alias = self.sql(expression, "alias") 1973 alias = f" AS {alias}" if alias else "" 1974 direction = self.seg("UNPIVOT" if expression.unpivot else "PIVOT") 1975 1976 field = self.sql(expression, "field") 1977 1978 include_nulls = expression.args.get("include_nulls") 1979 if include_nulls is not None: 1980 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 1981 else: 1982 nulls = "" 1983 1984 default_on_null = self.sql(expression, "default_on_null") 1985 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 1986 return f"{direction}{nulls}({expressions} FOR {field}{default_on_null}){alias}" 1987 1988 def version_sql(self, expression: exp.Version) -> str: 1989 this = f"FOR {expression.name}" 1990 kind = expression.text("kind") 1991 expr = self.sql(expression, "expression") 1992 return f"{this} {kind} {expr}" 1993 1994 def tuple_sql(self, expression: exp.Tuple) -> str: 1995 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 1996 1997 def update_sql(self, expression: exp.Update) -> str: 1998 this = self.sql(expression, "this") 1999 set_sql = self.expressions(expression, flat=True) 2000 from_sql = self.sql(expression, "from") 2001 where_sql = self.sql(expression, "where") 2002 returning = self.sql(expression, "returning") 2003 order = self.sql(expression, "order") 2004 limit = self.sql(expression, "limit") 2005 if self.RETURNING_END: 2006 expression_sql = f"{from_sql}{where_sql}{returning}" 2007 else: 2008 expression_sql = f"{returning}{from_sql}{where_sql}" 2009 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2010 return self.prepend_ctes(expression, sql) 2011 2012 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2013 values_as_table = values_as_table and self.VALUES_AS_TABLE 2014 2015 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2016 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2017 args = self.expressions(expression) 2018 alias = self.sql(expression, "alias") 2019 values = f"VALUES{self.seg('')}{args}" 2020 values = ( 2021 f"({values})" 2022 if self.WRAP_DERIVED_VALUES 2023 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2024 else values 2025 ) 2026 return f"{values} AS {alias}" if alias else values 2027 2028 # Converts `VALUES...` expression into a series of select unions. 2029 alias_node = expression.args.get("alias") 2030 column_names = alias_node and alias_node.columns 2031 2032 selects: t.List[exp.Query] = [] 2033 2034 for i, tup in enumerate(expression.expressions): 2035 row = tup.expressions 2036 2037 if i == 0 and column_names: 2038 row = [ 2039 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2040 ] 2041 2042 selects.append(exp.Select(expressions=row)) 2043 2044 if self.pretty: 2045 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2046 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2047 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2048 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2049 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2050 2051 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2052 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2053 return f"({unions}){alias}" 2054 2055 def var_sql(self, expression: exp.Var) -> str: 2056 return self.sql(expression, "this") 2057 2058 @unsupported_args("expressions") 2059 def into_sql(self, expression: exp.Into) -> str: 2060 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2061 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2062 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2063 2064 def from_sql(self, expression: exp.From) -> str: 2065 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2066 2067 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2068 grouping_sets = self.expressions(expression, indent=False) 2069 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2070 2071 def rollup_sql(self, expression: exp.Rollup) -> str: 2072 expressions = self.expressions(expression, indent=False) 2073 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2074 2075 def cube_sql(self, expression: exp.Cube) -> str: 2076 expressions = self.expressions(expression, indent=False) 2077 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2078 2079 def group_sql(self, expression: exp.Group) -> str: 2080 group_by_all = expression.args.get("all") 2081 if group_by_all is True: 2082 modifier = " ALL" 2083 elif group_by_all is False: 2084 modifier = " DISTINCT" 2085 else: 2086 modifier = "" 2087 2088 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2089 2090 grouping_sets = self.expressions(expression, key="grouping_sets") 2091 cube = self.expressions(expression, key="cube") 2092 rollup = self.expressions(expression, key="rollup") 2093 2094 groupings = csv( 2095 self.seg(grouping_sets) if grouping_sets else "", 2096 self.seg(cube) if cube else "", 2097 self.seg(rollup) if rollup else "", 2098 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2099 sep=self.GROUPINGS_SEP, 2100 ) 2101 2102 if ( 2103 expression.expressions 2104 and groupings 2105 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2106 ): 2107 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2108 2109 return f"{group_by}{groupings}" 2110 2111 def having_sql(self, expression: exp.Having) -> str: 2112 this = self.indent(self.sql(expression, "this")) 2113 return f"{self.seg('HAVING')}{self.sep()}{this}" 2114 2115 def connect_sql(self, expression: exp.Connect) -> str: 2116 start = self.sql(expression, "start") 2117 start = self.seg(f"START WITH {start}") if start else "" 2118 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2119 connect = self.sql(expression, "connect") 2120 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2121 return start + connect 2122 2123 def prior_sql(self, expression: exp.Prior) -> str: 2124 return f"PRIOR {self.sql(expression, 'this')}" 2125 2126 def join_sql(self, expression: exp.Join) -> str: 2127 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2128 side = None 2129 else: 2130 side = expression.side 2131 2132 op_sql = " ".join( 2133 op 2134 for op in ( 2135 expression.method, 2136 "GLOBAL" if expression.args.get("global") else None, 2137 side, 2138 expression.kind, 2139 expression.hint if self.JOIN_HINTS else None, 2140 ) 2141 if op 2142 ) 2143 match_cond = self.sql(expression, "match_condition") 2144 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2145 on_sql = self.sql(expression, "on") 2146 using = expression.args.get("using") 2147 2148 if not on_sql and using: 2149 on_sql = csv(*(self.sql(column) for column in using)) 2150 2151 this = expression.this 2152 this_sql = self.sql(this) 2153 2154 exprs = self.expressions(expression) 2155 if exprs: 2156 this_sql = f"{this_sql},{self.seg(exprs)}" 2157 2158 if on_sql: 2159 on_sql = self.indent(on_sql, skip_first=True) 2160 space = self.seg(" " * self.pad) if self.pretty else " " 2161 if using: 2162 on_sql = f"{space}USING ({on_sql})" 2163 else: 2164 on_sql = f"{space}ON {on_sql}" 2165 elif not op_sql: 2166 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2167 return f" {this_sql}" 2168 2169 return f", {this_sql}" 2170 2171 if op_sql != "STRAIGHT_JOIN": 2172 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2173 2174 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}" 2175 2176 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 2177 args = self.expressions(expression, flat=True) 2178 args = f"({args})" if len(args.split(",")) > 1 else args 2179 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2180 2181 def lateral_op(self, expression: exp.Lateral) -> str: 2182 cross_apply = expression.args.get("cross_apply") 2183 2184 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2185 if cross_apply is True: 2186 op = "INNER JOIN " 2187 elif cross_apply is False: 2188 op = "LEFT JOIN " 2189 else: 2190 op = "" 2191 2192 return f"{op}LATERAL" 2193 2194 def lateral_sql(self, expression: exp.Lateral) -> str: 2195 this = self.sql(expression, "this") 2196 2197 if expression.args.get("view"): 2198 alias = expression.args["alias"] 2199 columns = self.expressions(alias, key="columns", flat=True) 2200 table = f" {alias.name}" if alias.name else "" 2201 columns = f" AS {columns}" if columns else "" 2202 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2203 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2204 2205 alias = self.sql(expression, "alias") 2206 alias = f" AS {alias}" if alias else "" 2207 return f"{self.lateral_op(expression)} {this}{alias}" 2208 2209 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2210 this = self.sql(expression, "this") 2211 2212 args = [ 2213 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2214 for e in (expression.args.get(k) for k in ("offset", "expression")) 2215 if e 2216 ] 2217 2218 args_sql = ", ".join(self.sql(e) for e in args) 2219 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2220 expressions = self.expressions(expression, flat=True) 2221 expressions = f" BY {expressions}" if expressions else "" 2222 2223 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{expressions}" 2224 2225 def offset_sql(self, expression: exp.Offset) -> str: 2226 this = self.sql(expression, "this") 2227 value = expression.expression 2228 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2229 expressions = self.expressions(expression, flat=True) 2230 expressions = f" BY {expressions}" if expressions else "" 2231 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2232 2233 def setitem_sql(self, expression: exp.SetItem) -> str: 2234 kind = self.sql(expression, "kind") 2235 kind = f"{kind} " if kind else "" 2236 this = self.sql(expression, "this") 2237 expressions = self.expressions(expression) 2238 collate = self.sql(expression, "collate") 2239 collate = f" COLLATE {collate}" if collate else "" 2240 global_ = "GLOBAL " if expression.args.get("global") else "" 2241 return f"{global_}{kind}{this}{expressions}{collate}" 2242 2243 def set_sql(self, expression: exp.Set) -> str: 2244 expressions = ( 2245 f" {self.expressions(expression, flat=True)}" if expression.expressions else "" 2246 ) 2247 tag = " TAG" if expression.args.get("tag") else "" 2248 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2249 2250 def pragma_sql(self, expression: exp.Pragma) -> str: 2251 return f"PRAGMA {self.sql(expression, 'this')}" 2252 2253 def lock_sql(self, expression: exp.Lock) -> str: 2254 if not self.LOCKING_READS_SUPPORTED: 2255 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2256 return "" 2257 2258 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2259 expressions = self.expressions(expression, flat=True) 2260 expressions = f" OF {expressions}" if expressions else "" 2261 wait = expression.args.get("wait") 2262 2263 if wait is not None: 2264 if isinstance(wait, exp.Literal): 2265 wait = f" WAIT {self.sql(wait)}" 2266 else: 2267 wait = " NOWAIT" if wait else " SKIP LOCKED" 2268 2269 return f"{lock_type}{expressions}{wait or ''}" 2270 2271 def literal_sql(self, expression: exp.Literal) -> str: 2272 text = expression.this or "" 2273 if expression.is_string: 2274 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2275 return text 2276 2277 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2278 if self.dialect.ESCAPED_SEQUENCES: 2279 to_escaped = self.dialect.ESCAPED_SEQUENCES 2280 text = "".join( 2281 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2282 ) 2283 2284 return self._replace_line_breaks(text).replace( 2285 self.dialect.QUOTE_END, self._escaped_quote_end 2286 ) 2287 2288 def loaddata_sql(self, expression: exp.LoadData) -> str: 2289 local = " LOCAL" if expression.args.get("local") else "" 2290 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2291 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2292 this = f" INTO TABLE {self.sql(expression, 'this')}" 2293 partition = self.sql(expression, "partition") 2294 partition = f" {partition}" if partition else "" 2295 input_format = self.sql(expression, "input_format") 2296 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2297 serde = self.sql(expression, "serde") 2298 serde = f" SERDE {serde}" if serde else "" 2299 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2300 2301 def null_sql(self, *_) -> str: 2302 return "NULL" 2303 2304 def boolean_sql(self, expression: exp.Boolean) -> str: 2305 return "TRUE" if expression.this else "FALSE" 2306 2307 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2308 this = self.sql(expression, "this") 2309 this = f"{this} " if this else this 2310 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2311 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2312 2313 def withfill_sql(self, expression: exp.WithFill) -> str: 2314 from_sql = self.sql(expression, "from") 2315 from_sql = f" FROM {from_sql}" if from_sql else "" 2316 to_sql = self.sql(expression, "to") 2317 to_sql = f" TO {to_sql}" if to_sql else "" 2318 step_sql = self.sql(expression, "step") 2319 step_sql = f" STEP {step_sql}" if step_sql else "" 2320 interpolated_values = [ 2321 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2322 if isinstance(e, exp.Alias) 2323 else self.sql(e, "this") 2324 for e in expression.args.get("interpolate") or [] 2325 ] 2326 interpolate = ( 2327 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2328 ) 2329 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2330 2331 def cluster_sql(self, expression: exp.Cluster) -> str: 2332 return self.op_expressions("CLUSTER BY", expression) 2333 2334 def distribute_sql(self, expression: exp.Distribute) -> str: 2335 return self.op_expressions("DISTRIBUTE BY", expression) 2336 2337 def sort_sql(self, expression: exp.Sort) -> str: 2338 return self.op_expressions("SORT BY", expression) 2339 2340 def ordered_sql(self, expression: exp.Ordered) -> str: 2341 desc = expression.args.get("desc") 2342 asc = not desc 2343 2344 nulls_first = expression.args.get("nulls_first") 2345 nulls_last = not nulls_first 2346 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2347 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2348 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2349 2350 this = self.sql(expression, "this") 2351 2352 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2353 nulls_sort_change = "" 2354 if nulls_first and ( 2355 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2356 ): 2357 nulls_sort_change = " NULLS FIRST" 2358 elif ( 2359 nulls_last 2360 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2361 and not nulls_are_last 2362 ): 2363 nulls_sort_change = " NULLS LAST" 2364 2365 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2366 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2367 window = expression.find_ancestor(exp.Window, exp.Select) 2368 if isinstance(window, exp.Window) and window.args.get("spec"): 2369 self.unsupported( 2370 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2371 ) 2372 nulls_sort_change = "" 2373 elif ( 2374 self.NULL_ORDERING_SUPPORTED is False 2375 and (isinstance(expression.find_ancestor(exp.AggFunc, exp.Select), exp.AggFunc)) 2376 and ( 2377 (asc and nulls_sort_change == " NULLS LAST") 2378 or (desc and nulls_sort_change == " NULLS FIRST") 2379 ) 2380 ): 2381 self.unsupported( 2382 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2383 ) 2384 nulls_sort_change = "" 2385 elif self.NULL_ORDERING_SUPPORTED is None: 2386 if expression.this.is_int: 2387 self.unsupported( 2388 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2389 ) 2390 elif not isinstance(expression.this, exp.Rand): 2391 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2392 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2393 nulls_sort_change = "" 2394 2395 with_fill = self.sql(expression, "with_fill") 2396 with_fill = f" {with_fill}" if with_fill else "" 2397 2398 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2399 2400 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2401 window_frame = self.sql(expression, "window_frame") 2402 window_frame = f"{window_frame} " if window_frame else "" 2403 2404 this = self.sql(expression, "this") 2405 2406 return f"{window_frame}{this}" 2407 2408 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2409 partition = self.partition_by_sql(expression) 2410 order = self.sql(expression, "order") 2411 measures = self.expressions(expression, key="measures") 2412 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2413 rows = self.sql(expression, "rows") 2414 rows = self.seg(rows) if rows else "" 2415 after = self.sql(expression, "after") 2416 after = self.seg(after) if after else "" 2417 pattern = self.sql(expression, "pattern") 2418 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2419 definition_sqls = [ 2420 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2421 for definition in expression.args.get("define", []) 2422 ] 2423 definitions = self.expressions(sqls=definition_sqls) 2424 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2425 body = "".join( 2426 ( 2427 partition, 2428 order, 2429 measures, 2430 rows, 2431 after, 2432 pattern, 2433 define, 2434 ) 2435 ) 2436 alias = self.sql(expression, "alias") 2437 alias = f" {alias}" if alias else "" 2438 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2439 2440 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2441 limit = expression.args.get("limit") 2442 2443 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2444 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2445 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2446 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2447 2448 return csv( 2449 *sqls, 2450 *[self.sql(join) for join in expression.args.get("joins") or []], 2451 self.sql(expression, "connect"), 2452 self.sql(expression, "match"), 2453 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2454 self.sql(expression, "prewhere"), 2455 self.sql(expression, "where"), 2456 self.sql(expression, "group"), 2457 self.sql(expression, "having"), 2458 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2459 self.sql(expression, "order"), 2460 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2461 *self.after_limit_modifiers(expression), 2462 self.options_modifier(expression), 2463 sep="", 2464 ) 2465 2466 def options_modifier(self, expression: exp.Expression) -> str: 2467 options = self.expressions(expression, key="options") 2468 return f" {options}" if options else "" 2469 2470 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2471 return "" 2472 2473 def offset_limit_modifiers( 2474 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2475 ) -> t.List[str]: 2476 return [ 2477 self.sql(expression, "offset") if fetch else self.sql(limit), 2478 self.sql(limit) if fetch else self.sql(expression, "offset"), 2479 ] 2480 2481 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2482 locks = self.expressions(expression, key="locks", sep=" ") 2483 locks = f" {locks}" if locks else "" 2484 return [locks, self.sql(expression, "sample")] 2485 2486 def select_sql(self, expression: exp.Select) -> str: 2487 into = expression.args.get("into") 2488 if not self.SUPPORTS_SELECT_INTO and into: 2489 into.pop() 2490 2491 hint = self.sql(expression, "hint") 2492 distinct = self.sql(expression, "distinct") 2493 distinct = f" {distinct}" if distinct else "" 2494 kind = self.sql(expression, "kind") 2495 2496 limit = expression.args.get("limit") 2497 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2498 top = self.limit_sql(limit, top=True) 2499 limit.pop() 2500 else: 2501 top = "" 2502 2503 expressions = self.expressions(expression) 2504 2505 if kind: 2506 if kind in self.SELECT_KINDS: 2507 kind = f" AS {kind}" 2508 else: 2509 if kind == "STRUCT": 2510 expressions = self.expressions( 2511 sqls=[ 2512 self.sql( 2513 exp.Struct( 2514 expressions=[ 2515 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2516 if isinstance(e, exp.Alias) 2517 else e 2518 for e in expression.expressions 2519 ] 2520 ) 2521 ) 2522 ] 2523 ) 2524 kind = "" 2525 2526 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2527 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2528 2529 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2530 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2531 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2532 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2533 sql = self.query_modifiers( 2534 expression, 2535 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2536 self.sql(expression, "into", comment=False), 2537 self.sql(expression, "from", comment=False), 2538 ) 2539 2540 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2541 if expression.args.get("with"): 2542 sql = self.maybe_comment(sql, expression) 2543 expression.pop_comments() 2544 2545 sql = self.prepend_ctes(expression, sql) 2546 2547 if not self.SUPPORTS_SELECT_INTO and into: 2548 if into.args.get("temporary"): 2549 table_kind = " TEMPORARY" 2550 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2551 table_kind = " UNLOGGED" 2552 else: 2553 table_kind = "" 2554 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2555 2556 return sql 2557 2558 def schema_sql(self, expression: exp.Schema) -> str: 2559 this = self.sql(expression, "this") 2560 sql = self.schema_columns_sql(expression) 2561 return f"{this} {sql}" if this and sql else this or sql 2562 2563 def schema_columns_sql(self, expression: exp.Schema) -> str: 2564 if expression.expressions: 2565 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2566 return "" 2567 2568 def star_sql(self, expression: exp.Star) -> str: 2569 except_ = self.expressions(expression, key="except", flat=True) 2570 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2571 replace = self.expressions(expression, key="replace", flat=True) 2572 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2573 rename = self.expressions(expression, key="rename", flat=True) 2574 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2575 return f"*{except_}{replace}{rename}" 2576 2577 def parameter_sql(self, expression: exp.Parameter) -> str: 2578 this = self.sql(expression, "this") 2579 return f"{self.PARAMETER_TOKEN}{this}" 2580 2581 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2582 this = self.sql(expression, "this") 2583 kind = expression.text("kind") 2584 if kind: 2585 kind = f"{kind}." 2586 return f"@@{kind}{this}" 2587 2588 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2589 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2590 2591 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2592 alias = self.sql(expression, "alias") 2593 alias = f"{sep}{alias}" if alias else "" 2594 sample = self.sql(expression, "sample") 2595 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2596 alias = f"{sample}{alias}" 2597 2598 # Set to None so it's not generated again by self.query_modifiers() 2599 expression.set("sample", None) 2600 2601 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2602 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2603 return self.prepend_ctes(expression, sql) 2604 2605 def qualify_sql(self, expression: exp.Qualify) -> str: 2606 this = self.indent(self.sql(expression, "this")) 2607 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2608 2609 def unnest_sql(self, expression: exp.Unnest) -> str: 2610 args = self.expressions(expression, flat=True) 2611 2612 alias = expression.args.get("alias") 2613 offset = expression.args.get("offset") 2614 2615 if self.UNNEST_WITH_ORDINALITY: 2616 if alias and isinstance(offset, exp.Expression): 2617 alias.append("columns", offset) 2618 2619 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2620 columns = alias.columns 2621 alias = self.sql(columns[0]) if columns else "" 2622 else: 2623 alias = self.sql(alias) 2624 2625 alias = f" AS {alias}" if alias else alias 2626 if self.UNNEST_WITH_ORDINALITY: 2627 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2628 else: 2629 if isinstance(offset, exp.Expression): 2630 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2631 elif offset: 2632 suffix = f"{alias} WITH OFFSET" 2633 else: 2634 suffix = alias 2635 2636 return f"UNNEST({args}){suffix}" 2637 2638 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2639 return "" 2640 2641 def where_sql(self, expression: exp.Where) -> str: 2642 this = self.indent(self.sql(expression, "this")) 2643 return f"{self.seg('WHERE')}{self.sep()}{this}" 2644 2645 def window_sql(self, expression: exp.Window) -> str: 2646 this = self.sql(expression, "this") 2647 partition = self.partition_by_sql(expression) 2648 order = expression.args.get("order") 2649 order = self.order_sql(order, flat=True) if order else "" 2650 spec = self.sql(expression, "spec") 2651 alias = self.sql(expression, "alias") 2652 over = self.sql(expression, "over") or "OVER" 2653 2654 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2655 2656 first = expression.args.get("first") 2657 if first is None: 2658 first = "" 2659 else: 2660 first = "FIRST" if first else "LAST" 2661 2662 if not partition and not order and not spec and alias: 2663 return f"{this} {alias}" 2664 2665 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 2666 return f"{this} ({args})" 2667 2668 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2669 partition = self.expressions(expression, key="partition_by", flat=True) 2670 return f"PARTITION BY {partition}" if partition else "" 2671 2672 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2673 kind = self.sql(expression, "kind") 2674 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2675 end = ( 2676 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2677 or "CURRENT ROW" 2678 ) 2679 return f"{kind} BETWEEN {start} AND {end}" 2680 2681 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2682 this = self.sql(expression, "this") 2683 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2684 return f"{this} WITHIN GROUP ({expression_sql})" 2685 2686 def between_sql(self, expression: exp.Between) -> str: 2687 this = self.sql(expression, "this") 2688 low = self.sql(expression, "low") 2689 high = self.sql(expression, "high") 2690 return f"{this} BETWEEN {low} AND {high}" 2691 2692 def bracket_offset_expressions( 2693 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2694 ) -> t.List[exp.Expression]: 2695 return apply_index_offset( 2696 expression.this, 2697 expression.expressions, 2698 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2699 ) 2700 2701 def bracket_sql(self, expression: exp.Bracket) -> str: 2702 expressions = self.bracket_offset_expressions(expression) 2703 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2704 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2705 2706 def all_sql(self, expression: exp.All) -> str: 2707 return f"ALL {self.wrap(expression)}" 2708 2709 def any_sql(self, expression: exp.Any) -> str: 2710 this = self.sql(expression, "this") 2711 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2712 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2713 this = self.wrap(this) 2714 return f"ANY{this}" 2715 return f"ANY {this}" 2716 2717 def exists_sql(self, expression: exp.Exists) -> str: 2718 return f"EXISTS{self.wrap(expression)}" 2719 2720 def case_sql(self, expression: exp.Case) -> str: 2721 this = self.sql(expression, "this") 2722 statements = [f"CASE {this}" if this else "CASE"] 2723 2724 for e in expression.args["ifs"]: 2725 statements.append(f"WHEN {self.sql(e, 'this')}") 2726 statements.append(f"THEN {self.sql(e, 'true')}") 2727 2728 default = self.sql(expression, "default") 2729 2730 if default: 2731 statements.append(f"ELSE {default}") 2732 2733 statements.append("END") 2734 2735 if self.pretty and self.too_wide(statements): 2736 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2737 2738 return " ".join(statements) 2739 2740 def constraint_sql(self, expression: exp.Constraint) -> str: 2741 this = self.sql(expression, "this") 2742 expressions = self.expressions(expression, flat=True) 2743 return f"CONSTRAINT {this} {expressions}" 2744 2745 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2746 order = expression.args.get("order") 2747 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2748 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2749 2750 def extract_sql(self, expression: exp.Extract) -> str: 2751 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name 2752 expression_sql = self.sql(expression, "expression") 2753 return f"EXTRACT({this} FROM {expression_sql})" 2754 2755 def trim_sql(self, expression: exp.Trim) -> str: 2756 trim_type = self.sql(expression, "position") 2757 2758 if trim_type == "LEADING": 2759 func_name = "LTRIM" 2760 elif trim_type == "TRAILING": 2761 func_name = "RTRIM" 2762 else: 2763 func_name = "TRIM" 2764 2765 return self.func(func_name, expression.this, expression.expression) 2766 2767 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2768 args = expression.expressions 2769 if isinstance(expression, exp.ConcatWs): 2770 args = args[1:] # Skip the delimiter 2771 2772 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2773 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2774 2775 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2776 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2777 2778 return args 2779 2780 def concat_sql(self, expression: exp.Concat) -> str: 2781 expressions = self.convert_concat_args(expression) 2782 2783 # Some dialects don't allow a single-argument CONCAT call 2784 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2785 return self.sql(expressions[0]) 2786 2787 return self.func("CONCAT", *expressions) 2788 2789 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2790 return self.func( 2791 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2792 ) 2793 2794 def check_sql(self, expression: exp.Check) -> str: 2795 this = self.sql(expression, key="this") 2796 return f"CHECK ({this})" 2797 2798 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2799 expressions = self.expressions(expression, flat=True) 2800 reference = self.sql(expression, "reference") 2801 reference = f" {reference}" if reference else "" 2802 delete = self.sql(expression, "delete") 2803 delete = f" ON DELETE {delete}" if delete else "" 2804 update = self.sql(expression, "update") 2805 update = f" ON UPDATE {update}" if update else "" 2806 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}" 2807 2808 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2809 expressions = self.expressions(expression, flat=True) 2810 options = self.expressions(expression, key="options", flat=True, sep=" ") 2811 options = f" {options}" if options else "" 2812 return f"PRIMARY KEY ({expressions}){options}" 2813 2814 def if_sql(self, expression: exp.If) -> str: 2815 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 2816 2817 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 2818 modifier = expression.args.get("modifier") 2819 modifier = f" {modifier}" if modifier else "" 2820 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 2821 2822 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 2823 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 2824 2825 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2826 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2827 2828 if expression.args.get("escape"): 2829 path = self.escape_str(path) 2830 2831 if self.QUOTE_JSON_PATH: 2832 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2833 2834 return path 2835 2836 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2837 if isinstance(expression, exp.JSONPathPart): 2838 transform = self.TRANSFORMS.get(expression.__class__) 2839 if not callable(transform): 2840 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2841 return "" 2842 2843 return transform(self, expression) 2844 2845 if isinstance(expression, int): 2846 return str(expression) 2847 2848 if self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 2849 escaped = expression.replace("'", "\\'") 2850 escaped = f"\\'{expression}\\'" 2851 else: 2852 escaped = expression.replace('"', '\\"') 2853 escaped = f'"{escaped}"' 2854 2855 return escaped 2856 2857 def formatjson_sql(self, expression: exp.FormatJson) -> str: 2858 return f"{self.sql(expression, 'this')} FORMAT JSON" 2859 2860 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 2861 null_handling = expression.args.get("null_handling") 2862 null_handling = f" {null_handling}" if null_handling else "" 2863 2864 unique_keys = expression.args.get("unique_keys") 2865 if unique_keys is not None: 2866 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2867 else: 2868 unique_keys = "" 2869 2870 return_type = self.sql(expression, "return_type") 2871 return_type = f" RETURNING {return_type}" if return_type else "" 2872 encoding = self.sql(expression, "encoding") 2873 encoding = f" ENCODING {encoding}" if encoding else "" 2874 2875 return self.func( 2876 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 2877 *expression.expressions, 2878 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2879 ) 2880 2881 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 2882 return self.jsonobject_sql(expression) 2883 2884 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2885 null_handling = expression.args.get("null_handling") 2886 null_handling = f" {null_handling}" if null_handling else "" 2887 return_type = self.sql(expression, "return_type") 2888 return_type = f" RETURNING {return_type}" if return_type else "" 2889 strict = " STRICT" if expression.args.get("strict") else "" 2890 return self.func( 2891 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 2892 ) 2893 2894 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 2895 this = self.sql(expression, "this") 2896 order = self.sql(expression, "order") 2897 null_handling = expression.args.get("null_handling") 2898 null_handling = f" {null_handling}" if null_handling else "" 2899 return_type = self.sql(expression, "return_type") 2900 return_type = f" RETURNING {return_type}" if return_type else "" 2901 strict = " STRICT" if expression.args.get("strict") else "" 2902 return self.func( 2903 "JSON_ARRAYAGG", 2904 this, 2905 suffix=f"{order}{null_handling}{return_type}{strict})", 2906 ) 2907 2908 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 2909 path = self.sql(expression, "path") 2910 path = f" PATH {path}" if path else "" 2911 nested_schema = self.sql(expression, "nested_schema") 2912 2913 if nested_schema: 2914 return f"NESTED{path} {nested_schema}" 2915 2916 this = self.sql(expression, "this") 2917 kind = self.sql(expression, "kind") 2918 kind = f" {kind}" if kind else "" 2919 return f"{this}{kind}{path}" 2920 2921 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 2922 return self.func("COLUMNS", *expression.expressions) 2923 2924 def jsontable_sql(self, expression: exp.JSONTable) -> str: 2925 this = self.sql(expression, "this") 2926 path = self.sql(expression, "path") 2927 path = f", {path}" if path else "" 2928 error_handling = expression.args.get("error_handling") 2929 error_handling = f" {error_handling}" if error_handling else "" 2930 empty_handling = expression.args.get("empty_handling") 2931 empty_handling = f" {empty_handling}" if empty_handling else "" 2932 schema = self.sql(expression, "schema") 2933 return self.func( 2934 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 2935 ) 2936 2937 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 2938 this = self.sql(expression, "this") 2939 kind = self.sql(expression, "kind") 2940 path = self.sql(expression, "path") 2941 path = f" {path}" if path else "" 2942 as_json = " AS JSON" if expression.args.get("as_json") else "" 2943 return f"{this} {kind}{path}{as_json}" 2944 2945 def openjson_sql(self, expression: exp.OpenJSON) -> str: 2946 this = self.sql(expression, "this") 2947 path = self.sql(expression, "path") 2948 path = f", {path}" if path else "" 2949 expressions = self.expressions(expression) 2950 with_ = ( 2951 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 2952 if expressions 2953 else "" 2954 ) 2955 return f"OPENJSON({this}{path}){with_}" 2956 2957 def in_sql(self, expression: exp.In) -> str: 2958 query = expression.args.get("query") 2959 unnest = expression.args.get("unnest") 2960 field = expression.args.get("field") 2961 is_global = " GLOBAL" if expression.args.get("is_global") else "" 2962 2963 if query: 2964 in_sql = self.sql(query) 2965 elif unnest: 2966 in_sql = self.in_unnest_op(unnest) 2967 elif field: 2968 in_sql = self.sql(field) 2969 else: 2970 in_sql = f"({self.expressions(expression, flat=True)})" 2971 2972 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 2973 2974 def in_unnest_op(self, unnest: exp.Unnest) -> str: 2975 return f"(SELECT {self.sql(unnest)})" 2976 2977 def interval_sql(self, expression: exp.Interval) -> str: 2978 unit = self.sql(expression, "unit") 2979 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 2980 unit = self.TIME_PART_SINGULARS.get(unit, unit) 2981 unit = f" {unit}" if unit else "" 2982 2983 if self.SINGLE_STRING_INTERVAL: 2984 this = expression.this.name if expression.this else "" 2985 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 2986 2987 this = self.sql(expression, "this") 2988 if this: 2989 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 2990 this = f" {this}" if unwrapped else f" ({this})" 2991 2992 return f"INTERVAL{this}{unit}" 2993 2994 def return_sql(self, expression: exp.Return) -> str: 2995 return f"RETURN {self.sql(expression, 'this')}" 2996 2997 def reference_sql(self, expression: exp.Reference) -> str: 2998 this = self.sql(expression, "this") 2999 expressions = self.expressions(expression, flat=True) 3000 expressions = f"({expressions})" if expressions else "" 3001 options = self.expressions(expression, key="options", flat=True, sep=" ") 3002 options = f" {options}" if options else "" 3003 return f"REFERENCES {this}{expressions}{options}" 3004 3005 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3006 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3007 parent = expression.parent 3008 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3009 return self.func( 3010 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3011 ) 3012 3013 def paren_sql(self, expression: exp.Paren) -> str: 3014 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3015 return f"({sql}{self.seg(')', sep='')}" 3016 3017 def neg_sql(self, expression: exp.Neg) -> str: 3018 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3019 this_sql = self.sql(expression, "this") 3020 sep = " " if this_sql[0] == "-" else "" 3021 return f"-{sep}{this_sql}" 3022 3023 def not_sql(self, expression: exp.Not) -> str: 3024 return f"NOT {self.sql(expression, 'this')}" 3025 3026 def alias_sql(self, expression: exp.Alias) -> str: 3027 alias = self.sql(expression, "alias") 3028 alias = f" AS {alias}" if alias else "" 3029 return f"{self.sql(expression, 'this')}{alias}" 3030 3031 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3032 alias = expression.args["alias"] 3033 3034 identifier_alias = isinstance(alias, exp.Identifier) 3035 literal_alias = isinstance(alias, exp.Literal) 3036 3037 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3038 alias.replace(exp.Literal.string(alias.output_name)) 3039 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3040 alias.replace(exp.to_identifier(alias.output_name)) 3041 3042 return self.alias_sql(expression) 3043 3044 def aliases_sql(self, expression: exp.Aliases) -> str: 3045 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3046 3047 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3048 this = self.sql(expression, "this") 3049 index = self.sql(expression, "expression") 3050 return f"{this} AT {index}" 3051 3052 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3053 this = self.sql(expression, "this") 3054 zone = self.sql(expression, "zone") 3055 return f"{this} AT TIME ZONE {zone}" 3056 3057 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3058 this = self.sql(expression, "this") 3059 zone = self.sql(expression, "zone") 3060 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3061 3062 def add_sql(self, expression: exp.Add) -> str: 3063 return self.binary(expression, "+") 3064 3065 def and_sql( 3066 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3067 ) -> str: 3068 return self.connector_sql(expression, "AND", stack) 3069 3070 def or_sql( 3071 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3072 ) -> str: 3073 return self.connector_sql(expression, "OR", stack) 3074 3075 def xor_sql( 3076 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3077 ) -> str: 3078 return self.connector_sql(expression, "XOR", stack) 3079 3080 def connector_sql( 3081 self, 3082 expression: exp.Connector, 3083 op: str, 3084 stack: t.Optional[t.List[str | exp.Expression]] = None, 3085 ) -> str: 3086 if stack is not None: 3087 if expression.expressions: 3088 stack.append(self.expressions(expression, sep=f" {op} ")) 3089 else: 3090 stack.append(expression.right) 3091 if expression.comments and self.comments: 3092 for comment in expression.comments: 3093 if comment: 3094 op += f" /*{self.pad_comment(comment)}*/" 3095 stack.extend((op, expression.left)) 3096 return op 3097 3098 stack = [expression] 3099 sqls: t.List[str] = [] 3100 ops = set() 3101 3102 while stack: 3103 node = stack.pop() 3104 if isinstance(node, exp.Connector): 3105 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3106 else: 3107 sql = self.sql(node) 3108 if sqls and sqls[-1] in ops: 3109 sqls[-1] += f" {sql}" 3110 else: 3111 sqls.append(sql) 3112 3113 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3114 return sep.join(sqls) 3115 3116 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3117 return self.binary(expression, "&") 3118 3119 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3120 return self.binary(expression, "<<") 3121 3122 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3123 return f"~{self.sql(expression, 'this')}" 3124 3125 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3126 return self.binary(expression, "|") 3127 3128 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3129 return self.binary(expression, ">>") 3130 3131 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3132 return self.binary(expression, "^") 3133 3134 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3135 format_sql = self.sql(expression, "format") 3136 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3137 to_sql = self.sql(expression, "to") 3138 to_sql = f" {to_sql}" if to_sql else "" 3139 action = self.sql(expression, "action") 3140 action = f" {action}" if action else "" 3141 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{format_sql}{action})" 3142 3143 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3144 zone = self.sql(expression, "this") 3145 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3146 3147 def collate_sql(self, expression: exp.Collate) -> str: 3148 if self.COLLATE_IS_FUNC: 3149 return self.function_fallback_sql(expression) 3150 return self.binary(expression, "COLLATE") 3151 3152 def command_sql(self, expression: exp.Command) -> str: 3153 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3154 3155 def comment_sql(self, expression: exp.Comment) -> str: 3156 this = self.sql(expression, "this") 3157 kind = expression.args["kind"] 3158 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3159 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3160 expression_sql = self.sql(expression, "expression") 3161 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3162 3163 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3164 this = self.sql(expression, "this") 3165 delete = " DELETE" if expression.args.get("delete") else "" 3166 recompress = self.sql(expression, "recompress") 3167 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3168 to_disk = self.sql(expression, "to_disk") 3169 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3170 to_volume = self.sql(expression, "to_volume") 3171 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3172 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3173 3174 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3175 where = self.sql(expression, "where") 3176 group = self.sql(expression, "group") 3177 aggregates = self.expressions(expression, key="aggregates") 3178 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3179 3180 if not (where or group or aggregates) and len(expression.expressions) == 1: 3181 return f"TTL {self.expressions(expression, flat=True)}" 3182 3183 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3184 3185 def transaction_sql(self, expression: exp.Transaction) -> str: 3186 return "BEGIN" 3187 3188 def commit_sql(self, expression: exp.Commit) -> str: 3189 chain = expression.args.get("chain") 3190 if chain is not None: 3191 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3192 3193 return f"COMMIT{chain or ''}" 3194 3195 def rollback_sql(self, expression: exp.Rollback) -> str: 3196 savepoint = expression.args.get("savepoint") 3197 savepoint = f" TO {savepoint}" if savepoint else "" 3198 return f"ROLLBACK{savepoint}" 3199 3200 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3201 this = self.sql(expression, "this") 3202 3203 dtype = self.sql(expression, "dtype") 3204 if dtype: 3205 collate = self.sql(expression, "collate") 3206 collate = f" COLLATE {collate}" if collate else "" 3207 using = self.sql(expression, "using") 3208 using = f" USING {using}" if using else "" 3209 return f"ALTER COLUMN {this} SET DATA TYPE {dtype}{collate}{using}" 3210 3211 default = self.sql(expression, "default") 3212 if default: 3213 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3214 3215 comment = self.sql(expression, "comment") 3216 if comment: 3217 return f"ALTER COLUMN {this} COMMENT {comment}" 3218 3219 allow_null = expression.args.get("allow_null") 3220 drop = expression.args.get("drop") 3221 3222 if not drop and not allow_null: 3223 self.unsupported("Unsupported ALTER COLUMN syntax") 3224 3225 if allow_null is not None: 3226 keyword = "DROP" if drop else "SET" 3227 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3228 3229 return f"ALTER COLUMN {this} DROP DEFAULT" 3230 3231 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3232 this = self.sql(expression, "this") 3233 if not isinstance(expression.this, exp.Var): 3234 this = f"KEY DISTKEY {this}" 3235 return f"ALTER DISTSTYLE {this}" 3236 3237 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3238 compound = " COMPOUND" if expression.args.get("compound") else "" 3239 this = self.sql(expression, "this") 3240 expressions = self.expressions(expression, flat=True) 3241 expressions = f"({expressions})" if expressions else "" 3242 return f"ALTER{compound} SORTKEY {this or expressions}" 3243 3244 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3245 if not self.RENAME_TABLE_WITH_DB: 3246 # Remove db from tables 3247 expression = expression.transform( 3248 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3249 ).assert_is(exp.AlterRename) 3250 this = self.sql(expression, "this") 3251 return f"RENAME TO {this}" 3252 3253 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3254 exists = " IF EXISTS" if expression.args.get("exists") else "" 3255 old_column = self.sql(expression, "this") 3256 new_column = self.sql(expression, "to") 3257 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3258 3259 def alterset_sql(self, expression: exp.AlterSet) -> str: 3260 exprs = self.expressions(expression, flat=True) 3261 return f"SET {exprs}" 3262 3263 def alter_sql(self, expression: exp.Alter) -> str: 3264 actions = expression.args["actions"] 3265 3266 if isinstance(actions[0], exp.ColumnDef): 3267 actions = self.add_column_sql(expression) 3268 elif isinstance(actions[0], exp.Schema): 3269 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3270 elif isinstance(actions[0], exp.Delete): 3271 actions = self.expressions(expression, key="actions", flat=True) 3272 elif isinstance(actions[0], exp.Query): 3273 actions = "AS " + self.expressions(expression, key="actions") 3274 else: 3275 actions = self.expressions(expression, key="actions", flat=True) 3276 3277 exists = " IF EXISTS" if expression.args.get("exists") else "" 3278 on_cluster = self.sql(expression, "cluster") 3279 on_cluster = f" {on_cluster}" if on_cluster else "" 3280 only = " ONLY" if expression.args.get("only") else "" 3281 options = self.expressions(expression, key="options") 3282 options = f", {options}" if options else "" 3283 kind = self.sql(expression, "kind") 3284 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3285 3286 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{not_valid}{options}" 3287 3288 def add_column_sql(self, expression: exp.Alter) -> str: 3289 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3290 return self.expressions( 3291 expression, 3292 key="actions", 3293 prefix="ADD COLUMN ", 3294 skip_first=True, 3295 ) 3296 return f"ADD {self.expressions(expression, key='actions', flat=True)}" 3297 3298 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3299 expressions = self.expressions(expression) 3300 exists = " IF EXISTS " if expression.args.get("exists") else " " 3301 return f"DROP{exists}{expressions}" 3302 3303 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3304 return f"ADD {self.expressions(expression)}" 3305 3306 def distinct_sql(self, expression: exp.Distinct) -> str: 3307 this = self.expressions(expression, flat=True) 3308 3309 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3310 case = exp.case() 3311 for arg in expression.expressions: 3312 case = case.when(arg.is_(exp.null()), exp.null()) 3313 this = self.sql(case.else_(f"({this})")) 3314 3315 this = f" {this}" if this else "" 3316 3317 on = self.sql(expression, "on") 3318 on = f" ON {on}" if on else "" 3319 return f"DISTINCT{this}{on}" 3320 3321 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3322 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3323 3324 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3325 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3326 3327 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3328 this_sql = self.sql(expression, "this") 3329 expression_sql = self.sql(expression, "expression") 3330 kind = "MAX" if expression.args.get("max") else "MIN" 3331 return f"{this_sql} HAVING {kind} {expression_sql}" 3332 3333 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3334 return self.sql( 3335 exp.Cast( 3336 this=exp.Div(this=expression.this, expression=expression.expression), 3337 to=exp.DataType(this=exp.DataType.Type.INT), 3338 ) 3339 ) 3340 3341 def dpipe_sql(self, expression: exp.DPipe) -> str: 3342 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3343 return self.func( 3344 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3345 ) 3346 return self.binary(expression, "||") 3347 3348 def div_sql(self, expression: exp.Div) -> str: 3349 l, r = expression.left, expression.right 3350 3351 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3352 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3353 3354 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3355 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3356 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3357 3358 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3359 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3360 return self.sql( 3361 exp.cast( 3362 l / r, 3363 to=exp.DataType.Type.BIGINT, 3364 ) 3365 ) 3366 3367 return self.binary(expression, "/") 3368 3369 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3370 return self.binary(expression, "OVERLAPS") 3371 3372 def distance_sql(self, expression: exp.Distance) -> str: 3373 return self.binary(expression, "<->") 3374 3375 def dot_sql(self, expression: exp.Dot) -> str: 3376 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3377 3378 def eq_sql(self, expression: exp.EQ) -> str: 3379 return self.binary(expression, "=") 3380 3381 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3382 return self.binary(expression, ":=") 3383 3384 def escape_sql(self, expression: exp.Escape) -> str: 3385 return self.binary(expression, "ESCAPE") 3386 3387 def glob_sql(self, expression: exp.Glob) -> str: 3388 return self.binary(expression, "GLOB") 3389 3390 def gt_sql(self, expression: exp.GT) -> str: 3391 return self.binary(expression, ">") 3392 3393 def gte_sql(self, expression: exp.GTE) -> str: 3394 return self.binary(expression, ">=") 3395 3396 def ilike_sql(self, expression: exp.ILike) -> str: 3397 return self.binary(expression, "ILIKE") 3398 3399 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3400 return self.binary(expression, "ILIKE ANY") 3401 3402 def is_sql(self, expression: exp.Is) -> str: 3403 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3404 return self.sql( 3405 expression.this if expression.expression.this else exp.not_(expression.this) 3406 ) 3407 return self.binary(expression, "IS") 3408 3409 def like_sql(self, expression: exp.Like) -> str: 3410 return self.binary(expression, "LIKE") 3411 3412 def likeany_sql(self, expression: exp.LikeAny) -> str: 3413 return self.binary(expression, "LIKE ANY") 3414 3415 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3416 return self.binary(expression, "SIMILAR TO") 3417 3418 def lt_sql(self, expression: exp.LT) -> str: 3419 return self.binary(expression, "<") 3420 3421 def lte_sql(self, expression: exp.LTE) -> str: 3422 return self.binary(expression, "<=") 3423 3424 def mod_sql(self, expression: exp.Mod) -> str: 3425 return self.binary(expression, "%") 3426 3427 def mul_sql(self, expression: exp.Mul) -> str: 3428 return self.binary(expression, "*") 3429 3430 def neq_sql(self, expression: exp.NEQ) -> str: 3431 return self.binary(expression, "<>") 3432 3433 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3434 return self.binary(expression, "IS NOT DISTINCT FROM") 3435 3436 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3437 return self.binary(expression, "IS DISTINCT FROM") 3438 3439 def slice_sql(self, expression: exp.Slice) -> str: 3440 return self.binary(expression, ":") 3441 3442 def sub_sql(self, expression: exp.Sub) -> str: 3443 return self.binary(expression, "-") 3444 3445 def trycast_sql(self, expression: exp.TryCast) -> str: 3446 return self.cast_sql(expression, safe_prefix="TRY_") 3447 3448 def try_sql(self, expression: exp.Try) -> str: 3449 if not self.TRY_SUPPORTED: 3450 self.unsupported("Unsupported TRY function") 3451 return self.sql(expression, "this") 3452 3453 return self.func("TRY", expression.this) 3454 3455 def log_sql(self, expression: exp.Log) -> str: 3456 this = expression.this 3457 expr = expression.expression 3458 3459 if self.dialect.LOG_BASE_FIRST is False: 3460 this, expr = expr, this 3461 elif self.dialect.LOG_BASE_FIRST is None and expr: 3462 if this.name in ("2", "10"): 3463 return self.func(f"LOG{this.name}", expr) 3464 3465 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3466 3467 return self.func("LOG", this, expr) 3468 3469 def use_sql(self, expression: exp.Use) -> str: 3470 kind = self.sql(expression, "kind") 3471 kind = f" {kind}" if kind else "" 3472 this = self.sql(expression, "this") 3473 this = f" {this}" if this else "" 3474 return f"USE{kind}{this}" 3475 3476 def binary(self, expression: exp.Binary, op: str) -> str: 3477 sqls: t.List[str] = [] 3478 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3479 binary_type = type(expression) 3480 3481 while stack: 3482 node = stack.pop() 3483 3484 if type(node) is binary_type: 3485 op_func = node.args.get("operator") 3486 if op_func: 3487 op = f"OPERATOR({self.sql(op_func)})" 3488 3489 stack.append(node.right) 3490 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3491 stack.append(node.left) 3492 else: 3493 sqls.append(self.sql(node)) 3494 3495 return "".join(sqls) 3496 3497 def function_fallback_sql(self, expression: exp.Func) -> str: 3498 args = [] 3499 3500 for key in expression.arg_types: 3501 arg_value = expression.args.get(key) 3502 3503 if isinstance(arg_value, list): 3504 for value in arg_value: 3505 args.append(value) 3506 elif arg_value is not None: 3507 args.append(arg_value) 3508 3509 if self.normalize_functions: 3510 name = expression.sql_name() 3511 else: 3512 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3513 3514 return self.func(name, *args) 3515 3516 def func( 3517 self, 3518 name: str, 3519 *args: t.Optional[exp.Expression | str], 3520 prefix: str = "(", 3521 suffix: str = ")", 3522 normalize: bool = True, 3523 ) -> str: 3524 name = self.normalize_func(name) if normalize else name 3525 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3526 3527 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3528 arg_sqls = tuple( 3529 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3530 ) 3531 if self.pretty and self.too_wide(arg_sqls): 3532 return self.indent( 3533 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3534 ) 3535 return sep.join(arg_sqls) 3536 3537 def too_wide(self, args: t.Iterable) -> bool: 3538 return sum(len(arg) for arg in args) > self.max_text_width 3539 3540 def format_time( 3541 self, 3542 expression: exp.Expression, 3543 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3544 inverse_time_trie: t.Optional[t.Dict] = None, 3545 ) -> t.Optional[str]: 3546 return format_time( 3547 self.sql(expression, "format"), 3548 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3549 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3550 ) 3551 3552 def expressions( 3553 self, 3554 expression: t.Optional[exp.Expression] = None, 3555 key: t.Optional[str] = None, 3556 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3557 flat: bool = False, 3558 indent: bool = True, 3559 skip_first: bool = False, 3560 skip_last: bool = False, 3561 sep: str = ", ", 3562 prefix: str = "", 3563 dynamic: bool = False, 3564 new_line: bool = False, 3565 ) -> str: 3566 expressions = expression.args.get(key or "expressions") if expression else sqls 3567 3568 if not expressions: 3569 return "" 3570 3571 if flat: 3572 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3573 3574 num_sqls = len(expressions) 3575 result_sqls = [] 3576 3577 for i, e in enumerate(expressions): 3578 sql = self.sql(e, comment=False) 3579 if not sql: 3580 continue 3581 3582 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3583 3584 if self.pretty: 3585 if self.leading_comma: 3586 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3587 else: 3588 result_sqls.append( 3589 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3590 ) 3591 else: 3592 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3593 3594 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3595 if new_line: 3596 result_sqls.insert(0, "") 3597 result_sqls.append("") 3598 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3599 else: 3600 result_sql = "".join(result_sqls) 3601 3602 return ( 3603 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3604 if indent 3605 else result_sql 3606 ) 3607 3608 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3609 flat = flat or isinstance(expression.parent, exp.Properties) 3610 expressions_sql = self.expressions(expression, flat=flat) 3611 if flat: 3612 return f"{op} {expressions_sql}" 3613 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3614 3615 def naked_property(self, expression: exp.Property) -> str: 3616 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3617 if not property_name: 3618 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3619 return f"{property_name} {self.sql(expression, 'this')}" 3620 3621 def tag_sql(self, expression: exp.Tag) -> str: 3622 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3623 3624 def token_sql(self, token_type: TokenType) -> str: 3625 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3626 3627 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3628 this = self.sql(expression, "this") 3629 expressions = self.no_identify(self.expressions, expression) 3630 expressions = ( 3631 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3632 ) 3633 return f"{this}{expressions}" if expressions.strip() != "" else this 3634 3635 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3636 this = self.sql(expression, "this") 3637 expressions = self.expressions(expression, flat=True) 3638 return f"{this}({expressions})" 3639 3640 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3641 return self.binary(expression, "=>") 3642 3643 def when_sql(self, expression: exp.When) -> str: 3644 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3645 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3646 condition = self.sql(expression, "condition") 3647 condition = f" AND {condition}" if condition else "" 3648 3649 then_expression = expression.args.get("then") 3650 if isinstance(then_expression, exp.Insert): 3651 this = self.sql(then_expression, "this") 3652 this = f"INSERT {this}" if this else "INSERT" 3653 then = self.sql(then_expression, "expression") 3654 then = f"{this} VALUES {then}" if then else this 3655 elif isinstance(then_expression, exp.Update): 3656 if isinstance(then_expression.args.get("expressions"), exp.Star): 3657 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3658 else: 3659 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 3660 else: 3661 then = self.sql(then_expression) 3662 return f"WHEN {matched}{source}{condition} THEN {then}" 3663 3664 def merge_sql(self, expression: exp.Merge) -> str: 3665 table = expression.this 3666 table_alias = "" 3667 3668 hints = table.args.get("hints") 3669 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3670 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3671 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3672 3673 this = self.sql(table) 3674 using = f"USING {self.sql(expression, 'using')}" 3675 on = f"ON {self.sql(expression, 'on')}" 3676 expressions = self.expressions(expression, sep=" ", indent=False) 3677 returning = self.sql(expression, "returning") 3678 if returning: 3679 expressions = f"{expressions}{returning}" 3680 3681 sep = self.sep() 3682 3683 return self.prepend_ctes( 3684 expression, 3685 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{expressions}", 3686 ) 3687 3688 @unsupported_args("format") 3689 def tochar_sql(self, expression: exp.ToChar) -> str: 3690 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3691 3692 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3693 if not self.SUPPORTS_TO_NUMBER: 3694 self.unsupported("Unsupported TO_NUMBER function") 3695 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3696 3697 fmt = expression.args.get("format") 3698 if not fmt: 3699 self.unsupported("Conversion format is required for TO_NUMBER") 3700 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3701 3702 return self.func("TO_NUMBER", expression.this, fmt) 3703 3704 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3705 this = self.sql(expression, "this") 3706 kind = self.sql(expression, "kind") 3707 settings_sql = self.expressions(expression, key="settings", sep=" ") 3708 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3709 return f"{this}({kind}{args})" 3710 3711 def dictrange_sql(self, expression: exp.DictRange) -> str: 3712 this = self.sql(expression, "this") 3713 max = self.sql(expression, "max") 3714 min = self.sql(expression, "min") 3715 return f"{this}(MIN {min} MAX {max})" 3716 3717 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3718 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3719 3720 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3721 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3722 3723 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3724 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3725 expressions = self.expressions(expression, flat=True) 3726 expressions = f" {self.wrap(expressions)}" if expressions else "" 3727 buckets = self.sql(expression, "buckets") 3728 kind = self.sql(expression, "kind") 3729 buckets = f" BUCKETS {buckets}" if buckets else "" 3730 order = self.sql(expression, "order") 3731 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 3732 3733 def oncluster_sql(self, expression: exp.OnCluster) -> str: 3734 return "" 3735 3736 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3737 expressions = self.expressions(expression, key="expressions", flat=True) 3738 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3739 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3740 buckets = self.sql(expression, "buckets") 3741 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 3742 3743 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3744 this = self.sql(expression, "this") 3745 having = self.sql(expression, "having") 3746 3747 if having: 3748 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3749 3750 return self.func("ANY_VALUE", this) 3751 3752 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3753 transform = self.func("TRANSFORM", *expression.expressions) 3754 row_format_before = self.sql(expression, "row_format_before") 3755 row_format_before = f" {row_format_before}" if row_format_before else "" 3756 record_writer = self.sql(expression, "record_writer") 3757 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3758 using = f" USING {self.sql(expression, 'command_script')}" 3759 schema = self.sql(expression, "schema") 3760 schema = f" AS {schema}" if schema else "" 3761 row_format_after = self.sql(expression, "row_format_after") 3762 row_format_after = f" {row_format_after}" if row_format_after else "" 3763 record_reader = self.sql(expression, "record_reader") 3764 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3765 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 3766 3767 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3768 key_block_size = self.sql(expression, "key_block_size") 3769 if key_block_size: 3770 return f"KEY_BLOCK_SIZE = {key_block_size}" 3771 3772 using = self.sql(expression, "using") 3773 if using: 3774 return f"USING {using}" 3775 3776 parser = self.sql(expression, "parser") 3777 if parser: 3778 return f"WITH PARSER {parser}" 3779 3780 comment = self.sql(expression, "comment") 3781 if comment: 3782 return f"COMMENT {comment}" 3783 3784 visible = expression.args.get("visible") 3785 if visible is not None: 3786 return "VISIBLE" if visible else "INVISIBLE" 3787 3788 engine_attr = self.sql(expression, "engine_attr") 3789 if engine_attr: 3790 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3791 3792 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3793 if secondary_engine_attr: 3794 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3795 3796 self.unsupported("Unsupported index constraint option.") 3797 return "" 3798 3799 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 3800 enforced = " ENFORCED" if expression.args.get("enforced") else "" 3801 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 3802 3803 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3804 kind = self.sql(expression, "kind") 3805 kind = f"{kind} INDEX" if kind else "INDEX" 3806 this = self.sql(expression, "this") 3807 this = f" {this}" if this else "" 3808 index_type = self.sql(expression, "index_type") 3809 index_type = f" USING {index_type}" if index_type else "" 3810 expressions = self.expressions(expression, flat=True) 3811 expressions = f" ({expressions})" if expressions else "" 3812 options = self.expressions(expression, key="options", sep=" ") 3813 options = f" {options}" if options else "" 3814 return f"{kind}{this}{index_type}{expressions}{options}" 3815 3816 def nvl2_sql(self, expression: exp.Nvl2) -> str: 3817 if self.NVL2_SUPPORTED: 3818 return self.function_fallback_sql(expression) 3819 3820 case = exp.Case().when( 3821 expression.this.is_(exp.null()).not_(copy=False), 3822 expression.args["true"], 3823 copy=False, 3824 ) 3825 else_cond = expression.args.get("false") 3826 if else_cond: 3827 case.else_(else_cond, copy=False) 3828 3829 return self.sql(case) 3830 3831 def comprehension_sql(self, expression: exp.Comprehension) -> str: 3832 this = self.sql(expression, "this") 3833 expr = self.sql(expression, "expression") 3834 iterator = self.sql(expression, "iterator") 3835 condition = self.sql(expression, "condition") 3836 condition = f" IF {condition}" if condition else "" 3837 return f"{this} FOR {expr} IN {iterator}{condition}" 3838 3839 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 3840 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 3841 3842 def opclass_sql(self, expression: exp.Opclass) -> str: 3843 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 3844 3845 def predict_sql(self, expression: exp.Predict) -> str: 3846 model = self.sql(expression, "this") 3847 model = f"MODEL {model}" 3848 table = self.sql(expression, "expression") 3849 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 3850 parameters = self.sql(expression, "params_struct") 3851 return self.func("PREDICT", model, table, parameters or None) 3852 3853 def forin_sql(self, expression: exp.ForIn) -> str: 3854 this = self.sql(expression, "this") 3855 expression_sql = self.sql(expression, "expression") 3856 return f"FOR {this} DO {expression_sql}" 3857 3858 def refresh_sql(self, expression: exp.Refresh) -> str: 3859 this = self.sql(expression, "this") 3860 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 3861 return f"REFRESH {table}{this}" 3862 3863 def toarray_sql(self, expression: exp.ToArray) -> str: 3864 arg = expression.this 3865 if not arg.type: 3866 from sqlglot.optimizer.annotate_types import annotate_types 3867 3868 arg = annotate_types(arg) 3869 3870 if arg.is_type(exp.DataType.Type.ARRAY): 3871 return self.sql(arg) 3872 3873 cond_for_null = arg.is_(exp.null()) 3874 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 3875 3876 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 3877 this = expression.this 3878 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 3879 return self.sql(this) 3880 3881 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 3882 3883 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 3884 this = expression.this 3885 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 3886 return self.sql(this) 3887 3888 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP)) 3889 3890 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 3891 this = expression.this 3892 time_format = self.format_time(expression) 3893 3894 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 3895 return self.sql( 3896 exp.cast( 3897 exp.StrToTime(this=this, format=expression.args["format"]), 3898 exp.DataType.Type.DATE, 3899 ) 3900 ) 3901 3902 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 3903 return self.sql(this) 3904 3905 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 3906 3907 def unixdate_sql(self, expression: exp.UnixDate) -> str: 3908 return self.sql( 3909 exp.func( 3910 "DATEDIFF", 3911 expression.this, 3912 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 3913 "day", 3914 ) 3915 ) 3916 3917 def lastday_sql(self, expression: exp.LastDay) -> str: 3918 if self.LAST_DAY_SUPPORTS_DATE_PART: 3919 return self.function_fallback_sql(expression) 3920 3921 unit = expression.text("unit") 3922 if unit and unit != "MONTH": 3923 self.unsupported("Date parts are not supported in LAST_DAY.") 3924 3925 return self.func("LAST_DAY", expression.this) 3926 3927 def dateadd_sql(self, expression: exp.DateAdd) -> str: 3928 from sqlglot.dialects.dialect import unit_to_str 3929 3930 return self.func( 3931 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 3932 ) 3933 3934 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 3935 if self.CAN_IMPLEMENT_ARRAY_ANY: 3936 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 3937 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 3938 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 3939 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 3940 3941 from sqlglot.dialects import Dialect 3942 3943 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 3944 if self.dialect.__class__ != Dialect: 3945 self.unsupported("ARRAY_ANY is unsupported") 3946 3947 return self.function_fallback_sql(expression) 3948 3949 def struct_sql(self, expression: exp.Struct) -> str: 3950 expression.set( 3951 "expressions", 3952 [ 3953 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 3954 if isinstance(e, exp.PropertyEQ) 3955 else e 3956 for e in expression.expressions 3957 ], 3958 ) 3959 3960 return self.function_fallback_sql(expression) 3961 3962 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 3963 low = self.sql(expression, "this") 3964 high = self.sql(expression, "expression") 3965 3966 return f"{low} TO {high}" 3967 3968 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 3969 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 3970 tables = f" {self.expressions(expression)}" 3971 3972 exists = " IF EXISTS" if expression.args.get("exists") else "" 3973 3974 on_cluster = self.sql(expression, "cluster") 3975 on_cluster = f" {on_cluster}" if on_cluster else "" 3976 3977 identity = self.sql(expression, "identity") 3978 identity = f" {identity} IDENTITY" if identity else "" 3979 3980 option = self.sql(expression, "option") 3981 option = f" {option}" if option else "" 3982 3983 partition = self.sql(expression, "partition") 3984 partition = f" {partition}" if partition else "" 3985 3986 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 3987 3988 # This transpiles T-SQL's CONVERT function 3989 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 3990 def convert_sql(self, expression: exp.Convert) -> str: 3991 to = expression.this 3992 value = expression.expression 3993 style = expression.args.get("style") 3994 safe = expression.args.get("safe") 3995 strict = expression.args.get("strict") 3996 3997 if not to or not value: 3998 return "" 3999 4000 # Retrieve length of datatype and override to default if not specified 4001 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4002 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4003 4004 transformed: t.Optional[exp.Expression] = None 4005 cast = exp.Cast if strict else exp.TryCast 4006 4007 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4008 if isinstance(style, exp.Literal) and style.is_int: 4009 from sqlglot.dialects.tsql import TSQL 4010 4011 style_value = style.name 4012 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4013 if not converted_style: 4014 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4015 4016 fmt = exp.Literal.string(converted_style) 4017 4018 if to.this == exp.DataType.Type.DATE: 4019 transformed = exp.StrToDate(this=value, format=fmt) 4020 elif to.this == exp.DataType.Type.DATETIME: 4021 transformed = exp.StrToTime(this=value, format=fmt) 4022 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4023 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4024 elif to.this == exp.DataType.Type.TEXT: 4025 transformed = exp.TimeToStr(this=value, format=fmt) 4026 4027 if not transformed: 4028 transformed = cast(this=value, to=to, safe=safe) 4029 4030 return self.sql(transformed) 4031 4032 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4033 this = expression.this 4034 if isinstance(this, exp.JSONPathWildcard): 4035 this = self.json_path_part(this) 4036 return f".{this}" if this else "" 4037 4038 if exp.SAFE_IDENTIFIER_RE.match(this): 4039 return f".{this}" 4040 4041 this = self.json_path_part(this) 4042 return f"[{this}]" if self.JSON_PATH_BRACKETED_KEY_SUPPORTED else f".{this}" 4043 4044 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4045 this = self.json_path_part(expression.this) 4046 return f"[{this}]" if this else "" 4047 4048 def _simplify_unless_literal(self, expression: E) -> E: 4049 if not isinstance(expression, exp.Literal): 4050 from sqlglot.optimizer.simplify import simplify 4051 4052 expression = simplify(expression, dialect=self.dialect) 4053 4054 return expression 4055 4056 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4057 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4058 # The first modifier here will be the one closest to the AggFunc's arg 4059 mods = sorted( 4060 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4061 key=lambda x: 0 4062 if isinstance(x, exp.HavingMax) 4063 else (1 if isinstance(x, exp.Order) else 2), 4064 ) 4065 4066 if mods: 4067 mod = mods[0] 4068 this = expression.__class__(this=mod.this.copy()) 4069 this.meta["inline"] = True 4070 mod.this.replace(this) 4071 return self.sql(expression.this) 4072 4073 agg_func = expression.find(exp.AggFunc) 4074 4075 if agg_func: 4076 return self.sql(agg_func)[:-1] + f" {text})" 4077 4078 return f"{self.sql(expression, 'this')} {text}" 4079 4080 def _replace_line_breaks(self, string: str) -> str: 4081 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4082 if self.pretty: 4083 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4084 return string 4085 4086 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4087 option = self.sql(expression, "this") 4088 4089 if expression.expressions: 4090 upper = option.upper() 4091 4092 # Snowflake FILE_FORMAT options are separated by whitespace 4093 sep = " " if upper == "FILE_FORMAT" else ", " 4094 4095 # Databricks copy/format options do not set their list of values with EQ 4096 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4097 values = self.expressions(expression, flat=True, sep=sep) 4098 return f"{option}{op}({values})" 4099 4100 value = self.sql(expression, "expression") 4101 4102 if not value: 4103 return option 4104 4105 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4106 4107 return f"{option}{op}{value}" 4108 4109 def credentials_sql(self, expression: exp.Credentials) -> str: 4110 cred_expr = expression.args.get("credentials") 4111 if isinstance(cred_expr, exp.Literal): 4112 # Redshift case: CREDENTIALS <string> 4113 credentials = self.sql(expression, "credentials") 4114 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4115 else: 4116 # Snowflake case: CREDENTIALS = (...) 4117 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4118 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4119 4120 storage = self.sql(expression, "storage") 4121 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4122 4123 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4124 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4125 4126 iam_role = self.sql(expression, "iam_role") 4127 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4128 4129 region = self.sql(expression, "region") 4130 region = f" REGION {region}" if region else "" 4131 4132 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4133 4134 def copy_sql(self, expression: exp.Copy) -> str: 4135 this = self.sql(expression, "this") 4136 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4137 4138 credentials = self.sql(expression, "credentials") 4139 credentials = self.seg(credentials) if credentials else "" 4140 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4141 files = self.expressions(expression, key="files", flat=True) 4142 4143 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4144 params = self.expressions( 4145 expression, 4146 key="params", 4147 sep=sep, 4148 new_line=True, 4149 skip_last=True, 4150 skip_first=True, 4151 indent=self.COPY_PARAMS_ARE_WRAPPED, 4152 ) 4153 4154 if params: 4155 if self.COPY_PARAMS_ARE_WRAPPED: 4156 params = f" WITH ({params})" 4157 elif not self.pretty: 4158 params = f" {params}" 4159 4160 return f"COPY{this}{kind} {files}{credentials}{params}" 4161 4162 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4163 return "" 4164 4165 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4166 on_sql = "ON" if expression.args.get("on") else "OFF" 4167 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4168 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4169 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4170 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4171 4172 if filter_col or retention_period: 4173 on_sql = self.func("ON", filter_col, retention_period) 4174 4175 return f"DATA_DELETION={on_sql}" 4176 4177 def maskingpolicycolumnconstraint_sql( 4178 self, expression: exp.MaskingPolicyColumnConstraint 4179 ) -> str: 4180 this = self.sql(expression, "this") 4181 expressions = self.expressions(expression, flat=True) 4182 expressions = f" USING ({expressions})" if expressions else "" 4183 return f"MASKING POLICY {this}{expressions}" 4184 4185 def gapfill_sql(self, expression: exp.GapFill) -> str: 4186 this = self.sql(expression, "this") 4187 this = f"TABLE {this}" 4188 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4189 4190 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4191 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4192 4193 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4194 this = self.sql(expression, "this") 4195 expr = expression.expression 4196 4197 if isinstance(expr, exp.Func): 4198 # T-SQL's CLR functions are case sensitive 4199 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4200 else: 4201 expr = self.sql(expression, "expression") 4202 4203 return self.scope_resolution(expr, this) 4204 4205 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4206 if self.PARSE_JSON_NAME is None: 4207 return self.sql(expression.this) 4208 4209 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4210 4211 def rand_sql(self, expression: exp.Rand) -> str: 4212 lower = self.sql(expression, "lower") 4213 upper = self.sql(expression, "upper") 4214 4215 if lower and upper: 4216 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4217 return self.func("RAND", expression.this) 4218 4219 def changes_sql(self, expression: exp.Changes) -> str: 4220 information = self.sql(expression, "information") 4221 information = f"INFORMATION => {information}" 4222 at_before = self.sql(expression, "at_before") 4223 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4224 end = self.sql(expression, "end") 4225 end = f"{self.seg('')}{end}" if end else "" 4226 4227 return f"CHANGES ({information}){at_before}{end}" 4228 4229 def pad_sql(self, expression: exp.Pad) -> str: 4230 prefix = "L" if expression.args.get("is_left") else "R" 4231 4232 fill_pattern = self.sql(expression, "fill_pattern") or None 4233 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4234 fill_pattern = "' '" 4235 4236 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4237 4238 def summarize_sql(self, expression: exp.Summarize) -> str: 4239 table = " TABLE" if expression.args.get("table") else "" 4240 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4241 4242 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4243 generate_series = exp.GenerateSeries(**expression.args) 4244 4245 parent = expression.parent 4246 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4247 parent = parent.parent 4248 4249 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4250 return self.sql(exp.Unnest(expressions=[generate_series])) 4251 4252 if isinstance(parent, exp.Select): 4253 self.unsupported("GenerateSeries projection unnesting is not supported.") 4254 4255 return self.sql(generate_series) 4256 4257 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4258 exprs = expression.expressions 4259 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4260 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4261 else: 4262 rhs = self.expressions(expression) 4263 4264 return self.func(name, expression.this, rhs or None) 4265 4266 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4267 if self.SUPPORTS_CONVERT_TIMEZONE: 4268 return self.function_fallback_sql(expression) 4269 4270 source_tz = expression.args.get("source_tz") 4271 target_tz = expression.args.get("target_tz") 4272 timestamp = expression.args.get("timestamp") 4273 4274 if source_tz and timestamp: 4275 timestamp = exp.AtTimeZone( 4276 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4277 ) 4278 4279 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4280 4281 return self.sql(expr) 4282 4283 def json_sql(self, expression: exp.JSON) -> str: 4284 this = self.sql(expression, "this") 4285 this = f" {this}" if this else "" 4286 4287 _with = expression.args.get("with") 4288 4289 if _with is None: 4290 with_sql = "" 4291 elif not _with: 4292 with_sql = " WITHOUT" 4293 else: 4294 with_sql = " WITH" 4295 4296 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4297 4298 return f"JSON{this}{with_sql}{unique_sql}" 4299 4300 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4301 def _generate_on_options(arg: t.Any) -> str: 4302 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4303 4304 path = self.sql(expression, "path") 4305 returning = self.sql(expression, "returning") 4306 returning = f" RETURNING {returning}" if returning else "" 4307 4308 on_condition = self.sql(expression, "on_condition") 4309 on_condition = f" {on_condition}" if on_condition else "" 4310 4311 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4312 4313 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4314 else_ = "ELSE " if expression.args.get("else_") else "" 4315 condition = self.sql(expression, "expression") 4316 condition = f"WHEN {condition} THEN " if condition else else_ 4317 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4318 return f"{condition}{insert}" 4319 4320 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4321 kind = self.sql(expression, "kind") 4322 expressions = self.seg(self.expressions(expression, sep=" ")) 4323 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4324 return res 4325 4326 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4327 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4328 empty = expression.args.get("empty") 4329 empty = ( 4330 f"DEFAULT {empty} ON EMPTY" 4331 if isinstance(empty, exp.Expression) 4332 else self.sql(expression, "empty") 4333 ) 4334 4335 error = expression.args.get("error") 4336 error = ( 4337 f"DEFAULT {error} ON ERROR" 4338 if isinstance(error, exp.Expression) 4339 else self.sql(expression, "error") 4340 ) 4341 4342 if error and empty: 4343 error = ( 4344 f"{empty} {error}" 4345 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4346 else f"{error} {empty}" 4347 ) 4348 empty = "" 4349 4350 null = self.sql(expression, "null") 4351 4352 return f"{empty}{error}{null}" 4353 4354 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4355 this = self.sql(expression, "this") 4356 path = self.sql(expression, "path") 4357 4358 passing = self.expressions(expression, "passing") 4359 passing = f" PASSING {passing}" if passing else "" 4360 4361 on_condition = self.sql(expression, "on_condition") 4362 on_condition = f" {on_condition}" if on_condition else "" 4363 4364 path = f"{path}{passing}{on_condition}" 4365 4366 return self.func("JSON_EXISTS", this, path) 4367 4368 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4369 array_agg = self.function_fallback_sql(expression) 4370 4371 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4372 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4373 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4374 parent = expression.parent 4375 if isinstance(parent, exp.Filter): 4376 parent_cond = parent.expression.this 4377 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4378 else: 4379 this = expression.this 4380 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4381 if this.find(exp.Column): 4382 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4383 this_sql = ( 4384 self.expressions(this) 4385 if isinstance(this, exp.Distinct) 4386 else self.sql(expression, "this") 4387 ) 4388 4389 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4390 4391 return array_agg 4392 4393 def apply_sql(self, expression: exp.Apply) -> str: 4394 this = self.sql(expression, "this") 4395 expr = self.sql(expression, "expression") 4396 4397 return f"{this} APPLY({expr})" 4398 4399 def grant_sql(self, expression: exp.Grant) -> str: 4400 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4401 4402 kind = self.sql(expression, "kind") 4403 kind = f" {kind}" if kind else "" 4404 4405 securable = self.sql(expression, "securable") 4406 securable = f" {securable}" if securable else "" 4407 4408 principals = self.expressions(expression, key="principals", flat=True) 4409 4410 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4411 4412 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4413 4414 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4415 this = self.sql(expression, "this") 4416 columns = self.expressions(expression, flat=True) 4417 columns = f"({columns})" if columns else "" 4418 4419 return f"{this}{columns}" 4420 4421 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4422 this = self.sql(expression, "this") 4423 4424 kind = self.sql(expression, "kind") 4425 kind = f"{kind} " if kind else "" 4426 4427 return f"{kind}{this}" 4428 4429 def columns_sql(self, expression: exp.Columns): 4430 func = self.function_fallback_sql(expression) 4431 if expression.args.get("unpack"): 4432 func = f"*{func}" 4433 4434 return func 4435 4436 def overlay_sql(self, expression: exp.Overlay): 4437 this = self.sql(expression, "this") 4438 expr = self.sql(expression, "expression") 4439 from_sql = self.sql(expression, "from") 4440 for_sql = self.sql(expression, "for") 4441 for_sql = f" FOR {for_sql}" if for_sql else "" 4442 4443 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4444 4445 @unsupported_args("format") 4446 def todouble_sql(self, expression: exp.ToDouble) -> str: 4447 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4448 4449 def string_sql(self, expression: exp.String) -> str: 4450 this = expression.this 4451 zone = expression.args.get("zone") 4452 4453 if zone: 4454 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4455 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4456 # set for source_tz to transpile the time conversion before the STRING cast 4457 this = exp.ConvertTimezone( 4458 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4459 ) 4460 4461 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4462 4463 def median_sql(self, expression: exp.Median): 4464 if not self.SUPPORTS_MEDIAN: 4465 return self.sql( 4466 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4467 ) 4468 4469 return self.function_fallback_sql(expression) 4470 4471 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4472 filler = self.sql(expression, "this") 4473 filler = f" {filler}" if filler else "" 4474 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4475 return f"TRUNCATE{filler} {with_count}"
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.Dialect, Type[sqlglot.dialects.dialect.Dialect], NoneType] = None)
645 def __init__( 646 self, 647 pretty: t.Optional[bool] = None, 648 identify: str | bool = False, 649 normalize: bool = False, 650 pad: int = 2, 651 indent: int = 2, 652 normalize_functions: t.Optional[str | bool] = None, 653 unsupported_level: ErrorLevel = ErrorLevel.WARN, 654 max_unsupported: int = 3, 655 leading_comma: bool = False, 656 max_text_width: int = 80, 657 comments: bool = True, 658 dialect: DialectType = None, 659 ): 660 import sqlglot 661 from sqlglot.dialects import Dialect 662 663 self.pretty = pretty if pretty is not None else sqlglot.pretty 664 self.identify = identify 665 self.normalize = normalize 666 self.pad = pad 667 self._indent = indent 668 self.unsupported_level = unsupported_level 669 self.max_unsupported = max_unsupported 670 self.leading_comma = leading_comma 671 self.max_text_width = max_text_width 672 self.comments = comments 673 self.dialect = Dialect.get_or_raise(dialect) 674 675 # This is both a Dialect property and a Generator argument, so we prioritize the latter 676 self.normalize_functions = ( 677 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 678 ) 679 680 self.unsupported_messages: t.List[str] = [] 681 self._escaped_quote_end: str = ( 682 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 683 ) 684 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 685 686 self._next_name = name_sequence("_t") 687 688 self._identifier_start = self.dialect.IDENTIFIER_START 689 self._identifier_end = self.dialect.IDENTIFIER_END
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.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.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.CopyGrantsProperty'>: <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.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.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.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.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <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.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TagColumnConstraint'>: <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.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>>}
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.JSONPathRecursive'>, <class 'sqlglot.expressions.JSONPathKey'>, <class 'sqlglot.expressions.JSONPathWildcard'>, <class 'sqlglot.expressions.JSONPathFilter'>, <class 'sqlglot.expressions.JSONPathUnion'>, <class 'sqlglot.expressions.JSONPathSubscript'>, <class 'sqlglot.expressions.JSONPathSelector'>, <class 'sqlglot.expressions.JSONPathSlice'>, <class 'sqlglot.expressions.JSONPathScript'>, <class 'sqlglot.expressions.JSONPathRoot'>}
TYPE_MAPPING =
{<Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY'}
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.EngineProperty'>: <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.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.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <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.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'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Command'>, <class 'sqlglot.expressions.Create'>, <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.Select'>, <class 'sqlglot.expressions.SetOperation'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES =
{<Type.VARCHAR: 'VARCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.CHAR: 'CHAR'>, <Type.NCHAR: 'NCHAR'>}
691 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 692 """ 693 Generates the SQL string corresponding to the given syntax tree. 694 695 Args: 696 expression: The syntax tree. 697 copy: Whether to copy the expression. The generator performs mutations so 698 it is safer to copy. 699 700 Returns: 701 The SQL string corresponding to `expression`. 702 """ 703 if copy: 704 expression = expression.copy() 705 706 expression = self.preprocess(expression) 707 708 self.unsupported_messages = [] 709 sql = self.sql(expression).strip() 710 711 if self.pretty: 712 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 713 714 if self.unsupported_level == ErrorLevel.IGNORE: 715 return sql 716 717 if self.unsupported_level == ErrorLevel.WARN: 718 for msg in self.unsupported_messages: 719 logger.warning(msg) 720 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 721 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 722 723 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:
725 def preprocess(self, expression: exp.Expression) -> exp.Expression: 726 """Apply generic preprocessing transformations to a given expression.""" 727 expression = self._move_ctes_to_top_level(expression) 728 729 if self.ENSURE_BOOLS: 730 from sqlglot.transforms import ensure_bools 731 732 expression = ensure_bools(expression) 733 734 return expression
Apply generic preprocessing transformations to a given expression.
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
763 def maybe_comment( 764 self, 765 sql: str, 766 expression: t.Optional[exp.Expression] = None, 767 comments: t.Optional[t.List[str]] = None, 768 separated: bool = False, 769 ) -> str: 770 comments = ( 771 ((expression and expression.comments) if comments is None else comments) # type: ignore 772 if self.comments 773 else None 774 ) 775 776 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 777 return sql 778 779 comments_sql = " ".join( 780 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 781 ) 782 783 if not comments_sql: 784 return sql 785 786 comments_sql = self._replace_line_breaks(comments_sql) 787 788 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 789 return ( 790 f"{self.sep()}{comments_sql}{sql}" 791 if not sql or sql[0].isspace() 792 else f"{comments_sql}{self.sep()}{sql}" 793 ) 794 795 return f"{sql} {comments_sql}"
797 def wrap(self, expression: exp.Expression | str) -> str: 798 this_sql = ( 799 self.sql(expression) 800 if isinstance(expression, exp.UNWRAPPED_QUERIES) 801 else self.sql(expression, "this") 802 ) 803 if not this_sql: 804 return "()" 805 806 this_sql = self.indent(this_sql, level=1, pad=0) 807 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:
823 def indent( 824 self, 825 sql: str, 826 level: int = 0, 827 pad: t.Optional[int] = None, 828 skip_first: bool = False, 829 skip_last: bool = False, 830 ) -> str: 831 if not self.pretty or not sql: 832 return sql 833 834 pad = self.pad if pad is None else pad 835 lines = sql.split("\n") 836 837 return "\n".join( 838 ( 839 line 840 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 841 else f"{' ' * (level * self._indent + pad)}{line}" 842 ) 843 for i, line in enumerate(lines) 844 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
846 def sql( 847 self, 848 expression: t.Optional[str | exp.Expression], 849 key: t.Optional[str] = None, 850 comment: bool = True, 851 ) -> str: 852 if not expression: 853 return "" 854 855 if isinstance(expression, str): 856 return expression 857 858 if key: 859 value = expression.args.get(key) 860 if value: 861 return self.sql(value) 862 return "" 863 864 transform = self.TRANSFORMS.get(expression.__class__) 865 866 if callable(transform): 867 sql = transform(self, expression) 868 elif isinstance(expression, exp.Expression): 869 exp_handler_name = f"{expression.key}_sql" 870 871 if hasattr(self, exp_handler_name): 872 sql = getattr(self, exp_handler_name)(expression) 873 elif isinstance(expression, exp.Func): 874 sql = self.function_fallback_sql(expression) 875 elif isinstance(expression, exp.Property): 876 sql = self.property_sql(expression) 877 else: 878 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 879 else: 880 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 881 882 return self.maybe_comment(sql, expression) if self.comments and comment else sql
889 def cache_sql(self, expression: exp.Cache) -> str: 890 lazy = " LAZY" if expression.args.get("lazy") else "" 891 table = self.sql(expression, "this") 892 options = expression.args.get("options") 893 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 894 sql = self.sql(expression, "expression") 895 sql = f" AS{self.sep()}{sql}" if sql else "" 896 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 897 return self.prepend_ctes(expression, sql)
899 def characterset_sql(self, expression: exp.CharacterSet) -> str: 900 if isinstance(expression.parent, exp.Cast): 901 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 902 default = "DEFAULT " if expression.args.get("default") else "" 903 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
917 def column_sql(self, expression: exp.Column) -> str: 918 join_mark = " (+)" if expression.args.get("join_mark") else "" 919 920 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 921 join_mark = "" 922 self.unsupported("Outer join syntax using the (+) operator is not supported.") 923 924 return f"{self.column_parts(expression)}{join_mark}"
932 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 933 column = self.sql(expression, "this") 934 kind = self.sql(expression, "kind") 935 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 936 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 937 kind = f"{sep}{kind}" if kind else "" 938 constraints = f" {constraints}" if constraints else "" 939 position = self.sql(expression, "position") 940 position = f" {position}" if position else "" 941 942 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 943 kind = "" 944 945 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
952 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 953 this = self.sql(expression, "this") 954 if expression.args.get("not_null"): 955 persisted = " PERSISTED NOT NULL" 956 elif expression.args.get("persisted"): 957 persisted = " PERSISTED" 958 else: 959 persisted = "" 960 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
973 def generatedasidentitycolumnconstraint_sql( 974 self, expression: exp.GeneratedAsIdentityColumnConstraint 975 ) -> str: 976 this = "" 977 if expression.this is not None: 978 on_null = " ON NULL" if expression.args.get("on_null") else "" 979 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 980 981 start = expression.args.get("start") 982 start = f"START WITH {start}" if start else "" 983 increment = expression.args.get("increment") 984 increment = f" INCREMENT BY {increment}" if increment else "" 985 minvalue = expression.args.get("minvalue") 986 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 987 maxvalue = expression.args.get("maxvalue") 988 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 989 cycle = expression.args.get("cycle") 990 cycle_sql = "" 991 992 if cycle is not None: 993 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 994 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 995 996 sequence_opts = "" 997 if start or increment or cycle_sql: 998 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 999 sequence_opts = f" ({sequence_opts.strip()})" 1000 1001 expr = self.sql(expression, "expression") 1002 expr = f"({expr})" if expr else "IDENTITY" 1003 1004 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1006 def generatedasrowcolumnconstraint_sql( 1007 self, expression: exp.GeneratedAsRowColumnConstraint 1008 ) -> str: 1009 start = "START" if expression.args.get("start") else "END" 1010 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1011 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
transformcolumnconstraint_sql(self, expression: sqlglot.expressions.TransformColumnConstraint) -> str:
def
primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str:
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1030 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1031 this = self.sql(expression, "this") 1032 this = f" {this}" if this else "" 1033 index_type = expression.args.get("index_type") 1034 index_type = f" USING {index_type}" if index_type else "" 1035 on_conflict = self.sql(expression, "on_conflict") 1036 on_conflict = f" {on_conflict}" if on_conflict else "" 1037 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1038 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}"
1043 def create_sql(self, expression: exp.Create) -> str: 1044 kind = self.sql(expression, "kind") 1045 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1046 properties = expression.args.get("properties") 1047 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1048 1049 this = self.createable_sql(expression, properties_locs) 1050 1051 properties_sql = "" 1052 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1053 exp.Properties.Location.POST_WITH 1054 ): 1055 properties_sql = self.sql( 1056 exp.Properties( 1057 expressions=[ 1058 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1059 *properties_locs[exp.Properties.Location.POST_WITH], 1060 ] 1061 ) 1062 ) 1063 1064 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1065 properties_sql = self.sep() + properties_sql 1066 elif not self.pretty: 1067 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1068 properties_sql = f" {properties_sql}" 1069 1070 begin = " BEGIN" if expression.args.get("begin") else "" 1071 end = " END" if expression.args.get("end") else "" 1072 1073 expression_sql = self.sql(expression, "expression") 1074 if expression_sql: 1075 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1076 1077 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1078 postalias_props_sql = "" 1079 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1080 postalias_props_sql = self.properties( 1081 exp.Properties( 1082 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1083 ), 1084 wrapped=False, 1085 ) 1086 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1087 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1088 1089 postindex_props_sql = "" 1090 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1091 postindex_props_sql = self.properties( 1092 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1093 wrapped=False, 1094 prefix=" ", 1095 ) 1096 1097 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1098 indexes = f" {indexes}" if indexes else "" 1099 index_sql = indexes + postindex_props_sql 1100 1101 replace = " OR REPLACE" if expression.args.get("replace") else "" 1102 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1103 unique = " UNIQUE" if expression.args.get("unique") else "" 1104 1105 clustered = expression.args.get("clustered") 1106 if clustered is None: 1107 clustered_sql = "" 1108 elif clustered: 1109 clustered_sql = " CLUSTERED COLUMNSTORE" 1110 else: 1111 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1112 1113 postcreate_props_sql = "" 1114 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1115 postcreate_props_sql = self.properties( 1116 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1117 sep=" ", 1118 prefix=" ", 1119 wrapped=False, 1120 ) 1121 1122 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1123 1124 postexpression_props_sql = "" 1125 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1126 postexpression_props_sql = self.properties( 1127 exp.Properties( 1128 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1129 ), 1130 sep=" ", 1131 prefix=" ", 1132 wrapped=False, 1133 ) 1134 1135 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1136 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1137 no_schema_binding = ( 1138 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1139 ) 1140 1141 clone = self.sql(expression, "clone") 1142 clone = f" {clone}" if clone else "" 1143 1144 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1145 return self.prepend_ctes(expression, expression_sql)
1147 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1148 start = self.sql(expression, "start") 1149 start = f"START WITH {start}" if start else "" 1150 increment = self.sql(expression, "increment") 1151 increment = f" INCREMENT BY {increment}" if increment else "" 1152 minvalue = self.sql(expression, "minvalue") 1153 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1154 maxvalue = self.sql(expression, "maxvalue") 1155 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1156 owned = self.sql(expression, "owned") 1157 owned = f" OWNED BY {owned}" if owned else "" 1158 1159 cache = expression.args.get("cache") 1160 if cache is None: 1161 cache_str = "" 1162 elif cache is True: 1163 cache_str = " CACHE" 1164 else: 1165 cache_str = f" CACHE {cache}" 1166 1167 options = self.expressions(expression, key="options", flat=True, sep=" ") 1168 options = f" {options}" if options else "" 1169 1170 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1172 def clone_sql(self, expression: exp.Clone) -> str: 1173 this = self.sql(expression, "this") 1174 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1175 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1176 return f"{shallow}{keyword} {this}"
1178 def describe_sql(self, expression: exp.Describe) -> str: 1179 style = expression.args.get("style") 1180 style = f" {style}" if style else "" 1181 partition = self.sql(expression, "partition") 1182 partition = f" {partition}" if partition else "" 1183 return f"DESCRIBE{style} {self.sql(expression, 'this')}{partition}"
1205 def cte_sql(self, expression: exp.CTE) -> str: 1206 alias = expression.args.get("alias") 1207 if alias: 1208 alias.add_comments(expression.pop_comments()) 1209 1210 alias_sql = self.sql(expression, "alias") 1211 1212 materialized = expression.args.get("materialized") 1213 if materialized is False: 1214 materialized = "NOT MATERIALIZED " 1215 elif materialized: 1216 materialized = "MATERIALIZED " 1217 1218 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1220 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1221 alias = self.sql(expression, "this") 1222 columns = self.expressions(expression, key="columns", flat=True) 1223 columns = f"({columns})" if columns else "" 1224 1225 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1226 columns = "" 1227 self.unsupported("Named columns are not supported in table alias.") 1228 1229 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1230 alias = self._next_name() 1231 1232 return f"{alias}{columns}"
1252 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1253 this = self.sql(expression, "this") 1254 escape = expression.args.get("escape") 1255 1256 if self.dialect.UNICODE_START: 1257 escape_substitute = r"\\\1" 1258 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1259 else: 1260 escape_substitute = r"\\u\1" 1261 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1262 1263 if escape: 1264 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1265 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1266 else: 1267 escape_pattern = ESCAPED_UNICODE_RE 1268 escape_sql = "" 1269 1270 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1271 this = escape_pattern.sub(escape_substitute, this) 1272 1273 return f"{left_quote}{this}{right_quote}{escape_sql}"
1285 def datatype_sql(self, expression: exp.DataType) -> str: 1286 nested = "" 1287 values = "" 1288 interior = self.expressions(expression, flat=True) 1289 1290 type_value = expression.this 1291 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1292 type_sql = self.sql(expression, "kind") 1293 else: 1294 type_sql = ( 1295 self.TYPE_MAPPING.get(type_value, type_value.value) 1296 if isinstance(type_value, exp.DataType.Type) 1297 else type_value 1298 ) 1299 1300 if interior: 1301 if expression.args.get("nested"): 1302 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1303 if expression.args.get("values") is not None: 1304 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1305 values = self.expressions(expression, key="values", flat=True) 1306 values = f"{delimiters[0]}{values}{delimiters[1]}" 1307 elif type_value == exp.DataType.Type.INTERVAL: 1308 nested = f" {interior}" 1309 else: 1310 nested = f"({interior})" 1311 1312 type_sql = f"{type_sql}{nested}{values}" 1313 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1314 exp.DataType.Type.TIMETZ, 1315 exp.DataType.Type.TIMESTAMPTZ, 1316 ): 1317 type_sql = f"{type_sql} WITH TIME ZONE" 1318 1319 return type_sql
1321 def directory_sql(self, expression: exp.Directory) -> str: 1322 local = "LOCAL " if expression.args.get("local") else "" 1323 row_format = self.sql(expression, "row_format") 1324 row_format = f" {row_format}" if row_format else "" 1325 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1327 def delete_sql(self, expression: exp.Delete) -> str: 1328 this = self.sql(expression, "this") 1329 this = f" FROM {this}" if this else "" 1330 using = self.sql(expression, "using") 1331 using = f" USING {using}" if using else "" 1332 cluster = self.sql(expression, "cluster") 1333 cluster = f" {cluster}" if cluster else "" 1334 where = self.sql(expression, "where") 1335 returning = self.sql(expression, "returning") 1336 limit = self.sql(expression, "limit") 1337 tables = self.expressions(expression, key="tables") 1338 tables = f" {tables}" if tables else "" 1339 if self.RETURNING_END: 1340 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1341 else: 1342 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1343 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1345 def drop_sql(self, expression: exp.Drop) -> str: 1346 this = self.sql(expression, "this") 1347 expressions = self.expressions(expression, flat=True) 1348 expressions = f" ({expressions})" if expressions else "" 1349 kind = expression.args["kind"] 1350 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1351 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1352 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1353 on_cluster = self.sql(expression, "cluster") 1354 on_cluster = f" {on_cluster}" if on_cluster else "" 1355 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1356 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1357 cascade = " CASCADE" if expression.args.get("cascade") else "" 1358 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1359 purge = " PURGE" if expression.args.get("purge") else "" 1360 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1362 def set_operation(self, expression: exp.SetOperation) -> str: 1363 op_type = type(expression) 1364 op_name = op_type.key.upper() 1365 1366 distinct = expression.args.get("distinct") 1367 if ( 1368 distinct is False 1369 and op_type in (exp.Except, exp.Intersect) 1370 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1371 ): 1372 self.unsupported(f"{op_name} ALL is not supported") 1373 1374 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1375 1376 if distinct is None: 1377 distinct = default_distinct 1378 if distinct is None: 1379 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1380 1381 if distinct is default_distinct: 1382 kind = "" 1383 else: 1384 kind = " DISTINCT" if distinct else " ALL" 1385 1386 by_name = " BY NAME" if expression.args.get("by_name") else "" 1387 return f"{op_name}{kind}{by_name}"
1389 def set_operations(self, expression: exp.SetOperation) -> str: 1390 if not self.SET_OP_MODIFIERS: 1391 limit = expression.args.get("limit") 1392 order = expression.args.get("order") 1393 1394 if limit or order: 1395 select = self._move_ctes_to_top_level( 1396 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1397 ) 1398 1399 if limit: 1400 select = select.limit(limit.pop(), copy=False) 1401 if order: 1402 select = select.order_by(order.pop(), copy=False) 1403 return self.sql(select) 1404 1405 sqls: t.List[str] = [] 1406 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1407 1408 while stack: 1409 node = stack.pop() 1410 1411 if isinstance(node, exp.SetOperation): 1412 stack.append(node.expression) 1413 stack.append( 1414 self.maybe_comment( 1415 self.set_operation(node), comments=node.comments, separated=True 1416 ) 1417 ) 1418 stack.append(node.this) 1419 else: 1420 sqls.append(self.sql(node)) 1421 1422 this = self.sep().join(sqls) 1423 this = self.query_modifiers(expression, this) 1424 return self.prepend_ctes(expression, this)
1426 def fetch_sql(self, expression: exp.Fetch) -> str: 1427 direction = expression.args.get("direction") 1428 direction = f" {direction}" if direction else "" 1429 count = self.sql(expression, "count") 1430 count = f" {count}" if count else "" 1431 if expression.args.get("percent"): 1432 count = f"{count} PERCENT" 1433 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY" 1434 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}"
1436 def filter_sql(self, expression: exp.Filter) -> str: 1437 if self.AGGREGATE_FILTER_SUPPORTED: 1438 this = self.sql(expression, "this") 1439 where = self.sql(expression, "expression").strip() 1440 return f"{this} FILTER({where})" 1441 1442 agg = expression.this 1443 agg_arg = agg.this 1444 cond = expression.expression.this 1445 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1446 return self.sql(agg)
1455 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1456 using = self.sql(expression, "using") 1457 using = f" USING {using}" if using else "" 1458 columns = self.expressions(expression, key="columns", flat=True) 1459 columns = f"({columns})" if columns else "" 1460 partition_by = self.expressions(expression, key="partition_by", flat=True) 1461 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1462 where = self.sql(expression, "where") 1463 include = self.expressions(expression, key="include", flat=True) 1464 if include: 1465 include = f" INCLUDE ({include})" 1466 with_storage = self.expressions(expression, key="with_storage", flat=True) 1467 with_storage = f" WITH ({with_storage})" if with_storage else "" 1468 tablespace = self.sql(expression, "tablespace") 1469 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1470 on = self.sql(expression, "on") 1471 on = f" ON {on}" if on else "" 1472 1473 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1475 def index_sql(self, expression: exp.Index) -> str: 1476 unique = "UNIQUE " if expression.args.get("unique") else "" 1477 primary = "PRIMARY " if expression.args.get("primary") else "" 1478 amp = "AMP " if expression.args.get("amp") else "" 1479 name = self.sql(expression, "this") 1480 name = f"{name} " if name else "" 1481 table = self.sql(expression, "table") 1482 table = f"{self.INDEX_ON} {table}" if table else "" 1483 1484 index = "INDEX " if not table else "" 1485 1486 params = self.sql(expression, "params") 1487 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1489 def identifier_sql(self, expression: exp.Identifier) -> str: 1490 text = expression.name 1491 lower = text.lower() 1492 text = lower if self.normalize and not expression.quoted else text 1493 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1494 if ( 1495 expression.quoted 1496 or self.dialect.can_identify(text, self.identify) 1497 or lower in self.RESERVED_KEYWORDS 1498 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1499 ): 1500 text = f"{self._identifier_start}{text}{self._identifier_end}" 1501 return text
1516 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1517 input_format = self.sql(expression, "input_format") 1518 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1519 output_format = self.sql(expression, "output_format") 1520 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1521 return self.sep().join((input_format, output_format))
1530 def properties_sql(self, expression: exp.Properties) -> str: 1531 root_properties = [] 1532 with_properties = [] 1533 1534 for p in expression.expressions: 1535 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1536 if p_loc == exp.Properties.Location.POST_WITH: 1537 with_properties.append(p) 1538 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1539 root_properties.append(p) 1540 1541 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1542 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1543 1544 if root_props and with_props and not self.pretty: 1545 with_props = " " + with_props 1546 1547 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1554 def properties( 1555 self, 1556 properties: exp.Properties, 1557 prefix: str = "", 1558 sep: str = ", ", 1559 suffix: str = "", 1560 wrapped: bool = True, 1561 ) -> str: 1562 if properties.expressions: 1563 expressions = self.expressions(properties, sep=sep, indent=False) 1564 if expressions: 1565 expressions = self.wrap(expressions) if wrapped else expressions 1566 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1567 return ""
1572 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1573 properties_locs = defaultdict(list) 1574 for p in properties.expressions: 1575 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1576 if p_loc != exp.Properties.Location.UNSUPPORTED: 1577 properties_locs[p_loc].append(p) 1578 else: 1579 self.unsupported(f"Unsupported property {p.key}") 1580 1581 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1588 def property_sql(self, expression: exp.Property) -> str: 1589 property_cls = expression.__class__ 1590 if property_cls == exp.Property: 1591 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1592 1593 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1594 if not property_name: 1595 self.unsupported(f"Unsupported property {expression.key}") 1596 1597 return f"{property_name}={self.sql(expression, 'this')}"
1599 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1600 if self.SUPPORTS_CREATE_TABLE_LIKE: 1601 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1602 options = f" {options}" if options else "" 1603 1604 like = f"LIKE {self.sql(expression, 'this')}{options}" 1605 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1606 like = f"({like})" 1607 1608 return like 1609 1610 if expression.expressions: 1611 self.unsupported("Transpilation of LIKE property options is unsupported") 1612 1613 select = exp.select("*").from_(expression.this).limit(0) 1614 return f"AS {self.sql(select)}"
1621 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1622 no = "NO " if expression.args.get("no") else "" 1623 local = expression.args.get("local") 1624 local = f"{local} " if local else "" 1625 dual = "DUAL " if expression.args.get("dual") else "" 1626 before = "BEFORE " if expression.args.get("before") else "" 1627 after = "AFTER " if expression.args.get("after") else "" 1628 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1644 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1645 if expression.args.get("no"): 1646 return "NO MERGEBLOCKRATIO" 1647 if expression.args.get("default"): 1648 return "DEFAULT MERGEBLOCKRATIO" 1649 1650 percent = " PERCENT" if expression.args.get("percent") else "" 1651 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1653 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1654 default = expression.args.get("default") 1655 minimum = expression.args.get("minimum") 1656 maximum = expression.args.get("maximum") 1657 if default or minimum or maximum: 1658 if default: 1659 prop = "DEFAULT" 1660 elif minimum: 1661 prop = "MINIMUM" 1662 else: 1663 prop = "MAXIMUM" 1664 return f"{prop} DATABLOCKSIZE" 1665 units = expression.args.get("units") 1666 units = f" {units}" if units else "" 1667 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1669 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1670 autotemp = expression.args.get("autotemp") 1671 always = expression.args.get("always") 1672 default = expression.args.get("default") 1673 manual = expression.args.get("manual") 1674 never = expression.args.get("never") 1675 1676 if autotemp is not None: 1677 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1678 elif always: 1679 prop = "ALWAYS" 1680 elif default: 1681 prop = "DEFAULT" 1682 elif manual: 1683 prop = "MANUAL" 1684 elif never: 1685 prop = "NEVER" 1686 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1688 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1689 no = expression.args.get("no") 1690 no = " NO" if no else "" 1691 concurrent = expression.args.get("concurrent") 1692 concurrent = " CONCURRENT" if concurrent else "" 1693 target = self.sql(expression, "target") 1694 target = f" {target}" if target else "" 1695 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1697 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1698 if isinstance(expression.this, list): 1699 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1700 if expression.this: 1701 modulus = self.sql(expression, "this") 1702 remainder = self.sql(expression, "expression") 1703 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1704 1705 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1706 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1707 return f"FROM ({from_expressions}) TO ({to_expressions})"
1709 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1710 this = self.sql(expression, "this") 1711 1712 for_values_or_default = expression.expression 1713 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1714 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1715 else: 1716 for_values_or_default = " DEFAULT" 1717 1718 return f"PARTITION OF {this}{for_values_or_default}"
1720 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1721 kind = expression.args.get("kind") 1722 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1723 for_or_in = expression.args.get("for_or_in") 1724 for_or_in = f" {for_or_in}" if for_or_in else "" 1725 lock_type = expression.args.get("lock_type") 1726 override = " OVERRIDE" if expression.args.get("override") else "" 1727 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1729 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1730 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1731 statistics = expression.args.get("statistics") 1732 statistics_sql = "" 1733 if statistics is not None: 1734 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1735 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1737 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1738 this = self.sql(expression, "this") 1739 this = f"HISTORY_TABLE={this}" if this else "" 1740 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1741 data_consistency = ( 1742 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1743 ) 1744 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1745 retention_period = ( 1746 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1747 ) 1748 1749 if this: 1750 on_sql = self.func("ON", this, data_consistency, retention_period) 1751 else: 1752 on_sql = "ON" if expression.args.get("on") else "OFF" 1753 1754 sql = f"SYSTEM_VERSIONING={on_sql}" 1755 1756 return f"WITH({sql})" if expression.args.get("with") else sql
1758 def insert_sql(self, expression: exp.Insert) -> str: 1759 hint = self.sql(expression, "hint") 1760 overwrite = expression.args.get("overwrite") 1761 1762 if isinstance(expression.this, exp.Directory): 1763 this = " OVERWRITE" if overwrite else " INTO" 1764 else: 1765 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1766 1767 stored = self.sql(expression, "stored") 1768 stored = f" {stored}" if stored else "" 1769 alternative = expression.args.get("alternative") 1770 alternative = f" OR {alternative}" if alternative else "" 1771 ignore = " IGNORE" if expression.args.get("ignore") else "" 1772 is_function = expression.args.get("is_function") 1773 if is_function: 1774 this = f"{this} FUNCTION" 1775 this = f"{this} {self.sql(expression, 'this')}" 1776 1777 exists = " IF EXISTS" if expression.args.get("exists") else "" 1778 where = self.sql(expression, "where") 1779 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1780 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1781 on_conflict = self.sql(expression, "conflict") 1782 on_conflict = f" {on_conflict}" if on_conflict else "" 1783 by_name = " BY NAME" if expression.args.get("by_name") else "" 1784 returning = self.sql(expression, "returning") 1785 1786 if self.RETURNING_END: 1787 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1788 else: 1789 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1790 1791 partition_by = self.sql(expression, "partition") 1792 partition_by = f" {partition_by}" if partition_by else "" 1793 settings = self.sql(expression, "settings") 1794 settings = f" {settings}" if settings else "" 1795 1796 source = self.sql(expression, "source") 1797 source = f"TABLE {source}" if source else "" 1798 1799 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1800 return self.prepend_ctes(expression, sql)
1818 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1819 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1820 1821 constraint = self.sql(expression, "constraint") 1822 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1823 1824 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1825 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1826 action = self.sql(expression, "action") 1827 1828 expressions = self.expressions(expression, flat=True) 1829 if expressions: 1830 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1831 expressions = f" {set_keyword}{expressions}" 1832 1833 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1838 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1839 fields = self.sql(expression, "fields") 1840 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1841 escaped = self.sql(expression, "escaped") 1842 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1843 items = self.sql(expression, "collection_items") 1844 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1845 keys = self.sql(expression, "map_keys") 1846 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1847 lines = self.sql(expression, "lines") 1848 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1849 null = self.sql(expression, "null") 1850 null = f" NULL DEFINED AS {null}" if null else "" 1851 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1879 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1880 table = self.table_parts(expression) 1881 only = "ONLY " if expression.args.get("only") else "" 1882 partition = self.sql(expression, "partition") 1883 partition = f" {partition}" if partition else "" 1884 version = self.sql(expression, "version") 1885 version = f" {version}" if version else "" 1886 alias = self.sql(expression, "alias") 1887 alias = f"{sep}{alias}" if alias else "" 1888 1889 sample = self.sql(expression, "sample") 1890 if self.dialect.ALIAS_POST_TABLESAMPLE: 1891 sample_pre_alias = sample 1892 sample_post_alias = "" 1893 else: 1894 sample_pre_alias = "" 1895 sample_post_alias = sample 1896 1897 hints = self.expressions(expression, key="hints", sep=" ") 1898 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1899 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 1900 joins = self.indent( 1901 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 1902 ) 1903 laterals = self.expressions(expression, key="laterals", sep="") 1904 1905 file_format = self.sql(expression, "format") 1906 if file_format: 1907 pattern = self.sql(expression, "pattern") 1908 pattern = f", PATTERN => {pattern}" if pattern else "" 1909 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 1910 1911 ordinality = expression.args.get("ordinality") or "" 1912 if ordinality: 1913 ordinality = f" WITH ORDINALITY{alias}" 1914 alias = "" 1915 1916 when = self.sql(expression, "when") 1917 if when: 1918 table = f"{table} {when}" 1919 1920 changes = self.sql(expression, "changes") 1921 changes = f" {changes}" if changes else "" 1922 1923 rows_from = self.expressions(expression, key="rows_from") 1924 if rows_from: 1925 table = f"ROWS FROM {self.wrap(rows_from)}" 1926 1927 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
1929 def tablesample_sql( 1930 self, 1931 expression: exp.TableSample, 1932 tablesample_keyword: t.Optional[str] = None, 1933 ) -> str: 1934 method = self.sql(expression, "method") 1935 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 1936 numerator = self.sql(expression, "bucket_numerator") 1937 denominator = self.sql(expression, "bucket_denominator") 1938 field = self.sql(expression, "bucket_field") 1939 field = f" ON {field}" if field else "" 1940 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 1941 seed = self.sql(expression, "seed") 1942 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 1943 1944 size = self.sql(expression, "size") 1945 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 1946 size = f"{size} ROWS" 1947 1948 percent = self.sql(expression, "percent") 1949 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 1950 percent = f"{percent} PERCENT" 1951 1952 expr = f"{bucket}{percent}{size}" 1953 if self.TABLESAMPLE_REQUIRES_PARENS: 1954 expr = f"({expr})" 1955 1956 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
1958 def pivot_sql(self, expression: exp.Pivot) -> str: 1959 expressions = self.expressions(expression, flat=True) 1960 1961 if expression.this: 1962 this = self.sql(expression, "this") 1963 if not expressions: 1964 return f"UNPIVOT {this}" 1965 1966 on = f"{self.seg('ON')} {expressions}" 1967 using = self.expressions(expression, key="using", flat=True) 1968 using = f"{self.seg('USING')} {using}" if using else "" 1969 group = self.sql(expression, "group") 1970 return f"PIVOT {this}{on}{using}{group}" 1971 1972 alias = self.sql(expression, "alias") 1973 alias = f" AS {alias}" if alias else "" 1974 direction = self.seg("UNPIVOT" if expression.unpivot else "PIVOT") 1975 1976 field = self.sql(expression, "field") 1977 1978 include_nulls = expression.args.get("include_nulls") 1979 if include_nulls is not None: 1980 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 1981 else: 1982 nulls = "" 1983 1984 default_on_null = self.sql(expression, "default_on_null") 1985 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 1986 return f"{direction}{nulls}({expressions} FOR {field}{default_on_null}){alias}"
1997 def update_sql(self, expression: exp.Update) -> str: 1998 this = self.sql(expression, "this") 1999 set_sql = self.expressions(expression, flat=True) 2000 from_sql = self.sql(expression, "from") 2001 where_sql = self.sql(expression, "where") 2002 returning = self.sql(expression, "returning") 2003 order = self.sql(expression, "order") 2004 limit = self.sql(expression, "limit") 2005 if self.RETURNING_END: 2006 expression_sql = f"{from_sql}{where_sql}{returning}" 2007 else: 2008 expression_sql = f"{returning}{from_sql}{where_sql}" 2009 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2010 return self.prepend_ctes(expression, sql)
2012 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2013 values_as_table = values_as_table and self.VALUES_AS_TABLE 2014 2015 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2016 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2017 args = self.expressions(expression) 2018 alias = self.sql(expression, "alias") 2019 values = f"VALUES{self.seg('')}{args}" 2020 values = ( 2021 f"({values})" 2022 if self.WRAP_DERIVED_VALUES 2023 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2024 else values 2025 ) 2026 return f"{values} AS {alias}" if alias else values 2027 2028 # Converts `VALUES...` expression into a series of select unions. 2029 alias_node = expression.args.get("alias") 2030 column_names = alias_node and alias_node.columns 2031 2032 selects: t.List[exp.Query] = [] 2033 2034 for i, tup in enumerate(expression.expressions): 2035 row = tup.expressions 2036 2037 if i == 0 and column_names: 2038 row = [ 2039 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2040 ] 2041 2042 selects.append(exp.Select(expressions=row)) 2043 2044 if self.pretty: 2045 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2046 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2047 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2048 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2049 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2050 2051 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2052 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2053 return f"({unions}){alias}"
2058 @unsupported_args("expressions") 2059 def into_sql(self, expression: exp.Into) -> str: 2060 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2061 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2062 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2079 def group_sql(self, expression: exp.Group) -> str: 2080 group_by_all = expression.args.get("all") 2081 if group_by_all is True: 2082 modifier = " ALL" 2083 elif group_by_all is False: 2084 modifier = " DISTINCT" 2085 else: 2086 modifier = "" 2087 2088 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2089 2090 grouping_sets = self.expressions(expression, key="grouping_sets") 2091 cube = self.expressions(expression, key="cube") 2092 rollup = self.expressions(expression, key="rollup") 2093 2094 groupings = csv( 2095 self.seg(grouping_sets) if grouping_sets else "", 2096 self.seg(cube) if cube else "", 2097 self.seg(rollup) if rollup else "", 2098 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2099 sep=self.GROUPINGS_SEP, 2100 ) 2101 2102 if ( 2103 expression.expressions 2104 and groupings 2105 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2106 ): 2107 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2108 2109 return f"{group_by}{groupings}"
2115 def connect_sql(self, expression: exp.Connect) -> str: 2116 start = self.sql(expression, "start") 2117 start = self.seg(f"START WITH {start}") if start else "" 2118 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2119 connect = self.sql(expression, "connect") 2120 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2121 return start + connect
2126 def join_sql(self, expression: exp.Join) -> str: 2127 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2128 side = None 2129 else: 2130 side = expression.side 2131 2132 op_sql = " ".join( 2133 op 2134 for op in ( 2135 expression.method, 2136 "GLOBAL" if expression.args.get("global") else None, 2137 side, 2138 expression.kind, 2139 expression.hint if self.JOIN_HINTS else None, 2140 ) 2141 if op 2142 ) 2143 match_cond = self.sql(expression, "match_condition") 2144 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2145 on_sql = self.sql(expression, "on") 2146 using = expression.args.get("using") 2147 2148 if not on_sql and using: 2149 on_sql = csv(*(self.sql(column) for column in using)) 2150 2151 this = expression.this 2152 this_sql = self.sql(this) 2153 2154 exprs = self.expressions(expression) 2155 if exprs: 2156 this_sql = f"{this_sql},{self.seg(exprs)}" 2157 2158 if on_sql: 2159 on_sql = self.indent(on_sql, skip_first=True) 2160 space = self.seg(" " * self.pad) if self.pretty else " " 2161 if using: 2162 on_sql = f"{space}USING ({on_sql})" 2163 else: 2164 on_sql = f"{space}ON {on_sql}" 2165 elif not op_sql: 2166 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2167 return f" {this_sql}" 2168 2169 return f", {this_sql}" 2170 2171 if op_sql != "STRAIGHT_JOIN": 2172 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2173 2174 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}"
2181 def lateral_op(self, expression: exp.Lateral) -> str: 2182 cross_apply = expression.args.get("cross_apply") 2183 2184 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2185 if cross_apply is True: 2186 op = "INNER JOIN " 2187 elif cross_apply is False: 2188 op = "LEFT JOIN " 2189 else: 2190 op = "" 2191 2192 return f"{op}LATERAL"
2194 def lateral_sql(self, expression: exp.Lateral) -> str: 2195 this = self.sql(expression, "this") 2196 2197 if expression.args.get("view"): 2198 alias = expression.args["alias"] 2199 columns = self.expressions(alias, key="columns", flat=True) 2200 table = f" {alias.name}" if alias.name else "" 2201 columns = f" AS {columns}" if columns else "" 2202 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2203 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2204 2205 alias = self.sql(expression, "alias") 2206 alias = f" AS {alias}" if alias else "" 2207 return f"{self.lateral_op(expression)} {this}{alias}"
2209 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2210 this = self.sql(expression, "this") 2211 2212 args = [ 2213 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2214 for e in (expression.args.get(k) for k in ("offset", "expression")) 2215 if e 2216 ] 2217 2218 args_sql = ", ".join(self.sql(e) for e in args) 2219 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2220 expressions = self.expressions(expression, flat=True) 2221 expressions = f" BY {expressions}" if expressions else "" 2222 2223 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{expressions}"
2225 def offset_sql(self, expression: exp.Offset) -> str: 2226 this = self.sql(expression, "this") 2227 value = expression.expression 2228 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2229 expressions = self.expressions(expression, flat=True) 2230 expressions = f" BY {expressions}" if expressions else "" 2231 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2233 def setitem_sql(self, expression: exp.SetItem) -> str: 2234 kind = self.sql(expression, "kind") 2235 kind = f"{kind} " if kind else "" 2236 this = self.sql(expression, "this") 2237 expressions = self.expressions(expression) 2238 collate = self.sql(expression, "collate") 2239 collate = f" COLLATE {collate}" if collate else "" 2240 global_ = "GLOBAL " if expression.args.get("global") else "" 2241 return f"{global_}{kind}{this}{expressions}{collate}"
2243 def set_sql(self, expression: exp.Set) -> str: 2244 expressions = ( 2245 f" {self.expressions(expression, flat=True)}" if expression.expressions else "" 2246 ) 2247 tag = " TAG" if expression.args.get("tag") else "" 2248 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2253 def lock_sql(self, expression: exp.Lock) -> str: 2254 if not self.LOCKING_READS_SUPPORTED: 2255 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2256 return "" 2257 2258 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2259 expressions = self.expressions(expression, flat=True) 2260 expressions = f" OF {expressions}" if expressions else "" 2261 wait = expression.args.get("wait") 2262 2263 if wait is not None: 2264 if isinstance(wait, exp.Literal): 2265 wait = f" WAIT {self.sql(wait)}" 2266 else: 2267 wait = " NOWAIT" if wait else " SKIP LOCKED" 2268 2269 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2277 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2278 if self.dialect.ESCAPED_SEQUENCES: 2279 to_escaped = self.dialect.ESCAPED_SEQUENCES 2280 text = "".join( 2281 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2282 ) 2283 2284 return self._replace_line_breaks(text).replace( 2285 self.dialect.QUOTE_END, self._escaped_quote_end 2286 )
2288 def loaddata_sql(self, expression: exp.LoadData) -> str: 2289 local = " LOCAL" if expression.args.get("local") else "" 2290 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2291 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2292 this = f" INTO TABLE {self.sql(expression, 'this')}" 2293 partition = self.sql(expression, "partition") 2294 partition = f" {partition}" if partition else "" 2295 input_format = self.sql(expression, "input_format") 2296 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2297 serde = self.sql(expression, "serde") 2298 serde = f" SERDE {serde}" if serde else "" 2299 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2307 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2308 this = self.sql(expression, "this") 2309 this = f"{this} " if this else this 2310 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2311 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2313 def withfill_sql(self, expression: exp.WithFill) -> str: 2314 from_sql = self.sql(expression, "from") 2315 from_sql = f" FROM {from_sql}" if from_sql else "" 2316 to_sql = self.sql(expression, "to") 2317 to_sql = f" TO {to_sql}" if to_sql else "" 2318 step_sql = self.sql(expression, "step") 2319 step_sql = f" STEP {step_sql}" if step_sql else "" 2320 interpolated_values = [ 2321 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2322 if isinstance(e, exp.Alias) 2323 else self.sql(e, "this") 2324 for e in expression.args.get("interpolate") or [] 2325 ] 2326 interpolate = ( 2327 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2328 ) 2329 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2340 def ordered_sql(self, expression: exp.Ordered) -> str: 2341 desc = expression.args.get("desc") 2342 asc = not desc 2343 2344 nulls_first = expression.args.get("nulls_first") 2345 nulls_last = not nulls_first 2346 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2347 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2348 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2349 2350 this = self.sql(expression, "this") 2351 2352 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2353 nulls_sort_change = "" 2354 if nulls_first and ( 2355 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2356 ): 2357 nulls_sort_change = " NULLS FIRST" 2358 elif ( 2359 nulls_last 2360 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2361 and not nulls_are_last 2362 ): 2363 nulls_sort_change = " NULLS LAST" 2364 2365 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2366 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2367 window = expression.find_ancestor(exp.Window, exp.Select) 2368 if isinstance(window, exp.Window) and window.args.get("spec"): 2369 self.unsupported( 2370 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2371 ) 2372 nulls_sort_change = "" 2373 elif ( 2374 self.NULL_ORDERING_SUPPORTED is False 2375 and (isinstance(expression.find_ancestor(exp.AggFunc, exp.Select), exp.AggFunc)) 2376 and ( 2377 (asc and nulls_sort_change == " NULLS LAST") 2378 or (desc and nulls_sort_change == " NULLS FIRST") 2379 ) 2380 ): 2381 self.unsupported( 2382 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2383 ) 2384 nulls_sort_change = "" 2385 elif self.NULL_ORDERING_SUPPORTED is None: 2386 if expression.this.is_int: 2387 self.unsupported( 2388 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2389 ) 2390 elif not isinstance(expression.this, exp.Rand): 2391 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2392 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2393 nulls_sort_change = "" 2394 2395 with_fill = self.sql(expression, "with_fill") 2396 with_fill = f" {with_fill}" if with_fill else "" 2397 2398 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2408 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2409 partition = self.partition_by_sql(expression) 2410 order = self.sql(expression, "order") 2411 measures = self.expressions(expression, key="measures") 2412 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2413 rows = self.sql(expression, "rows") 2414 rows = self.seg(rows) if rows else "" 2415 after = self.sql(expression, "after") 2416 after = self.seg(after) if after else "" 2417 pattern = self.sql(expression, "pattern") 2418 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2419 definition_sqls = [ 2420 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2421 for definition in expression.args.get("define", []) 2422 ] 2423 definitions = self.expressions(sqls=definition_sqls) 2424 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2425 body = "".join( 2426 ( 2427 partition, 2428 order, 2429 measures, 2430 rows, 2431 after, 2432 pattern, 2433 define, 2434 ) 2435 ) 2436 alias = self.sql(expression, "alias") 2437 alias = f" {alias}" if alias else "" 2438 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2440 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2441 limit = expression.args.get("limit") 2442 2443 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2444 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2445 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2446 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2447 2448 return csv( 2449 *sqls, 2450 *[self.sql(join) for join in expression.args.get("joins") or []], 2451 self.sql(expression, "connect"), 2452 self.sql(expression, "match"), 2453 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2454 self.sql(expression, "prewhere"), 2455 self.sql(expression, "where"), 2456 self.sql(expression, "group"), 2457 self.sql(expression, "having"), 2458 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2459 self.sql(expression, "order"), 2460 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2461 *self.after_limit_modifiers(expression), 2462 self.options_modifier(expression), 2463 sep="", 2464 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2473 def offset_limit_modifiers( 2474 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2475 ) -> t.List[str]: 2476 return [ 2477 self.sql(expression, "offset") if fetch else self.sql(limit), 2478 self.sql(limit) if fetch else self.sql(expression, "offset"), 2479 ]
2486 def select_sql(self, expression: exp.Select) -> str: 2487 into = expression.args.get("into") 2488 if not self.SUPPORTS_SELECT_INTO and into: 2489 into.pop() 2490 2491 hint = self.sql(expression, "hint") 2492 distinct = self.sql(expression, "distinct") 2493 distinct = f" {distinct}" if distinct else "" 2494 kind = self.sql(expression, "kind") 2495 2496 limit = expression.args.get("limit") 2497 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2498 top = self.limit_sql(limit, top=True) 2499 limit.pop() 2500 else: 2501 top = "" 2502 2503 expressions = self.expressions(expression) 2504 2505 if kind: 2506 if kind in self.SELECT_KINDS: 2507 kind = f" AS {kind}" 2508 else: 2509 if kind == "STRUCT": 2510 expressions = self.expressions( 2511 sqls=[ 2512 self.sql( 2513 exp.Struct( 2514 expressions=[ 2515 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2516 if isinstance(e, exp.Alias) 2517 else e 2518 for e in expression.expressions 2519 ] 2520 ) 2521 ) 2522 ] 2523 ) 2524 kind = "" 2525 2526 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2527 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2528 2529 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2530 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2531 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2532 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2533 sql = self.query_modifiers( 2534 expression, 2535 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2536 self.sql(expression, "into", comment=False), 2537 self.sql(expression, "from", comment=False), 2538 ) 2539 2540 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2541 if expression.args.get("with"): 2542 sql = self.maybe_comment(sql, expression) 2543 expression.pop_comments() 2544 2545 sql = self.prepend_ctes(expression, sql) 2546 2547 if not self.SUPPORTS_SELECT_INTO and into: 2548 if into.args.get("temporary"): 2549 table_kind = " TEMPORARY" 2550 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2551 table_kind = " UNLOGGED" 2552 else: 2553 table_kind = "" 2554 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2555 2556 return sql
2568 def star_sql(self, expression: exp.Star) -> str: 2569 except_ = self.expressions(expression, key="except", flat=True) 2570 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2571 replace = self.expressions(expression, key="replace", flat=True) 2572 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2573 rename = self.expressions(expression, key="rename", flat=True) 2574 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2575 return f"*{except_}{replace}{rename}"
2591 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2592 alias = self.sql(expression, "alias") 2593 alias = f"{sep}{alias}" if alias else "" 2594 sample = self.sql(expression, "sample") 2595 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2596 alias = f"{sample}{alias}" 2597 2598 # Set to None so it's not generated again by self.query_modifiers() 2599 expression.set("sample", None) 2600 2601 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2602 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2603 return self.prepend_ctes(expression, sql)
2609 def unnest_sql(self, expression: exp.Unnest) -> str: 2610 args = self.expressions(expression, flat=True) 2611 2612 alias = expression.args.get("alias") 2613 offset = expression.args.get("offset") 2614 2615 if self.UNNEST_WITH_ORDINALITY: 2616 if alias and isinstance(offset, exp.Expression): 2617 alias.append("columns", offset) 2618 2619 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2620 columns = alias.columns 2621 alias = self.sql(columns[0]) if columns else "" 2622 else: 2623 alias = self.sql(alias) 2624 2625 alias = f" AS {alias}" if alias else alias 2626 if self.UNNEST_WITH_ORDINALITY: 2627 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2628 else: 2629 if isinstance(offset, exp.Expression): 2630 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2631 elif offset: 2632 suffix = f"{alias} WITH OFFSET" 2633 else: 2634 suffix = alias 2635 2636 return f"UNNEST({args}){suffix}"
2645 def window_sql(self, expression: exp.Window) -> str: 2646 this = self.sql(expression, "this") 2647 partition = self.partition_by_sql(expression) 2648 order = expression.args.get("order") 2649 order = self.order_sql(order, flat=True) if order else "" 2650 spec = self.sql(expression, "spec") 2651 alias = self.sql(expression, "alias") 2652 over = self.sql(expression, "over") or "OVER" 2653 2654 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2655 2656 first = expression.args.get("first") 2657 if first is None: 2658 first = "" 2659 else: 2660 first = "FIRST" if first else "LAST" 2661 2662 if not partition and not order and not spec and alias: 2663 return f"{this} {alias}" 2664 2665 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 2666 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2672 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2673 kind = self.sql(expression, "kind") 2674 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2675 end = ( 2676 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2677 or "CURRENT ROW" 2678 ) 2679 return f"{kind} BETWEEN {start} AND {end}"
def
bracket_offset_expressions( self, expression: sqlglot.expressions.Bracket, index_offset: Optional[int] = None) -> List[sqlglot.expressions.Expression]:
2692 def bracket_offset_expressions( 2693 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2694 ) -> t.List[exp.Expression]: 2695 return apply_index_offset( 2696 expression.this, 2697 expression.expressions, 2698 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2699 )
2709 def any_sql(self, expression: exp.Any) -> str: 2710 this = self.sql(expression, "this") 2711 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2712 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2713 this = self.wrap(this) 2714 return f"ANY{this}" 2715 return f"ANY {this}"
2720 def case_sql(self, expression: exp.Case) -> str: 2721 this = self.sql(expression, "this") 2722 statements = [f"CASE {this}" if this else "CASE"] 2723 2724 for e in expression.args["ifs"]: 2725 statements.append(f"WHEN {self.sql(e, 'this')}") 2726 statements.append(f"THEN {self.sql(e, 'true')}") 2727 2728 default = self.sql(expression, "default") 2729 2730 if default: 2731 statements.append(f"ELSE {default}") 2732 2733 statements.append("END") 2734 2735 if self.pretty and self.too_wide(statements): 2736 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2737 2738 return " ".join(statements)
2755 def trim_sql(self, expression: exp.Trim) -> str: 2756 trim_type = self.sql(expression, "position") 2757 2758 if trim_type == "LEADING": 2759 func_name = "LTRIM" 2760 elif trim_type == "TRAILING": 2761 func_name = "RTRIM" 2762 else: 2763 func_name = "TRIM" 2764 2765 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]:
2767 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2768 args = expression.expressions 2769 if isinstance(expression, exp.ConcatWs): 2770 args = args[1:] # Skip the delimiter 2771 2772 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2773 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2774 2775 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2776 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2777 2778 return args
2780 def concat_sql(self, expression: exp.Concat) -> str: 2781 expressions = self.convert_concat_args(expression) 2782 2783 # Some dialects don't allow a single-argument CONCAT call 2784 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2785 return self.sql(expressions[0]) 2786 2787 return self.func("CONCAT", *expressions)
2798 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2799 expressions = self.expressions(expression, flat=True) 2800 reference = self.sql(expression, "reference") 2801 reference = f" {reference}" if reference else "" 2802 delete = self.sql(expression, "delete") 2803 delete = f" ON DELETE {delete}" if delete else "" 2804 update = self.sql(expression, "update") 2805 update = f" ON UPDATE {update}" if update else "" 2806 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}"
2808 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2809 expressions = self.expressions(expression, flat=True) 2810 options = self.expressions(expression, key="options", flat=True, sep=" ") 2811 options = f" {options}" if options else "" 2812 return f"PRIMARY KEY ({expressions}){options}"
2825 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2826 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2827 2828 if expression.args.get("escape"): 2829 path = self.escape_str(path) 2830 2831 if self.QUOTE_JSON_PATH: 2832 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2833 2834 return path
2836 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2837 if isinstance(expression, exp.JSONPathPart): 2838 transform = self.TRANSFORMS.get(expression.__class__) 2839 if not callable(transform): 2840 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2841 return "" 2842 2843 return transform(self, expression) 2844 2845 if isinstance(expression, int): 2846 return str(expression) 2847 2848 if self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 2849 escaped = expression.replace("'", "\\'") 2850 escaped = f"\\'{expression}\\'" 2851 else: 2852 escaped = expression.replace('"', '\\"') 2853 escaped = f'"{escaped}"' 2854 2855 return escaped
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
2860 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 2861 null_handling = expression.args.get("null_handling") 2862 null_handling = f" {null_handling}" if null_handling else "" 2863 2864 unique_keys = expression.args.get("unique_keys") 2865 if unique_keys is not None: 2866 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2867 else: 2868 unique_keys = "" 2869 2870 return_type = self.sql(expression, "return_type") 2871 return_type = f" RETURNING {return_type}" if return_type else "" 2872 encoding = self.sql(expression, "encoding") 2873 encoding = f" ENCODING {encoding}" if encoding else "" 2874 2875 return self.func( 2876 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 2877 *expression.expressions, 2878 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2879 )
2884 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2885 null_handling = expression.args.get("null_handling") 2886 null_handling = f" {null_handling}" if null_handling else "" 2887 return_type = self.sql(expression, "return_type") 2888 return_type = f" RETURNING {return_type}" if return_type else "" 2889 strict = " STRICT" if expression.args.get("strict") else "" 2890 return self.func( 2891 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 2892 )
2894 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 2895 this = self.sql(expression, "this") 2896 order = self.sql(expression, "order") 2897 null_handling = expression.args.get("null_handling") 2898 null_handling = f" {null_handling}" if null_handling else "" 2899 return_type = self.sql(expression, "return_type") 2900 return_type = f" RETURNING {return_type}" if return_type else "" 2901 strict = " STRICT" if expression.args.get("strict") else "" 2902 return self.func( 2903 "JSON_ARRAYAGG", 2904 this, 2905 suffix=f"{order}{null_handling}{return_type}{strict})", 2906 )
2908 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 2909 path = self.sql(expression, "path") 2910 path = f" PATH {path}" if path else "" 2911 nested_schema = self.sql(expression, "nested_schema") 2912 2913 if nested_schema: 2914 return f"NESTED{path} {nested_schema}" 2915 2916 this = self.sql(expression, "this") 2917 kind = self.sql(expression, "kind") 2918 kind = f" {kind}" if kind else "" 2919 return f"{this}{kind}{path}"
2924 def jsontable_sql(self, expression: exp.JSONTable) -> str: 2925 this = self.sql(expression, "this") 2926 path = self.sql(expression, "path") 2927 path = f", {path}" if path else "" 2928 error_handling = expression.args.get("error_handling") 2929 error_handling = f" {error_handling}" if error_handling else "" 2930 empty_handling = expression.args.get("empty_handling") 2931 empty_handling = f" {empty_handling}" if empty_handling else "" 2932 schema = self.sql(expression, "schema") 2933 return self.func( 2934 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 2935 )
2937 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 2938 this = self.sql(expression, "this") 2939 kind = self.sql(expression, "kind") 2940 path = self.sql(expression, "path") 2941 path = f" {path}" if path else "" 2942 as_json = " AS JSON" if expression.args.get("as_json") else "" 2943 return f"{this} {kind}{path}{as_json}"
2945 def openjson_sql(self, expression: exp.OpenJSON) -> str: 2946 this = self.sql(expression, "this") 2947 path = self.sql(expression, "path") 2948 path = f", {path}" if path else "" 2949 expressions = self.expressions(expression) 2950 with_ = ( 2951 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 2952 if expressions 2953 else "" 2954 ) 2955 return f"OPENJSON({this}{path}){with_}"
2957 def in_sql(self, expression: exp.In) -> str: 2958 query = expression.args.get("query") 2959 unnest = expression.args.get("unnest") 2960 field = expression.args.get("field") 2961 is_global = " GLOBAL" if expression.args.get("is_global") else "" 2962 2963 if query: 2964 in_sql = self.sql(query) 2965 elif unnest: 2966 in_sql = self.in_unnest_op(unnest) 2967 elif field: 2968 in_sql = self.sql(field) 2969 else: 2970 in_sql = f"({self.expressions(expression, flat=True)})" 2971 2972 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
2977 def interval_sql(self, expression: exp.Interval) -> str: 2978 unit = self.sql(expression, "unit") 2979 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 2980 unit = self.TIME_PART_SINGULARS.get(unit, unit) 2981 unit = f" {unit}" if unit else "" 2982 2983 if self.SINGLE_STRING_INTERVAL: 2984 this = expression.this.name if expression.this else "" 2985 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 2986 2987 this = self.sql(expression, "this") 2988 if this: 2989 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 2990 this = f" {this}" if unwrapped else f" ({this})" 2991 2992 return f"INTERVAL{this}{unit}"
2997 def reference_sql(self, expression: exp.Reference) -> str: 2998 this = self.sql(expression, "this") 2999 expressions = self.expressions(expression, flat=True) 3000 expressions = f"({expressions})" if expressions else "" 3001 options = self.expressions(expression, key="options", flat=True, sep=" ") 3002 options = f" {options}" if options else "" 3003 return f"REFERENCES {this}{expressions}{options}"
3005 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3006 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3007 parent = expression.parent 3008 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3009 return self.func( 3010 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3011 )
3031 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3032 alias = expression.args["alias"] 3033 3034 identifier_alias = isinstance(alias, exp.Identifier) 3035 literal_alias = isinstance(alias, exp.Literal) 3036 3037 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3038 alias.replace(exp.Literal.string(alias.output_name)) 3039 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3040 alias.replace(exp.to_identifier(alias.output_name)) 3041 3042 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:
3080 def connector_sql( 3081 self, 3082 expression: exp.Connector, 3083 op: str, 3084 stack: t.Optional[t.List[str | exp.Expression]] = None, 3085 ) -> str: 3086 if stack is not None: 3087 if expression.expressions: 3088 stack.append(self.expressions(expression, sep=f" {op} ")) 3089 else: 3090 stack.append(expression.right) 3091 if expression.comments and self.comments: 3092 for comment in expression.comments: 3093 if comment: 3094 op += f" /*{self.pad_comment(comment)}*/" 3095 stack.extend((op, expression.left)) 3096 return op 3097 3098 stack = [expression] 3099 sqls: t.List[str] = [] 3100 ops = set() 3101 3102 while stack: 3103 node = stack.pop() 3104 if isinstance(node, exp.Connector): 3105 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3106 else: 3107 sql = self.sql(node) 3108 if sqls and sqls[-1] in ops: 3109 sqls[-1] += f" {sql}" 3110 else: 3111 sqls.append(sql) 3112 3113 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3114 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3134 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3135 format_sql = self.sql(expression, "format") 3136 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3137 to_sql = self.sql(expression, "to") 3138 to_sql = f" {to_sql}" if to_sql else "" 3139 action = self.sql(expression, "action") 3140 action = f" {action}" if action else "" 3141 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{format_sql}{action})"
3155 def comment_sql(self, expression: exp.Comment) -> str: 3156 this = self.sql(expression, "this") 3157 kind = expression.args["kind"] 3158 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3159 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3160 expression_sql = self.sql(expression, "expression") 3161 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3163 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3164 this = self.sql(expression, "this") 3165 delete = " DELETE" if expression.args.get("delete") else "" 3166 recompress = self.sql(expression, "recompress") 3167 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3168 to_disk = self.sql(expression, "to_disk") 3169 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3170 to_volume = self.sql(expression, "to_volume") 3171 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3172 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3174 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3175 where = self.sql(expression, "where") 3176 group = self.sql(expression, "group") 3177 aggregates = self.expressions(expression, key="aggregates") 3178 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3179 3180 if not (where or group or aggregates) and len(expression.expressions) == 1: 3181 return f"TTL {self.expressions(expression, flat=True)}" 3182 3183 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3200 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3201 this = self.sql(expression, "this") 3202 3203 dtype = self.sql(expression, "dtype") 3204 if dtype: 3205 collate = self.sql(expression, "collate") 3206 collate = f" COLLATE {collate}" if collate else "" 3207 using = self.sql(expression, "using") 3208 using = f" USING {using}" if using else "" 3209 return f"ALTER COLUMN {this} SET DATA TYPE {dtype}{collate}{using}" 3210 3211 default = self.sql(expression, "default") 3212 if default: 3213 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3214 3215 comment = self.sql(expression, "comment") 3216 if comment: 3217 return f"ALTER COLUMN {this} COMMENT {comment}" 3218 3219 allow_null = expression.args.get("allow_null") 3220 drop = expression.args.get("drop") 3221 3222 if not drop and not allow_null: 3223 self.unsupported("Unsupported ALTER COLUMN syntax") 3224 3225 if allow_null is not None: 3226 keyword = "DROP" if drop else "SET" 3227 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3228 3229 return f"ALTER COLUMN {this} DROP DEFAULT"
3237 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3238 compound = " COMPOUND" if expression.args.get("compound") else "" 3239 this = self.sql(expression, "this") 3240 expressions = self.expressions(expression, flat=True) 3241 expressions = f"({expressions})" if expressions else "" 3242 return f"ALTER{compound} SORTKEY {this or expressions}"
3244 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3245 if not self.RENAME_TABLE_WITH_DB: 3246 # Remove db from tables 3247 expression = expression.transform( 3248 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3249 ).assert_is(exp.AlterRename) 3250 this = self.sql(expression, "this") 3251 return f"RENAME TO {this}"
3263 def alter_sql(self, expression: exp.Alter) -> str: 3264 actions = expression.args["actions"] 3265 3266 if isinstance(actions[0], exp.ColumnDef): 3267 actions = self.add_column_sql(expression) 3268 elif isinstance(actions[0], exp.Schema): 3269 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3270 elif isinstance(actions[0], exp.Delete): 3271 actions = self.expressions(expression, key="actions", flat=True) 3272 elif isinstance(actions[0], exp.Query): 3273 actions = "AS " + self.expressions(expression, key="actions") 3274 else: 3275 actions = self.expressions(expression, key="actions", flat=True) 3276 3277 exists = " IF EXISTS" if expression.args.get("exists") else "" 3278 on_cluster = self.sql(expression, "cluster") 3279 on_cluster = f" {on_cluster}" if on_cluster else "" 3280 only = " ONLY" if expression.args.get("only") else "" 3281 options = self.expressions(expression, key="options") 3282 options = f", {options}" if options else "" 3283 kind = self.sql(expression, "kind") 3284 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3285 3286 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{not_valid}{options}"
3288 def add_column_sql(self, expression: exp.Alter) -> str: 3289 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3290 return self.expressions( 3291 expression, 3292 key="actions", 3293 prefix="ADD COLUMN ", 3294 skip_first=True, 3295 ) 3296 return f"ADD {self.expressions(expression, key='actions', flat=True)}"
3306 def distinct_sql(self, expression: exp.Distinct) -> str: 3307 this = self.expressions(expression, flat=True) 3308 3309 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3310 case = exp.case() 3311 for arg in expression.expressions: 3312 case = case.when(arg.is_(exp.null()), exp.null()) 3313 this = self.sql(case.else_(f"({this})")) 3314 3315 this = f" {this}" if this else "" 3316 3317 on = self.sql(expression, "on") 3318 on = f" ON {on}" if on else "" 3319 return f"DISTINCT{this}{on}"
3348 def div_sql(self, expression: exp.Div) -> str: 3349 l, r = expression.left, expression.right 3350 3351 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3352 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3353 3354 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3355 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3356 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3357 3358 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3359 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3360 return self.sql( 3361 exp.cast( 3362 l / r, 3363 to=exp.DataType.Type.BIGINT, 3364 ) 3365 ) 3366 3367 return self.binary(expression, "/")
3455 def log_sql(self, expression: exp.Log) -> str: 3456 this = expression.this 3457 expr = expression.expression 3458 3459 if self.dialect.LOG_BASE_FIRST is False: 3460 this, expr = expr, this 3461 elif self.dialect.LOG_BASE_FIRST is None and expr: 3462 if this.name in ("2", "10"): 3463 return self.func(f"LOG{this.name}", expr) 3464 3465 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3466 3467 return self.func("LOG", this, expr)
3476 def binary(self, expression: exp.Binary, op: str) -> str: 3477 sqls: t.List[str] = [] 3478 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3479 binary_type = type(expression) 3480 3481 while stack: 3482 node = stack.pop() 3483 3484 if type(node) is binary_type: 3485 op_func = node.args.get("operator") 3486 if op_func: 3487 op = f"OPERATOR({self.sql(op_func)})" 3488 3489 stack.append(node.right) 3490 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3491 stack.append(node.left) 3492 else: 3493 sqls.append(self.sql(node)) 3494 3495 return "".join(sqls)
3497 def function_fallback_sql(self, expression: exp.Func) -> str: 3498 args = [] 3499 3500 for key in expression.arg_types: 3501 arg_value = expression.args.get(key) 3502 3503 if isinstance(arg_value, list): 3504 for value in arg_value: 3505 args.append(value) 3506 elif arg_value is not None: 3507 args.append(arg_value) 3508 3509 if self.normalize_functions: 3510 name = expression.sql_name() 3511 else: 3512 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3513 3514 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:
3516 def func( 3517 self, 3518 name: str, 3519 *args: t.Optional[exp.Expression | str], 3520 prefix: str = "(", 3521 suffix: str = ")", 3522 normalize: bool = True, 3523 ) -> str: 3524 name = self.normalize_func(name) if normalize else name 3525 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3527 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3528 arg_sqls = tuple( 3529 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3530 ) 3531 if self.pretty and self.too_wide(arg_sqls): 3532 return self.indent( 3533 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3534 ) 3535 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]:
3540 def format_time( 3541 self, 3542 expression: exp.Expression, 3543 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3544 inverse_time_trie: t.Optional[t.Dict] = None, 3545 ) -> t.Optional[str]: 3546 return format_time( 3547 self.sql(expression, "format"), 3548 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3549 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3550 )
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:
3552 def expressions( 3553 self, 3554 expression: t.Optional[exp.Expression] = None, 3555 key: t.Optional[str] = None, 3556 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3557 flat: bool = False, 3558 indent: bool = True, 3559 skip_first: bool = False, 3560 skip_last: bool = False, 3561 sep: str = ", ", 3562 prefix: str = "", 3563 dynamic: bool = False, 3564 new_line: bool = False, 3565 ) -> str: 3566 expressions = expression.args.get(key or "expressions") if expression else sqls 3567 3568 if not expressions: 3569 return "" 3570 3571 if flat: 3572 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3573 3574 num_sqls = len(expressions) 3575 result_sqls = [] 3576 3577 for i, e in enumerate(expressions): 3578 sql = self.sql(e, comment=False) 3579 if not sql: 3580 continue 3581 3582 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3583 3584 if self.pretty: 3585 if self.leading_comma: 3586 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3587 else: 3588 result_sqls.append( 3589 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3590 ) 3591 else: 3592 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3593 3594 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3595 if new_line: 3596 result_sqls.insert(0, "") 3597 result_sqls.append("") 3598 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3599 else: 3600 result_sql = "".join(result_sqls) 3601 3602 return ( 3603 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3604 if indent 3605 else result_sql 3606 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3608 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3609 flat = flat or isinstance(expression.parent, exp.Properties) 3610 expressions_sql = self.expressions(expression, flat=flat) 3611 if flat: 3612 return f"{op} {expressions_sql}" 3613 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3615 def naked_property(self, expression: exp.Property) -> str: 3616 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3617 if not property_name: 3618 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3619 return f"{property_name} {self.sql(expression, 'this')}"
3627 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3628 this = self.sql(expression, "this") 3629 expressions = self.no_identify(self.expressions, expression) 3630 expressions = ( 3631 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3632 ) 3633 return f"{this}{expressions}" if expressions.strip() != "" else this
3643 def when_sql(self, expression: exp.When) -> str: 3644 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3645 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3646 condition = self.sql(expression, "condition") 3647 condition = f" AND {condition}" if condition else "" 3648 3649 then_expression = expression.args.get("then") 3650 if isinstance(then_expression, exp.Insert): 3651 this = self.sql(then_expression, "this") 3652 this = f"INSERT {this}" if this else "INSERT" 3653 then = self.sql(then_expression, "expression") 3654 then = f"{this} VALUES {then}" if then else this 3655 elif isinstance(then_expression, exp.Update): 3656 if isinstance(then_expression.args.get("expressions"), exp.Star): 3657 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3658 else: 3659 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 3660 else: 3661 then = self.sql(then_expression) 3662 return f"WHEN {matched}{source}{condition} THEN {then}"
3664 def merge_sql(self, expression: exp.Merge) -> str: 3665 table = expression.this 3666 table_alias = "" 3667 3668 hints = table.args.get("hints") 3669 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3670 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3671 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3672 3673 this = self.sql(table) 3674 using = f"USING {self.sql(expression, 'using')}" 3675 on = f"ON {self.sql(expression, 'on')}" 3676 expressions = self.expressions(expression, sep=" ", indent=False) 3677 returning = self.sql(expression, "returning") 3678 if returning: 3679 expressions = f"{expressions}{returning}" 3680 3681 sep = self.sep() 3682 3683 return self.prepend_ctes( 3684 expression, 3685 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{expressions}", 3686 )
3692 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3693 if not self.SUPPORTS_TO_NUMBER: 3694 self.unsupported("Unsupported TO_NUMBER function") 3695 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3696 3697 fmt = expression.args.get("format") 3698 if not fmt: 3699 self.unsupported("Conversion format is required for TO_NUMBER") 3700 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3701 3702 return self.func("TO_NUMBER", expression.this, fmt)
3704 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3705 this = self.sql(expression, "this") 3706 kind = self.sql(expression, "kind") 3707 settings_sql = self.expressions(expression, key="settings", sep=" ") 3708 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3709 return f"{this}({kind}{args})"
3724 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3725 expressions = self.expressions(expression, flat=True) 3726 expressions = f" {self.wrap(expressions)}" if expressions else "" 3727 buckets = self.sql(expression, "buckets") 3728 kind = self.sql(expression, "kind") 3729 buckets = f" BUCKETS {buckets}" if buckets else "" 3730 order = self.sql(expression, "order") 3731 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
3736 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3737 expressions = self.expressions(expression, key="expressions", flat=True) 3738 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3739 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3740 buckets = self.sql(expression, "buckets") 3741 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
3743 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3744 this = self.sql(expression, "this") 3745 having = self.sql(expression, "having") 3746 3747 if having: 3748 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3749 3750 return self.func("ANY_VALUE", this)
3752 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3753 transform = self.func("TRANSFORM", *expression.expressions) 3754 row_format_before = self.sql(expression, "row_format_before") 3755 row_format_before = f" {row_format_before}" if row_format_before else "" 3756 record_writer = self.sql(expression, "record_writer") 3757 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3758 using = f" USING {self.sql(expression, 'command_script')}" 3759 schema = self.sql(expression, "schema") 3760 schema = f" AS {schema}" if schema else "" 3761 row_format_after = self.sql(expression, "row_format_after") 3762 row_format_after = f" {row_format_after}" if row_format_after else "" 3763 record_reader = self.sql(expression, "record_reader") 3764 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3765 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
3767 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3768 key_block_size = self.sql(expression, "key_block_size") 3769 if key_block_size: 3770 return f"KEY_BLOCK_SIZE = {key_block_size}" 3771 3772 using = self.sql(expression, "using") 3773 if using: 3774 return f"USING {using}" 3775 3776 parser = self.sql(expression, "parser") 3777 if parser: 3778 return f"WITH PARSER {parser}" 3779 3780 comment = self.sql(expression, "comment") 3781 if comment: 3782 return f"COMMENT {comment}" 3783 3784 visible = expression.args.get("visible") 3785 if visible is not None: 3786 return "VISIBLE" if visible else "INVISIBLE" 3787 3788 engine_attr = self.sql(expression, "engine_attr") 3789 if engine_attr: 3790 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3791 3792 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3793 if secondary_engine_attr: 3794 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3795 3796 self.unsupported("Unsupported index constraint option.") 3797 return ""
3803 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3804 kind = self.sql(expression, "kind") 3805 kind = f"{kind} INDEX" if kind else "INDEX" 3806 this = self.sql(expression, "this") 3807 this = f" {this}" if this else "" 3808 index_type = self.sql(expression, "index_type") 3809 index_type = f" USING {index_type}" if index_type else "" 3810 expressions = self.expressions(expression, flat=True) 3811 expressions = f" ({expressions})" if expressions else "" 3812 options = self.expressions(expression, key="options", sep=" ") 3813 options = f" {options}" if options else "" 3814 return f"{kind}{this}{index_type}{expressions}{options}"
3816 def nvl2_sql(self, expression: exp.Nvl2) -> str: 3817 if self.NVL2_SUPPORTED: 3818 return self.function_fallback_sql(expression) 3819 3820 case = exp.Case().when( 3821 expression.this.is_(exp.null()).not_(copy=False), 3822 expression.args["true"], 3823 copy=False, 3824 ) 3825 else_cond = expression.args.get("false") 3826 if else_cond: 3827 case.else_(else_cond, copy=False) 3828 3829 return self.sql(case)
3831 def comprehension_sql(self, expression: exp.Comprehension) -> str: 3832 this = self.sql(expression, "this") 3833 expr = self.sql(expression, "expression") 3834 iterator = self.sql(expression, "iterator") 3835 condition = self.sql(expression, "condition") 3836 condition = f" IF {condition}" if condition else "" 3837 return f"{this} FOR {expr} IN {iterator}{condition}"
3845 def predict_sql(self, expression: exp.Predict) -> str: 3846 model = self.sql(expression, "this") 3847 model = f"MODEL {model}" 3848 table = self.sql(expression, "expression") 3849 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 3850 parameters = self.sql(expression, "params_struct") 3851 return self.func("PREDICT", model, table, parameters or None)
3863 def toarray_sql(self, expression: exp.ToArray) -> str: 3864 arg = expression.this 3865 if not arg.type: 3866 from sqlglot.optimizer.annotate_types import annotate_types 3867 3868 arg = annotate_types(arg) 3869 3870 if arg.is_type(exp.DataType.Type.ARRAY): 3871 return self.sql(arg) 3872 3873 cond_for_null = arg.is_(exp.null()) 3874 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
3890 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 3891 this = expression.this 3892 time_format = self.format_time(expression) 3893 3894 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 3895 return self.sql( 3896 exp.cast( 3897 exp.StrToTime(this=this, format=expression.args["format"]), 3898 exp.DataType.Type.DATE, 3899 ) 3900 ) 3901 3902 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 3903 return self.sql(this) 3904 3905 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
3917 def lastday_sql(self, expression: exp.LastDay) -> str: 3918 if self.LAST_DAY_SUPPORTS_DATE_PART: 3919 return self.function_fallback_sql(expression) 3920 3921 unit = expression.text("unit") 3922 if unit and unit != "MONTH": 3923 self.unsupported("Date parts are not supported in LAST_DAY.") 3924 3925 return self.func("LAST_DAY", expression.this)
3934 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 3935 if self.CAN_IMPLEMENT_ARRAY_ANY: 3936 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 3937 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 3938 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 3939 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 3940 3941 from sqlglot.dialects import Dialect 3942 3943 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 3944 if self.dialect.__class__ != Dialect: 3945 self.unsupported("ARRAY_ANY is unsupported") 3946 3947 return self.function_fallback_sql(expression)
3949 def struct_sql(self, expression: exp.Struct) -> str: 3950 expression.set( 3951 "expressions", 3952 [ 3953 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 3954 if isinstance(e, exp.PropertyEQ) 3955 else e 3956 for e in expression.expressions 3957 ], 3958 ) 3959 3960 return self.function_fallback_sql(expression)
3968 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 3969 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 3970 tables = f" {self.expressions(expression)}" 3971 3972 exists = " IF EXISTS" if expression.args.get("exists") else "" 3973 3974 on_cluster = self.sql(expression, "cluster") 3975 on_cluster = f" {on_cluster}" if on_cluster else "" 3976 3977 identity = self.sql(expression, "identity") 3978 identity = f" {identity} IDENTITY" if identity else "" 3979 3980 option = self.sql(expression, "option") 3981 option = f" {option}" if option else "" 3982 3983 partition = self.sql(expression, "partition") 3984 partition = f" {partition}" if partition else "" 3985 3986 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
3990 def convert_sql(self, expression: exp.Convert) -> str: 3991 to = expression.this 3992 value = expression.expression 3993 style = expression.args.get("style") 3994 safe = expression.args.get("safe") 3995 strict = expression.args.get("strict") 3996 3997 if not to or not value: 3998 return "" 3999 4000 # Retrieve length of datatype and override to default if not specified 4001 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4002 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4003 4004 transformed: t.Optional[exp.Expression] = None 4005 cast = exp.Cast if strict else exp.TryCast 4006 4007 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4008 if isinstance(style, exp.Literal) and style.is_int: 4009 from sqlglot.dialects.tsql import TSQL 4010 4011 style_value = style.name 4012 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4013 if not converted_style: 4014 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4015 4016 fmt = exp.Literal.string(converted_style) 4017 4018 if to.this == exp.DataType.Type.DATE: 4019 transformed = exp.StrToDate(this=value, format=fmt) 4020 elif to.this == exp.DataType.Type.DATETIME: 4021 transformed = exp.StrToTime(this=value, format=fmt) 4022 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4023 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4024 elif to.this == exp.DataType.Type.TEXT: 4025 transformed = exp.TimeToStr(this=value, format=fmt) 4026 4027 if not transformed: 4028 transformed = cast(this=value, to=to, safe=safe) 4029 4030 return self.sql(transformed)
4086 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4087 option = self.sql(expression, "this") 4088 4089 if expression.expressions: 4090 upper = option.upper() 4091 4092 # Snowflake FILE_FORMAT options are separated by whitespace 4093 sep = " " if upper == "FILE_FORMAT" else ", " 4094 4095 # Databricks copy/format options do not set their list of values with EQ 4096 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4097 values = self.expressions(expression, flat=True, sep=sep) 4098 return f"{option}{op}({values})" 4099 4100 value = self.sql(expression, "expression") 4101 4102 if not value: 4103 return option 4104 4105 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4106 4107 return f"{option}{op}{value}"
4109 def credentials_sql(self, expression: exp.Credentials) -> str: 4110 cred_expr = expression.args.get("credentials") 4111 if isinstance(cred_expr, exp.Literal): 4112 # Redshift case: CREDENTIALS <string> 4113 credentials = self.sql(expression, "credentials") 4114 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4115 else: 4116 # Snowflake case: CREDENTIALS = (...) 4117 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4118 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4119 4120 storage = self.sql(expression, "storage") 4121 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4122 4123 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4124 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4125 4126 iam_role = self.sql(expression, "iam_role") 4127 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4128 4129 region = self.sql(expression, "region") 4130 region = f" REGION {region}" if region else "" 4131 4132 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4134 def copy_sql(self, expression: exp.Copy) -> str: 4135 this = self.sql(expression, "this") 4136 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4137 4138 credentials = self.sql(expression, "credentials") 4139 credentials = self.seg(credentials) if credentials else "" 4140 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4141 files = self.expressions(expression, key="files", flat=True) 4142 4143 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4144 params = self.expressions( 4145 expression, 4146 key="params", 4147 sep=sep, 4148 new_line=True, 4149 skip_last=True, 4150 skip_first=True, 4151 indent=self.COPY_PARAMS_ARE_WRAPPED, 4152 ) 4153 4154 if params: 4155 if self.COPY_PARAMS_ARE_WRAPPED: 4156 params = f" WITH ({params})" 4157 elif not self.pretty: 4158 params = f" {params}" 4159 4160 return f"COPY{this}{kind} {files}{credentials}{params}"
4165 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4166 on_sql = "ON" if expression.args.get("on") else "OFF" 4167 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4168 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4169 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4170 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4171 4172 if filter_col or retention_period: 4173 on_sql = self.func("ON", filter_col, retention_period) 4174 4175 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4177 def maskingpolicycolumnconstraint_sql( 4178 self, expression: exp.MaskingPolicyColumnConstraint 4179 ) -> str: 4180 this = self.sql(expression, "this") 4181 expressions = self.expressions(expression, flat=True) 4182 expressions = f" USING ({expressions})" if expressions else "" 4183 return f"MASKING POLICY {this}{expressions}"
4193 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4194 this = self.sql(expression, "this") 4195 expr = expression.expression 4196 4197 if isinstance(expr, exp.Func): 4198 # T-SQL's CLR functions are case sensitive 4199 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4200 else: 4201 expr = self.sql(expression, "expression") 4202 4203 return self.scope_resolution(expr, this)
4211 def rand_sql(self, expression: exp.Rand) -> str: 4212 lower = self.sql(expression, "lower") 4213 upper = self.sql(expression, "upper") 4214 4215 if lower and upper: 4216 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4217 return self.func("RAND", expression.this)
4219 def changes_sql(self, expression: exp.Changes) -> str: 4220 information = self.sql(expression, "information") 4221 information = f"INFORMATION => {information}" 4222 at_before = self.sql(expression, "at_before") 4223 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4224 end = self.sql(expression, "end") 4225 end = f"{self.seg('')}{end}" if end else "" 4226 4227 return f"CHANGES ({information}){at_before}{end}"
4229 def pad_sql(self, expression: exp.Pad) -> str: 4230 prefix = "L" if expression.args.get("is_left") else "R" 4231 4232 fill_pattern = self.sql(expression, "fill_pattern") or None 4233 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4234 fill_pattern = "' '" 4235 4236 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4242 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4243 generate_series = exp.GenerateSeries(**expression.args) 4244 4245 parent = expression.parent 4246 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4247 parent = parent.parent 4248 4249 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4250 return self.sql(exp.Unnest(expressions=[generate_series])) 4251 4252 if isinstance(parent, exp.Select): 4253 self.unsupported("GenerateSeries projection unnesting is not supported.") 4254 4255 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4257 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4258 exprs = expression.expressions 4259 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4260 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4261 else: 4262 rhs = self.expressions(expression) 4263 4264 return self.func(name, expression.this, rhs or None)
4266 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4267 if self.SUPPORTS_CONVERT_TIMEZONE: 4268 return self.function_fallback_sql(expression) 4269 4270 source_tz = expression.args.get("source_tz") 4271 target_tz = expression.args.get("target_tz") 4272 timestamp = expression.args.get("timestamp") 4273 4274 if source_tz and timestamp: 4275 timestamp = exp.AtTimeZone( 4276 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4277 ) 4278 4279 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4280 4281 return self.sql(expr)
4283 def json_sql(self, expression: exp.JSON) -> str: 4284 this = self.sql(expression, "this") 4285 this = f" {this}" if this else "" 4286 4287 _with = expression.args.get("with") 4288 4289 if _with is None: 4290 with_sql = "" 4291 elif not _with: 4292 with_sql = " WITHOUT" 4293 else: 4294 with_sql = " WITH" 4295 4296 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4297 4298 return f"JSON{this}{with_sql}{unique_sql}"
4300 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4301 def _generate_on_options(arg: t.Any) -> str: 4302 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4303 4304 path = self.sql(expression, "path") 4305 returning = self.sql(expression, "returning") 4306 returning = f" RETURNING {returning}" if returning else "" 4307 4308 on_condition = self.sql(expression, "on_condition") 4309 on_condition = f" {on_condition}" if on_condition else "" 4310 4311 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4313 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4314 else_ = "ELSE " if expression.args.get("else_") else "" 4315 condition = self.sql(expression, "expression") 4316 condition = f"WHEN {condition} THEN " if condition else else_ 4317 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4318 return f"{condition}{insert}"
4326 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4327 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4328 empty = expression.args.get("empty") 4329 empty = ( 4330 f"DEFAULT {empty} ON EMPTY" 4331 if isinstance(empty, exp.Expression) 4332 else self.sql(expression, "empty") 4333 ) 4334 4335 error = expression.args.get("error") 4336 error = ( 4337 f"DEFAULT {error} ON ERROR" 4338 if isinstance(error, exp.Expression) 4339 else self.sql(expression, "error") 4340 ) 4341 4342 if error and empty: 4343 error = ( 4344 f"{empty} {error}" 4345 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4346 else f"{error} {empty}" 4347 ) 4348 empty = "" 4349 4350 null = self.sql(expression, "null") 4351 4352 return f"{empty}{error}{null}"
4354 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4355 this = self.sql(expression, "this") 4356 path = self.sql(expression, "path") 4357 4358 passing = self.expressions(expression, "passing") 4359 passing = f" PASSING {passing}" if passing else "" 4360 4361 on_condition = self.sql(expression, "on_condition") 4362 on_condition = f" {on_condition}" if on_condition else "" 4363 4364 path = f"{path}{passing}{on_condition}" 4365 4366 return self.func("JSON_EXISTS", this, path)
4368 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4369 array_agg = self.function_fallback_sql(expression) 4370 4371 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4372 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4373 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4374 parent = expression.parent 4375 if isinstance(parent, exp.Filter): 4376 parent_cond = parent.expression.this 4377 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4378 else: 4379 this = expression.this 4380 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4381 if this.find(exp.Column): 4382 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4383 this_sql = ( 4384 self.expressions(this) 4385 if isinstance(this, exp.Distinct) 4386 else self.sql(expression, "this") 4387 ) 4388 4389 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4390 4391 return array_agg
4399 def grant_sql(self, expression: exp.Grant) -> str: 4400 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4401 4402 kind = self.sql(expression, "kind") 4403 kind = f" {kind}" if kind else "" 4404 4405 securable = self.sql(expression, "securable") 4406 securable = f" {securable}" if securable else "" 4407 4408 principals = self.expressions(expression, key="principals", flat=True) 4409 4410 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4411 4412 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4436 def overlay_sql(self, expression: exp.Overlay): 4437 this = self.sql(expression, "this") 4438 expr = self.sql(expression, "expression") 4439 from_sql = self.sql(expression, "from") 4440 for_sql = self.sql(expression, "for") 4441 for_sql = f" FOR {for_sql}" if for_sql else "" 4442 4443 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4449 def string_sql(self, expression: exp.String) -> str: 4450 this = expression.this 4451 zone = expression.args.get("zone") 4452 4453 if zone: 4454 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4455 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4456 # set for source_tz to transpile the time conversion before the STRING cast 4457 this = exp.ConvertTimezone( 4458 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4459 ) 4460 4461 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4471 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4472 filler = self.sql(expression, "this") 4473 filler = f" {filler}" if filler else "" 4474 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4475 return f"TRUNCATE{filler} {with_count}"