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.TemporaryProperty: lambda *_: "TEMPORARY", 189 exp.TagColumnConstraint: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 190 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 191 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 192 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 193 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 194 exp.TransientProperty: lambda *_: "TRANSIENT", 195 exp.Union: lambda self, e: self.set_operations(e), 196 exp.UnloggedProperty: lambda *_: "UNLOGGED", 197 exp.Uuid: lambda *_: "UUID()", 198 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 199 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 200 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 201 exp.VolatileProperty: lambda *_: "VOLATILE", 202 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 203 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 204 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 205 } 206 207 # Whether null ordering is supported in order by 208 # True: Full Support, None: No support, False: No support for certain cases 209 # such as window specifications, aggregate functions etc 210 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 211 212 # Whether ignore nulls is inside the agg or outside. 213 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 214 IGNORE_NULLS_IN_FUNC = False 215 216 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 217 LOCKING_READS_SUPPORTED = False 218 219 # Whether the EXCEPT and INTERSECT operations can return duplicates 220 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 221 222 # Wrap derived values in parens, usually standard but spark doesn't support it 223 WRAP_DERIVED_VALUES = True 224 225 # Whether create function uses an AS before the RETURN 226 CREATE_FUNCTION_RETURN_AS = True 227 228 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 229 MATCHED_BY_SOURCE = True 230 231 # Whether the INTERVAL expression works only with values like '1 day' 232 SINGLE_STRING_INTERVAL = False 233 234 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 235 INTERVAL_ALLOWS_PLURAL_FORM = True 236 237 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 238 LIMIT_FETCH = "ALL" 239 240 # Whether limit and fetch allows expresions or just limits 241 LIMIT_ONLY_LITERALS = False 242 243 # Whether a table is allowed to be renamed with a db 244 RENAME_TABLE_WITH_DB = True 245 246 # The separator for grouping sets and rollups 247 GROUPINGS_SEP = "," 248 249 # The string used for creating an index on a table 250 INDEX_ON = "ON" 251 252 # Whether join hints should be generated 253 JOIN_HINTS = True 254 255 # Whether table hints should be generated 256 TABLE_HINTS = True 257 258 # Whether query hints should be generated 259 QUERY_HINTS = True 260 261 # What kind of separator to use for query hints 262 QUERY_HINT_SEP = ", " 263 264 # Whether comparing against booleans (e.g. x IS TRUE) is supported 265 IS_BOOL_ALLOWED = True 266 267 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 268 DUPLICATE_KEY_UPDATE_WITH_SET = True 269 270 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 271 LIMIT_IS_TOP = False 272 273 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 274 RETURNING_END = True 275 276 # Whether to generate an unquoted value for EXTRACT's date part argument 277 EXTRACT_ALLOWS_QUOTES = True 278 279 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 280 TZ_TO_WITH_TIME_ZONE = False 281 282 # Whether the NVL2 function is supported 283 NVL2_SUPPORTED = True 284 285 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 286 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 287 288 # Whether VALUES statements can be used as derived tables. 289 # MySQL 5 and Redshift do not allow this, so when False, it will convert 290 # SELECT * VALUES into SELECT UNION 291 VALUES_AS_TABLE = True 292 293 # Whether the word COLUMN is included when adding a column with ALTER TABLE 294 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 295 296 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 297 UNNEST_WITH_ORDINALITY = True 298 299 # Whether FILTER (WHERE cond) can be used for conditional aggregation 300 AGGREGATE_FILTER_SUPPORTED = True 301 302 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 303 SEMI_ANTI_JOIN_WITH_SIDE = True 304 305 # Whether to include the type of a computed column in the CREATE DDL 306 COMPUTED_COLUMN_WITH_TYPE = True 307 308 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 309 SUPPORTS_TABLE_COPY = True 310 311 # Whether parentheses are required around the table sample's expression 312 TABLESAMPLE_REQUIRES_PARENS = True 313 314 # Whether a table sample clause's size needs to be followed by the ROWS keyword 315 TABLESAMPLE_SIZE_IS_ROWS = True 316 317 # The keyword(s) to use when generating a sample clause 318 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 319 320 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 321 TABLESAMPLE_WITH_METHOD = True 322 323 # The keyword to use when specifying the seed of a sample clause 324 TABLESAMPLE_SEED_KEYWORD = "SEED" 325 326 # Whether COLLATE is a function instead of a binary operator 327 COLLATE_IS_FUNC = False 328 329 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 330 DATA_TYPE_SPECIFIERS_ALLOWED = False 331 332 # Whether conditions require booleans WHERE x = 0 vs WHERE x 333 ENSURE_BOOLS = False 334 335 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 336 CTE_RECURSIVE_KEYWORD_REQUIRED = True 337 338 # Whether CONCAT requires >1 arguments 339 SUPPORTS_SINGLE_ARG_CONCAT = True 340 341 # Whether LAST_DAY function supports a date part argument 342 LAST_DAY_SUPPORTS_DATE_PART = True 343 344 # Whether named columns are allowed in table aliases 345 SUPPORTS_TABLE_ALIAS_COLUMNS = True 346 347 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 348 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 349 350 # What delimiter to use for separating JSON key/value pairs 351 JSON_KEY_VALUE_PAIR_SEP = ":" 352 353 # INSERT OVERWRITE TABLE x override 354 INSERT_OVERWRITE = " OVERWRITE TABLE" 355 356 # Whether the SELECT .. INTO syntax is used instead of CTAS 357 SUPPORTS_SELECT_INTO = False 358 359 # Whether UNLOGGED tables can be created 360 SUPPORTS_UNLOGGED_TABLES = False 361 362 # Whether the CREATE TABLE LIKE statement is supported 363 SUPPORTS_CREATE_TABLE_LIKE = True 364 365 # Whether the LikeProperty needs to be specified inside of the schema clause 366 LIKE_PROPERTY_INSIDE_SCHEMA = False 367 368 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 369 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 370 MULTI_ARG_DISTINCT = True 371 372 # Whether the JSON extraction operators expect a value of type JSON 373 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 374 375 # Whether bracketed keys like ["foo"] are supported in JSON paths 376 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 377 378 # Whether to escape keys using single quotes in JSON paths 379 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 380 381 # The JSONPathPart expressions supported by this dialect 382 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 383 384 # Whether any(f(x) for x in array) can be implemented by this dialect 385 CAN_IMPLEMENT_ARRAY_ANY = False 386 387 # Whether the function TO_NUMBER is supported 388 SUPPORTS_TO_NUMBER = True 389 390 # Whether or not set op modifiers apply to the outer set op or select. 391 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 392 # True means limit 1 happens after the set op, False means it it happens on y. 393 SET_OP_MODIFIERS = True 394 395 # Whether parameters from COPY statement are wrapped in parentheses 396 COPY_PARAMS_ARE_WRAPPED = True 397 398 # Whether values of params are set with "=" token or empty space 399 COPY_PARAMS_EQ_REQUIRED = False 400 401 # Whether COPY statement has INTO keyword 402 COPY_HAS_INTO_KEYWORD = True 403 404 # Whether the conditional TRY(expression) function is supported 405 TRY_SUPPORTED = True 406 407 # Whether the UESCAPE syntax in unicode strings is supported 408 SUPPORTS_UESCAPE = True 409 410 # The keyword to use when generating a star projection with excluded columns 411 STAR_EXCEPT = "EXCEPT" 412 413 # The HEX function name 414 HEX_FUNC = "HEX" 415 416 # The keywords to use when prefixing & separating WITH based properties 417 WITH_PROPERTIES_PREFIX = "WITH" 418 419 # Whether to quote the generated expression of exp.JsonPath 420 QUOTE_JSON_PATH = True 421 422 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 423 PAD_FILL_PATTERN_IS_REQUIRED = False 424 425 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 426 SUPPORTS_EXPLODING_PROJECTIONS = True 427 428 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 429 ARRAY_CONCAT_IS_VAR_LEN = True 430 431 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 432 SUPPORTS_CONVERT_TIMEZONE = False 433 434 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 435 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 436 437 TYPE_MAPPING = { 438 exp.DataType.Type.NCHAR: "CHAR", 439 exp.DataType.Type.NVARCHAR: "VARCHAR", 440 exp.DataType.Type.MEDIUMTEXT: "TEXT", 441 exp.DataType.Type.LONGTEXT: "TEXT", 442 exp.DataType.Type.TINYTEXT: "TEXT", 443 exp.DataType.Type.MEDIUMBLOB: "BLOB", 444 exp.DataType.Type.LONGBLOB: "BLOB", 445 exp.DataType.Type.TINYBLOB: "BLOB", 446 exp.DataType.Type.INET: "INET", 447 exp.DataType.Type.ROWVERSION: "VARBINARY", 448 } 449 450 TIME_PART_SINGULARS = { 451 "MICROSECONDS": "MICROSECOND", 452 "SECONDS": "SECOND", 453 "MINUTES": "MINUTE", 454 "HOURS": "HOUR", 455 "DAYS": "DAY", 456 "WEEKS": "WEEK", 457 "MONTHS": "MONTH", 458 "QUARTERS": "QUARTER", 459 "YEARS": "YEAR", 460 } 461 462 AFTER_HAVING_MODIFIER_TRANSFORMS = { 463 "cluster": lambda self, e: self.sql(e, "cluster"), 464 "distribute": lambda self, e: self.sql(e, "distribute"), 465 "sort": lambda self, e: self.sql(e, "sort"), 466 "windows": lambda self, e: ( 467 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 468 if e.args.get("windows") 469 else "" 470 ), 471 "qualify": lambda self, e: self.sql(e, "qualify"), 472 } 473 474 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 475 476 STRUCT_DELIMITER = ("<", ">") 477 478 PARAMETER_TOKEN = "@" 479 NAMED_PLACEHOLDER_TOKEN = ":" 480 481 PROPERTIES_LOCATION = { 482 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 483 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 484 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 485 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 486 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 487 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 488 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 489 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 490 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 491 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 492 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 493 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 494 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 495 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 496 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 497 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 498 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 499 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 500 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 501 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 502 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 503 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 504 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 505 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 506 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 507 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 508 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 509 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 510 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 511 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 512 exp.HeapProperty: exp.Properties.Location.POST_WITH, 513 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 514 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 515 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 516 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 517 exp.JournalProperty: exp.Properties.Location.POST_NAME, 518 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 519 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 520 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 521 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 522 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 523 exp.LogProperty: exp.Properties.Location.POST_NAME, 524 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 525 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 526 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 527 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 528 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 529 exp.Order: exp.Properties.Location.POST_SCHEMA, 530 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 531 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 532 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 533 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 534 exp.Property: exp.Properties.Location.POST_WITH, 535 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 536 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 537 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 538 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 539 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 540 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 541 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 542 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 543 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 544 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 545 exp.Set: exp.Properties.Location.POST_SCHEMA, 546 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 547 exp.SetProperty: exp.Properties.Location.POST_CREATE, 548 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 549 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 550 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 551 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 552 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 553 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 554 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 555 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 556 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 557 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 558 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 559 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 560 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 561 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 562 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 563 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 564 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 565 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 566 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 567 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 569 } 570 571 # Keywords that can't be used as unquoted identifier names 572 RESERVED_KEYWORDS: t.Set[str] = set() 573 574 # Expressions whose comments are separated from them for better formatting 575 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 576 exp.Command, 577 exp.Create, 578 exp.Delete, 579 exp.Drop, 580 exp.From, 581 exp.Insert, 582 exp.Join, 583 exp.MultitableInserts, 584 exp.Select, 585 exp.SetOperation, 586 exp.Update, 587 exp.Where, 588 exp.With, 589 ) 590 591 # Expressions that should not have their comments generated in maybe_comment 592 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 593 exp.Binary, 594 exp.SetOperation, 595 ) 596 597 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 598 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 599 exp.Column, 600 exp.Literal, 601 exp.Neg, 602 exp.Paren, 603 ) 604 605 PARAMETERIZABLE_TEXT_TYPES = { 606 exp.DataType.Type.NVARCHAR, 607 exp.DataType.Type.VARCHAR, 608 exp.DataType.Type.CHAR, 609 exp.DataType.Type.NCHAR, 610 } 611 612 # Expressions that need to have all CTEs under them bubbled up to them 613 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 614 615 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 616 617 __slots__ = ( 618 "pretty", 619 "identify", 620 "normalize", 621 "pad", 622 "_indent", 623 "normalize_functions", 624 "unsupported_level", 625 "max_unsupported", 626 "leading_comma", 627 "max_text_width", 628 "comments", 629 "dialect", 630 "unsupported_messages", 631 "_escaped_quote_end", 632 "_escaped_identifier_end", 633 "_next_name", 634 "_identifier_start", 635 "_identifier_end", 636 ) 637 638 def __init__( 639 self, 640 pretty: t.Optional[bool] = None, 641 identify: str | bool = False, 642 normalize: bool = False, 643 pad: int = 2, 644 indent: int = 2, 645 normalize_functions: t.Optional[str | bool] = None, 646 unsupported_level: ErrorLevel = ErrorLevel.WARN, 647 max_unsupported: int = 3, 648 leading_comma: bool = False, 649 max_text_width: int = 80, 650 comments: bool = True, 651 dialect: DialectType = None, 652 ): 653 import sqlglot 654 from sqlglot.dialects import Dialect 655 656 self.pretty = pretty if pretty is not None else sqlglot.pretty 657 self.identify = identify 658 self.normalize = normalize 659 self.pad = pad 660 self._indent = indent 661 self.unsupported_level = unsupported_level 662 self.max_unsupported = max_unsupported 663 self.leading_comma = leading_comma 664 self.max_text_width = max_text_width 665 self.comments = comments 666 self.dialect = Dialect.get_or_raise(dialect) 667 668 # This is both a Dialect property and a Generator argument, so we prioritize the latter 669 self.normalize_functions = ( 670 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 671 ) 672 673 self.unsupported_messages: t.List[str] = [] 674 self._escaped_quote_end: str = ( 675 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 676 ) 677 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 678 679 self._next_name = name_sequence("_t") 680 681 self._identifier_start = self.dialect.IDENTIFIER_START 682 self._identifier_end = self.dialect.IDENTIFIER_END 683 684 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 685 """ 686 Generates the SQL string corresponding to the given syntax tree. 687 688 Args: 689 expression: The syntax tree. 690 copy: Whether to copy the expression. The generator performs mutations so 691 it is safer to copy. 692 693 Returns: 694 The SQL string corresponding to `expression`. 695 """ 696 if copy: 697 expression = expression.copy() 698 699 expression = self.preprocess(expression) 700 701 self.unsupported_messages = [] 702 sql = self.sql(expression).strip() 703 704 if self.pretty: 705 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 706 707 if self.unsupported_level == ErrorLevel.IGNORE: 708 return sql 709 710 if self.unsupported_level == ErrorLevel.WARN: 711 for msg in self.unsupported_messages: 712 logger.warning(msg) 713 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 714 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 715 716 return sql 717 718 def preprocess(self, expression: exp.Expression) -> exp.Expression: 719 """Apply generic preprocessing transformations to a given expression.""" 720 expression = self._move_ctes_to_top_level(expression) 721 722 if self.ENSURE_BOOLS: 723 from sqlglot.transforms import ensure_bools 724 725 expression = ensure_bools(expression) 726 727 return expression 728 729 def _move_ctes_to_top_level(self, expression: E) -> E: 730 if ( 731 not expression.parent 732 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 733 and any(node.parent is not expression for node in expression.find_all(exp.With)) 734 ): 735 from sqlglot.transforms import move_ctes_to_top_level 736 737 expression = move_ctes_to_top_level(expression) 738 return expression 739 740 def unsupported(self, message: str) -> None: 741 if self.unsupported_level == ErrorLevel.IMMEDIATE: 742 raise UnsupportedError(message) 743 self.unsupported_messages.append(message) 744 745 def sep(self, sep: str = " ") -> str: 746 return f"{sep.strip()}\n" if self.pretty else sep 747 748 def seg(self, sql: str, sep: str = " ") -> str: 749 return f"{self.sep(sep)}{sql}" 750 751 def pad_comment(self, comment: str) -> str: 752 comment = " " + comment if comment[0].strip() else comment 753 comment = comment + " " if comment[-1].strip() else comment 754 return comment 755 756 def maybe_comment( 757 self, 758 sql: str, 759 expression: t.Optional[exp.Expression] = None, 760 comments: t.Optional[t.List[str]] = None, 761 separated: bool = False, 762 ) -> str: 763 comments = ( 764 ((expression and expression.comments) if comments is None else comments) # type: ignore 765 if self.comments 766 else None 767 ) 768 769 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 770 return sql 771 772 comments_sql = " ".join( 773 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 774 ) 775 776 if not comments_sql: 777 return sql 778 779 comments_sql = self._replace_line_breaks(comments_sql) 780 781 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 782 return ( 783 f"{self.sep()}{comments_sql}{sql}" 784 if not sql or sql[0].isspace() 785 else f"{comments_sql}{self.sep()}{sql}" 786 ) 787 788 return f"{sql} {comments_sql}" 789 790 def wrap(self, expression: exp.Expression | str) -> str: 791 this_sql = ( 792 self.sql(expression) 793 if isinstance(expression, exp.UNWRAPPED_QUERIES) 794 else self.sql(expression, "this") 795 ) 796 if not this_sql: 797 return "()" 798 799 this_sql = self.indent(this_sql, level=1, pad=0) 800 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 801 802 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 803 original = self.identify 804 self.identify = False 805 result = func(*args, **kwargs) 806 self.identify = original 807 return result 808 809 def normalize_func(self, name: str) -> str: 810 if self.normalize_functions == "upper" or self.normalize_functions is True: 811 return name.upper() 812 if self.normalize_functions == "lower": 813 return name.lower() 814 return name 815 816 def indent( 817 self, 818 sql: str, 819 level: int = 0, 820 pad: t.Optional[int] = None, 821 skip_first: bool = False, 822 skip_last: bool = False, 823 ) -> str: 824 if not self.pretty or not sql: 825 return sql 826 827 pad = self.pad if pad is None else pad 828 lines = sql.split("\n") 829 830 return "\n".join( 831 ( 832 line 833 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 834 else f"{' ' * (level * self._indent + pad)}{line}" 835 ) 836 for i, line in enumerate(lines) 837 ) 838 839 def sql( 840 self, 841 expression: t.Optional[str | exp.Expression], 842 key: t.Optional[str] = None, 843 comment: bool = True, 844 ) -> str: 845 if not expression: 846 return "" 847 848 if isinstance(expression, str): 849 return expression 850 851 if key: 852 value = expression.args.get(key) 853 if value: 854 return self.sql(value) 855 return "" 856 857 transform = self.TRANSFORMS.get(expression.__class__) 858 859 if callable(transform): 860 sql = transform(self, expression) 861 elif isinstance(expression, exp.Expression): 862 exp_handler_name = f"{expression.key}_sql" 863 864 if hasattr(self, exp_handler_name): 865 sql = getattr(self, exp_handler_name)(expression) 866 elif isinstance(expression, exp.Func): 867 sql = self.function_fallback_sql(expression) 868 elif isinstance(expression, exp.Property): 869 sql = self.property_sql(expression) 870 else: 871 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 872 else: 873 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 874 875 return self.maybe_comment(sql, expression) if self.comments and comment else sql 876 877 def uncache_sql(self, expression: exp.Uncache) -> str: 878 table = self.sql(expression, "this") 879 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 880 return f"UNCACHE TABLE{exists_sql} {table}" 881 882 def cache_sql(self, expression: exp.Cache) -> str: 883 lazy = " LAZY" if expression.args.get("lazy") else "" 884 table = self.sql(expression, "this") 885 options = expression.args.get("options") 886 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 887 sql = self.sql(expression, "expression") 888 sql = f" AS{self.sep()}{sql}" if sql else "" 889 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 890 return self.prepend_ctes(expression, sql) 891 892 def characterset_sql(self, expression: exp.CharacterSet) -> str: 893 if isinstance(expression.parent, exp.Cast): 894 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 895 default = "DEFAULT " if expression.args.get("default") else "" 896 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 897 898 def column_parts(self, expression: exp.Column) -> str: 899 return ".".join( 900 self.sql(part) 901 for part in ( 902 expression.args.get("catalog"), 903 expression.args.get("db"), 904 expression.args.get("table"), 905 expression.args.get("this"), 906 ) 907 if part 908 ) 909 910 def column_sql(self, expression: exp.Column) -> str: 911 join_mark = " (+)" if expression.args.get("join_mark") else "" 912 913 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 914 join_mark = "" 915 self.unsupported("Outer join syntax using the (+) operator is not supported.") 916 917 return f"{self.column_parts(expression)}{join_mark}" 918 919 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 920 this = self.sql(expression, "this") 921 this = f" {this}" if this else "" 922 position = self.sql(expression, "position") 923 return f"{position}{this}" 924 925 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 926 column = self.sql(expression, "this") 927 kind = self.sql(expression, "kind") 928 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 929 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 930 kind = f"{sep}{kind}" if kind else "" 931 constraints = f" {constraints}" if constraints else "" 932 position = self.sql(expression, "position") 933 position = f" {position}" if position else "" 934 935 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 936 kind = "" 937 938 return f"{exists}{column}{kind}{constraints}{position}" 939 940 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 941 this = self.sql(expression, "this") 942 kind_sql = self.sql(expression, "kind").strip() 943 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 944 945 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 946 this = self.sql(expression, "this") 947 if expression.args.get("not_null"): 948 persisted = " PERSISTED NOT NULL" 949 elif expression.args.get("persisted"): 950 persisted = " PERSISTED" 951 else: 952 persisted = "" 953 return f"AS {this}{persisted}" 954 955 def autoincrementcolumnconstraint_sql(self, _) -> str: 956 return self.token_sql(TokenType.AUTO_INCREMENT) 957 958 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 959 if isinstance(expression.this, list): 960 this = self.wrap(self.expressions(expression, key="this", flat=True)) 961 else: 962 this = self.sql(expression, "this") 963 964 return f"COMPRESS {this}" 965 966 def generatedasidentitycolumnconstraint_sql( 967 self, expression: exp.GeneratedAsIdentityColumnConstraint 968 ) -> str: 969 this = "" 970 if expression.this is not None: 971 on_null = " ON NULL" if expression.args.get("on_null") else "" 972 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 973 974 start = expression.args.get("start") 975 start = f"START WITH {start}" if start else "" 976 increment = expression.args.get("increment") 977 increment = f" INCREMENT BY {increment}" if increment else "" 978 minvalue = expression.args.get("minvalue") 979 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 980 maxvalue = expression.args.get("maxvalue") 981 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 982 cycle = expression.args.get("cycle") 983 cycle_sql = "" 984 985 if cycle is not None: 986 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 987 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 988 989 sequence_opts = "" 990 if start or increment or cycle_sql: 991 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 992 sequence_opts = f" ({sequence_opts.strip()})" 993 994 expr = self.sql(expression, "expression") 995 expr = f"({expr})" if expr else "IDENTITY" 996 997 return f"GENERATED{this} AS {expr}{sequence_opts}" 998 999 def generatedasrowcolumnconstraint_sql( 1000 self, expression: exp.GeneratedAsRowColumnConstraint 1001 ) -> str: 1002 start = "START" if expression.args.get("start") else "END" 1003 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1004 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1005 1006 def periodforsystemtimeconstraint_sql( 1007 self, expression: exp.PeriodForSystemTimeConstraint 1008 ) -> str: 1009 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1010 1011 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1012 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1013 1014 def transformcolumnconstraint_sql(self, expression: exp.TransformColumnConstraint) -> str: 1015 return f"AS {self.sql(expression, 'this')}" 1016 1017 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1018 desc = expression.args.get("desc") 1019 if desc is not None: 1020 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1021 return "PRIMARY KEY" 1022 1023 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1024 this = self.sql(expression, "this") 1025 this = f" {this}" if this else "" 1026 index_type = expression.args.get("index_type") 1027 index_type = f" USING {index_type}" if index_type else "" 1028 on_conflict = self.sql(expression, "on_conflict") 1029 on_conflict = f" {on_conflict}" if on_conflict else "" 1030 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1031 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}" 1032 1033 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1034 return self.sql(expression, "this") 1035 1036 def create_sql(self, expression: exp.Create) -> str: 1037 kind = self.sql(expression, "kind") 1038 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1039 properties = expression.args.get("properties") 1040 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1041 1042 this = self.createable_sql(expression, properties_locs) 1043 1044 properties_sql = "" 1045 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1046 exp.Properties.Location.POST_WITH 1047 ): 1048 properties_sql = self.sql( 1049 exp.Properties( 1050 expressions=[ 1051 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1052 *properties_locs[exp.Properties.Location.POST_WITH], 1053 ] 1054 ) 1055 ) 1056 1057 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1058 properties_sql = self.sep() + properties_sql 1059 elif not self.pretty: 1060 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1061 properties_sql = f" {properties_sql}" 1062 1063 begin = " BEGIN" if expression.args.get("begin") else "" 1064 end = " END" if expression.args.get("end") else "" 1065 1066 expression_sql = self.sql(expression, "expression") 1067 if expression_sql: 1068 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1069 1070 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1071 postalias_props_sql = "" 1072 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1073 postalias_props_sql = self.properties( 1074 exp.Properties( 1075 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1076 ), 1077 wrapped=False, 1078 ) 1079 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1080 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1081 1082 postindex_props_sql = "" 1083 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1084 postindex_props_sql = self.properties( 1085 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1086 wrapped=False, 1087 prefix=" ", 1088 ) 1089 1090 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1091 indexes = f" {indexes}" if indexes else "" 1092 index_sql = indexes + postindex_props_sql 1093 1094 replace = " OR REPLACE" if expression.args.get("replace") else "" 1095 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1096 unique = " UNIQUE" if expression.args.get("unique") else "" 1097 1098 clustered = expression.args.get("clustered") 1099 if clustered is None: 1100 clustered_sql = "" 1101 elif clustered: 1102 clustered_sql = " CLUSTERED COLUMNSTORE" 1103 else: 1104 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1105 1106 postcreate_props_sql = "" 1107 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1108 postcreate_props_sql = self.properties( 1109 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1110 sep=" ", 1111 prefix=" ", 1112 wrapped=False, 1113 ) 1114 1115 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1116 1117 postexpression_props_sql = "" 1118 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1119 postexpression_props_sql = self.properties( 1120 exp.Properties( 1121 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1122 ), 1123 sep=" ", 1124 prefix=" ", 1125 wrapped=False, 1126 ) 1127 1128 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1129 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1130 no_schema_binding = ( 1131 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1132 ) 1133 1134 clone = self.sql(expression, "clone") 1135 clone = f" {clone}" if clone else "" 1136 1137 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1138 return self.prepend_ctes(expression, expression_sql) 1139 1140 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1141 start = self.sql(expression, "start") 1142 start = f"START WITH {start}" if start else "" 1143 increment = self.sql(expression, "increment") 1144 increment = f" INCREMENT BY {increment}" if increment else "" 1145 minvalue = self.sql(expression, "minvalue") 1146 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1147 maxvalue = self.sql(expression, "maxvalue") 1148 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1149 owned = self.sql(expression, "owned") 1150 owned = f" OWNED BY {owned}" if owned else "" 1151 1152 cache = expression.args.get("cache") 1153 if cache is None: 1154 cache_str = "" 1155 elif cache is True: 1156 cache_str = " CACHE" 1157 else: 1158 cache_str = f" CACHE {cache}" 1159 1160 options = self.expressions(expression, key="options", flat=True, sep=" ") 1161 options = f" {options}" if options else "" 1162 1163 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1164 1165 def clone_sql(self, expression: exp.Clone) -> str: 1166 this = self.sql(expression, "this") 1167 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1168 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1169 return f"{shallow}{keyword} {this}" 1170 1171 def describe_sql(self, expression: exp.Describe) -> str: 1172 style = expression.args.get("style") 1173 style = f" {style}" if style else "" 1174 partition = self.sql(expression, "partition") 1175 partition = f" {partition}" if partition else "" 1176 return f"DESCRIBE{style} {self.sql(expression, 'this')}{partition}" 1177 1178 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1179 tag = self.sql(expression, "tag") 1180 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1181 1182 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1183 with_ = self.sql(expression, "with") 1184 if with_: 1185 sql = f"{with_}{self.sep()}{sql}" 1186 return sql 1187 1188 def with_sql(self, expression: exp.With) -> str: 1189 sql = self.expressions(expression, flat=True) 1190 recursive = ( 1191 "RECURSIVE " 1192 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1193 else "" 1194 ) 1195 1196 return f"WITH {recursive}{sql}" 1197 1198 def cte_sql(self, expression: exp.CTE) -> str: 1199 alias = self.sql(expression, "alias") 1200 1201 materialized = expression.args.get("materialized") 1202 if materialized is False: 1203 materialized = "NOT MATERIALIZED " 1204 elif materialized: 1205 materialized = "MATERIALIZED " 1206 1207 return f"{alias} AS {materialized or ''}{self.wrap(expression)}" 1208 1209 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1210 alias = self.sql(expression, "this") 1211 columns = self.expressions(expression, key="columns", flat=True) 1212 columns = f"({columns})" if columns else "" 1213 1214 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1215 columns = "" 1216 self.unsupported("Named columns are not supported in table alias.") 1217 1218 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1219 alias = self._next_name() 1220 1221 return f"{alias}{columns}" 1222 1223 def bitstring_sql(self, expression: exp.BitString) -> str: 1224 this = self.sql(expression, "this") 1225 if self.dialect.BIT_START: 1226 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1227 return f"{int(this, 2)}" 1228 1229 def hexstring_sql(self, expression: exp.HexString) -> str: 1230 this = self.sql(expression, "this") 1231 if self.dialect.HEX_START: 1232 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1233 return f"{int(this, 16)}" 1234 1235 def bytestring_sql(self, expression: exp.ByteString) -> str: 1236 this = self.sql(expression, "this") 1237 if self.dialect.BYTE_START: 1238 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1239 return this 1240 1241 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1242 this = self.sql(expression, "this") 1243 escape = expression.args.get("escape") 1244 1245 if self.dialect.UNICODE_START: 1246 escape_substitute = r"\\\1" 1247 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1248 else: 1249 escape_substitute = r"\\u\1" 1250 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1251 1252 if escape: 1253 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1254 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1255 else: 1256 escape_pattern = ESCAPED_UNICODE_RE 1257 escape_sql = "" 1258 1259 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1260 this = escape_pattern.sub(escape_substitute, this) 1261 1262 return f"{left_quote}{this}{right_quote}{escape_sql}" 1263 1264 def rawstring_sql(self, expression: exp.RawString) -> str: 1265 string = self.escape_str(expression.this.replace("\\", "\\\\"), escape_backslash=False) 1266 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1267 1268 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1269 this = self.sql(expression, "this") 1270 specifier = self.sql(expression, "expression") 1271 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1272 return f"{this}{specifier}" 1273 1274 def datatype_sql(self, expression: exp.DataType) -> str: 1275 nested = "" 1276 values = "" 1277 interior = self.expressions(expression, flat=True) 1278 1279 type_value = expression.this 1280 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1281 type_sql = self.sql(expression, "kind") 1282 else: 1283 type_sql = ( 1284 self.TYPE_MAPPING.get(type_value, type_value.value) 1285 if isinstance(type_value, exp.DataType.Type) 1286 else type_value 1287 ) 1288 1289 if interior: 1290 if expression.args.get("nested"): 1291 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1292 if expression.args.get("values") is not None: 1293 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1294 values = self.expressions(expression, key="values", flat=True) 1295 values = f"{delimiters[0]}{values}{delimiters[1]}" 1296 elif type_value == exp.DataType.Type.INTERVAL: 1297 nested = f" {interior}" 1298 else: 1299 nested = f"({interior})" 1300 1301 type_sql = f"{type_sql}{nested}{values}" 1302 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1303 exp.DataType.Type.TIMETZ, 1304 exp.DataType.Type.TIMESTAMPTZ, 1305 ): 1306 type_sql = f"{type_sql} WITH TIME ZONE" 1307 1308 return type_sql 1309 1310 def directory_sql(self, expression: exp.Directory) -> str: 1311 local = "LOCAL " if expression.args.get("local") else "" 1312 row_format = self.sql(expression, "row_format") 1313 row_format = f" {row_format}" if row_format else "" 1314 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1315 1316 def delete_sql(self, expression: exp.Delete) -> str: 1317 this = self.sql(expression, "this") 1318 this = f" FROM {this}" if this else "" 1319 using = self.sql(expression, "using") 1320 using = f" USING {using}" if using else "" 1321 cluster = self.sql(expression, "cluster") 1322 cluster = f" {cluster}" if cluster else "" 1323 where = self.sql(expression, "where") 1324 returning = self.sql(expression, "returning") 1325 limit = self.sql(expression, "limit") 1326 tables = self.expressions(expression, key="tables") 1327 tables = f" {tables}" if tables else "" 1328 if self.RETURNING_END: 1329 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1330 else: 1331 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1332 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1333 1334 def drop_sql(self, expression: exp.Drop) -> str: 1335 this = self.sql(expression, "this") 1336 expressions = self.expressions(expression, flat=True) 1337 expressions = f" ({expressions})" if expressions else "" 1338 kind = expression.args["kind"] 1339 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1340 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1341 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1342 on_cluster = self.sql(expression, "cluster") 1343 on_cluster = f" {on_cluster}" if on_cluster else "" 1344 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1345 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1346 cascade = " CASCADE" if expression.args.get("cascade") else "" 1347 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1348 purge = " PURGE" if expression.args.get("purge") else "" 1349 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1350 1351 def set_operation(self, expression: exp.SetOperation) -> str: 1352 op_type = type(expression) 1353 op_name = op_type.key.upper() 1354 1355 distinct = expression.args.get("distinct") 1356 if ( 1357 distinct is False 1358 and op_type in (exp.Except, exp.Intersect) 1359 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1360 ): 1361 self.unsupported(f"{op_name} ALL is not supported") 1362 1363 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1364 1365 if distinct is None: 1366 distinct = default_distinct 1367 if distinct is None: 1368 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1369 1370 if distinct is default_distinct: 1371 kind = "" 1372 else: 1373 kind = " DISTINCT" if distinct else " ALL" 1374 1375 by_name = " BY NAME" if expression.args.get("by_name") else "" 1376 return f"{op_name}{kind}{by_name}" 1377 1378 def set_operations(self, expression: exp.SetOperation) -> str: 1379 if not self.SET_OP_MODIFIERS: 1380 limit = expression.args.get("limit") 1381 order = expression.args.get("order") 1382 1383 if limit or order: 1384 select = self._move_ctes_to_top_level( 1385 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1386 ) 1387 1388 if limit: 1389 select = select.limit(limit.pop(), copy=False) 1390 if order: 1391 select = select.order_by(order.pop(), copy=False) 1392 return self.sql(select) 1393 1394 sqls: t.List[str] = [] 1395 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1396 1397 while stack: 1398 node = stack.pop() 1399 1400 if isinstance(node, exp.SetOperation): 1401 stack.append(node.expression) 1402 stack.append( 1403 self.maybe_comment( 1404 self.set_operation(node), comments=node.comments, separated=True 1405 ) 1406 ) 1407 stack.append(node.this) 1408 else: 1409 sqls.append(self.sql(node)) 1410 1411 this = self.sep().join(sqls) 1412 this = self.query_modifiers(expression, this) 1413 return self.prepend_ctes(expression, this) 1414 1415 def fetch_sql(self, expression: exp.Fetch) -> str: 1416 direction = expression.args.get("direction") 1417 direction = f" {direction}" if direction else "" 1418 count = self.sql(expression, "count") 1419 count = f" {count}" if count else "" 1420 if expression.args.get("percent"): 1421 count = f"{count} PERCENT" 1422 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY" 1423 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}" 1424 1425 def filter_sql(self, expression: exp.Filter) -> str: 1426 if self.AGGREGATE_FILTER_SUPPORTED: 1427 this = self.sql(expression, "this") 1428 where = self.sql(expression, "expression").strip() 1429 return f"{this} FILTER({where})" 1430 1431 agg = expression.this 1432 agg_arg = agg.this 1433 cond = expression.expression.this 1434 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1435 return self.sql(agg) 1436 1437 def hint_sql(self, expression: exp.Hint) -> str: 1438 if not self.QUERY_HINTS: 1439 self.unsupported("Hints are not supported") 1440 return "" 1441 1442 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1443 1444 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1445 using = self.sql(expression, "using") 1446 using = f" USING {using}" if using else "" 1447 columns = self.expressions(expression, key="columns", flat=True) 1448 columns = f"({columns})" if columns else "" 1449 partition_by = self.expressions(expression, key="partition_by", flat=True) 1450 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1451 where = self.sql(expression, "where") 1452 include = self.expressions(expression, key="include", flat=True) 1453 if include: 1454 include = f" INCLUDE ({include})" 1455 with_storage = self.expressions(expression, key="with_storage", flat=True) 1456 with_storage = f" WITH ({with_storage})" if with_storage else "" 1457 tablespace = self.sql(expression, "tablespace") 1458 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1459 on = self.sql(expression, "on") 1460 on = f" ON {on}" if on else "" 1461 1462 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1463 1464 def index_sql(self, expression: exp.Index) -> str: 1465 unique = "UNIQUE " if expression.args.get("unique") else "" 1466 primary = "PRIMARY " if expression.args.get("primary") else "" 1467 amp = "AMP " if expression.args.get("amp") else "" 1468 name = self.sql(expression, "this") 1469 name = f"{name} " if name else "" 1470 table = self.sql(expression, "table") 1471 table = f"{self.INDEX_ON} {table}" if table else "" 1472 1473 index = "INDEX " if not table else "" 1474 1475 params = self.sql(expression, "params") 1476 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1477 1478 def identifier_sql(self, expression: exp.Identifier) -> str: 1479 text = expression.name 1480 lower = text.lower() 1481 text = lower if self.normalize and not expression.quoted else text 1482 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1483 if ( 1484 expression.quoted 1485 or self.dialect.can_identify(text, self.identify) 1486 or lower in self.RESERVED_KEYWORDS 1487 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1488 ): 1489 text = f"{self._identifier_start}{text}{self._identifier_end}" 1490 return text 1491 1492 def hex_sql(self, expression: exp.Hex) -> str: 1493 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1494 if self.dialect.HEX_LOWERCASE: 1495 text = self.func("LOWER", text) 1496 1497 return text 1498 1499 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1500 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1501 if not self.dialect.HEX_LOWERCASE: 1502 text = self.func("LOWER", text) 1503 return text 1504 1505 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1506 input_format = self.sql(expression, "input_format") 1507 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1508 output_format = self.sql(expression, "output_format") 1509 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1510 return self.sep().join((input_format, output_format)) 1511 1512 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1513 string = self.sql(exp.Literal.string(expression.name)) 1514 return f"{prefix}{string}" 1515 1516 def partition_sql(self, expression: exp.Partition) -> str: 1517 return f"PARTITION({self.expressions(expression, flat=True)})" 1518 1519 def properties_sql(self, expression: exp.Properties) -> str: 1520 root_properties = [] 1521 with_properties = [] 1522 1523 for p in expression.expressions: 1524 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1525 if p_loc == exp.Properties.Location.POST_WITH: 1526 with_properties.append(p) 1527 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1528 root_properties.append(p) 1529 1530 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1531 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1532 1533 if root_props and with_props and not self.pretty: 1534 with_props = " " + with_props 1535 1536 return root_props + with_props 1537 1538 def root_properties(self, properties: exp.Properties) -> str: 1539 if properties.expressions: 1540 return self.expressions(properties, indent=False, sep=" ") 1541 return "" 1542 1543 def properties( 1544 self, 1545 properties: exp.Properties, 1546 prefix: str = "", 1547 sep: str = ", ", 1548 suffix: str = "", 1549 wrapped: bool = True, 1550 ) -> str: 1551 if properties.expressions: 1552 expressions = self.expressions(properties, sep=sep, indent=False) 1553 if expressions: 1554 expressions = self.wrap(expressions) if wrapped else expressions 1555 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1556 return "" 1557 1558 def with_properties(self, properties: exp.Properties) -> str: 1559 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1560 1561 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1562 properties_locs = defaultdict(list) 1563 for p in properties.expressions: 1564 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1565 if p_loc != exp.Properties.Location.UNSUPPORTED: 1566 properties_locs[p_loc].append(p) 1567 else: 1568 self.unsupported(f"Unsupported property {p.key}") 1569 1570 return properties_locs 1571 1572 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1573 if isinstance(expression.this, exp.Dot): 1574 return self.sql(expression, "this") 1575 return f"'{expression.name}'" if string_key else expression.name 1576 1577 def property_sql(self, expression: exp.Property) -> str: 1578 property_cls = expression.__class__ 1579 if property_cls == exp.Property: 1580 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1581 1582 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1583 if not property_name: 1584 self.unsupported(f"Unsupported property {expression.key}") 1585 1586 return f"{property_name}={self.sql(expression, 'this')}" 1587 1588 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1589 if self.SUPPORTS_CREATE_TABLE_LIKE: 1590 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1591 options = f" {options}" if options else "" 1592 1593 like = f"LIKE {self.sql(expression, 'this')}{options}" 1594 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1595 like = f"({like})" 1596 1597 return like 1598 1599 if expression.expressions: 1600 self.unsupported("Transpilation of LIKE property options is unsupported") 1601 1602 select = exp.select("*").from_(expression.this).limit(0) 1603 return f"AS {self.sql(select)}" 1604 1605 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1606 no = "NO " if expression.args.get("no") else "" 1607 protection = " PROTECTION" if expression.args.get("protection") else "" 1608 return f"{no}FALLBACK{protection}" 1609 1610 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1611 no = "NO " if expression.args.get("no") else "" 1612 local = expression.args.get("local") 1613 local = f"{local} " if local else "" 1614 dual = "DUAL " if expression.args.get("dual") else "" 1615 before = "BEFORE " if expression.args.get("before") else "" 1616 after = "AFTER " if expression.args.get("after") else "" 1617 return f"{no}{local}{dual}{before}{after}JOURNAL" 1618 1619 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1620 freespace = self.sql(expression, "this") 1621 percent = " PERCENT" if expression.args.get("percent") else "" 1622 return f"FREESPACE={freespace}{percent}" 1623 1624 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1625 if expression.args.get("default"): 1626 property = "DEFAULT" 1627 elif expression.args.get("on"): 1628 property = "ON" 1629 else: 1630 property = "OFF" 1631 return f"CHECKSUM={property}" 1632 1633 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1634 if expression.args.get("no"): 1635 return "NO MERGEBLOCKRATIO" 1636 if expression.args.get("default"): 1637 return "DEFAULT MERGEBLOCKRATIO" 1638 1639 percent = " PERCENT" if expression.args.get("percent") else "" 1640 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1641 1642 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1643 default = expression.args.get("default") 1644 minimum = expression.args.get("minimum") 1645 maximum = expression.args.get("maximum") 1646 if default or minimum or maximum: 1647 if default: 1648 prop = "DEFAULT" 1649 elif minimum: 1650 prop = "MINIMUM" 1651 else: 1652 prop = "MAXIMUM" 1653 return f"{prop} DATABLOCKSIZE" 1654 units = expression.args.get("units") 1655 units = f" {units}" if units else "" 1656 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1657 1658 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1659 autotemp = expression.args.get("autotemp") 1660 always = expression.args.get("always") 1661 default = expression.args.get("default") 1662 manual = expression.args.get("manual") 1663 never = expression.args.get("never") 1664 1665 if autotemp is not None: 1666 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1667 elif always: 1668 prop = "ALWAYS" 1669 elif default: 1670 prop = "DEFAULT" 1671 elif manual: 1672 prop = "MANUAL" 1673 elif never: 1674 prop = "NEVER" 1675 return f"BLOCKCOMPRESSION={prop}" 1676 1677 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1678 no = expression.args.get("no") 1679 no = " NO" if no else "" 1680 concurrent = expression.args.get("concurrent") 1681 concurrent = " CONCURRENT" if concurrent else "" 1682 target = self.sql(expression, "target") 1683 target = f" {target}" if target else "" 1684 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1685 1686 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1687 if isinstance(expression.this, list): 1688 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1689 if expression.this: 1690 modulus = self.sql(expression, "this") 1691 remainder = self.sql(expression, "expression") 1692 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1693 1694 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1695 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1696 return f"FROM ({from_expressions}) TO ({to_expressions})" 1697 1698 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1699 this = self.sql(expression, "this") 1700 1701 for_values_or_default = expression.expression 1702 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1703 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1704 else: 1705 for_values_or_default = " DEFAULT" 1706 1707 return f"PARTITION OF {this}{for_values_or_default}" 1708 1709 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1710 kind = expression.args.get("kind") 1711 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1712 for_or_in = expression.args.get("for_or_in") 1713 for_or_in = f" {for_or_in}" if for_or_in else "" 1714 lock_type = expression.args.get("lock_type") 1715 override = " OVERRIDE" if expression.args.get("override") else "" 1716 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1717 1718 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1719 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1720 statistics = expression.args.get("statistics") 1721 statistics_sql = "" 1722 if statistics is not None: 1723 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1724 return f"{data_sql}{statistics_sql}" 1725 1726 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1727 this = self.sql(expression, "this") 1728 this = f"HISTORY_TABLE={this}" if this else "" 1729 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1730 data_consistency = ( 1731 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1732 ) 1733 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1734 retention_period = ( 1735 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1736 ) 1737 1738 if this: 1739 on_sql = self.func("ON", this, data_consistency, retention_period) 1740 else: 1741 on_sql = "ON" if expression.args.get("on") else "OFF" 1742 1743 sql = f"SYSTEM_VERSIONING={on_sql}" 1744 1745 return f"WITH({sql})" if expression.args.get("with") else sql 1746 1747 def insert_sql(self, expression: exp.Insert) -> str: 1748 hint = self.sql(expression, "hint") 1749 overwrite = expression.args.get("overwrite") 1750 1751 if isinstance(expression.this, exp.Directory): 1752 this = " OVERWRITE" if overwrite else " INTO" 1753 else: 1754 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1755 1756 stored = self.sql(expression, "stored") 1757 stored = f" {stored}" if stored else "" 1758 alternative = expression.args.get("alternative") 1759 alternative = f" OR {alternative}" if alternative else "" 1760 ignore = " IGNORE" if expression.args.get("ignore") else "" 1761 is_function = expression.args.get("is_function") 1762 if is_function: 1763 this = f"{this} FUNCTION" 1764 this = f"{this} {self.sql(expression, 'this')}" 1765 1766 exists = " IF EXISTS" if expression.args.get("exists") else "" 1767 where = self.sql(expression, "where") 1768 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1769 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1770 on_conflict = self.sql(expression, "conflict") 1771 on_conflict = f" {on_conflict}" if on_conflict else "" 1772 by_name = " BY NAME" if expression.args.get("by_name") else "" 1773 returning = self.sql(expression, "returning") 1774 1775 if self.RETURNING_END: 1776 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1777 else: 1778 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1779 1780 partition_by = self.sql(expression, "partition") 1781 partition_by = f" {partition_by}" if partition_by else "" 1782 settings = self.sql(expression, "settings") 1783 settings = f" {settings}" if settings else "" 1784 1785 source = self.sql(expression, "source") 1786 source = f"TABLE {source}" if source else "" 1787 1788 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1789 return self.prepend_ctes(expression, sql) 1790 1791 def introducer_sql(self, expression: exp.Introducer) -> str: 1792 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1793 1794 def kill_sql(self, expression: exp.Kill) -> str: 1795 kind = self.sql(expression, "kind") 1796 kind = f" {kind}" if kind else "" 1797 this = self.sql(expression, "this") 1798 this = f" {this}" if this else "" 1799 return f"KILL{kind}{this}" 1800 1801 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1802 return expression.name 1803 1804 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1805 return expression.name 1806 1807 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1808 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1809 1810 constraint = self.sql(expression, "constraint") 1811 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1812 1813 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1814 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1815 action = self.sql(expression, "action") 1816 1817 expressions = self.expressions(expression, flat=True) 1818 if expressions: 1819 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1820 expressions = f" {set_keyword}{expressions}" 1821 1822 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}" 1823 1824 def returning_sql(self, expression: exp.Returning) -> str: 1825 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1826 1827 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1828 fields = self.sql(expression, "fields") 1829 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1830 escaped = self.sql(expression, "escaped") 1831 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1832 items = self.sql(expression, "collection_items") 1833 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1834 keys = self.sql(expression, "map_keys") 1835 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1836 lines = self.sql(expression, "lines") 1837 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1838 null = self.sql(expression, "null") 1839 null = f" NULL DEFINED AS {null}" if null else "" 1840 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1841 1842 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1843 return f"WITH ({self.expressions(expression, flat=True)})" 1844 1845 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1846 this = f"{self.sql(expression, 'this')} INDEX" 1847 target = self.sql(expression, "target") 1848 target = f" FOR {target}" if target else "" 1849 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1850 1851 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1852 this = self.sql(expression, "this") 1853 kind = self.sql(expression, "kind") 1854 expr = self.sql(expression, "expression") 1855 return f"{this} ({kind} => {expr})" 1856 1857 def table_parts(self, expression: exp.Table) -> str: 1858 return ".".join( 1859 self.sql(part) 1860 for part in ( 1861 expression.args.get("catalog"), 1862 expression.args.get("db"), 1863 expression.args.get("this"), 1864 ) 1865 if part is not None 1866 ) 1867 1868 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1869 table = self.table_parts(expression) 1870 only = "ONLY " if expression.args.get("only") else "" 1871 partition = self.sql(expression, "partition") 1872 partition = f" {partition}" if partition else "" 1873 version = self.sql(expression, "version") 1874 version = f" {version}" if version else "" 1875 alias = self.sql(expression, "alias") 1876 alias = f"{sep}{alias}" if alias else "" 1877 1878 sample = self.sql(expression, "sample") 1879 if self.dialect.ALIAS_POST_TABLESAMPLE: 1880 sample_pre_alias = sample 1881 sample_post_alias = "" 1882 else: 1883 sample_pre_alias = "" 1884 sample_post_alias = sample 1885 1886 hints = self.expressions(expression, key="hints", sep=" ") 1887 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1888 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 1889 joins = self.indent( 1890 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 1891 ) 1892 laterals = self.expressions(expression, key="laterals", sep="") 1893 1894 file_format = self.sql(expression, "format") 1895 if file_format: 1896 pattern = self.sql(expression, "pattern") 1897 pattern = f", PATTERN => {pattern}" if pattern else "" 1898 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 1899 1900 ordinality = expression.args.get("ordinality") or "" 1901 if ordinality: 1902 ordinality = f" WITH ORDINALITY{alias}" 1903 alias = "" 1904 1905 when = self.sql(expression, "when") 1906 if when: 1907 table = f"{table} {when}" 1908 1909 changes = self.sql(expression, "changes") 1910 changes = f" {changes}" if changes else "" 1911 1912 rows_from = self.expressions(expression, key="rows_from") 1913 if rows_from: 1914 table = f"ROWS FROM {self.wrap(rows_from)}" 1915 1916 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 1917 1918 def tablesample_sql( 1919 self, 1920 expression: exp.TableSample, 1921 tablesample_keyword: t.Optional[str] = None, 1922 ) -> str: 1923 method = self.sql(expression, "method") 1924 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 1925 numerator = self.sql(expression, "bucket_numerator") 1926 denominator = self.sql(expression, "bucket_denominator") 1927 field = self.sql(expression, "bucket_field") 1928 field = f" ON {field}" if field else "" 1929 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 1930 seed = self.sql(expression, "seed") 1931 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 1932 1933 size = self.sql(expression, "size") 1934 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 1935 size = f"{size} ROWS" 1936 1937 percent = self.sql(expression, "percent") 1938 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 1939 percent = f"{percent} PERCENT" 1940 1941 expr = f"{bucket}{percent}{size}" 1942 if self.TABLESAMPLE_REQUIRES_PARENS: 1943 expr = f"({expr})" 1944 1945 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 1946 1947 def pivot_sql(self, expression: exp.Pivot) -> str: 1948 expressions = self.expressions(expression, flat=True) 1949 1950 if expression.this: 1951 this = self.sql(expression, "this") 1952 if not expressions: 1953 return f"UNPIVOT {this}" 1954 1955 on = f"{self.seg('ON')} {expressions}" 1956 using = self.expressions(expression, key="using", flat=True) 1957 using = f"{self.seg('USING')} {using}" if using else "" 1958 group = self.sql(expression, "group") 1959 return f"PIVOT {this}{on}{using}{group}" 1960 1961 alias = self.sql(expression, "alias") 1962 alias = f" AS {alias}" if alias else "" 1963 direction = self.seg("UNPIVOT" if expression.unpivot else "PIVOT") 1964 1965 field = self.sql(expression, "field") 1966 1967 include_nulls = expression.args.get("include_nulls") 1968 if include_nulls is not None: 1969 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 1970 else: 1971 nulls = "" 1972 1973 default_on_null = self.sql(expression, "default_on_null") 1974 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 1975 return f"{direction}{nulls}({expressions} FOR {field}{default_on_null}){alias}" 1976 1977 def version_sql(self, expression: exp.Version) -> str: 1978 this = f"FOR {expression.name}" 1979 kind = expression.text("kind") 1980 expr = self.sql(expression, "expression") 1981 return f"{this} {kind} {expr}" 1982 1983 def tuple_sql(self, expression: exp.Tuple) -> str: 1984 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 1985 1986 def update_sql(self, expression: exp.Update) -> str: 1987 this = self.sql(expression, "this") 1988 set_sql = self.expressions(expression, flat=True) 1989 from_sql = self.sql(expression, "from") 1990 where_sql = self.sql(expression, "where") 1991 returning = self.sql(expression, "returning") 1992 order = self.sql(expression, "order") 1993 limit = self.sql(expression, "limit") 1994 if self.RETURNING_END: 1995 expression_sql = f"{from_sql}{where_sql}{returning}" 1996 else: 1997 expression_sql = f"{returning}{from_sql}{where_sql}" 1998 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 1999 return self.prepend_ctes(expression, sql) 2000 2001 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2002 values_as_table = values_as_table and self.VALUES_AS_TABLE 2003 2004 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2005 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2006 args = self.expressions(expression) 2007 alias = self.sql(expression, "alias") 2008 values = f"VALUES{self.seg('')}{args}" 2009 values = ( 2010 f"({values})" 2011 if self.WRAP_DERIVED_VALUES 2012 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2013 else values 2014 ) 2015 return f"{values} AS {alias}" if alias else values 2016 2017 # Converts `VALUES...` expression into a series of select unions. 2018 alias_node = expression.args.get("alias") 2019 column_names = alias_node and alias_node.columns 2020 2021 selects: t.List[exp.Query] = [] 2022 2023 for i, tup in enumerate(expression.expressions): 2024 row = tup.expressions 2025 2026 if i == 0 and column_names: 2027 row = [ 2028 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2029 ] 2030 2031 selects.append(exp.Select(expressions=row)) 2032 2033 if self.pretty: 2034 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2035 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2036 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2037 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2038 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2039 2040 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2041 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2042 return f"({unions}){alias}" 2043 2044 def var_sql(self, expression: exp.Var) -> str: 2045 return self.sql(expression, "this") 2046 2047 @unsupported_args("expressions") 2048 def into_sql(self, expression: exp.Into) -> str: 2049 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2050 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2051 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2052 2053 def from_sql(self, expression: exp.From) -> str: 2054 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2055 2056 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2057 grouping_sets = self.expressions(expression, indent=False) 2058 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2059 2060 def rollup_sql(self, expression: exp.Rollup) -> str: 2061 expressions = self.expressions(expression, indent=False) 2062 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2063 2064 def cube_sql(self, expression: exp.Cube) -> str: 2065 expressions = self.expressions(expression, indent=False) 2066 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2067 2068 def group_sql(self, expression: exp.Group) -> str: 2069 group_by_all = expression.args.get("all") 2070 if group_by_all is True: 2071 modifier = " ALL" 2072 elif group_by_all is False: 2073 modifier = " DISTINCT" 2074 else: 2075 modifier = "" 2076 2077 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2078 2079 grouping_sets = self.expressions(expression, key="grouping_sets") 2080 cube = self.expressions(expression, key="cube") 2081 rollup = self.expressions(expression, key="rollup") 2082 2083 groupings = csv( 2084 self.seg(grouping_sets) if grouping_sets else "", 2085 self.seg(cube) if cube else "", 2086 self.seg(rollup) if rollup else "", 2087 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2088 sep=self.GROUPINGS_SEP, 2089 ) 2090 2091 if ( 2092 expression.expressions 2093 and groupings 2094 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2095 ): 2096 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2097 2098 return f"{group_by}{groupings}" 2099 2100 def having_sql(self, expression: exp.Having) -> str: 2101 this = self.indent(self.sql(expression, "this")) 2102 return f"{self.seg('HAVING')}{self.sep()}{this}" 2103 2104 def connect_sql(self, expression: exp.Connect) -> str: 2105 start = self.sql(expression, "start") 2106 start = self.seg(f"START WITH {start}") if start else "" 2107 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2108 connect = self.sql(expression, "connect") 2109 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2110 return start + connect 2111 2112 def prior_sql(self, expression: exp.Prior) -> str: 2113 return f"PRIOR {self.sql(expression, 'this')}" 2114 2115 def join_sql(self, expression: exp.Join) -> str: 2116 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2117 side = None 2118 else: 2119 side = expression.side 2120 2121 op_sql = " ".join( 2122 op 2123 for op in ( 2124 expression.method, 2125 "GLOBAL" if expression.args.get("global") else None, 2126 side, 2127 expression.kind, 2128 expression.hint if self.JOIN_HINTS else None, 2129 ) 2130 if op 2131 ) 2132 match_cond = self.sql(expression, "match_condition") 2133 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2134 on_sql = self.sql(expression, "on") 2135 using = expression.args.get("using") 2136 2137 if not on_sql and using: 2138 on_sql = csv(*(self.sql(column) for column in using)) 2139 2140 this = expression.this 2141 this_sql = self.sql(this) 2142 2143 if on_sql: 2144 on_sql = self.indent(on_sql, skip_first=True) 2145 space = self.seg(" " * self.pad) if self.pretty else " " 2146 if using: 2147 on_sql = f"{space}USING ({on_sql})" 2148 else: 2149 on_sql = f"{space}ON {on_sql}" 2150 elif not op_sql: 2151 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2152 return f" {this_sql}" 2153 2154 return f", {this_sql}" 2155 2156 if op_sql != "STRAIGHT_JOIN": 2157 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2158 2159 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}" 2160 2161 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 2162 args = self.expressions(expression, flat=True) 2163 args = f"({args})" if len(args.split(",")) > 1 else args 2164 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2165 2166 def lateral_op(self, expression: exp.Lateral) -> str: 2167 cross_apply = expression.args.get("cross_apply") 2168 2169 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2170 if cross_apply is True: 2171 op = "INNER JOIN " 2172 elif cross_apply is False: 2173 op = "LEFT JOIN " 2174 else: 2175 op = "" 2176 2177 return f"{op}LATERAL" 2178 2179 def lateral_sql(self, expression: exp.Lateral) -> str: 2180 this = self.sql(expression, "this") 2181 2182 if expression.args.get("view"): 2183 alias = expression.args["alias"] 2184 columns = self.expressions(alias, key="columns", flat=True) 2185 table = f" {alias.name}" if alias.name else "" 2186 columns = f" AS {columns}" if columns else "" 2187 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2188 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2189 2190 alias = self.sql(expression, "alias") 2191 alias = f" AS {alias}" if alias else "" 2192 return f"{self.lateral_op(expression)} {this}{alias}" 2193 2194 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2195 this = self.sql(expression, "this") 2196 2197 args = [ 2198 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2199 for e in (expression.args.get(k) for k in ("offset", "expression")) 2200 if e 2201 ] 2202 2203 args_sql = ", ".join(self.sql(e) for e in args) 2204 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2205 expressions = self.expressions(expression, flat=True) 2206 expressions = f" BY {expressions}" if expressions else "" 2207 2208 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{expressions}" 2209 2210 def offset_sql(self, expression: exp.Offset) -> str: 2211 this = self.sql(expression, "this") 2212 value = expression.expression 2213 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2214 expressions = self.expressions(expression, flat=True) 2215 expressions = f" BY {expressions}" if expressions else "" 2216 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2217 2218 def setitem_sql(self, expression: exp.SetItem) -> str: 2219 kind = self.sql(expression, "kind") 2220 kind = f"{kind} " if kind else "" 2221 this = self.sql(expression, "this") 2222 expressions = self.expressions(expression) 2223 collate = self.sql(expression, "collate") 2224 collate = f" COLLATE {collate}" if collate else "" 2225 global_ = "GLOBAL " if expression.args.get("global") else "" 2226 return f"{global_}{kind}{this}{expressions}{collate}" 2227 2228 def set_sql(self, expression: exp.Set) -> str: 2229 expressions = ( 2230 f" {self.expressions(expression, flat=True)}" if expression.expressions else "" 2231 ) 2232 tag = " TAG" if expression.args.get("tag") else "" 2233 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2234 2235 def pragma_sql(self, expression: exp.Pragma) -> str: 2236 return f"PRAGMA {self.sql(expression, 'this')}" 2237 2238 def lock_sql(self, expression: exp.Lock) -> str: 2239 if not self.LOCKING_READS_SUPPORTED: 2240 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2241 return "" 2242 2243 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2244 expressions = self.expressions(expression, flat=True) 2245 expressions = f" OF {expressions}" if expressions else "" 2246 wait = expression.args.get("wait") 2247 2248 if wait is not None: 2249 if isinstance(wait, exp.Literal): 2250 wait = f" WAIT {self.sql(wait)}" 2251 else: 2252 wait = " NOWAIT" if wait else " SKIP LOCKED" 2253 2254 return f"{lock_type}{expressions}{wait or ''}" 2255 2256 def literal_sql(self, expression: exp.Literal) -> str: 2257 text = expression.this or "" 2258 if expression.is_string: 2259 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2260 return text 2261 2262 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2263 if self.dialect.ESCAPED_SEQUENCES: 2264 to_escaped = self.dialect.ESCAPED_SEQUENCES 2265 text = "".join( 2266 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2267 ) 2268 2269 return self._replace_line_breaks(text).replace( 2270 self.dialect.QUOTE_END, self._escaped_quote_end 2271 ) 2272 2273 def loaddata_sql(self, expression: exp.LoadData) -> str: 2274 local = " LOCAL" if expression.args.get("local") else "" 2275 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2276 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2277 this = f" INTO TABLE {self.sql(expression, 'this')}" 2278 partition = self.sql(expression, "partition") 2279 partition = f" {partition}" if partition else "" 2280 input_format = self.sql(expression, "input_format") 2281 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2282 serde = self.sql(expression, "serde") 2283 serde = f" SERDE {serde}" if serde else "" 2284 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2285 2286 def null_sql(self, *_) -> str: 2287 return "NULL" 2288 2289 def boolean_sql(self, expression: exp.Boolean) -> str: 2290 return "TRUE" if expression.this else "FALSE" 2291 2292 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2293 this = self.sql(expression, "this") 2294 this = f"{this} " if this else this 2295 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2296 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2297 2298 def withfill_sql(self, expression: exp.WithFill) -> str: 2299 from_sql = self.sql(expression, "from") 2300 from_sql = f" FROM {from_sql}" if from_sql else "" 2301 to_sql = self.sql(expression, "to") 2302 to_sql = f" TO {to_sql}" if to_sql else "" 2303 step_sql = self.sql(expression, "step") 2304 step_sql = f" STEP {step_sql}" if step_sql else "" 2305 interpolated_values = [ 2306 f"{self.sql(named_expression, 'alias')} AS {self.sql(named_expression, 'this')}" 2307 for named_expression in expression.args.get("interpolate") or [] 2308 ] 2309 interpolate = ( 2310 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2311 ) 2312 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2313 2314 def cluster_sql(self, expression: exp.Cluster) -> str: 2315 return self.op_expressions("CLUSTER BY", expression) 2316 2317 def distribute_sql(self, expression: exp.Distribute) -> str: 2318 return self.op_expressions("DISTRIBUTE BY", expression) 2319 2320 def sort_sql(self, expression: exp.Sort) -> str: 2321 return self.op_expressions("SORT BY", expression) 2322 2323 def ordered_sql(self, expression: exp.Ordered) -> str: 2324 desc = expression.args.get("desc") 2325 asc = not desc 2326 2327 nulls_first = expression.args.get("nulls_first") 2328 nulls_last = not nulls_first 2329 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2330 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2331 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2332 2333 this = self.sql(expression, "this") 2334 2335 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2336 nulls_sort_change = "" 2337 if nulls_first and ( 2338 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2339 ): 2340 nulls_sort_change = " NULLS FIRST" 2341 elif ( 2342 nulls_last 2343 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2344 and not nulls_are_last 2345 ): 2346 nulls_sort_change = " NULLS LAST" 2347 2348 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2349 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2350 window = expression.find_ancestor(exp.Window, exp.Select) 2351 if isinstance(window, exp.Window) and window.args.get("spec"): 2352 self.unsupported( 2353 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2354 ) 2355 nulls_sort_change = "" 2356 elif ( 2357 self.NULL_ORDERING_SUPPORTED is False 2358 and (isinstance(expression.find_ancestor(exp.AggFunc, exp.Select), exp.AggFunc)) 2359 and ( 2360 (asc and nulls_sort_change == " NULLS LAST") 2361 or (desc and nulls_sort_change == " NULLS FIRST") 2362 ) 2363 ): 2364 self.unsupported( 2365 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2366 ) 2367 nulls_sort_change = "" 2368 elif self.NULL_ORDERING_SUPPORTED is None: 2369 if expression.this.is_int: 2370 self.unsupported( 2371 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2372 ) 2373 elif not isinstance(expression.this, exp.Rand): 2374 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2375 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2376 nulls_sort_change = "" 2377 2378 with_fill = self.sql(expression, "with_fill") 2379 with_fill = f" {with_fill}" if with_fill else "" 2380 2381 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2382 2383 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2384 window_frame = self.sql(expression, "window_frame") 2385 window_frame = f"{window_frame} " if window_frame else "" 2386 2387 this = self.sql(expression, "this") 2388 2389 return f"{window_frame}{this}" 2390 2391 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2392 partition = self.partition_by_sql(expression) 2393 order = self.sql(expression, "order") 2394 measures = self.expressions(expression, key="measures") 2395 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2396 rows = self.sql(expression, "rows") 2397 rows = self.seg(rows) if rows else "" 2398 after = self.sql(expression, "after") 2399 after = self.seg(after) if after else "" 2400 pattern = self.sql(expression, "pattern") 2401 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2402 definition_sqls = [ 2403 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2404 for definition in expression.args.get("define", []) 2405 ] 2406 definitions = self.expressions(sqls=definition_sqls) 2407 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2408 body = "".join( 2409 ( 2410 partition, 2411 order, 2412 measures, 2413 rows, 2414 after, 2415 pattern, 2416 define, 2417 ) 2418 ) 2419 alias = self.sql(expression, "alias") 2420 alias = f" {alias}" if alias else "" 2421 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2422 2423 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2424 limit = expression.args.get("limit") 2425 2426 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2427 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2428 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2429 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2430 2431 return csv( 2432 *sqls, 2433 *[self.sql(join) for join in expression.args.get("joins") or []], 2434 self.sql(expression, "connect"), 2435 self.sql(expression, "match"), 2436 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2437 self.sql(expression, "prewhere"), 2438 self.sql(expression, "where"), 2439 self.sql(expression, "group"), 2440 self.sql(expression, "having"), 2441 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2442 self.sql(expression, "order"), 2443 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2444 *self.after_limit_modifiers(expression), 2445 self.options_modifier(expression), 2446 sep="", 2447 ) 2448 2449 def options_modifier(self, expression: exp.Expression) -> str: 2450 options = self.expressions(expression, key="options") 2451 return f" {options}" if options else "" 2452 2453 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2454 return "" 2455 2456 def offset_limit_modifiers( 2457 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2458 ) -> t.List[str]: 2459 return [ 2460 self.sql(expression, "offset") if fetch else self.sql(limit), 2461 self.sql(limit) if fetch else self.sql(expression, "offset"), 2462 ] 2463 2464 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2465 locks = self.expressions(expression, key="locks", sep=" ") 2466 locks = f" {locks}" if locks else "" 2467 return [locks, self.sql(expression, "sample")] 2468 2469 def select_sql(self, expression: exp.Select) -> str: 2470 into = expression.args.get("into") 2471 if not self.SUPPORTS_SELECT_INTO and into: 2472 into.pop() 2473 2474 hint = self.sql(expression, "hint") 2475 distinct = self.sql(expression, "distinct") 2476 distinct = f" {distinct}" if distinct else "" 2477 kind = self.sql(expression, "kind") 2478 2479 limit = expression.args.get("limit") 2480 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2481 top = self.limit_sql(limit, top=True) 2482 limit.pop() 2483 else: 2484 top = "" 2485 2486 expressions = self.expressions(expression) 2487 2488 if kind: 2489 if kind in self.SELECT_KINDS: 2490 kind = f" AS {kind}" 2491 else: 2492 if kind == "STRUCT": 2493 expressions = self.expressions( 2494 sqls=[ 2495 self.sql( 2496 exp.Struct( 2497 expressions=[ 2498 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2499 if isinstance(e, exp.Alias) 2500 else e 2501 for e in expression.expressions 2502 ] 2503 ) 2504 ) 2505 ] 2506 ) 2507 kind = "" 2508 2509 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2510 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2511 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2512 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2513 sql = self.query_modifiers( 2514 expression, 2515 f"SELECT{top_distinct}{kind}{expressions}", 2516 self.sql(expression, "into", comment=False), 2517 self.sql(expression, "from", comment=False), 2518 ) 2519 2520 sql = self.prepend_ctes(expression, sql) 2521 2522 if not self.SUPPORTS_SELECT_INTO and into: 2523 if into.args.get("temporary"): 2524 table_kind = " TEMPORARY" 2525 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2526 table_kind = " UNLOGGED" 2527 else: 2528 table_kind = "" 2529 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2530 2531 return sql 2532 2533 def schema_sql(self, expression: exp.Schema) -> str: 2534 this = self.sql(expression, "this") 2535 sql = self.schema_columns_sql(expression) 2536 return f"{this} {sql}" if this and sql else this or sql 2537 2538 def schema_columns_sql(self, expression: exp.Schema) -> str: 2539 if expression.expressions: 2540 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2541 return "" 2542 2543 def star_sql(self, expression: exp.Star) -> str: 2544 except_ = self.expressions(expression, key="except", flat=True) 2545 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2546 replace = self.expressions(expression, key="replace", flat=True) 2547 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2548 rename = self.expressions(expression, key="rename", flat=True) 2549 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2550 return f"*{except_}{replace}{rename}" 2551 2552 def parameter_sql(self, expression: exp.Parameter) -> str: 2553 this = self.sql(expression, "this") 2554 return f"{self.PARAMETER_TOKEN}{this}" 2555 2556 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2557 this = self.sql(expression, "this") 2558 kind = expression.text("kind") 2559 if kind: 2560 kind = f"{kind}." 2561 return f"@@{kind}{this}" 2562 2563 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2564 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2565 2566 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2567 alias = self.sql(expression, "alias") 2568 alias = f"{sep}{alias}" if alias else "" 2569 sample = self.sql(expression, "sample") 2570 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2571 alias = f"{sample}{alias}" 2572 2573 # Set to None so it's not generated again by self.query_modifiers() 2574 expression.set("sample", None) 2575 2576 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2577 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2578 return self.prepend_ctes(expression, sql) 2579 2580 def qualify_sql(self, expression: exp.Qualify) -> str: 2581 this = self.indent(self.sql(expression, "this")) 2582 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2583 2584 def unnest_sql(self, expression: exp.Unnest) -> str: 2585 args = self.expressions(expression, flat=True) 2586 2587 alias = expression.args.get("alias") 2588 offset = expression.args.get("offset") 2589 2590 if self.UNNEST_WITH_ORDINALITY: 2591 if alias and isinstance(offset, exp.Expression): 2592 alias.append("columns", offset) 2593 2594 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2595 columns = alias.columns 2596 alias = self.sql(columns[0]) if columns else "" 2597 else: 2598 alias = self.sql(alias) 2599 2600 alias = f" AS {alias}" if alias else alias 2601 if self.UNNEST_WITH_ORDINALITY: 2602 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2603 else: 2604 if isinstance(offset, exp.Expression): 2605 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2606 elif offset: 2607 suffix = f"{alias} WITH OFFSET" 2608 else: 2609 suffix = alias 2610 2611 return f"UNNEST({args}){suffix}" 2612 2613 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2614 return "" 2615 2616 def where_sql(self, expression: exp.Where) -> str: 2617 this = self.indent(self.sql(expression, "this")) 2618 return f"{self.seg('WHERE')}{self.sep()}{this}" 2619 2620 def window_sql(self, expression: exp.Window) -> str: 2621 this = self.sql(expression, "this") 2622 partition = self.partition_by_sql(expression) 2623 order = expression.args.get("order") 2624 order = self.order_sql(order, flat=True) if order else "" 2625 spec = self.sql(expression, "spec") 2626 alias = self.sql(expression, "alias") 2627 over = self.sql(expression, "over") or "OVER" 2628 2629 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2630 2631 first = expression.args.get("first") 2632 if first is None: 2633 first = "" 2634 else: 2635 first = "FIRST" if first else "LAST" 2636 2637 if not partition and not order and not spec and alias: 2638 return f"{this} {alias}" 2639 2640 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 2641 return f"{this} ({args})" 2642 2643 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2644 partition = self.expressions(expression, key="partition_by", flat=True) 2645 return f"PARTITION BY {partition}" if partition else "" 2646 2647 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2648 kind = self.sql(expression, "kind") 2649 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2650 end = ( 2651 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2652 or "CURRENT ROW" 2653 ) 2654 return f"{kind} BETWEEN {start} AND {end}" 2655 2656 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2657 this = self.sql(expression, "this") 2658 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2659 return f"{this} WITHIN GROUP ({expression_sql})" 2660 2661 def between_sql(self, expression: exp.Between) -> str: 2662 this = self.sql(expression, "this") 2663 low = self.sql(expression, "low") 2664 high = self.sql(expression, "high") 2665 return f"{this} BETWEEN {low} AND {high}" 2666 2667 def bracket_offset_expressions( 2668 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2669 ) -> t.List[exp.Expression]: 2670 return apply_index_offset( 2671 expression.this, 2672 expression.expressions, 2673 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2674 ) 2675 2676 def bracket_sql(self, expression: exp.Bracket) -> str: 2677 expressions = self.bracket_offset_expressions(expression) 2678 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2679 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2680 2681 def all_sql(self, expression: exp.All) -> str: 2682 return f"ALL {self.wrap(expression)}" 2683 2684 def any_sql(self, expression: exp.Any) -> str: 2685 this = self.sql(expression, "this") 2686 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2687 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2688 this = self.wrap(this) 2689 return f"ANY{this}" 2690 return f"ANY {this}" 2691 2692 def exists_sql(self, expression: exp.Exists) -> str: 2693 return f"EXISTS{self.wrap(expression)}" 2694 2695 def case_sql(self, expression: exp.Case) -> str: 2696 this = self.sql(expression, "this") 2697 statements = [f"CASE {this}" if this else "CASE"] 2698 2699 for e in expression.args["ifs"]: 2700 statements.append(f"WHEN {self.sql(e, 'this')}") 2701 statements.append(f"THEN {self.sql(e, 'true')}") 2702 2703 default = self.sql(expression, "default") 2704 2705 if default: 2706 statements.append(f"ELSE {default}") 2707 2708 statements.append("END") 2709 2710 if self.pretty and self.too_wide(statements): 2711 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2712 2713 return " ".join(statements) 2714 2715 def constraint_sql(self, expression: exp.Constraint) -> str: 2716 this = self.sql(expression, "this") 2717 expressions = self.expressions(expression, flat=True) 2718 return f"CONSTRAINT {this} {expressions}" 2719 2720 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2721 order = expression.args.get("order") 2722 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2723 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2724 2725 def extract_sql(self, expression: exp.Extract) -> str: 2726 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name 2727 expression_sql = self.sql(expression, "expression") 2728 return f"EXTRACT({this} FROM {expression_sql})" 2729 2730 def trim_sql(self, expression: exp.Trim) -> str: 2731 trim_type = self.sql(expression, "position") 2732 2733 if trim_type == "LEADING": 2734 func_name = "LTRIM" 2735 elif trim_type == "TRAILING": 2736 func_name = "RTRIM" 2737 else: 2738 func_name = "TRIM" 2739 2740 return self.func(func_name, expression.this, expression.expression) 2741 2742 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2743 args = expression.expressions 2744 if isinstance(expression, exp.ConcatWs): 2745 args = args[1:] # Skip the delimiter 2746 2747 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2748 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2749 2750 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2751 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2752 2753 return args 2754 2755 def concat_sql(self, expression: exp.Concat) -> str: 2756 expressions = self.convert_concat_args(expression) 2757 2758 # Some dialects don't allow a single-argument CONCAT call 2759 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2760 return self.sql(expressions[0]) 2761 2762 return self.func("CONCAT", *expressions) 2763 2764 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2765 return self.func( 2766 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2767 ) 2768 2769 def check_sql(self, expression: exp.Check) -> str: 2770 this = self.sql(expression, key="this") 2771 return f"CHECK ({this})" 2772 2773 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2774 expressions = self.expressions(expression, flat=True) 2775 reference = self.sql(expression, "reference") 2776 reference = f" {reference}" if reference else "" 2777 delete = self.sql(expression, "delete") 2778 delete = f" ON DELETE {delete}" if delete else "" 2779 update = self.sql(expression, "update") 2780 update = f" ON UPDATE {update}" if update else "" 2781 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}" 2782 2783 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2784 expressions = self.expressions(expression, flat=True) 2785 options = self.expressions(expression, key="options", flat=True, sep=" ") 2786 options = f" {options}" if options else "" 2787 return f"PRIMARY KEY ({expressions}){options}" 2788 2789 def if_sql(self, expression: exp.If) -> str: 2790 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 2791 2792 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 2793 modifier = expression.args.get("modifier") 2794 modifier = f" {modifier}" if modifier else "" 2795 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 2796 2797 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 2798 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 2799 2800 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2801 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2802 2803 if expression.args.get("escape"): 2804 path = self.escape_str(path) 2805 2806 if self.QUOTE_JSON_PATH: 2807 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2808 2809 return path 2810 2811 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2812 if isinstance(expression, exp.JSONPathPart): 2813 transform = self.TRANSFORMS.get(expression.__class__) 2814 if not callable(transform): 2815 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2816 return "" 2817 2818 return transform(self, expression) 2819 2820 if isinstance(expression, int): 2821 return str(expression) 2822 2823 if self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 2824 escaped = expression.replace("'", "\\'") 2825 escaped = f"\\'{expression}\\'" 2826 else: 2827 escaped = expression.replace('"', '\\"') 2828 escaped = f'"{escaped}"' 2829 2830 return escaped 2831 2832 def formatjson_sql(self, expression: exp.FormatJson) -> str: 2833 return f"{self.sql(expression, 'this')} FORMAT JSON" 2834 2835 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 2836 null_handling = expression.args.get("null_handling") 2837 null_handling = f" {null_handling}" if null_handling else "" 2838 2839 unique_keys = expression.args.get("unique_keys") 2840 if unique_keys is not None: 2841 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2842 else: 2843 unique_keys = "" 2844 2845 return_type = self.sql(expression, "return_type") 2846 return_type = f" RETURNING {return_type}" if return_type else "" 2847 encoding = self.sql(expression, "encoding") 2848 encoding = f" ENCODING {encoding}" if encoding else "" 2849 2850 return self.func( 2851 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 2852 *expression.expressions, 2853 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2854 ) 2855 2856 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 2857 return self.jsonobject_sql(expression) 2858 2859 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2860 null_handling = expression.args.get("null_handling") 2861 null_handling = f" {null_handling}" if null_handling else "" 2862 return_type = self.sql(expression, "return_type") 2863 return_type = f" RETURNING {return_type}" if return_type else "" 2864 strict = " STRICT" if expression.args.get("strict") else "" 2865 return self.func( 2866 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 2867 ) 2868 2869 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 2870 this = self.sql(expression, "this") 2871 order = self.sql(expression, "order") 2872 null_handling = expression.args.get("null_handling") 2873 null_handling = f" {null_handling}" if null_handling else "" 2874 return_type = self.sql(expression, "return_type") 2875 return_type = f" RETURNING {return_type}" if return_type else "" 2876 strict = " STRICT" if expression.args.get("strict") else "" 2877 return self.func( 2878 "JSON_ARRAYAGG", 2879 this, 2880 suffix=f"{order}{null_handling}{return_type}{strict})", 2881 ) 2882 2883 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 2884 path = self.sql(expression, "path") 2885 path = f" PATH {path}" if path else "" 2886 nested_schema = self.sql(expression, "nested_schema") 2887 2888 if nested_schema: 2889 return f"NESTED{path} {nested_schema}" 2890 2891 this = self.sql(expression, "this") 2892 kind = self.sql(expression, "kind") 2893 kind = f" {kind}" if kind else "" 2894 return f"{this}{kind}{path}" 2895 2896 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 2897 return self.func("COLUMNS", *expression.expressions) 2898 2899 def jsontable_sql(self, expression: exp.JSONTable) -> str: 2900 this = self.sql(expression, "this") 2901 path = self.sql(expression, "path") 2902 path = f", {path}" if path else "" 2903 error_handling = expression.args.get("error_handling") 2904 error_handling = f" {error_handling}" if error_handling else "" 2905 empty_handling = expression.args.get("empty_handling") 2906 empty_handling = f" {empty_handling}" if empty_handling else "" 2907 schema = self.sql(expression, "schema") 2908 return self.func( 2909 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 2910 ) 2911 2912 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 2913 this = self.sql(expression, "this") 2914 kind = self.sql(expression, "kind") 2915 path = self.sql(expression, "path") 2916 path = f" {path}" if path else "" 2917 as_json = " AS JSON" if expression.args.get("as_json") else "" 2918 return f"{this} {kind}{path}{as_json}" 2919 2920 def openjson_sql(self, expression: exp.OpenJSON) -> str: 2921 this = self.sql(expression, "this") 2922 path = self.sql(expression, "path") 2923 path = f", {path}" if path else "" 2924 expressions = self.expressions(expression) 2925 with_ = ( 2926 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 2927 if expressions 2928 else "" 2929 ) 2930 return f"OPENJSON({this}{path}){with_}" 2931 2932 def in_sql(self, expression: exp.In) -> str: 2933 query = expression.args.get("query") 2934 unnest = expression.args.get("unnest") 2935 field = expression.args.get("field") 2936 is_global = " GLOBAL" if expression.args.get("is_global") else "" 2937 2938 if query: 2939 in_sql = self.sql(query) 2940 elif unnest: 2941 in_sql = self.in_unnest_op(unnest) 2942 elif field: 2943 in_sql = self.sql(field) 2944 else: 2945 in_sql = f"({self.expressions(expression, flat=True)})" 2946 2947 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 2948 2949 def in_unnest_op(self, unnest: exp.Unnest) -> str: 2950 return f"(SELECT {self.sql(unnest)})" 2951 2952 def interval_sql(self, expression: exp.Interval) -> str: 2953 unit = self.sql(expression, "unit") 2954 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 2955 unit = self.TIME_PART_SINGULARS.get(unit, unit) 2956 unit = f" {unit}" if unit else "" 2957 2958 if self.SINGLE_STRING_INTERVAL: 2959 this = expression.this.name if expression.this else "" 2960 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 2961 2962 this = self.sql(expression, "this") 2963 if this: 2964 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 2965 this = f" {this}" if unwrapped else f" ({this})" 2966 2967 return f"INTERVAL{this}{unit}" 2968 2969 def return_sql(self, expression: exp.Return) -> str: 2970 return f"RETURN {self.sql(expression, 'this')}" 2971 2972 def reference_sql(self, expression: exp.Reference) -> str: 2973 this = self.sql(expression, "this") 2974 expressions = self.expressions(expression, flat=True) 2975 expressions = f"({expressions})" if expressions else "" 2976 options = self.expressions(expression, key="options", flat=True, sep=" ") 2977 options = f" {options}" if options else "" 2978 return f"REFERENCES {this}{expressions}{options}" 2979 2980 def anonymous_sql(self, expression: exp.Anonymous) -> str: 2981 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 2982 parent = expression.parent 2983 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 2984 return self.func( 2985 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 2986 ) 2987 2988 def paren_sql(self, expression: exp.Paren) -> str: 2989 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 2990 return f"({sql}{self.seg(')', sep='')}" 2991 2992 def neg_sql(self, expression: exp.Neg) -> str: 2993 # This makes sure we don't convert "- - 5" to "--5", which is a comment 2994 this_sql = self.sql(expression, "this") 2995 sep = " " if this_sql[0] == "-" else "" 2996 return f"-{sep}{this_sql}" 2997 2998 def not_sql(self, expression: exp.Not) -> str: 2999 return f"NOT {self.sql(expression, 'this')}" 3000 3001 def alias_sql(self, expression: exp.Alias) -> str: 3002 alias = self.sql(expression, "alias") 3003 alias = f" AS {alias}" if alias else "" 3004 return f"{self.sql(expression, 'this')}{alias}" 3005 3006 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3007 alias = expression.args["alias"] 3008 3009 identifier_alias = isinstance(alias, exp.Identifier) 3010 literal_alias = isinstance(alias, exp.Literal) 3011 3012 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3013 alias.replace(exp.Literal.string(alias.output_name)) 3014 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3015 alias.replace(exp.to_identifier(alias.output_name)) 3016 3017 return self.alias_sql(expression) 3018 3019 def aliases_sql(self, expression: exp.Aliases) -> str: 3020 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3021 3022 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3023 this = self.sql(expression, "this") 3024 index = self.sql(expression, "expression") 3025 return f"{this} AT {index}" 3026 3027 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3028 this = self.sql(expression, "this") 3029 zone = self.sql(expression, "zone") 3030 return f"{this} AT TIME ZONE {zone}" 3031 3032 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3033 this = self.sql(expression, "this") 3034 zone = self.sql(expression, "zone") 3035 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3036 3037 def add_sql(self, expression: exp.Add) -> str: 3038 return self.binary(expression, "+") 3039 3040 def and_sql( 3041 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3042 ) -> str: 3043 return self.connector_sql(expression, "AND", stack) 3044 3045 def or_sql( 3046 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3047 ) -> str: 3048 return self.connector_sql(expression, "OR", stack) 3049 3050 def xor_sql( 3051 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3052 ) -> str: 3053 return self.connector_sql(expression, "XOR", stack) 3054 3055 def connector_sql( 3056 self, 3057 expression: exp.Connector, 3058 op: str, 3059 stack: t.Optional[t.List[str | exp.Expression]] = None, 3060 ) -> str: 3061 if stack is not None: 3062 if expression.expressions: 3063 stack.append(self.expressions(expression, sep=f" {op} ")) 3064 else: 3065 stack.append(expression.right) 3066 if expression.comments and self.comments: 3067 for comment in expression.comments: 3068 if comment: 3069 op += f" /*{self.pad_comment(comment)}*/" 3070 stack.extend((op, expression.left)) 3071 return op 3072 3073 stack = [expression] 3074 sqls: t.List[str] = [] 3075 ops = set() 3076 3077 while stack: 3078 node = stack.pop() 3079 if isinstance(node, exp.Connector): 3080 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3081 else: 3082 sql = self.sql(node) 3083 if sqls and sqls[-1] in ops: 3084 sqls[-1] += f" {sql}" 3085 else: 3086 sqls.append(sql) 3087 3088 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3089 return sep.join(sqls) 3090 3091 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3092 return self.binary(expression, "&") 3093 3094 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3095 return self.binary(expression, "<<") 3096 3097 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3098 return f"~{self.sql(expression, 'this')}" 3099 3100 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3101 return self.binary(expression, "|") 3102 3103 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3104 return self.binary(expression, ">>") 3105 3106 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3107 return self.binary(expression, "^") 3108 3109 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3110 format_sql = self.sql(expression, "format") 3111 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3112 to_sql = self.sql(expression, "to") 3113 to_sql = f" {to_sql}" if to_sql else "" 3114 action = self.sql(expression, "action") 3115 action = f" {action}" if action else "" 3116 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{format_sql}{action})" 3117 3118 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3119 zone = self.sql(expression, "this") 3120 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3121 3122 def collate_sql(self, expression: exp.Collate) -> str: 3123 if self.COLLATE_IS_FUNC: 3124 return self.function_fallback_sql(expression) 3125 return self.binary(expression, "COLLATE") 3126 3127 def command_sql(self, expression: exp.Command) -> str: 3128 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3129 3130 def comment_sql(self, expression: exp.Comment) -> str: 3131 this = self.sql(expression, "this") 3132 kind = expression.args["kind"] 3133 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3134 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3135 expression_sql = self.sql(expression, "expression") 3136 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3137 3138 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3139 this = self.sql(expression, "this") 3140 delete = " DELETE" if expression.args.get("delete") else "" 3141 recompress = self.sql(expression, "recompress") 3142 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3143 to_disk = self.sql(expression, "to_disk") 3144 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3145 to_volume = self.sql(expression, "to_volume") 3146 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3147 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3148 3149 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3150 where = self.sql(expression, "where") 3151 group = self.sql(expression, "group") 3152 aggregates = self.expressions(expression, key="aggregates") 3153 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3154 3155 if not (where or group or aggregates) and len(expression.expressions) == 1: 3156 return f"TTL {self.expressions(expression, flat=True)}" 3157 3158 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3159 3160 def transaction_sql(self, expression: exp.Transaction) -> str: 3161 return "BEGIN" 3162 3163 def commit_sql(self, expression: exp.Commit) -> str: 3164 chain = expression.args.get("chain") 3165 if chain is not None: 3166 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3167 3168 return f"COMMIT{chain or ''}" 3169 3170 def rollback_sql(self, expression: exp.Rollback) -> str: 3171 savepoint = expression.args.get("savepoint") 3172 savepoint = f" TO {savepoint}" if savepoint else "" 3173 return f"ROLLBACK{savepoint}" 3174 3175 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3176 this = self.sql(expression, "this") 3177 3178 dtype = self.sql(expression, "dtype") 3179 if dtype: 3180 collate = self.sql(expression, "collate") 3181 collate = f" COLLATE {collate}" if collate else "" 3182 using = self.sql(expression, "using") 3183 using = f" USING {using}" if using else "" 3184 return f"ALTER COLUMN {this} SET DATA TYPE {dtype}{collate}{using}" 3185 3186 default = self.sql(expression, "default") 3187 if default: 3188 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3189 3190 comment = self.sql(expression, "comment") 3191 if comment: 3192 return f"ALTER COLUMN {this} COMMENT {comment}" 3193 3194 allow_null = expression.args.get("allow_null") 3195 drop = expression.args.get("drop") 3196 3197 if not drop and not allow_null: 3198 self.unsupported("Unsupported ALTER COLUMN syntax") 3199 3200 if allow_null is not None: 3201 keyword = "DROP" if drop else "SET" 3202 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3203 3204 return f"ALTER COLUMN {this} DROP DEFAULT" 3205 3206 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3207 this = self.sql(expression, "this") 3208 if not isinstance(expression.this, exp.Var): 3209 this = f"KEY DISTKEY {this}" 3210 return f"ALTER DISTSTYLE {this}" 3211 3212 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3213 compound = " COMPOUND" if expression.args.get("compound") else "" 3214 this = self.sql(expression, "this") 3215 expressions = self.expressions(expression, flat=True) 3216 expressions = f"({expressions})" if expressions else "" 3217 return f"ALTER{compound} SORTKEY {this or expressions}" 3218 3219 def renametable_sql(self, expression: exp.RenameTable) -> str: 3220 if not self.RENAME_TABLE_WITH_DB: 3221 # Remove db from tables 3222 expression = expression.transform( 3223 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3224 ).assert_is(exp.RenameTable) 3225 this = self.sql(expression, "this") 3226 return f"RENAME TO {this}" 3227 3228 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3229 exists = " IF EXISTS" if expression.args.get("exists") else "" 3230 old_column = self.sql(expression, "this") 3231 new_column = self.sql(expression, "to") 3232 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3233 3234 def alterset_sql(self, expression: exp.AlterSet) -> str: 3235 exprs = self.expressions(expression, flat=True) 3236 return f"SET {exprs}" 3237 3238 def alter_sql(self, expression: exp.Alter) -> str: 3239 actions = expression.args["actions"] 3240 3241 if isinstance(actions[0], exp.ColumnDef): 3242 actions = self.add_column_sql(expression) 3243 elif isinstance(actions[0], exp.Schema): 3244 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3245 elif isinstance(actions[0], exp.Delete): 3246 actions = self.expressions(expression, key="actions", flat=True) 3247 elif isinstance(actions[0], exp.Query): 3248 actions = "AS " + self.expressions(expression, key="actions") 3249 else: 3250 actions = self.expressions(expression, key="actions", flat=True) 3251 3252 exists = " IF EXISTS" if expression.args.get("exists") else "" 3253 on_cluster = self.sql(expression, "cluster") 3254 on_cluster = f" {on_cluster}" if on_cluster else "" 3255 only = " ONLY" if expression.args.get("only") else "" 3256 options = self.expressions(expression, key="options") 3257 options = f", {options}" if options else "" 3258 kind = self.sql(expression, "kind") 3259 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3260 3261 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{not_valid}{options}" 3262 3263 def add_column_sql(self, expression: exp.Alter) -> str: 3264 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3265 return self.expressions( 3266 expression, 3267 key="actions", 3268 prefix="ADD COLUMN ", 3269 skip_first=True, 3270 ) 3271 return f"ADD {self.expressions(expression, key='actions', flat=True)}" 3272 3273 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3274 expressions = self.expressions(expression) 3275 exists = " IF EXISTS " if expression.args.get("exists") else " " 3276 return f"DROP{exists}{expressions}" 3277 3278 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3279 return f"ADD {self.expressions(expression)}" 3280 3281 def distinct_sql(self, expression: exp.Distinct) -> str: 3282 this = self.expressions(expression, flat=True) 3283 3284 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3285 case = exp.case() 3286 for arg in expression.expressions: 3287 case = case.when(arg.is_(exp.null()), exp.null()) 3288 this = self.sql(case.else_(f"({this})")) 3289 3290 this = f" {this}" if this else "" 3291 3292 on = self.sql(expression, "on") 3293 on = f" ON {on}" if on else "" 3294 return f"DISTINCT{this}{on}" 3295 3296 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3297 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3298 3299 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3300 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3301 3302 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3303 this_sql = self.sql(expression, "this") 3304 expression_sql = self.sql(expression, "expression") 3305 kind = "MAX" if expression.args.get("max") else "MIN" 3306 return f"{this_sql} HAVING {kind} {expression_sql}" 3307 3308 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3309 return self.sql( 3310 exp.Cast( 3311 this=exp.Div(this=expression.this, expression=expression.expression), 3312 to=exp.DataType(this=exp.DataType.Type.INT), 3313 ) 3314 ) 3315 3316 def dpipe_sql(self, expression: exp.DPipe) -> str: 3317 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3318 return self.func( 3319 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3320 ) 3321 return self.binary(expression, "||") 3322 3323 def div_sql(self, expression: exp.Div) -> str: 3324 l, r = expression.left, expression.right 3325 3326 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3327 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3328 3329 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3330 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3331 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3332 3333 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3334 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3335 return self.sql( 3336 exp.cast( 3337 l / r, 3338 to=exp.DataType.Type.BIGINT, 3339 ) 3340 ) 3341 3342 return self.binary(expression, "/") 3343 3344 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3345 return self.binary(expression, "OVERLAPS") 3346 3347 def distance_sql(self, expression: exp.Distance) -> str: 3348 return self.binary(expression, "<->") 3349 3350 def dot_sql(self, expression: exp.Dot) -> str: 3351 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3352 3353 def eq_sql(self, expression: exp.EQ) -> str: 3354 return self.binary(expression, "=") 3355 3356 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3357 return self.binary(expression, ":=") 3358 3359 def escape_sql(self, expression: exp.Escape) -> str: 3360 return self.binary(expression, "ESCAPE") 3361 3362 def glob_sql(self, expression: exp.Glob) -> str: 3363 return self.binary(expression, "GLOB") 3364 3365 def gt_sql(self, expression: exp.GT) -> str: 3366 return self.binary(expression, ">") 3367 3368 def gte_sql(self, expression: exp.GTE) -> str: 3369 return self.binary(expression, ">=") 3370 3371 def ilike_sql(self, expression: exp.ILike) -> str: 3372 return self.binary(expression, "ILIKE") 3373 3374 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3375 return self.binary(expression, "ILIKE ANY") 3376 3377 def is_sql(self, expression: exp.Is) -> str: 3378 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3379 return self.sql( 3380 expression.this if expression.expression.this else exp.not_(expression.this) 3381 ) 3382 return self.binary(expression, "IS") 3383 3384 def like_sql(self, expression: exp.Like) -> str: 3385 return self.binary(expression, "LIKE") 3386 3387 def likeany_sql(self, expression: exp.LikeAny) -> str: 3388 return self.binary(expression, "LIKE ANY") 3389 3390 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3391 return self.binary(expression, "SIMILAR TO") 3392 3393 def lt_sql(self, expression: exp.LT) -> str: 3394 return self.binary(expression, "<") 3395 3396 def lte_sql(self, expression: exp.LTE) -> str: 3397 return self.binary(expression, "<=") 3398 3399 def mod_sql(self, expression: exp.Mod) -> str: 3400 return self.binary(expression, "%") 3401 3402 def mul_sql(self, expression: exp.Mul) -> str: 3403 return self.binary(expression, "*") 3404 3405 def neq_sql(self, expression: exp.NEQ) -> str: 3406 return self.binary(expression, "<>") 3407 3408 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3409 return self.binary(expression, "IS NOT DISTINCT FROM") 3410 3411 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3412 return self.binary(expression, "IS DISTINCT FROM") 3413 3414 def slice_sql(self, expression: exp.Slice) -> str: 3415 return self.binary(expression, ":") 3416 3417 def sub_sql(self, expression: exp.Sub) -> str: 3418 return self.binary(expression, "-") 3419 3420 def trycast_sql(self, expression: exp.TryCast) -> str: 3421 return self.cast_sql(expression, safe_prefix="TRY_") 3422 3423 def try_sql(self, expression: exp.Try) -> str: 3424 if not self.TRY_SUPPORTED: 3425 self.unsupported("Unsupported TRY function") 3426 return self.sql(expression, "this") 3427 3428 return self.func("TRY", expression.this) 3429 3430 def log_sql(self, expression: exp.Log) -> str: 3431 this = expression.this 3432 expr = expression.expression 3433 3434 if self.dialect.LOG_BASE_FIRST is False: 3435 this, expr = expr, this 3436 elif self.dialect.LOG_BASE_FIRST is None and expr: 3437 if this.name in ("2", "10"): 3438 return self.func(f"LOG{this.name}", expr) 3439 3440 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3441 3442 return self.func("LOG", this, expr) 3443 3444 def use_sql(self, expression: exp.Use) -> str: 3445 kind = self.sql(expression, "kind") 3446 kind = f" {kind}" if kind else "" 3447 this = self.sql(expression, "this") 3448 this = f" {this}" if this else "" 3449 return f"USE{kind}{this}" 3450 3451 def binary(self, expression: exp.Binary, op: str) -> str: 3452 sqls: t.List[str] = [] 3453 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3454 binary_type = type(expression) 3455 3456 while stack: 3457 node = stack.pop() 3458 3459 if type(node) is binary_type: 3460 op_func = node.args.get("operator") 3461 if op_func: 3462 op = f"OPERATOR({self.sql(op_func)})" 3463 3464 stack.append(node.right) 3465 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3466 stack.append(node.left) 3467 else: 3468 sqls.append(self.sql(node)) 3469 3470 return "".join(sqls) 3471 3472 def function_fallback_sql(self, expression: exp.Func) -> str: 3473 args = [] 3474 3475 for key in expression.arg_types: 3476 arg_value = expression.args.get(key) 3477 3478 if isinstance(arg_value, list): 3479 for value in arg_value: 3480 args.append(value) 3481 elif arg_value is not None: 3482 args.append(arg_value) 3483 3484 if self.normalize_functions: 3485 name = expression.sql_name() 3486 else: 3487 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3488 3489 return self.func(name, *args) 3490 3491 def func( 3492 self, 3493 name: str, 3494 *args: t.Optional[exp.Expression | str], 3495 prefix: str = "(", 3496 suffix: str = ")", 3497 normalize: bool = True, 3498 ) -> str: 3499 name = self.normalize_func(name) if normalize else name 3500 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3501 3502 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str: 3503 arg_sqls = tuple( 3504 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3505 ) 3506 if self.pretty and self.too_wide(arg_sqls): 3507 return self.indent("\n" + ",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True) 3508 return ", ".join(arg_sqls) 3509 3510 def too_wide(self, args: t.Iterable) -> bool: 3511 return sum(len(arg) for arg in args) > self.max_text_width 3512 3513 def format_time( 3514 self, 3515 expression: exp.Expression, 3516 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3517 inverse_time_trie: t.Optional[t.Dict] = None, 3518 ) -> t.Optional[str]: 3519 return format_time( 3520 self.sql(expression, "format"), 3521 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3522 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3523 ) 3524 3525 def expressions( 3526 self, 3527 expression: t.Optional[exp.Expression] = None, 3528 key: t.Optional[str] = None, 3529 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3530 flat: bool = False, 3531 indent: bool = True, 3532 skip_first: bool = False, 3533 skip_last: bool = False, 3534 sep: str = ", ", 3535 prefix: str = "", 3536 dynamic: bool = False, 3537 new_line: bool = False, 3538 ) -> str: 3539 expressions = expression.args.get(key or "expressions") if expression else sqls 3540 3541 if not expressions: 3542 return "" 3543 3544 if flat: 3545 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3546 3547 num_sqls = len(expressions) 3548 result_sqls = [] 3549 3550 for i, e in enumerate(expressions): 3551 sql = self.sql(e, comment=False) 3552 if not sql: 3553 continue 3554 3555 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3556 3557 if self.pretty: 3558 if self.leading_comma: 3559 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3560 else: 3561 result_sqls.append( 3562 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3563 ) 3564 else: 3565 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3566 3567 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3568 if new_line: 3569 result_sqls.insert(0, "") 3570 result_sqls.append("") 3571 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3572 else: 3573 result_sql = "".join(result_sqls) 3574 3575 return ( 3576 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3577 if indent 3578 else result_sql 3579 ) 3580 3581 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3582 flat = flat or isinstance(expression.parent, exp.Properties) 3583 expressions_sql = self.expressions(expression, flat=flat) 3584 if flat: 3585 return f"{op} {expressions_sql}" 3586 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3587 3588 def naked_property(self, expression: exp.Property) -> str: 3589 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3590 if not property_name: 3591 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3592 return f"{property_name} {self.sql(expression, 'this')}" 3593 3594 def tag_sql(self, expression: exp.Tag) -> str: 3595 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3596 3597 def token_sql(self, token_type: TokenType) -> str: 3598 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3599 3600 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3601 this = self.sql(expression, "this") 3602 expressions = self.no_identify(self.expressions, expression) 3603 expressions = ( 3604 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3605 ) 3606 return f"{this}{expressions}" 3607 3608 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3609 this = self.sql(expression, "this") 3610 expressions = self.expressions(expression, flat=True) 3611 return f"{this}({expressions})" 3612 3613 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3614 return self.binary(expression, "=>") 3615 3616 def when_sql(self, expression: exp.When) -> str: 3617 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3618 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3619 condition = self.sql(expression, "condition") 3620 condition = f" AND {condition}" if condition else "" 3621 3622 then_expression = expression.args.get("then") 3623 if isinstance(then_expression, exp.Insert): 3624 this = self.sql(then_expression, "this") 3625 this = f"INSERT {this}" if this else "INSERT" 3626 then = self.sql(then_expression, "expression") 3627 then = f"{this} VALUES {then}" if then else this 3628 elif isinstance(then_expression, exp.Update): 3629 if isinstance(then_expression.args.get("expressions"), exp.Star): 3630 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3631 else: 3632 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 3633 else: 3634 then = self.sql(then_expression) 3635 return f"WHEN {matched}{source}{condition} THEN {then}" 3636 3637 def merge_sql(self, expression: exp.Merge) -> str: 3638 table = expression.this 3639 table_alias = "" 3640 3641 hints = table.args.get("hints") 3642 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3643 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3644 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3645 3646 this = self.sql(table) 3647 using = f"USING {self.sql(expression, 'using')}" 3648 on = f"ON {self.sql(expression, 'on')}" 3649 expressions = self.expressions(expression, sep=" ", indent=False) 3650 returning = self.sql(expression, "returning") 3651 if returning: 3652 expressions = f"{expressions}{returning}" 3653 3654 sep = self.sep() 3655 3656 return self.prepend_ctes( 3657 expression, 3658 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{expressions}", 3659 ) 3660 3661 @unsupported_args("format") 3662 def tochar_sql(self, expression: exp.ToChar) -> str: 3663 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3664 3665 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3666 if not self.SUPPORTS_TO_NUMBER: 3667 self.unsupported("Unsupported TO_NUMBER function") 3668 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3669 3670 fmt = expression.args.get("format") 3671 if not fmt: 3672 self.unsupported("Conversion format is required for TO_NUMBER") 3673 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3674 3675 return self.func("TO_NUMBER", expression.this, fmt) 3676 3677 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3678 this = self.sql(expression, "this") 3679 kind = self.sql(expression, "kind") 3680 settings_sql = self.expressions(expression, key="settings", sep=" ") 3681 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3682 return f"{this}({kind}{args})" 3683 3684 def dictrange_sql(self, expression: exp.DictRange) -> str: 3685 this = self.sql(expression, "this") 3686 max = self.sql(expression, "max") 3687 min = self.sql(expression, "min") 3688 return f"{this}(MIN {min} MAX {max})" 3689 3690 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3691 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3692 3693 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3694 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3695 3696 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3697 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3698 expressions = self.expressions(expression, flat=True) 3699 expressions = f" {self.wrap(expressions)}" if expressions else "" 3700 buckets = self.sql(expression, "buckets") 3701 kind = self.sql(expression, "kind") 3702 buckets = f" BUCKETS {buckets}" if buckets else "" 3703 order = self.sql(expression, "order") 3704 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 3705 3706 def oncluster_sql(self, expression: exp.OnCluster) -> str: 3707 return "" 3708 3709 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3710 expressions = self.expressions(expression, key="expressions", flat=True) 3711 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3712 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3713 buckets = self.sql(expression, "buckets") 3714 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 3715 3716 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3717 this = self.sql(expression, "this") 3718 having = self.sql(expression, "having") 3719 3720 if having: 3721 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3722 3723 return self.func("ANY_VALUE", this) 3724 3725 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3726 transform = self.func("TRANSFORM", *expression.expressions) 3727 row_format_before = self.sql(expression, "row_format_before") 3728 row_format_before = f" {row_format_before}" if row_format_before else "" 3729 record_writer = self.sql(expression, "record_writer") 3730 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3731 using = f" USING {self.sql(expression, 'command_script')}" 3732 schema = self.sql(expression, "schema") 3733 schema = f" AS {schema}" if schema else "" 3734 row_format_after = self.sql(expression, "row_format_after") 3735 row_format_after = f" {row_format_after}" if row_format_after else "" 3736 record_reader = self.sql(expression, "record_reader") 3737 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3738 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 3739 3740 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3741 key_block_size = self.sql(expression, "key_block_size") 3742 if key_block_size: 3743 return f"KEY_BLOCK_SIZE = {key_block_size}" 3744 3745 using = self.sql(expression, "using") 3746 if using: 3747 return f"USING {using}" 3748 3749 parser = self.sql(expression, "parser") 3750 if parser: 3751 return f"WITH PARSER {parser}" 3752 3753 comment = self.sql(expression, "comment") 3754 if comment: 3755 return f"COMMENT {comment}" 3756 3757 visible = expression.args.get("visible") 3758 if visible is not None: 3759 return "VISIBLE" if visible else "INVISIBLE" 3760 3761 engine_attr = self.sql(expression, "engine_attr") 3762 if engine_attr: 3763 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3764 3765 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3766 if secondary_engine_attr: 3767 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3768 3769 self.unsupported("Unsupported index constraint option.") 3770 return "" 3771 3772 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 3773 enforced = " ENFORCED" if expression.args.get("enforced") else "" 3774 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 3775 3776 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3777 kind = self.sql(expression, "kind") 3778 kind = f"{kind} INDEX" if kind else "INDEX" 3779 this = self.sql(expression, "this") 3780 this = f" {this}" if this else "" 3781 index_type = self.sql(expression, "index_type") 3782 index_type = f" USING {index_type}" if index_type else "" 3783 expressions = self.expressions(expression, flat=True) 3784 expressions = f" ({expressions})" if expressions else "" 3785 options = self.expressions(expression, key="options", sep=" ") 3786 options = f" {options}" if options else "" 3787 return f"{kind}{this}{index_type}{expressions}{options}" 3788 3789 def nvl2_sql(self, expression: exp.Nvl2) -> str: 3790 if self.NVL2_SUPPORTED: 3791 return self.function_fallback_sql(expression) 3792 3793 case = exp.Case().when( 3794 expression.this.is_(exp.null()).not_(copy=False), 3795 expression.args["true"], 3796 copy=False, 3797 ) 3798 else_cond = expression.args.get("false") 3799 if else_cond: 3800 case.else_(else_cond, copy=False) 3801 3802 return self.sql(case) 3803 3804 def comprehension_sql(self, expression: exp.Comprehension) -> str: 3805 this = self.sql(expression, "this") 3806 expr = self.sql(expression, "expression") 3807 iterator = self.sql(expression, "iterator") 3808 condition = self.sql(expression, "condition") 3809 condition = f" IF {condition}" if condition else "" 3810 return f"{this} FOR {expr} IN {iterator}{condition}" 3811 3812 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 3813 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 3814 3815 def opclass_sql(self, expression: exp.Opclass) -> str: 3816 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 3817 3818 def predict_sql(self, expression: exp.Predict) -> str: 3819 model = self.sql(expression, "this") 3820 model = f"MODEL {model}" 3821 table = self.sql(expression, "expression") 3822 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 3823 parameters = self.sql(expression, "params_struct") 3824 return self.func("PREDICT", model, table, parameters or None) 3825 3826 def forin_sql(self, expression: exp.ForIn) -> str: 3827 this = self.sql(expression, "this") 3828 expression_sql = self.sql(expression, "expression") 3829 return f"FOR {this} DO {expression_sql}" 3830 3831 def refresh_sql(self, expression: exp.Refresh) -> str: 3832 this = self.sql(expression, "this") 3833 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 3834 return f"REFRESH {table}{this}" 3835 3836 def toarray_sql(self, expression: exp.ToArray) -> str: 3837 arg = expression.this 3838 if not arg.type: 3839 from sqlglot.optimizer.annotate_types import annotate_types 3840 3841 arg = annotate_types(arg) 3842 3843 if arg.is_type(exp.DataType.Type.ARRAY): 3844 return self.sql(arg) 3845 3846 cond_for_null = arg.is_(exp.null()) 3847 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 3848 3849 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 3850 this = expression.this 3851 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 3852 return self.sql(this) 3853 3854 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 3855 3856 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 3857 this = expression.this 3858 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 3859 return self.sql(this) 3860 3861 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP)) 3862 3863 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 3864 this = expression.this 3865 time_format = self.format_time(expression) 3866 3867 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 3868 return self.sql( 3869 exp.cast( 3870 exp.StrToTime(this=this, format=expression.args["format"]), 3871 exp.DataType.Type.DATE, 3872 ) 3873 ) 3874 3875 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 3876 return self.sql(this) 3877 3878 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 3879 3880 def unixdate_sql(self, expression: exp.UnixDate) -> str: 3881 return self.sql( 3882 exp.func( 3883 "DATEDIFF", 3884 expression.this, 3885 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 3886 "day", 3887 ) 3888 ) 3889 3890 def lastday_sql(self, expression: exp.LastDay) -> str: 3891 if self.LAST_DAY_SUPPORTS_DATE_PART: 3892 return self.function_fallback_sql(expression) 3893 3894 unit = expression.text("unit") 3895 if unit and unit != "MONTH": 3896 self.unsupported("Date parts are not supported in LAST_DAY.") 3897 3898 return self.func("LAST_DAY", expression.this) 3899 3900 def dateadd_sql(self, expression: exp.DateAdd) -> str: 3901 from sqlglot.dialects.dialect import unit_to_str 3902 3903 return self.func( 3904 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 3905 ) 3906 3907 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 3908 if self.CAN_IMPLEMENT_ARRAY_ANY: 3909 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 3910 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 3911 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 3912 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 3913 3914 from sqlglot.dialects import Dialect 3915 3916 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 3917 if self.dialect.__class__ != Dialect: 3918 self.unsupported("ARRAY_ANY is unsupported") 3919 3920 return self.function_fallback_sql(expression) 3921 3922 def struct_sql(self, expression: exp.Struct) -> str: 3923 expression.set( 3924 "expressions", 3925 [ 3926 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 3927 if isinstance(e, exp.PropertyEQ) 3928 else e 3929 for e in expression.expressions 3930 ], 3931 ) 3932 3933 return self.function_fallback_sql(expression) 3934 3935 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 3936 low = self.sql(expression, "this") 3937 high = self.sql(expression, "expression") 3938 3939 return f"{low} TO {high}" 3940 3941 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 3942 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 3943 tables = f" {self.expressions(expression)}" 3944 3945 exists = " IF EXISTS" if expression.args.get("exists") else "" 3946 3947 on_cluster = self.sql(expression, "cluster") 3948 on_cluster = f" {on_cluster}" if on_cluster else "" 3949 3950 identity = self.sql(expression, "identity") 3951 identity = f" {identity} IDENTITY" if identity else "" 3952 3953 option = self.sql(expression, "option") 3954 option = f" {option}" if option else "" 3955 3956 partition = self.sql(expression, "partition") 3957 partition = f" {partition}" if partition else "" 3958 3959 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 3960 3961 # This transpiles T-SQL's CONVERT function 3962 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 3963 def convert_sql(self, expression: exp.Convert) -> str: 3964 to = expression.this 3965 value = expression.expression 3966 style = expression.args.get("style") 3967 safe = expression.args.get("safe") 3968 strict = expression.args.get("strict") 3969 3970 if not to or not value: 3971 return "" 3972 3973 # Retrieve length of datatype and override to default if not specified 3974 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 3975 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 3976 3977 transformed: t.Optional[exp.Expression] = None 3978 cast = exp.Cast if strict else exp.TryCast 3979 3980 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 3981 if isinstance(style, exp.Literal) and style.is_int: 3982 from sqlglot.dialects.tsql import TSQL 3983 3984 style_value = style.name 3985 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 3986 if not converted_style: 3987 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 3988 3989 fmt = exp.Literal.string(converted_style) 3990 3991 if to.this == exp.DataType.Type.DATE: 3992 transformed = exp.StrToDate(this=value, format=fmt) 3993 elif to.this == exp.DataType.Type.DATETIME: 3994 transformed = exp.StrToTime(this=value, format=fmt) 3995 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 3996 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 3997 elif to.this == exp.DataType.Type.TEXT: 3998 transformed = exp.TimeToStr(this=value, format=fmt) 3999 4000 if not transformed: 4001 transformed = cast(this=value, to=to, safe=safe) 4002 4003 return self.sql(transformed) 4004 4005 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4006 this = expression.this 4007 if isinstance(this, exp.JSONPathWildcard): 4008 this = self.json_path_part(this) 4009 return f".{this}" if this else "" 4010 4011 if exp.SAFE_IDENTIFIER_RE.match(this): 4012 return f".{this}" 4013 4014 this = self.json_path_part(this) 4015 return f"[{this}]" if self.JSON_PATH_BRACKETED_KEY_SUPPORTED else f".{this}" 4016 4017 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4018 this = self.json_path_part(expression.this) 4019 return f"[{this}]" if this else "" 4020 4021 def _simplify_unless_literal(self, expression: E) -> E: 4022 if not isinstance(expression, exp.Literal): 4023 from sqlglot.optimizer.simplify import simplify 4024 4025 expression = simplify(expression, dialect=self.dialect) 4026 4027 return expression 4028 4029 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4030 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4031 # The first modifier here will be the one closest to the AggFunc's arg 4032 mods = sorted( 4033 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4034 key=lambda x: 0 4035 if isinstance(x, exp.HavingMax) 4036 else (1 if isinstance(x, exp.Order) else 2), 4037 ) 4038 4039 if mods: 4040 mod = mods[0] 4041 this = expression.__class__(this=mod.this.copy()) 4042 this.meta["inline"] = True 4043 mod.this.replace(this) 4044 return self.sql(expression.this) 4045 4046 agg_func = expression.find(exp.AggFunc) 4047 4048 if agg_func: 4049 return self.sql(agg_func)[:-1] + f" {text})" 4050 4051 return f"{self.sql(expression, 'this')} {text}" 4052 4053 def _replace_line_breaks(self, string: str) -> str: 4054 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4055 if self.pretty: 4056 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4057 return string 4058 4059 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4060 option = self.sql(expression, "this") 4061 4062 if expression.expressions: 4063 upper = option.upper() 4064 4065 # Snowflake FILE_FORMAT options are separated by whitespace 4066 sep = " " if upper == "FILE_FORMAT" else ", " 4067 4068 # Databricks copy/format options do not set their list of values with EQ 4069 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4070 values = self.expressions(expression, flat=True, sep=sep) 4071 return f"{option}{op}({values})" 4072 4073 value = self.sql(expression, "expression") 4074 4075 if not value: 4076 return option 4077 4078 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4079 4080 return f"{option}{op}{value}" 4081 4082 def credentials_sql(self, expression: exp.Credentials) -> str: 4083 cred_expr = expression.args.get("credentials") 4084 if isinstance(cred_expr, exp.Literal): 4085 # Redshift case: CREDENTIALS <string> 4086 credentials = self.sql(expression, "credentials") 4087 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4088 else: 4089 # Snowflake case: CREDENTIALS = (...) 4090 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4091 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4092 4093 storage = self.sql(expression, "storage") 4094 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4095 4096 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4097 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4098 4099 iam_role = self.sql(expression, "iam_role") 4100 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4101 4102 region = self.sql(expression, "region") 4103 region = f" REGION {region}" if region else "" 4104 4105 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4106 4107 def copy_sql(self, expression: exp.Copy) -> str: 4108 this = self.sql(expression, "this") 4109 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4110 4111 credentials = self.sql(expression, "credentials") 4112 credentials = self.seg(credentials) if credentials else "" 4113 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4114 files = self.expressions(expression, key="files", flat=True) 4115 4116 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4117 params = self.expressions( 4118 expression, 4119 key="params", 4120 sep=sep, 4121 new_line=True, 4122 skip_last=True, 4123 skip_first=True, 4124 indent=self.COPY_PARAMS_ARE_WRAPPED, 4125 ) 4126 4127 if params: 4128 if self.COPY_PARAMS_ARE_WRAPPED: 4129 params = f" WITH ({params})" 4130 elif not self.pretty: 4131 params = f" {params}" 4132 4133 return f"COPY{this}{kind} {files}{credentials}{params}" 4134 4135 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4136 return "" 4137 4138 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4139 on_sql = "ON" if expression.args.get("on") else "OFF" 4140 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4141 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4142 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4143 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4144 4145 if filter_col or retention_period: 4146 on_sql = self.func("ON", filter_col, retention_period) 4147 4148 return f"DATA_DELETION={on_sql}" 4149 4150 def maskingpolicycolumnconstraint_sql( 4151 self, expression: exp.MaskingPolicyColumnConstraint 4152 ) -> str: 4153 this = self.sql(expression, "this") 4154 expressions = self.expressions(expression, flat=True) 4155 expressions = f" USING ({expressions})" if expressions else "" 4156 return f"MASKING POLICY {this}{expressions}" 4157 4158 def gapfill_sql(self, expression: exp.GapFill) -> str: 4159 this = self.sql(expression, "this") 4160 this = f"TABLE {this}" 4161 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4162 4163 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4164 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4165 4166 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4167 this = self.sql(expression, "this") 4168 expr = expression.expression 4169 4170 if isinstance(expr, exp.Func): 4171 # T-SQL's CLR functions are case sensitive 4172 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4173 else: 4174 expr = self.sql(expression, "expression") 4175 4176 return self.scope_resolution(expr, this) 4177 4178 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4179 if self.PARSE_JSON_NAME is None: 4180 return self.sql(expression.this) 4181 4182 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4183 4184 def rand_sql(self, expression: exp.Rand) -> str: 4185 lower = self.sql(expression, "lower") 4186 upper = self.sql(expression, "upper") 4187 4188 if lower and upper: 4189 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4190 return self.func("RAND", expression.this) 4191 4192 def changes_sql(self, expression: exp.Changes) -> str: 4193 information = self.sql(expression, "information") 4194 information = f"INFORMATION => {information}" 4195 at_before = self.sql(expression, "at_before") 4196 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4197 end = self.sql(expression, "end") 4198 end = f"{self.seg('')}{end}" if end else "" 4199 4200 return f"CHANGES ({information}){at_before}{end}" 4201 4202 def pad_sql(self, expression: exp.Pad) -> str: 4203 prefix = "L" if expression.args.get("is_left") else "R" 4204 4205 fill_pattern = self.sql(expression, "fill_pattern") or None 4206 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4207 fill_pattern = "' '" 4208 4209 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4210 4211 def summarize_sql(self, expression: exp.Summarize) -> str: 4212 table = " TABLE" if expression.args.get("table") else "" 4213 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4214 4215 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4216 generate_series = exp.GenerateSeries(**expression.args) 4217 4218 parent = expression.parent 4219 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4220 parent = parent.parent 4221 4222 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4223 return self.sql(exp.Unnest(expressions=[generate_series])) 4224 4225 if isinstance(parent, exp.Select): 4226 self.unsupported("GenerateSeries projection unnesting is not supported.") 4227 4228 return self.sql(generate_series) 4229 4230 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4231 exprs = expression.expressions 4232 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4233 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4234 else: 4235 rhs = self.expressions(expression) 4236 4237 return self.func(name, expression.this, rhs) 4238 4239 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4240 if self.SUPPORTS_CONVERT_TIMEZONE: 4241 return self.function_fallback_sql(expression) 4242 4243 source_tz = expression.args.get("source_tz") 4244 target_tz = expression.args.get("target_tz") 4245 timestamp = expression.args.get("timestamp") 4246 4247 if source_tz and timestamp: 4248 timestamp = exp.AtTimeZone( 4249 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4250 ) 4251 4252 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4253 4254 return self.sql(expr) 4255 4256 def json_sql(self, expression: exp.JSON) -> str: 4257 this = self.sql(expression, "this") 4258 this = f" {this}" if this else "" 4259 4260 _with = expression.args.get("with") 4261 4262 if _with is None: 4263 with_sql = "" 4264 elif not _with: 4265 with_sql = " WITHOUT" 4266 else: 4267 with_sql = " WITH" 4268 4269 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4270 4271 return f"JSON{this}{with_sql}{unique_sql}" 4272 4273 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4274 def _generate_on_options(arg: t.Any) -> str: 4275 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4276 4277 path = self.sql(expression, "path") 4278 returning = self.sql(expression, "returning") 4279 returning = f" RETURNING {returning}" if returning else "" 4280 4281 on_condition = self.sql(expression, "on_condition") 4282 on_condition = f" {on_condition}" if on_condition else "" 4283 4284 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4285 4286 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4287 else_ = "ELSE " if expression.args.get("else_") else "" 4288 condition = self.sql(expression, "expression") 4289 condition = f"WHEN {condition} THEN " if condition else else_ 4290 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4291 return f"{condition}{insert}" 4292 4293 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4294 kind = self.sql(expression, "kind") 4295 expressions = self.seg(self.expressions(expression, sep=" ")) 4296 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4297 return res 4298 4299 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4300 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4301 empty = expression.args.get("empty") 4302 empty = ( 4303 f"DEFAULT {empty} ON EMPTY" 4304 if isinstance(empty, exp.Expression) 4305 else self.sql(expression, "empty") 4306 ) 4307 4308 error = expression.args.get("error") 4309 error = ( 4310 f"DEFAULT {error} ON ERROR" 4311 if isinstance(error, exp.Expression) 4312 else self.sql(expression, "error") 4313 ) 4314 4315 if error and empty: 4316 error = ( 4317 f"{empty} {error}" 4318 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4319 else f"{error} {empty}" 4320 ) 4321 empty = "" 4322 4323 null = self.sql(expression, "null") 4324 4325 return f"{empty}{error}{null}" 4326 4327 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4328 this = self.sql(expression, "this") 4329 path = self.sql(expression, "path") 4330 4331 passing = self.expressions(expression, "passing") 4332 passing = f" PASSING {passing}" if passing else "" 4333 4334 on_condition = self.sql(expression, "on_condition") 4335 on_condition = f" {on_condition}" if on_condition else "" 4336 4337 path = f"{path}{passing}{on_condition}" 4338 4339 return self.func("JSON_EXISTS", this, path) 4340 4341 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4342 array_agg = self.function_fallback_sql(expression) 4343 4344 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4345 parent = expression.parent 4346 if isinstance(parent, exp.Filter): 4347 parent_cond = parent.expression.this 4348 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4349 else: 4350 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4351 this = expression.this 4352 this_sql = ( 4353 self.expressions(this) 4354 if isinstance(this, exp.Distinct) 4355 else self.sql(expression, "this") 4356 ) 4357 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4358 4359 return array_agg 4360 4361 def apply_sql(self, expression: exp.Apply) -> str: 4362 this = self.sql(expression, "this") 4363 expr = self.sql(expression, "expression") 4364 4365 return f"{this} APPLY({expr})" 4366 4367 def grant_sql(self, expression: exp.Grant) -> str: 4368 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4369 4370 kind = self.sql(expression, "kind") 4371 kind = f" {kind}" if kind else "" 4372 4373 securable = self.sql(expression, "securable") 4374 securable = f" {securable}" if securable else "" 4375 4376 principals = self.expressions(expression, key="principals", flat=True) 4377 4378 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4379 4380 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4381 4382 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4383 this = self.sql(expression, "this") 4384 columns = self.expressions(expression, flat=True) 4385 columns = f"({columns})" if columns else "" 4386 4387 return f"{this}{columns}" 4388 4389 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4390 this = self.sql(expression, "this") 4391 4392 kind = self.sql(expression, "kind") 4393 kind = f"{kind} " if kind else "" 4394 4395 return f"{kind}{this}" 4396 4397 def columns_sql(self, expression: exp.Columns): 4398 func = self.function_fallback_sql(expression) 4399 if expression.args.get("unpack"): 4400 func = f"*{func}" 4401 4402 return func 4403 4404 def overlay_sql(self, expression: exp.Overlay): 4405 this = self.sql(expression, "this") 4406 expr = self.sql(expression, "expression") 4407 from_sql = self.sql(expression, "from") 4408 for_sql = self.sql(expression, "for") 4409 for_sql = f" FOR {for_sql}" if for_sql else "" 4410 4411 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
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.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.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 205 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 206 } 207 208 # Whether null ordering is supported in order by 209 # True: Full Support, None: No support, False: No support for certain cases 210 # such as window specifications, aggregate functions etc 211 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 212 213 # Whether ignore nulls is inside the agg or outside. 214 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 215 IGNORE_NULLS_IN_FUNC = False 216 217 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 218 LOCKING_READS_SUPPORTED = False 219 220 # Whether the EXCEPT and INTERSECT operations can return duplicates 221 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 222 223 # Wrap derived values in parens, usually standard but spark doesn't support it 224 WRAP_DERIVED_VALUES = True 225 226 # Whether create function uses an AS before the RETURN 227 CREATE_FUNCTION_RETURN_AS = True 228 229 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 230 MATCHED_BY_SOURCE = True 231 232 # Whether the INTERVAL expression works only with values like '1 day' 233 SINGLE_STRING_INTERVAL = False 234 235 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 236 INTERVAL_ALLOWS_PLURAL_FORM = True 237 238 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 239 LIMIT_FETCH = "ALL" 240 241 # Whether limit and fetch allows expresions or just limits 242 LIMIT_ONLY_LITERALS = False 243 244 # Whether a table is allowed to be renamed with a db 245 RENAME_TABLE_WITH_DB = True 246 247 # The separator for grouping sets and rollups 248 GROUPINGS_SEP = "," 249 250 # The string used for creating an index on a table 251 INDEX_ON = "ON" 252 253 # Whether join hints should be generated 254 JOIN_HINTS = True 255 256 # Whether table hints should be generated 257 TABLE_HINTS = True 258 259 # Whether query hints should be generated 260 QUERY_HINTS = True 261 262 # What kind of separator to use for query hints 263 QUERY_HINT_SEP = ", " 264 265 # Whether comparing against booleans (e.g. x IS TRUE) is supported 266 IS_BOOL_ALLOWED = True 267 268 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 269 DUPLICATE_KEY_UPDATE_WITH_SET = True 270 271 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 272 LIMIT_IS_TOP = False 273 274 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 275 RETURNING_END = True 276 277 # Whether to generate an unquoted value for EXTRACT's date part argument 278 EXTRACT_ALLOWS_QUOTES = True 279 280 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 281 TZ_TO_WITH_TIME_ZONE = False 282 283 # Whether the NVL2 function is supported 284 NVL2_SUPPORTED = True 285 286 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 287 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 288 289 # Whether VALUES statements can be used as derived tables. 290 # MySQL 5 and Redshift do not allow this, so when False, it will convert 291 # SELECT * VALUES into SELECT UNION 292 VALUES_AS_TABLE = True 293 294 # Whether the word COLUMN is included when adding a column with ALTER TABLE 295 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 296 297 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 298 UNNEST_WITH_ORDINALITY = True 299 300 # Whether FILTER (WHERE cond) can be used for conditional aggregation 301 AGGREGATE_FILTER_SUPPORTED = True 302 303 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 304 SEMI_ANTI_JOIN_WITH_SIDE = True 305 306 # Whether to include the type of a computed column in the CREATE DDL 307 COMPUTED_COLUMN_WITH_TYPE = True 308 309 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 310 SUPPORTS_TABLE_COPY = True 311 312 # Whether parentheses are required around the table sample's expression 313 TABLESAMPLE_REQUIRES_PARENS = True 314 315 # Whether a table sample clause's size needs to be followed by the ROWS keyword 316 TABLESAMPLE_SIZE_IS_ROWS = True 317 318 # The keyword(s) to use when generating a sample clause 319 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 320 321 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 322 TABLESAMPLE_WITH_METHOD = True 323 324 # The keyword to use when specifying the seed of a sample clause 325 TABLESAMPLE_SEED_KEYWORD = "SEED" 326 327 # Whether COLLATE is a function instead of a binary operator 328 COLLATE_IS_FUNC = False 329 330 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 331 DATA_TYPE_SPECIFIERS_ALLOWED = False 332 333 # Whether conditions require booleans WHERE x = 0 vs WHERE x 334 ENSURE_BOOLS = False 335 336 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 337 CTE_RECURSIVE_KEYWORD_REQUIRED = True 338 339 # Whether CONCAT requires >1 arguments 340 SUPPORTS_SINGLE_ARG_CONCAT = True 341 342 # Whether LAST_DAY function supports a date part argument 343 LAST_DAY_SUPPORTS_DATE_PART = True 344 345 # Whether named columns are allowed in table aliases 346 SUPPORTS_TABLE_ALIAS_COLUMNS = True 347 348 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 349 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 350 351 # What delimiter to use for separating JSON key/value pairs 352 JSON_KEY_VALUE_PAIR_SEP = ":" 353 354 # INSERT OVERWRITE TABLE x override 355 INSERT_OVERWRITE = " OVERWRITE TABLE" 356 357 # Whether the SELECT .. INTO syntax is used instead of CTAS 358 SUPPORTS_SELECT_INTO = False 359 360 # Whether UNLOGGED tables can be created 361 SUPPORTS_UNLOGGED_TABLES = False 362 363 # Whether the CREATE TABLE LIKE statement is supported 364 SUPPORTS_CREATE_TABLE_LIKE = True 365 366 # Whether the LikeProperty needs to be specified inside of the schema clause 367 LIKE_PROPERTY_INSIDE_SCHEMA = False 368 369 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 370 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 371 MULTI_ARG_DISTINCT = True 372 373 # Whether the JSON extraction operators expect a value of type JSON 374 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 375 376 # Whether bracketed keys like ["foo"] are supported in JSON paths 377 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 378 379 # Whether to escape keys using single quotes in JSON paths 380 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 381 382 # The JSONPathPart expressions supported by this dialect 383 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 384 385 # Whether any(f(x) for x in array) can be implemented by this dialect 386 CAN_IMPLEMENT_ARRAY_ANY = False 387 388 # Whether the function TO_NUMBER is supported 389 SUPPORTS_TO_NUMBER = True 390 391 # Whether or not set op modifiers apply to the outer set op or select. 392 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 393 # True means limit 1 happens after the set op, False means it it happens on y. 394 SET_OP_MODIFIERS = True 395 396 # Whether parameters from COPY statement are wrapped in parentheses 397 COPY_PARAMS_ARE_WRAPPED = True 398 399 # Whether values of params are set with "=" token or empty space 400 COPY_PARAMS_EQ_REQUIRED = False 401 402 # Whether COPY statement has INTO keyword 403 COPY_HAS_INTO_KEYWORD = True 404 405 # Whether the conditional TRY(expression) function is supported 406 TRY_SUPPORTED = True 407 408 # Whether the UESCAPE syntax in unicode strings is supported 409 SUPPORTS_UESCAPE = True 410 411 # The keyword to use when generating a star projection with excluded columns 412 STAR_EXCEPT = "EXCEPT" 413 414 # The HEX function name 415 HEX_FUNC = "HEX" 416 417 # The keywords to use when prefixing & separating WITH based properties 418 WITH_PROPERTIES_PREFIX = "WITH" 419 420 # Whether to quote the generated expression of exp.JsonPath 421 QUOTE_JSON_PATH = True 422 423 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 424 PAD_FILL_PATTERN_IS_REQUIRED = False 425 426 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 427 SUPPORTS_EXPLODING_PROJECTIONS = True 428 429 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 430 ARRAY_CONCAT_IS_VAR_LEN = True 431 432 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 433 SUPPORTS_CONVERT_TIMEZONE = False 434 435 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 436 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 437 438 TYPE_MAPPING = { 439 exp.DataType.Type.NCHAR: "CHAR", 440 exp.DataType.Type.NVARCHAR: "VARCHAR", 441 exp.DataType.Type.MEDIUMTEXT: "TEXT", 442 exp.DataType.Type.LONGTEXT: "TEXT", 443 exp.DataType.Type.TINYTEXT: "TEXT", 444 exp.DataType.Type.MEDIUMBLOB: "BLOB", 445 exp.DataType.Type.LONGBLOB: "BLOB", 446 exp.DataType.Type.TINYBLOB: "BLOB", 447 exp.DataType.Type.INET: "INET", 448 exp.DataType.Type.ROWVERSION: "VARBINARY", 449 } 450 451 TIME_PART_SINGULARS = { 452 "MICROSECONDS": "MICROSECOND", 453 "SECONDS": "SECOND", 454 "MINUTES": "MINUTE", 455 "HOURS": "HOUR", 456 "DAYS": "DAY", 457 "WEEKS": "WEEK", 458 "MONTHS": "MONTH", 459 "QUARTERS": "QUARTER", 460 "YEARS": "YEAR", 461 } 462 463 AFTER_HAVING_MODIFIER_TRANSFORMS = { 464 "cluster": lambda self, e: self.sql(e, "cluster"), 465 "distribute": lambda self, e: self.sql(e, "distribute"), 466 "sort": lambda self, e: self.sql(e, "sort"), 467 "windows": lambda self, e: ( 468 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 469 if e.args.get("windows") 470 else "" 471 ), 472 "qualify": lambda self, e: self.sql(e, "qualify"), 473 } 474 475 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 476 477 STRUCT_DELIMITER = ("<", ">") 478 479 PARAMETER_TOKEN = "@" 480 NAMED_PLACEHOLDER_TOKEN = ":" 481 482 PROPERTIES_LOCATION = { 483 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 484 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 485 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 486 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 487 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 488 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 489 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 490 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 491 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 492 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 493 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 494 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 495 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 496 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 497 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 498 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 499 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 500 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 501 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 502 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 503 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 504 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 505 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 506 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 507 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 508 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 509 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 510 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 511 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 512 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 513 exp.HeapProperty: exp.Properties.Location.POST_WITH, 514 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 515 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 516 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 517 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 518 exp.JournalProperty: exp.Properties.Location.POST_NAME, 519 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 520 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 521 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 522 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 523 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 524 exp.LogProperty: exp.Properties.Location.POST_NAME, 525 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 526 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 527 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 528 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 529 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 530 exp.Order: exp.Properties.Location.POST_SCHEMA, 531 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 532 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 533 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 534 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 535 exp.Property: exp.Properties.Location.POST_WITH, 536 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 537 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 538 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 539 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 540 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 541 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 542 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 543 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 544 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 545 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 546 exp.Set: exp.Properties.Location.POST_SCHEMA, 547 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 548 exp.SetProperty: exp.Properties.Location.POST_CREATE, 549 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 550 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 551 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 552 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 553 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 555 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 556 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 557 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 558 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 559 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 560 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 561 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 562 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 563 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 564 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 566 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 567 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 568 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 570 } 571 572 # Keywords that can't be used as unquoted identifier names 573 RESERVED_KEYWORDS: t.Set[str] = set() 574 575 # Expressions whose comments are separated from them for better formatting 576 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 577 exp.Command, 578 exp.Create, 579 exp.Delete, 580 exp.Drop, 581 exp.From, 582 exp.Insert, 583 exp.Join, 584 exp.MultitableInserts, 585 exp.Select, 586 exp.SetOperation, 587 exp.Update, 588 exp.Where, 589 exp.With, 590 ) 591 592 # Expressions that should not have their comments generated in maybe_comment 593 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 594 exp.Binary, 595 exp.SetOperation, 596 ) 597 598 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 599 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 600 exp.Column, 601 exp.Literal, 602 exp.Neg, 603 exp.Paren, 604 ) 605 606 PARAMETERIZABLE_TEXT_TYPES = { 607 exp.DataType.Type.NVARCHAR, 608 exp.DataType.Type.VARCHAR, 609 exp.DataType.Type.CHAR, 610 exp.DataType.Type.NCHAR, 611 } 612 613 # Expressions that need to have all CTEs under them bubbled up to them 614 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 615 616 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 617 618 __slots__ = ( 619 "pretty", 620 "identify", 621 "normalize", 622 "pad", 623 "_indent", 624 "normalize_functions", 625 "unsupported_level", 626 "max_unsupported", 627 "leading_comma", 628 "max_text_width", 629 "comments", 630 "dialect", 631 "unsupported_messages", 632 "_escaped_quote_end", 633 "_escaped_identifier_end", 634 "_next_name", 635 "_identifier_start", 636 "_identifier_end", 637 ) 638 639 def __init__( 640 self, 641 pretty: t.Optional[bool] = None, 642 identify: str | bool = False, 643 normalize: bool = False, 644 pad: int = 2, 645 indent: int = 2, 646 normalize_functions: t.Optional[str | bool] = None, 647 unsupported_level: ErrorLevel = ErrorLevel.WARN, 648 max_unsupported: int = 3, 649 leading_comma: bool = False, 650 max_text_width: int = 80, 651 comments: bool = True, 652 dialect: DialectType = None, 653 ): 654 import sqlglot 655 from sqlglot.dialects import Dialect 656 657 self.pretty = pretty if pretty is not None else sqlglot.pretty 658 self.identify = identify 659 self.normalize = normalize 660 self.pad = pad 661 self._indent = indent 662 self.unsupported_level = unsupported_level 663 self.max_unsupported = max_unsupported 664 self.leading_comma = leading_comma 665 self.max_text_width = max_text_width 666 self.comments = comments 667 self.dialect = Dialect.get_or_raise(dialect) 668 669 # This is both a Dialect property and a Generator argument, so we prioritize the latter 670 self.normalize_functions = ( 671 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 672 ) 673 674 self.unsupported_messages: t.List[str] = [] 675 self._escaped_quote_end: str = ( 676 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 677 ) 678 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 679 680 self._next_name = name_sequence("_t") 681 682 self._identifier_start = self.dialect.IDENTIFIER_START 683 self._identifier_end = self.dialect.IDENTIFIER_END 684 685 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 686 """ 687 Generates the SQL string corresponding to the given syntax tree. 688 689 Args: 690 expression: The syntax tree. 691 copy: Whether to copy the expression. The generator performs mutations so 692 it is safer to copy. 693 694 Returns: 695 The SQL string corresponding to `expression`. 696 """ 697 if copy: 698 expression = expression.copy() 699 700 expression = self.preprocess(expression) 701 702 self.unsupported_messages = [] 703 sql = self.sql(expression).strip() 704 705 if self.pretty: 706 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 707 708 if self.unsupported_level == ErrorLevel.IGNORE: 709 return sql 710 711 if self.unsupported_level == ErrorLevel.WARN: 712 for msg in self.unsupported_messages: 713 logger.warning(msg) 714 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 715 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 716 717 return sql 718 719 def preprocess(self, expression: exp.Expression) -> exp.Expression: 720 """Apply generic preprocessing transformations to a given expression.""" 721 expression = self._move_ctes_to_top_level(expression) 722 723 if self.ENSURE_BOOLS: 724 from sqlglot.transforms import ensure_bools 725 726 expression = ensure_bools(expression) 727 728 return expression 729 730 def _move_ctes_to_top_level(self, expression: E) -> E: 731 if ( 732 not expression.parent 733 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 734 and any(node.parent is not expression for node in expression.find_all(exp.With)) 735 ): 736 from sqlglot.transforms import move_ctes_to_top_level 737 738 expression = move_ctes_to_top_level(expression) 739 return expression 740 741 def unsupported(self, message: str) -> None: 742 if self.unsupported_level == ErrorLevel.IMMEDIATE: 743 raise UnsupportedError(message) 744 self.unsupported_messages.append(message) 745 746 def sep(self, sep: str = " ") -> str: 747 return f"{sep.strip()}\n" if self.pretty else sep 748 749 def seg(self, sql: str, sep: str = " ") -> str: 750 return f"{self.sep(sep)}{sql}" 751 752 def pad_comment(self, comment: str) -> str: 753 comment = " " + comment if comment[0].strip() else comment 754 comment = comment + " " if comment[-1].strip() else comment 755 return comment 756 757 def maybe_comment( 758 self, 759 sql: str, 760 expression: t.Optional[exp.Expression] = None, 761 comments: t.Optional[t.List[str]] = None, 762 separated: bool = False, 763 ) -> str: 764 comments = ( 765 ((expression and expression.comments) if comments is None else comments) # type: ignore 766 if self.comments 767 else None 768 ) 769 770 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 771 return sql 772 773 comments_sql = " ".join( 774 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 775 ) 776 777 if not comments_sql: 778 return sql 779 780 comments_sql = self._replace_line_breaks(comments_sql) 781 782 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 783 return ( 784 f"{self.sep()}{comments_sql}{sql}" 785 if not sql or sql[0].isspace() 786 else f"{comments_sql}{self.sep()}{sql}" 787 ) 788 789 return f"{sql} {comments_sql}" 790 791 def wrap(self, expression: exp.Expression | str) -> str: 792 this_sql = ( 793 self.sql(expression) 794 if isinstance(expression, exp.UNWRAPPED_QUERIES) 795 else self.sql(expression, "this") 796 ) 797 if not this_sql: 798 return "()" 799 800 this_sql = self.indent(this_sql, level=1, pad=0) 801 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 802 803 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 804 original = self.identify 805 self.identify = False 806 result = func(*args, **kwargs) 807 self.identify = original 808 return result 809 810 def normalize_func(self, name: str) -> str: 811 if self.normalize_functions == "upper" or self.normalize_functions is True: 812 return name.upper() 813 if self.normalize_functions == "lower": 814 return name.lower() 815 return name 816 817 def indent( 818 self, 819 sql: str, 820 level: int = 0, 821 pad: t.Optional[int] = None, 822 skip_first: bool = False, 823 skip_last: bool = False, 824 ) -> str: 825 if not self.pretty or not sql: 826 return sql 827 828 pad = self.pad if pad is None else pad 829 lines = sql.split("\n") 830 831 return "\n".join( 832 ( 833 line 834 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 835 else f"{' ' * (level * self._indent + pad)}{line}" 836 ) 837 for i, line in enumerate(lines) 838 ) 839 840 def sql( 841 self, 842 expression: t.Optional[str | exp.Expression], 843 key: t.Optional[str] = None, 844 comment: bool = True, 845 ) -> str: 846 if not expression: 847 return "" 848 849 if isinstance(expression, str): 850 return expression 851 852 if key: 853 value = expression.args.get(key) 854 if value: 855 return self.sql(value) 856 return "" 857 858 transform = self.TRANSFORMS.get(expression.__class__) 859 860 if callable(transform): 861 sql = transform(self, expression) 862 elif isinstance(expression, exp.Expression): 863 exp_handler_name = f"{expression.key}_sql" 864 865 if hasattr(self, exp_handler_name): 866 sql = getattr(self, exp_handler_name)(expression) 867 elif isinstance(expression, exp.Func): 868 sql = self.function_fallback_sql(expression) 869 elif isinstance(expression, exp.Property): 870 sql = self.property_sql(expression) 871 else: 872 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 873 else: 874 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 875 876 return self.maybe_comment(sql, expression) if self.comments and comment else sql 877 878 def uncache_sql(self, expression: exp.Uncache) -> str: 879 table = self.sql(expression, "this") 880 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 881 return f"UNCACHE TABLE{exists_sql} {table}" 882 883 def cache_sql(self, expression: exp.Cache) -> str: 884 lazy = " LAZY" if expression.args.get("lazy") else "" 885 table = self.sql(expression, "this") 886 options = expression.args.get("options") 887 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 888 sql = self.sql(expression, "expression") 889 sql = f" AS{self.sep()}{sql}" if sql else "" 890 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 891 return self.prepend_ctes(expression, sql) 892 893 def characterset_sql(self, expression: exp.CharacterSet) -> str: 894 if isinstance(expression.parent, exp.Cast): 895 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 896 default = "DEFAULT " if expression.args.get("default") else "" 897 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 898 899 def column_parts(self, expression: exp.Column) -> str: 900 return ".".join( 901 self.sql(part) 902 for part in ( 903 expression.args.get("catalog"), 904 expression.args.get("db"), 905 expression.args.get("table"), 906 expression.args.get("this"), 907 ) 908 if part 909 ) 910 911 def column_sql(self, expression: exp.Column) -> str: 912 join_mark = " (+)" if expression.args.get("join_mark") else "" 913 914 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 915 join_mark = "" 916 self.unsupported("Outer join syntax using the (+) operator is not supported.") 917 918 return f"{self.column_parts(expression)}{join_mark}" 919 920 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 921 this = self.sql(expression, "this") 922 this = f" {this}" if this else "" 923 position = self.sql(expression, "position") 924 return f"{position}{this}" 925 926 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 927 column = self.sql(expression, "this") 928 kind = self.sql(expression, "kind") 929 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 930 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 931 kind = f"{sep}{kind}" if kind else "" 932 constraints = f" {constraints}" if constraints else "" 933 position = self.sql(expression, "position") 934 position = f" {position}" if position else "" 935 936 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 937 kind = "" 938 939 return f"{exists}{column}{kind}{constraints}{position}" 940 941 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 942 this = self.sql(expression, "this") 943 kind_sql = self.sql(expression, "kind").strip() 944 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 945 946 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 947 this = self.sql(expression, "this") 948 if expression.args.get("not_null"): 949 persisted = " PERSISTED NOT NULL" 950 elif expression.args.get("persisted"): 951 persisted = " PERSISTED" 952 else: 953 persisted = "" 954 return f"AS {this}{persisted}" 955 956 def autoincrementcolumnconstraint_sql(self, _) -> str: 957 return self.token_sql(TokenType.AUTO_INCREMENT) 958 959 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 960 if isinstance(expression.this, list): 961 this = self.wrap(self.expressions(expression, key="this", flat=True)) 962 else: 963 this = self.sql(expression, "this") 964 965 return f"COMPRESS {this}" 966 967 def generatedasidentitycolumnconstraint_sql( 968 self, expression: exp.GeneratedAsIdentityColumnConstraint 969 ) -> str: 970 this = "" 971 if expression.this is not None: 972 on_null = " ON NULL" if expression.args.get("on_null") else "" 973 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 974 975 start = expression.args.get("start") 976 start = f"START WITH {start}" if start else "" 977 increment = expression.args.get("increment") 978 increment = f" INCREMENT BY {increment}" if increment else "" 979 minvalue = expression.args.get("minvalue") 980 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 981 maxvalue = expression.args.get("maxvalue") 982 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 983 cycle = expression.args.get("cycle") 984 cycle_sql = "" 985 986 if cycle is not None: 987 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 988 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 989 990 sequence_opts = "" 991 if start or increment or cycle_sql: 992 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 993 sequence_opts = f" ({sequence_opts.strip()})" 994 995 expr = self.sql(expression, "expression") 996 expr = f"({expr})" if expr else "IDENTITY" 997 998 return f"GENERATED{this} AS {expr}{sequence_opts}" 999 1000 def generatedasrowcolumnconstraint_sql( 1001 self, expression: exp.GeneratedAsRowColumnConstraint 1002 ) -> str: 1003 start = "START" if expression.args.get("start") else "END" 1004 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1005 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1006 1007 def periodforsystemtimeconstraint_sql( 1008 self, expression: exp.PeriodForSystemTimeConstraint 1009 ) -> str: 1010 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1011 1012 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1013 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1014 1015 def transformcolumnconstraint_sql(self, expression: exp.TransformColumnConstraint) -> str: 1016 return f"AS {self.sql(expression, 'this')}" 1017 1018 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1019 desc = expression.args.get("desc") 1020 if desc is not None: 1021 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1022 return "PRIMARY KEY" 1023 1024 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1025 this = self.sql(expression, "this") 1026 this = f" {this}" if this else "" 1027 index_type = expression.args.get("index_type") 1028 index_type = f" USING {index_type}" if index_type else "" 1029 on_conflict = self.sql(expression, "on_conflict") 1030 on_conflict = f" {on_conflict}" if on_conflict else "" 1031 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1032 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}" 1033 1034 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1035 return self.sql(expression, "this") 1036 1037 def create_sql(self, expression: exp.Create) -> str: 1038 kind = self.sql(expression, "kind") 1039 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1040 properties = expression.args.get("properties") 1041 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1042 1043 this = self.createable_sql(expression, properties_locs) 1044 1045 properties_sql = "" 1046 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1047 exp.Properties.Location.POST_WITH 1048 ): 1049 properties_sql = self.sql( 1050 exp.Properties( 1051 expressions=[ 1052 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1053 *properties_locs[exp.Properties.Location.POST_WITH], 1054 ] 1055 ) 1056 ) 1057 1058 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1059 properties_sql = self.sep() + properties_sql 1060 elif not self.pretty: 1061 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1062 properties_sql = f" {properties_sql}" 1063 1064 begin = " BEGIN" if expression.args.get("begin") else "" 1065 end = " END" if expression.args.get("end") else "" 1066 1067 expression_sql = self.sql(expression, "expression") 1068 if expression_sql: 1069 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1070 1071 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1072 postalias_props_sql = "" 1073 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1074 postalias_props_sql = self.properties( 1075 exp.Properties( 1076 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1077 ), 1078 wrapped=False, 1079 ) 1080 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1081 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1082 1083 postindex_props_sql = "" 1084 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1085 postindex_props_sql = self.properties( 1086 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1087 wrapped=False, 1088 prefix=" ", 1089 ) 1090 1091 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1092 indexes = f" {indexes}" if indexes else "" 1093 index_sql = indexes + postindex_props_sql 1094 1095 replace = " OR REPLACE" if expression.args.get("replace") else "" 1096 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1097 unique = " UNIQUE" if expression.args.get("unique") else "" 1098 1099 clustered = expression.args.get("clustered") 1100 if clustered is None: 1101 clustered_sql = "" 1102 elif clustered: 1103 clustered_sql = " CLUSTERED COLUMNSTORE" 1104 else: 1105 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1106 1107 postcreate_props_sql = "" 1108 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1109 postcreate_props_sql = self.properties( 1110 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1111 sep=" ", 1112 prefix=" ", 1113 wrapped=False, 1114 ) 1115 1116 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1117 1118 postexpression_props_sql = "" 1119 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1120 postexpression_props_sql = self.properties( 1121 exp.Properties( 1122 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1123 ), 1124 sep=" ", 1125 prefix=" ", 1126 wrapped=False, 1127 ) 1128 1129 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1130 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1131 no_schema_binding = ( 1132 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1133 ) 1134 1135 clone = self.sql(expression, "clone") 1136 clone = f" {clone}" if clone else "" 1137 1138 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1139 return self.prepend_ctes(expression, expression_sql) 1140 1141 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1142 start = self.sql(expression, "start") 1143 start = f"START WITH {start}" if start else "" 1144 increment = self.sql(expression, "increment") 1145 increment = f" INCREMENT BY {increment}" if increment else "" 1146 minvalue = self.sql(expression, "minvalue") 1147 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1148 maxvalue = self.sql(expression, "maxvalue") 1149 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1150 owned = self.sql(expression, "owned") 1151 owned = f" OWNED BY {owned}" if owned else "" 1152 1153 cache = expression.args.get("cache") 1154 if cache is None: 1155 cache_str = "" 1156 elif cache is True: 1157 cache_str = " CACHE" 1158 else: 1159 cache_str = f" CACHE {cache}" 1160 1161 options = self.expressions(expression, key="options", flat=True, sep=" ") 1162 options = f" {options}" if options else "" 1163 1164 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1165 1166 def clone_sql(self, expression: exp.Clone) -> str: 1167 this = self.sql(expression, "this") 1168 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1169 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1170 return f"{shallow}{keyword} {this}" 1171 1172 def describe_sql(self, expression: exp.Describe) -> str: 1173 style = expression.args.get("style") 1174 style = f" {style}" if style else "" 1175 partition = self.sql(expression, "partition") 1176 partition = f" {partition}" if partition else "" 1177 return f"DESCRIBE{style} {self.sql(expression, 'this')}{partition}" 1178 1179 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1180 tag = self.sql(expression, "tag") 1181 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1182 1183 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1184 with_ = self.sql(expression, "with") 1185 if with_: 1186 sql = f"{with_}{self.sep()}{sql}" 1187 return sql 1188 1189 def with_sql(self, expression: exp.With) -> str: 1190 sql = self.expressions(expression, flat=True) 1191 recursive = ( 1192 "RECURSIVE " 1193 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1194 else "" 1195 ) 1196 1197 return f"WITH {recursive}{sql}" 1198 1199 def cte_sql(self, expression: exp.CTE) -> str: 1200 alias = self.sql(expression, "alias") 1201 1202 materialized = expression.args.get("materialized") 1203 if materialized is False: 1204 materialized = "NOT MATERIALIZED " 1205 elif materialized: 1206 materialized = "MATERIALIZED " 1207 1208 return f"{alias} AS {materialized or ''}{self.wrap(expression)}" 1209 1210 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1211 alias = self.sql(expression, "this") 1212 columns = self.expressions(expression, key="columns", flat=True) 1213 columns = f"({columns})" if columns else "" 1214 1215 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1216 columns = "" 1217 self.unsupported("Named columns are not supported in table alias.") 1218 1219 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1220 alias = self._next_name() 1221 1222 return f"{alias}{columns}" 1223 1224 def bitstring_sql(self, expression: exp.BitString) -> str: 1225 this = self.sql(expression, "this") 1226 if self.dialect.BIT_START: 1227 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1228 return f"{int(this, 2)}" 1229 1230 def hexstring_sql(self, expression: exp.HexString) -> str: 1231 this = self.sql(expression, "this") 1232 if self.dialect.HEX_START: 1233 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1234 return f"{int(this, 16)}" 1235 1236 def bytestring_sql(self, expression: exp.ByteString) -> str: 1237 this = self.sql(expression, "this") 1238 if self.dialect.BYTE_START: 1239 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1240 return this 1241 1242 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1243 this = self.sql(expression, "this") 1244 escape = expression.args.get("escape") 1245 1246 if self.dialect.UNICODE_START: 1247 escape_substitute = r"\\\1" 1248 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1249 else: 1250 escape_substitute = r"\\u\1" 1251 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1252 1253 if escape: 1254 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1255 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1256 else: 1257 escape_pattern = ESCAPED_UNICODE_RE 1258 escape_sql = "" 1259 1260 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1261 this = escape_pattern.sub(escape_substitute, this) 1262 1263 return f"{left_quote}{this}{right_quote}{escape_sql}" 1264 1265 def rawstring_sql(self, expression: exp.RawString) -> str: 1266 string = self.escape_str(expression.this.replace("\\", "\\\\"), escape_backslash=False) 1267 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1268 1269 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1270 this = self.sql(expression, "this") 1271 specifier = self.sql(expression, "expression") 1272 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1273 return f"{this}{specifier}" 1274 1275 def datatype_sql(self, expression: exp.DataType) -> str: 1276 nested = "" 1277 values = "" 1278 interior = self.expressions(expression, flat=True) 1279 1280 type_value = expression.this 1281 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1282 type_sql = self.sql(expression, "kind") 1283 else: 1284 type_sql = ( 1285 self.TYPE_MAPPING.get(type_value, type_value.value) 1286 if isinstance(type_value, exp.DataType.Type) 1287 else type_value 1288 ) 1289 1290 if interior: 1291 if expression.args.get("nested"): 1292 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1293 if expression.args.get("values") is not None: 1294 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1295 values = self.expressions(expression, key="values", flat=True) 1296 values = f"{delimiters[0]}{values}{delimiters[1]}" 1297 elif type_value == exp.DataType.Type.INTERVAL: 1298 nested = f" {interior}" 1299 else: 1300 nested = f"({interior})" 1301 1302 type_sql = f"{type_sql}{nested}{values}" 1303 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1304 exp.DataType.Type.TIMETZ, 1305 exp.DataType.Type.TIMESTAMPTZ, 1306 ): 1307 type_sql = f"{type_sql} WITH TIME ZONE" 1308 1309 return type_sql 1310 1311 def directory_sql(self, expression: exp.Directory) -> str: 1312 local = "LOCAL " if expression.args.get("local") else "" 1313 row_format = self.sql(expression, "row_format") 1314 row_format = f" {row_format}" if row_format else "" 1315 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1316 1317 def delete_sql(self, expression: exp.Delete) -> str: 1318 this = self.sql(expression, "this") 1319 this = f" FROM {this}" if this else "" 1320 using = self.sql(expression, "using") 1321 using = f" USING {using}" if using else "" 1322 cluster = self.sql(expression, "cluster") 1323 cluster = f" {cluster}" if cluster else "" 1324 where = self.sql(expression, "where") 1325 returning = self.sql(expression, "returning") 1326 limit = self.sql(expression, "limit") 1327 tables = self.expressions(expression, key="tables") 1328 tables = f" {tables}" if tables else "" 1329 if self.RETURNING_END: 1330 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1331 else: 1332 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1333 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1334 1335 def drop_sql(self, expression: exp.Drop) -> str: 1336 this = self.sql(expression, "this") 1337 expressions = self.expressions(expression, flat=True) 1338 expressions = f" ({expressions})" if expressions else "" 1339 kind = expression.args["kind"] 1340 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1341 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1342 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1343 on_cluster = self.sql(expression, "cluster") 1344 on_cluster = f" {on_cluster}" if on_cluster else "" 1345 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1346 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1347 cascade = " CASCADE" if expression.args.get("cascade") else "" 1348 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1349 purge = " PURGE" if expression.args.get("purge") else "" 1350 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1351 1352 def set_operation(self, expression: exp.SetOperation) -> str: 1353 op_type = type(expression) 1354 op_name = op_type.key.upper() 1355 1356 distinct = expression.args.get("distinct") 1357 if ( 1358 distinct is False 1359 and op_type in (exp.Except, exp.Intersect) 1360 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1361 ): 1362 self.unsupported(f"{op_name} ALL is not supported") 1363 1364 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1365 1366 if distinct is None: 1367 distinct = default_distinct 1368 if distinct is None: 1369 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1370 1371 if distinct is default_distinct: 1372 kind = "" 1373 else: 1374 kind = " DISTINCT" if distinct else " ALL" 1375 1376 by_name = " BY NAME" if expression.args.get("by_name") else "" 1377 return f"{op_name}{kind}{by_name}" 1378 1379 def set_operations(self, expression: exp.SetOperation) -> str: 1380 if not self.SET_OP_MODIFIERS: 1381 limit = expression.args.get("limit") 1382 order = expression.args.get("order") 1383 1384 if limit or order: 1385 select = self._move_ctes_to_top_level( 1386 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1387 ) 1388 1389 if limit: 1390 select = select.limit(limit.pop(), copy=False) 1391 if order: 1392 select = select.order_by(order.pop(), copy=False) 1393 return self.sql(select) 1394 1395 sqls: t.List[str] = [] 1396 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1397 1398 while stack: 1399 node = stack.pop() 1400 1401 if isinstance(node, exp.SetOperation): 1402 stack.append(node.expression) 1403 stack.append( 1404 self.maybe_comment( 1405 self.set_operation(node), comments=node.comments, separated=True 1406 ) 1407 ) 1408 stack.append(node.this) 1409 else: 1410 sqls.append(self.sql(node)) 1411 1412 this = self.sep().join(sqls) 1413 this = self.query_modifiers(expression, this) 1414 return self.prepend_ctes(expression, this) 1415 1416 def fetch_sql(self, expression: exp.Fetch) -> str: 1417 direction = expression.args.get("direction") 1418 direction = f" {direction}" if direction else "" 1419 count = self.sql(expression, "count") 1420 count = f" {count}" if count else "" 1421 if expression.args.get("percent"): 1422 count = f"{count} PERCENT" 1423 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY" 1424 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}" 1425 1426 def filter_sql(self, expression: exp.Filter) -> str: 1427 if self.AGGREGATE_FILTER_SUPPORTED: 1428 this = self.sql(expression, "this") 1429 where = self.sql(expression, "expression").strip() 1430 return f"{this} FILTER({where})" 1431 1432 agg = expression.this 1433 agg_arg = agg.this 1434 cond = expression.expression.this 1435 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1436 return self.sql(agg) 1437 1438 def hint_sql(self, expression: exp.Hint) -> str: 1439 if not self.QUERY_HINTS: 1440 self.unsupported("Hints are not supported") 1441 return "" 1442 1443 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1444 1445 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1446 using = self.sql(expression, "using") 1447 using = f" USING {using}" if using else "" 1448 columns = self.expressions(expression, key="columns", flat=True) 1449 columns = f"({columns})" if columns else "" 1450 partition_by = self.expressions(expression, key="partition_by", flat=True) 1451 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1452 where = self.sql(expression, "where") 1453 include = self.expressions(expression, key="include", flat=True) 1454 if include: 1455 include = f" INCLUDE ({include})" 1456 with_storage = self.expressions(expression, key="with_storage", flat=True) 1457 with_storage = f" WITH ({with_storage})" if with_storage else "" 1458 tablespace = self.sql(expression, "tablespace") 1459 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1460 on = self.sql(expression, "on") 1461 on = f" ON {on}" if on else "" 1462 1463 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1464 1465 def index_sql(self, expression: exp.Index) -> str: 1466 unique = "UNIQUE " if expression.args.get("unique") else "" 1467 primary = "PRIMARY " if expression.args.get("primary") else "" 1468 amp = "AMP " if expression.args.get("amp") else "" 1469 name = self.sql(expression, "this") 1470 name = f"{name} " if name else "" 1471 table = self.sql(expression, "table") 1472 table = f"{self.INDEX_ON} {table}" if table else "" 1473 1474 index = "INDEX " if not table else "" 1475 1476 params = self.sql(expression, "params") 1477 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1478 1479 def identifier_sql(self, expression: exp.Identifier) -> str: 1480 text = expression.name 1481 lower = text.lower() 1482 text = lower if self.normalize and not expression.quoted else text 1483 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1484 if ( 1485 expression.quoted 1486 or self.dialect.can_identify(text, self.identify) 1487 or lower in self.RESERVED_KEYWORDS 1488 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1489 ): 1490 text = f"{self._identifier_start}{text}{self._identifier_end}" 1491 return text 1492 1493 def hex_sql(self, expression: exp.Hex) -> str: 1494 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1495 if self.dialect.HEX_LOWERCASE: 1496 text = self.func("LOWER", text) 1497 1498 return text 1499 1500 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1501 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1502 if not self.dialect.HEX_LOWERCASE: 1503 text = self.func("LOWER", text) 1504 return text 1505 1506 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1507 input_format = self.sql(expression, "input_format") 1508 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1509 output_format = self.sql(expression, "output_format") 1510 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1511 return self.sep().join((input_format, output_format)) 1512 1513 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1514 string = self.sql(exp.Literal.string(expression.name)) 1515 return f"{prefix}{string}" 1516 1517 def partition_sql(self, expression: exp.Partition) -> str: 1518 return f"PARTITION({self.expressions(expression, flat=True)})" 1519 1520 def properties_sql(self, expression: exp.Properties) -> str: 1521 root_properties = [] 1522 with_properties = [] 1523 1524 for p in expression.expressions: 1525 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1526 if p_loc == exp.Properties.Location.POST_WITH: 1527 with_properties.append(p) 1528 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1529 root_properties.append(p) 1530 1531 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1532 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1533 1534 if root_props and with_props and not self.pretty: 1535 with_props = " " + with_props 1536 1537 return root_props + with_props 1538 1539 def root_properties(self, properties: exp.Properties) -> str: 1540 if properties.expressions: 1541 return self.expressions(properties, indent=False, sep=" ") 1542 return "" 1543 1544 def properties( 1545 self, 1546 properties: exp.Properties, 1547 prefix: str = "", 1548 sep: str = ", ", 1549 suffix: str = "", 1550 wrapped: bool = True, 1551 ) -> str: 1552 if properties.expressions: 1553 expressions = self.expressions(properties, sep=sep, indent=False) 1554 if expressions: 1555 expressions = self.wrap(expressions) if wrapped else expressions 1556 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1557 return "" 1558 1559 def with_properties(self, properties: exp.Properties) -> str: 1560 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1561 1562 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1563 properties_locs = defaultdict(list) 1564 for p in properties.expressions: 1565 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1566 if p_loc != exp.Properties.Location.UNSUPPORTED: 1567 properties_locs[p_loc].append(p) 1568 else: 1569 self.unsupported(f"Unsupported property {p.key}") 1570 1571 return properties_locs 1572 1573 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1574 if isinstance(expression.this, exp.Dot): 1575 return self.sql(expression, "this") 1576 return f"'{expression.name}'" if string_key else expression.name 1577 1578 def property_sql(self, expression: exp.Property) -> str: 1579 property_cls = expression.__class__ 1580 if property_cls == exp.Property: 1581 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1582 1583 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1584 if not property_name: 1585 self.unsupported(f"Unsupported property {expression.key}") 1586 1587 return f"{property_name}={self.sql(expression, 'this')}" 1588 1589 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1590 if self.SUPPORTS_CREATE_TABLE_LIKE: 1591 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1592 options = f" {options}" if options else "" 1593 1594 like = f"LIKE {self.sql(expression, 'this')}{options}" 1595 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1596 like = f"({like})" 1597 1598 return like 1599 1600 if expression.expressions: 1601 self.unsupported("Transpilation of LIKE property options is unsupported") 1602 1603 select = exp.select("*").from_(expression.this).limit(0) 1604 return f"AS {self.sql(select)}" 1605 1606 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1607 no = "NO " if expression.args.get("no") else "" 1608 protection = " PROTECTION" if expression.args.get("protection") else "" 1609 return f"{no}FALLBACK{protection}" 1610 1611 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1612 no = "NO " if expression.args.get("no") else "" 1613 local = expression.args.get("local") 1614 local = f"{local} " if local else "" 1615 dual = "DUAL " if expression.args.get("dual") else "" 1616 before = "BEFORE " if expression.args.get("before") else "" 1617 after = "AFTER " if expression.args.get("after") else "" 1618 return f"{no}{local}{dual}{before}{after}JOURNAL" 1619 1620 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1621 freespace = self.sql(expression, "this") 1622 percent = " PERCENT" if expression.args.get("percent") else "" 1623 return f"FREESPACE={freespace}{percent}" 1624 1625 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1626 if expression.args.get("default"): 1627 property = "DEFAULT" 1628 elif expression.args.get("on"): 1629 property = "ON" 1630 else: 1631 property = "OFF" 1632 return f"CHECKSUM={property}" 1633 1634 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1635 if expression.args.get("no"): 1636 return "NO MERGEBLOCKRATIO" 1637 if expression.args.get("default"): 1638 return "DEFAULT MERGEBLOCKRATIO" 1639 1640 percent = " PERCENT" if expression.args.get("percent") else "" 1641 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1642 1643 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1644 default = expression.args.get("default") 1645 minimum = expression.args.get("minimum") 1646 maximum = expression.args.get("maximum") 1647 if default or minimum or maximum: 1648 if default: 1649 prop = "DEFAULT" 1650 elif minimum: 1651 prop = "MINIMUM" 1652 else: 1653 prop = "MAXIMUM" 1654 return f"{prop} DATABLOCKSIZE" 1655 units = expression.args.get("units") 1656 units = f" {units}" if units else "" 1657 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1658 1659 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1660 autotemp = expression.args.get("autotemp") 1661 always = expression.args.get("always") 1662 default = expression.args.get("default") 1663 manual = expression.args.get("manual") 1664 never = expression.args.get("never") 1665 1666 if autotemp is not None: 1667 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1668 elif always: 1669 prop = "ALWAYS" 1670 elif default: 1671 prop = "DEFAULT" 1672 elif manual: 1673 prop = "MANUAL" 1674 elif never: 1675 prop = "NEVER" 1676 return f"BLOCKCOMPRESSION={prop}" 1677 1678 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1679 no = expression.args.get("no") 1680 no = " NO" if no else "" 1681 concurrent = expression.args.get("concurrent") 1682 concurrent = " CONCURRENT" if concurrent else "" 1683 target = self.sql(expression, "target") 1684 target = f" {target}" if target else "" 1685 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1686 1687 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1688 if isinstance(expression.this, list): 1689 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1690 if expression.this: 1691 modulus = self.sql(expression, "this") 1692 remainder = self.sql(expression, "expression") 1693 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1694 1695 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1696 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1697 return f"FROM ({from_expressions}) TO ({to_expressions})" 1698 1699 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1700 this = self.sql(expression, "this") 1701 1702 for_values_or_default = expression.expression 1703 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1704 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1705 else: 1706 for_values_or_default = " DEFAULT" 1707 1708 return f"PARTITION OF {this}{for_values_or_default}" 1709 1710 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1711 kind = expression.args.get("kind") 1712 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1713 for_or_in = expression.args.get("for_or_in") 1714 for_or_in = f" {for_or_in}" if for_or_in else "" 1715 lock_type = expression.args.get("lock_type") 1716 override = " OVERRIDE" if expression.args.get("override") else "" 1717 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1718 1719 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1720 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1721 statistics = expression.args.get("statistics") 1722 statistics_sql = "" 1723 if statistics is not None: 1724 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1725 return f"{data_sql}{statistics_sql}" 1726 1727 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1728 this = self.sql(expression, "this") 1729 this = f"HISTORY_TABLE={this}" if this else "" 1730 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1731 data_consistency = ( 1732 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1733 ) 1734 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1735 retention_period = ( 1736 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1737 ) 1738 1739 if this: 1740 on_sql = self.func("ON", this, data_consistency, retention_period) 1741 else: 1742 on_sql = "ON" if expression.args.get("on") else "OFF" 1743 1744 sql = f"SYSTEM_VERSIONING={on_sql}" 1745 1746 return f"WITH({sql})" if expression.args.get("with") else sql 1747 1748 def insert_sql(self, expression: exp.Insert) -> str: 1749 hint = self.sql(expression, "hint") 1750 overwrite = expression.args.get("overwrite") 1751 1752 if isinstance(expression.this, exp.Directory): 1753 this = " OVERWRITE" if overwrite else " INTO" 1754 else: 1755 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1756 1757 stored = self.sql(expression, "stored") 1758 stored = f" {stored}" if stored else "" 1759 alternative = expression.args.get("alternative") 1760 alternative = f" OR {alternative}" if alternative else "" 1761 ignore = " IGNORE" if expression.args.get("ignore") else "" 1762 is_function = expression.args.get("is_function") 1763 if is_function: 1764 this = f"{this} FUNCTION" 1765 this = f"{this} {self.sql(expression, 'this')}" 1766 1767 exists = " IF EXISTS" if expression.args.get("exists") else "" 1768 where = self.sql(expression, "where") 1769 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1770 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1771 on_conflict = self.sql(expression, "conflict") 1772 on_conflict = f" {on_conflict}" if on_conflict else "" 1773 by_name = " BY NAME" if expression.args.get("by_name") else "" 1774 returning = self.sql(expression, "returning") 1775 1776 if self.RETURNING_END: 1777 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1778 else: 1779 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1780 1781 partition_by = self.sql(expression, "partition") 1782 partition_by = f" {partition_by}" if partition_by else "" 1783 settings = self.sql(expression, "settings") 1784 settings = f" {settings}" if settings else "" 1785 1786 source = self.sql(expression, "source") 1787 source = f"TABLE {source}" if source else "" 1788 1789 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1790 return self.prepend_ctes(expression, sql) 1791 1792 def introducer_sql(self, expression: exp.Introducer) -> str: 1793 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1794 1795 def kill_sql(self, expression: exp.Kill) -> str: 1796 kind = self.sql(expression, "kind") 1797 kind = f" {kind}" if kind else "" 1798 this = self.sql(expression, "this") 1799 this = f" {this}" if this else "" 1800 return f"KILL{kind}{this}" 1801 1802 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1803 return expression.name 1804 1805 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1806 return expression.name 1807 1808 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1809 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1810 1811 constraint = self.sql(expression, "constraint") 1812 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1813 1814 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1815 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1816 action = self.sql(expression, "action") 1817 1818 expressions = self.expressions(expression, flat=True) 1819 if expressions: 1820 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1821 expressions = f" {set_keyword}{expressions}" 1822 1823 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}" 1824 1825 def returning_sql(self, expression: exp.Returning) -> str: 1826 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1827 1828 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1829 fields = self.sql(expression, "fields") 1830 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1831 escaped = self.sql(expression, "escaped") 1832 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1833 items = self.sql(expression, "collection_items") 1834 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1835 keys = self.sql(expression, "map_keys") 1836 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1837 lines = self.sql(expression, "lines") 1838 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1839 null = self.sql(expression, "null") 1840 null = f" NULL DEFINED AS {null}" if null else "" 1841 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1842 1843 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1844 return f"WITH ({self.expressions(expression, flat=True)})" 1845 1846 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1847 this = f"{self.sql(expression, 'this')} INDEX" 1848 target = self.sql(expression, "target") 1849 target = f" FOR {target}" if target else "" 1850 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1851 1852 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1853 this = self.sql(expression, "this") 1854 kind = self.sql(expression, "kind") 1855 expr = self.sql(expression, "expression") 1856 return f"{this} ({kind} => {expr})" 1857 1858 def table_parts(self, expression: exp.Table) -> str: 1859 return ".".join( 1860 self.sql(part) 1861 for part in ( 1862 expression.args.get("catalog"), 1863 expression.args.get("db"), 1864 expression.args.get("this"), 1865 ) 1866 if part is not None 1867 ) 1868 1869 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1870 table = self.table_parts(expression) 1871 only = "ONLY " if expression.args.get("only") else "" 1872 partition = self.sql(expression, "partition") 1873 partition = f" {partition}" if partition else "" 1874 version = self.sql(expression, "version") 1875 version = f" {version}" if version else "" 1876 alias = self.sql(expression, "alias") 1877 alias = f"{sep}{alias}" if alias else "" 1878 1879 sample = self.sql(expression, "sample") 1880 if self.dialect.ALIAS_POST_TABLESAMPLE: 1881 sample_pre_alias = sample 1882 sample_post_alias = "" 1883 else: 1884 sample_pre_alias = "" 1885 sample_post_alias = sample 1886 1887 hints = self.expressions(expression, key="hints", sep=" ") 1888 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1889 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 1890 joins = self.indent( 1891 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 1892 ) 1893 laterals = self.expressions(expression, key="laterals", sep="") 1894 1895 file_format = self.sql(expression, "format") 1896 if file_format: 1897 pattern = self.sql(expression, "pattern") 1898 pattern = f", PATTERN => {pattern}" if pattern else "" 1899 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 1900 1901 ordinality = expression.args.get("ordinality") or "" 1902 if ordinality: 1903 ordinality = f" WITH ORDINALITY{alias}" 1904 alias = "" 1905 1906 when = self.sql(expression, "when") 1907 if when: 1908 table = f"{table} {when}" 1909 1910 changes = self.sql(expression, "changes") 1911 changes = f" {changes}" if changes else "" 1912 1913 rows_from = self.expressions(expression, key="rows_from") 1914 if rows_from: 1915 table = f"ROWS FROM {self.wrap(rows_from)}" 1916 1917 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 1918 1919 def tablesample_sql( 1920 self, 1921 expression: exp.TableSample, 1922 tablesample_keyword: t.Optional[str] = None, 1923 ) -> str: 1924 method = self.sql(expression, "method") 1925 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 1926 numerator = self.sql(expression, "bucket_numerator") 1927 denominator = self.sql(expression, "bucket_denominator") 1928 field = self.sql(expression, "bucket_field") 1929 field = f" ON {field}" if field else "" 1930 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 1931 seed = self.sql(expression, "seed") 1932 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 1933 1934 size = self.sql(expression, "size") 1935 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 1936 size = f"{size} ROWS" 1937 1938 percent = self.sql(expression, "percent") 1939 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 1940 percent = f"{percent} PERCENT" 1941 1942 expr = f"{bucket}{percent}{size}" 1943 if self.TABLESAMPLE_REQUIRES_PARENS: 1944 expr = f"({expr})" 1945 1946 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 1947 1948 def pivot_sql(self, expression: exp.Pivot) -> str: 1949 expressions = self.expressions(expression, flat=True) 1950 1951 if expression.this: 1952 this = self.sql(expression, "this") 1953 if not expressions: 1954 return f"UNPIVOT {this}" 1955 1956 on = f"{self.seg('ON')} {expressions}" 1957 using = self.expressions(expression, key="using", flat=True) 1958 using = f"{self.seg('USING')} {using}" if using else "" 1959 group = self.sql(expression, "group") 1960 return f"PIVOT {this}{on}{using}{group}" 1961 1962 alias = self.sql(expression, "alias") 1963 alias = f" AS {alias}" if alias else "" 1964 direction = self.seg("UNPIVOT" if expression.unpivot else "PIVOT") 1965 1966 field = self.sql(expression, "field") 1967 1968 include_nulls = expression.args.get("include_nulls") 1969 if include_nulls is not None: 1970 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 1971 else: 1972 nulls = "" 1973 1974 default_on_null = self.sql(expression, "default_on_null") 1975 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 1976 return f"{direction}{nulls}({expressions} FOR {field}{default_on_null}){alias}" 1977 1978 def version_sql(self, expression: exp.Version) -> str: 1979 this = f"FOR {expression.name}" 1980 kind = expression.text("kind") 1981 expr = self.sql(expression, "expression") 1982 return f"{this} {kind} {expr}" 1983 1984 def tuple_sql(self, expression: exp.Tuple) -> str: 1985 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 1986 1987 def update_sql(self, expression: exp.Update) -> str: 1988 this = self.sql(expression, "this") 1989 set_sql = self.expressions(expression, flat=True) 1990 from_sql = self.sql(expression, "from") 1991 where_sql = self.sql(expression, "where") 1992 returning = self.sql(expression, "returning") 1993 order = self.sql(expression, "order") 1994 limit = self.sql(expression, "limit") 1995 if self.RETURNING_END: 1996 expression_sql = f"{from_sql}{where_sql}{returning}" 1997 else: 1998 expression_sql = f"{returning}{from_sql}{where_sql}" 1999 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2000 return self.prepend_ctes(expression, sql) 2001 2002 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2003 values_as_table = values_as_table and self.VALUES_AS_TABLE 2004 2005 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2006 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2007 args = self.expressions(expression) 2008 alias = self.sql(expression, "alias") 2009 values = f"VALUES{self.seg('')}{args}" 2010 values = ( 2011 f"({values})" 2012 if self.WRAP_DERIVED_VALUES 2013 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2014 else values 2015 ) 2016 return f"{values} AS {alias}" if alias else values 2017 2018 # Converts `VALUES...` expression into a series of select unions. 2019 alias_node = expression.args.get("alias") 2020 column_names = alias_node and alias_node.columns 2021 2022 selects: t.List[exp.Query] = [] 2023 2024 for i, tup in enumerate(expression.expressions): 2025 row = tup.expressions 2026 2027 if i == 0 and column_names: 2028 row = [ 2029 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2030 ] 2031 2032 selects.append(exp.Select(expressions=row)) 2033 2034 if self.pretty: 2035 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2036 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2037 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2038 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2039 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2040 2041 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2042 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2043 return f"({unions}){alias}" 2044 2045 def var_sql(self, expression: exp.Var) -> str: 2046 return self.sql(expression, "this") 2047 2048 @unsupported_args("expressions") 2049 def into_sql(self, expression: exp.Into) -> str: 2050 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2051 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2052 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2053 2054 def from_sql(self, expression: exp.From) -> str: 2055 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2056 2057 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2058 grouping_sets = self.expressions(expression, indent=False) 2059 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2060 2061 def rollup_sql(self, expression: exp.Rollup) -> str: 2062 expressions = self.expressions(expression, indent=False) 2063 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2064 2065 def cube_sql(self, expression: exp.Cube) -> str: 2066 expressions = self.expressions(expression, indent=False) 2067 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2068 2069 def group_sql(self, expression: exp.Group) -> str: 2070 group_by_all = expression.args.get("all") 2071 if group_by_all is True: 2072 modifier = " ALL" 2073 elif group_by_all is False: 2074 modifier = " DISTINCT" 2075 else: 2076 modifier = "" 2077 2078 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2079 2080 grouping_sets = self.expressions(expression, key="grouping_sets") 2081 cube = self.expressions(expression, key="cube") 2082 rollup = self.expressions(expression, key="rollup") 2083 2084 groupings = csv( 2085 self.seg(grouping_sets) if grouping_sets else "", 2086 self.seg(cube) if cube else "", 2087 self.seg(rollup) if rollup else "", 2088 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2089 sep=self.GROUPINGS_SEP, 2090 ) 2091 2092 if ( 2093 expression.expressions 2094 and groupings 2095 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2096 ): 2097 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2098 2099 return f"{group_by}{groupings}" 2100 2101 def having_sql(self, expression: exp.Having) -> str: 2102 this = self.indent(self.sql(expression, "this")) 2103 return f"{self.seg('HAVING')}{self.sep()}{this}" 2104 2105 def connect_sql(self, expression: exp.Connect) -> str: 2106 start = self.sql(expression, "start") 2107 start = self.seg(f"START WITH {start}") if start else "" 2108 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2109 connect = self.sql(expression, "connect") 2110 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2111 return start + connect 2112 2113 def prior_sql(self, expression: exp.Prior) -> str: 2114 return f"PRIOR {self.sql(expression, 'this')}" 2115 2116 def join_sql(self, expression: exp.Join) -> str: 2117 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2118 side = None 2119 else: 2120 side = expression.side 2121 2122 op_sql = " ".join( 2123 op 2124 for op in ( 2125 expression.method, 2126 "GLOBAL" if expression.args.get("global") else None, 2127 side, 2128 expression.kind, 2129 expression.hint if self.JOIN_HINTS else None, 2130 ) 2131 if op 2132 ) 2133 match_cond = self.sql(expression, "match_condition") 2134 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2135 on_sql = self.sql(expression, "on") 2136 using = expression.args.get("using") 2137 2138 if not on_sql and using: 2139 on_sql = csv(*(self.sql(column) for column in using)) 2140 2141 this = expression.this 2142 this_sql = self.sql(this) 2143 2144 if on_sql: 2145 on_sql = self.indent(on_sql, skip_first=True) 2146 space = self.seg(" " * self.pad) if self.pretty else " " 2147 if using: 2148 on_sql = f"{space}USING ({on_sql})" 2149 else: 2150 on_sql = f"{space}ON {on_sql}" 2151 elif not op_sql: 2152 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2153 return f" {this_sql}" 2154 2155 return f", {this_sql}" 2156 2157 if op_sql != "STRAIGHT_JOIN": 2158 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2159 2160 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}" 2161 2162 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 2163 args = self.expressions(expression, flat=True) 2164 args = f"({args})" if len(args.split(",")) > 1 else args 2165 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2166 2167 def lateral_op(self, expression: exp.Lateral) -> str: 2168 cross_apply = expression.args.get("cross_apply") 2169 2170 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2171 if cross_apply is True: 2172 op = "INNER JOIN " 2173 elif cross_apply is False: 2174 op = "LEFT JOIN " 2175 else: 2176 op = "" 2177 2178 return f"{op}LATERAL" 2179 2180 def lateral_sql(self, expression: exp.Lateral) -> str: 2181 this = self.sql(expression, "this") 2182 2183 if expression.args.get("view"): 2184 alias = expression.args["alias"] 2185 columns = self.expressions(alias, key="columns", flat=True) 2186 table = f" {alias.name}" if alias.name else "" 2187 columns = f" AS {columns}" if columns else "" 2188 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2189 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2190 2191 alias = self.sql(expression, "alias") 2192 alias = f" AS {alias}" if alias else "" 2193 return f"{self.lateral_op(expression)} {this}{alias}" 2194 2195 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2196 this = self.sql(expression, "this") 2197 2198 args = [ 2199 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2200 for e in (expression.args.get(k) for k in ("offset", "expression")) 2201 if e 2202 ] 2203 2204 args_sql = ", ".join(self.sql(e) for e in args) 2205 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2206 expressions = self.expressions(expression, flat=True) 2207 expressions = f" BY {expressions}" if expressions else "" 2208 2209 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{expressions}" 2210 2211 def offset_sql(self, expression: exp.Offset) -> str: 2212 this = self.sql(expression, "this") 2213 value = expression.expression 2214 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2215 expressions = self.expressions(expression, flat=True) 2216 expressions = f" BY {expressions}" if expressions else "" 2217 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2218 2219 def setitem_sql(self, expression: exp.SetItem) -> str: 2220 kind = self.sql(expression, "kind") 2221 kind = f"{kind} " if kind else "" 2222 this = self.sql(expression, "this") 2223 expressions = self.expressions(expression) 2224 collate = self.sql(expression, "collate") 2225 collate = f" COLLATE {collate}" if collate else "" 2226 global_ = "GLOBAL " if expression.args.get("global") else "" 2227 return f"{global_}{kind}{this}{expressions}{collate}" 2228 2229 def set_sql(self, expression: exp.Set) -> str: 2230 expressions = ( 2231 f" {self.expressions(expression, flat=True)}" if expression.expressions else "" 2232 ) 2233 tag = " TAG" if expression.args.get("tag") else "" 2234 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2235 2236 def pragma_sql(self, expression: exp.Pragma) -> str: 2237 return f"PRAGMA {self.sql(expression, 'this')}" 2238 2239 def lock_sql(self, expression: exp.Lock) -> str: 2240 if not self.LOCKING_READS_SUPPORTED: 2241 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2242 return "" 2243 2244 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2245 expressions = self.expressions(expression, flat=True) 2246 expressions = f" OF {expressions}" if expressions else "" 2247 wait = expression.args.get("wait") 2248 2249 if wait is not None: 2250 if isinstance(wait, exp.Literal): 2251 wait = f" WAIT {self.sql(wait)}" 2252 else: 2253 wait = " NOWAIT" if wait else " SKIP LOCKED" 2254 2255 return f"{lock_type}{expressions}{wait or ''}" 2256 2257 def literal_sql(self, expression: exp.Literal) -> str: 2258 text = expression.this or "" 2259 if expression.is_string: 2260 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2261 return text 2262 2263 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2264 if self.dialect.ESCAPED_SEQUENCES: 2265 to_escaped = self.dialect.ESCAPED_SEQUENCES 2266 text = "".join( 2267 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2268 ) 2269 2270 return self._replace_line_breaks(text).replace( 2271 self.dialect.QUOTE_END, self._escaped_quote_end 2272 ) 2273 2274 def loaddata_sql(self, expression: exp.LoadData) -> str: 2275 local = " LOCAL" if expression.args.get("local") else "" 2276 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2277 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2278 this = f" INTO TABLE {self.sql(expression, 'this')}" 2279 partition = self.sql(expression, "partition") 2280 partition = f" {partition}" if partition else "" 2281 input_format = self.sql(expression, "input_format") 2282 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2283 serde = self.sql(expression, "serde") 2284 serde = f" SERDE {serde}" if serde else "" 2285 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2286 2287 def null_sql(self, *_) -> str: 2288 return "NULL" 2289 2290 def boolean_sql(self, expression: exp.Boolean) -> str: 2291 return "TRUE" if expression.this else "FALSE" 2292 2293 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2294 this = self.sql(expression, "this") 2295 this = f"{this} " if this else this 2296 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2297 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2298 2299 def withfill_sql(self, expression: exp.WithFill) -> str: 2300 from_sql = self.sql(expression, "from") 2301 from_sql = f" FROM {from_sql}" if from_sql else "" 2302 to_sql = self.sql(expression, "to") 2303 to_sql = f" TO {to_sql}" if to_sql else "" 2304 step_sql = self.sql(expression, "step") 2305 step_sql = f" STEP {step_sql}" if step_sql else "" 2306 interpolated_values = [ 2307 f"{self.sql(named_expression, 'alias')} AS {self.sql(named_expression, 'this')}" 2308 for named_expression in expression.args.get("interpolate") or [] 2309 ] 2310 interpolate = ( 2311 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2312 ) 2313 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2314 2315 def cluster_sql(self, expression: exp.Cluster) -> str: 2316 return self.op_expressions("CLUSTER BY", expression) 2317 2318 def distribute_sql(self, expression: exp.Distribute) -> str: 2319 return self.op_expressions("DISTRIBUTE BY", expression) 2320 2321 def sort_sql(self, expression: exp.Sort) -> str: 2322 return self.op_expressions("SORT BY", expression) 2323 2324 def ordered_sql(self, expression: exp.Ordered) -> str: 2325 desc = expression.args.get("desc") 2326 asc = not desc 2327 2328 nulls_first = expression.args.get("nulls_first") 2329 nulls_last = not nulls_first 2330 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2331 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2332 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2333 2334 this = self.sql(expression, "this") 2335 2336 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2337 nulls_sort_change = "" 2338 if nulls_first and ( 2339 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2340 ): 2341 nulls_sort_change = " NULLS FIRST" 2342 elif ( 2343 nulls_last 2344 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2345 and not nulls_are_last 2346 ): 2347 nulls_sort_change = " NULLS LAST" 2348 2349 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2350 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2351 window = expression.find_ancestor(exp.Window, exp.Select) 2352 if isinstance(window, exp.Window) and window.args.get("spec"): 2353 self.unsupported( 2354 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2355 ) 2356 nulls_sort_change = "" 2357 elif ( 2358 self.NULL_ORDERING_SUPPORTED is False 2359 and (isinstance(expression.find_ancestor(exp.AggFunc, exp.Select), exp.AggFunc)) 2360 and ( 2361 (asc and nulls_sort_change == " NULLS LAST") 2362 or (desc and nulls_sort_change == " NULLS FIRST") 2363 ) 2364 ): 2365 self.unsupported( 2366 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2367 ) 2368 nulls_sort_change = "" 2369 elif self.NULL_ORDERING_SUPPORTED is None: 2370 if expression.this.is_int: 2371 self.unsupported( 2372 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2373 ) 2374 elif not isinstance(expression.this, exp.Rand): 2375 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2376 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2377 nulls_sort_change = "" 2378 2379 with_fill = self.sql(expression, "with_fill") 2380 with_fill = f" {with_fill}" if with_fill else "" 2381 2382 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2383 2384 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2385 window_frame = self.sql(expression, "window_frame") 2386 window_frame = f"{window_frame} " if window_frame else "" 2387 2388 this = self.sql(expression, "this") 2389 2390 return f"{window_frame}{this}" 2391 2392 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2393 partition = self.partition_by_sql(expression) 2394 order = self.sql(expression, "order") 2395 measures = self.expressions(expression, key="measures") 2396 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2397 rows = self.sql(expression, "rows") 2398 rows = self.seg(rows) if rows else "" 2399 after = self.sql(expression, "after") 2400 after = self.seg(after) if after else "" 2401 pattern = self.sql(expression, "pattern") 2402 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2403 definition_sqls = [ 2404 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2405 for definition in expression.args.get("define", []) 2406 ] 2407 definitions = self.expressions(sqls=definition_sqls) 2408 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2409 body = "".join( 2410 ( 2411 partition, 2412 order, 2413 measures, 2414 rows, 2415 after, 2416 pattern, 2417 define, 2418 ) 2419 ) 2420 alias = self.sql(expression, "alias") 2421 alias = f" {alias}" if alias else "" 2422 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2423 2424 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2425 limit = expression.args.get("limit") 2426 2427 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2428 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2429 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2430 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2431 2432 return csv( 2433 *sqls, 2434 *[self.sql(join) for join in expression.args.get("joins") or []], 2435 self.sql(expression, "connect"), 2436 self.sql(expression, "match"), 2437 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2438 self.sql(expression, "prewhere"), 2439 self.sql(expression, "where"), 2440 self.sql(expression, "group"), 2441 self.sql(expression, "having"), 2442 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2443 self.sql(expression, "order"), 2444 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2445 *self.after_limit_modifiers(expression), 2446 self.options_modifier(expression), 2447 sep="", 2448 ) 2449 2450 def options_modifier(self, expression: exp.Expression) -> str: 2451 options = self.expressions(expression, key="options") 2452 return f" {options}" if options else "" 2453 2454 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2455 return "" 2456 2457 def offset_limit_modifiers( 2458 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2459 ) -> t.List[str]: 2460 return [ 2461 self.sql(expression, "offset") if fetch else self.sql(limit), 2462 self.sql(limit) if fetch else self.sql(expression, "offset"), 2463 ] 2464 2465 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2466 locks = self.expressions(expression, key="locks", sep=" ") 2467 locks = f" {locks}" if locks else "" 2468 return [locks, self.sql(expression, "sample")] 2469 2470 def select_sql(self, expression: exp.Select) -> str: 2471 into = expression.args.get("into") 2472 if not self.SUPPORTS_SELECT_INTO and into: 2473 into.pop() 2474 2475 hint = self.sql(expression, "hint") 2476 distinct = self.sql(expression, "distinct") 2477 distinct = f" {distinct}" if distinct else "" 2478 kind = self.sql(expression, "kind") 2479 2480 limit = expression.args.get("limit") 2481 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2482 top = self.limit_sql(limit, top=True) 2483 limit.pop() 2484 else: 2485 top = "" 2486 2487 expressions = self.expressions(expression) 2488 2489 if kind: 2490 if kind in self.SELECT_KINDS: 2491 kind = f" AS {kind}" 2492 else: 2493 if kind == "STRUCT": 2494 expressions = self.expressions( 2495 sqls=[ 2496 self.sql( 2497 exp.Struct( 2498 expressions=[ 2499 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2500 if isinstance(e, exp.Alias) 2501 else e 2502 for e in expression.expressions 2503 ] 2504 ) 2505 ) 2506 ] 2507 ) 2508 kind = "" 2509 2510 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2511 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2512 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2513 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2514 sql = self.query_modifiers( 2515 expression, 2516 f"SELECT{top_distinct}{kind}{expressions}", 2517 self.sql(expression, "into", comment=False), 2518 self.sql(expression, "from", comment=False), 2519 ) 2520 2521 sql = self.prepend_ctes(expression, sql) 2522 2523 if not self.SUPPORTS_SELECT_INTO and into: 2524 if into.args.get("temporary"): 2525 table_kind = " TEMPORARY" 2526 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2527 table_kind = " UNLOGGED" 2528 else: 2529 table_kind = "" 2530 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2531 2532 return sql 2533 2534 def schema_sql(self, expression: exp.Schema) -> str: 2535 this = self.sql(expression, "this") 2536 sql = self.schema_columns_sql(expression) 2537 return f"{this} {sql}" if this and sql else this or sql 2538 2539 def schema_columns_sql(self, expression: exp.Schema) -> str: 2540 if expression.expressions: 2541 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2542 return "" 2543 2544 def star_sql(self, expression: exp.Star) -> str: 2545 except_ = self.expressions(expression, key="except", flat=True) 2546 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2547 replace = self.expressions(expression, key="replace", flat=True) 2548 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2549 rename = self.expressions(expression, key="rename", flat=True) 2550 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2551 return f"*{except_}{replace}{rename}" 2552 2553 def parameter_sql(self, expression: exp.Parameter) -> str: 2554 this = self.sql(expression, "this") 2555 return f"{self.PARAMETER_TOKEN}{this}" 2556 2557 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2558 this = self.sql(expression, "this") 2559 kind = expression.text("kind") 2560 if kind: 2561 kind = f"{kind}." 2562 return f"@@{kind}{this}" 2563 2564 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2565 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2566 2567 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2568 alias = self.sql(expression, "alias") 2569 alias = f"{sep}{alias}" if alias else "" 2570 sample = self.sql(expression, "sample") 2571 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2572 alias = f"{sample}{alias}" 2573 2574 # Set to None so it's not generated again by self.query_modifiers() 2575 expression.set("sample", None) 2576 2577 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2578 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2579 return self.prepend_ctes(expression, sql) 2580 2581 def qualify_sql(self, expression: exp.Qualify) -> str: 2582 this = self.indent(self.sql(expression, "this")) 2583 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2584 2585 def unnest_sql(self, expression: exp.Unnest) -> str: 2586 args = self.expressions(expression, flat=True) 2587 2588 alias = expression.args.get("alias") 2589 offset = expression.args.get("offset") 2590 2591 if self.UNNEST_WITH_ORDINALITY: 2592 if alias and isinstance(offset, exp.Expression): 2593 alias.append("columns", offset) 2594 2595 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2596 columns = alias.columns 2597 alias = self.sql(columns[0]) if columns else "" 2598 else: 2599 alias = self.sql(alias) 2600 2601 alias = f" AS {alias}" if alias else alias 2602 if self.UNNEST_WITH_ORDINALITY: 2603 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2604 else: 2605 if isinstance(offset, exp.Expression): 2606 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2607 elif offset: 2608 suffix = f"{alias} WITH OFFSET" 2609 else: 2610 suffix = alias 2611 2612 return f"UNNEST({args}){suffix}" 2613 2614 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2615 return "" 2616 2617 def where_sql(self, expression: exp.Where) -> str: 2618 this = self.indent(self.sql(expression, "this")) 2619 return f"{self.seg('WHERE')}{self.sep()}{this}" 2620 2621 def window_sql(self, expression: exp.Window) -> str: 2622 this = self.sql(expression, "this") 2623 partition = self.partition_by_sql(expression) 2624 order = expression.args.get("order") 2625 order = self.order_sql(order, flat=True) if order else "" 2626 spec = self.sql(expression, "spec") 2627 alias = self.sql(expression, "alias") 2628 over = self.sql(expression, "over") or "OVER" 2629 2630 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2631 2632 first = expression.args.get("first") 2633 if first is None: 2634 first = "" 2635 else: 2636 first = "FIRST" if first else "LAST" 2637 2638 if not partition and not order and not spec and alias: 2639 return f"{this} {alias}" 2640 2641 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 2642 return f"{this} ({args})" 2643 2644 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2645 partition = self.expressions(expression, key="partition_by", flat=True) 2646 return f"PARTITION BY {partition}" if partition else "" 2647 2648 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2649 kind = self.sql(expression, "kind") 2650 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2651 end = ( 2652 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2653 or "CURRENT ROW" 2654 ) 2655 return f"{kind} BETWEEN {start} AND {end}" 2656 2657 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2658 this = self.sql(expression, "this") 2659 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2660 return f"{this} WITHIN GROUP ({expression_sql})" 2661 2662 def between_sql(self, expression: exp.Between) -> str: 2663 this = self.sql(expression, "this") 2664 low = self.sql(expression, "low") 2665 high = self.sql(expression, "high") 2666 return f"{this} BETWEEN {low} AND {high}" 2667 2668 def bracket_offset_expressions( 2669 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2670 ) -> t.List[exp.Expression]: 2671 return apply_index_offset( 2672 expression.this, 2673 expression.expressions, 2674 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2675 ) 2676 2677 def bracket_sql(self, expression: exp.Bracket) -> str: 2678 expressions = self.bracket_offset_expressions(expression) 2679 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2680 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2681 2682 def all_sql(self, expression: exp.All) -> str: 2683 return f"ALL {self.wrap(expression)}" 2684 2685 def any_sql(self, expression: exp.Any) -> str: 2686 this = self.sql(expression, "this") 2687 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2688 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2689 this = self.wrap(this) 2690 return f"ANY{this}" 2691 return f"ANY {this}" 2692 2693 def exists_sql(self, expression: exp.Exists) -> str: 2694 return f"EXISTS{self.wrap(expression)}" 2695 2696 def case_sql(self, expression: exp.Case) -> str: 2697 this = self.sql(expression, "this") 2698 statements = [f"CASE {this}" if this else "CASE"] 2699 2700 for e in expression.args["ifs"]: 2701 statements.append(f"WHEN {self.sql(e, 'this')}") 2702 statements.append(f"THEN {self.sql(e, 'true')}") 2703 2704 default = self.sql(expression, "default") 2705 2706 if default: 2707 statements.append(f"ELSE {default}") 2708 2709 statements.append("END") 2710 2711 if self.pretty and self.too_wide(statements): 2712 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2713 2714 return " ".join(statements) 2715 2716 def constraint_sql(self, expression: exp.Constraint) -> str: 2717 this = self.sql(expression, "this") 2718 expressions = self.expressions(expression, flat=True) 2719 return f"CONSTRAINT {this} {expressions}" 2720 2721 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2722 order = expression.args.get("order") 2723 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2724 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2725 2726 def extract_sql(self, expression: exp.Extract) -> str: 2727 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name 2728 expression_sql = self.sql(expression, "expression") 2729 return f"EXTRACT({this} FROM {expression_sql})" 2730 2731 def trim_sql(self, expression: exp.Trim) -> str: 2732 trim_type = self.sql(expression, "position") 2733 2734 if trim_type == "LEADING": 2735 func_name = "LTRIM" 2736 elif trim_type == "TRAILING": 2737 func_name = "RTRIM" 2738 else: 2739 func_name = "TRIM" 2740 2741 return self.func(func_name, expression.this, expression.expression) 2742 2743 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2744 args = expression.expressions 2745 if isinstance(expression, exp.ConcatWs): 2746 args = args[1:] # Skip the delimiter 2747 2748 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2749 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2750 2751 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2752 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2753 2754 return args 2755 2756 def concat_sql(self, expression: exp.Concat) -> str: 2757 expressions = self.convert_concat_args(expression) 2758 2759 # Some dialects don't allow a single-argument CONCAT call 2760 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2761 return self.sql(expressions[0]) 2762 2763 return self.func("CONCAT", *expressions) 2764 2765 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2766 return self.func( 2767 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2768 ) 2769 2770 def check_sql(self, expression: exp.Check) -> str: 2771 this = self.sql(expression, key="this") 2772 return f"CHECK ({this})" 2773 2774 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2775 expressions = self.expressions(expression, flat=True) 2776 reference = self.sql(expression, "reference") 2777 reference = f" {reference}" if reference else "" 2778 delete = self.sql(expression, "delete") 2779 delete = f" ON DELETE {delete}" if delete else "" 2780 update = self.sql(expression, "update") 2781 update = f" ON UPDATE {update}" if update else "" 2782 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}" 2783 2784 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2785 expressions = self.expressions(expression, flat=True) 2786 options = self.expressions(expression, key="options", flat=True, sep=" ") 2787 options = f" {options}" if options else "" 2788 return f"PRIMARY KEY ({expressions}){options}" 2789 2790 def if_sql(self, expression: exp.If) -> str: 2791 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 2792 2793 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 2794 modifier = expression.args.get("modifier") 2795 modifier = f" {modifier}" if modifier else "" 2796 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 2797 2798 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 2799 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 2800 2801 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2802 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2803 2804 if expression.args.get("escape"): 2805 path = self.escape_str(path) 2806 2807 if self.QUOTE_JSON_PATH: 2808 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2809 2810 return path 2811 2812 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2813 if isinstance(expression, exp.JSONPathPart): 2814 transform = self.TRANSFORMS.get(expression.__class__) 2815 if not callable(transform): 2816 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2817 return "" 2818 2819 return transform(self, expression) 2820 2821 if isinstance(expression, int): 2822 return str(expression) 2823 2824 if self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 2825 escaped = expression.replace("'", "\\'") 2826 escaped = f"\\'{expression}\\'" 2827 else: 2828 escaped = expression.replace('"', '\\"') 2829 escaped = f'"{escaped}"' 2830 2831 return escaped 2832 2833 def formatjson_sql(self, expression: exp.FormatJson) -> str: 2834 return f"{self.sql(expression, 'this')} FORMAT JSON" 2835 2836 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 2837 null_handling = expression.args.get("null_handling") 2838 null_handling = f" {null_handling}" if null_handling else "" 2839 2840 unique_keys = expression.args.get("unique_keys") 2841 if unique_keys is not None: 2842 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2843 else: 2844 unique_keys = "" 2845 2846 return_type = self.sql(expression, "return_type") 2847 return_type = f" RETURNING {return_type}" if return_type else "" 2848 encoding = self.sql(expression, "encoding") 2849 encoding = f" ENCODING {encoding}" if encoding else "" 2850 2851 return self.func( 2852 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 2853 *expression.expressions, 2854 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2855 ) 2856 2857 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 2858 return self.jsonobject_sql(expression) 2859 2860 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2861 null_handling = expression.args.get("null_handling") 2862 null_handling = f" {null_handling}" if null_handling else "" 2863 return_type = self.sql(expression, "return_type") 2864 return_type = f" RETURNING {return_type}" if return_type else "" 2865 strict = " STRICT" if expression.args.get("strict") else "" 2866 return self.func( 2867 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 2868 ) 2869 2870 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 2871 this = self.sql(expression, "this") 2872 order = self.sql(expression, "order") 2873 null_handling = expression.args.get("null_handling") 2874 null_handling = f" {null_handling}" if null_handling else "" 2875 return_type = self.sql(expression, "return_type") 2876 return_type = f" RETURNING {return_type}" if return_type else "" 2877 strict = " STRICT" if expression.args.get("strict") else "" 2878 return self.func( 2879 "JSON_ARRAYAGG", 2880 this, 2881 suffix=f"{order}{null_handling}{return_type}{strict})", 2882 ) 2883 2884 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 2885 path = self.sql(expression, "path") 2886 path = f" PATH {path}" if path else "" 2887 nested_schema = self.sql(expression, "nested_schema") 2888 2889 if nested_schema: 2890 return f"NESTED{path} {nested_schema}" 2891 2892 this = self.sql(expression, "this") 2893 kind = self.sql(expression, "kind") 2894 kind = f" {kind}" if kind else "" 2895 return f"{this}{kind}{path}" 2896 2897 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 2898 return self.func("COLUMNS", *expression.expressions) 2899 2900 def jsontable_sql(self, expression: exp.JSONTable) -> str: 2901 this = self.sql(expression, "this") 2902 path = self.sql(expression, "path") 2903 path = f", {path}" if path else "" 2904 error_handling = expression.args.get("error_handling") 2905 error_handling = f" {error_handling}" if error_handling else "" 2906 empty_handling = expression.args.get("empty_handling") 2907 empty_handling = f" {empty_handling}" if empty_handling else "" 2908 schema = self.sql(expression, "schema") 2909 return self.func( 2910 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 2911 ) 2912 2913 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 2914 this = self.sql(expression, "this") 2915 kind = self.sql(expression, "kind") 2916 path = self.sql(expression, "path") 2917 path = f" {path}" if path else "" 2918 as_json = " AS JSON" if expression.args.get("as_json") else "" 2919 return f"{this} {kind}{path}{as_json}" 2920 2921 def openjson_sql(self, expression: exp.OpenJSON) -> str: 2922 this = self.sql(expression, "this") 2923 path = self.sql(expression, "path") 2924 path = f", {path}" if path else "" 2925 expressions = self.expressions(expression) 2926 with_ = ( 2927 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 2928 if expressions 2929 else "" 2930 ) 2931 return f"OPENJSON({this}{path}){with_}" 2932 2933 def in_sql(self, expression: exp.In) -> str: 2934 query = expression.args.get("query") 2935 unnest = expression.args.get("unnest") 2936 field = expression.args.get("field") 2937 is_global = " GLOBAL" if expression.args.get("is_global") else "" 2938 2939 if query: 2940 in_sql = self.sql(query) 2941 elif unnest: 2942 in_sql = self.in_unnest_op(unnest) 2943 elif field: 2944 in_sql = self.sql(field) 2945 else: 2946 in_sql = f"({self.expressions(expression, flat=True)})" 2947 2948 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 2949 2950 def in_unnest_op(self, unnest: exp.Unnest) -> str: 2951 return f"(SELECT {self.sql(unnest)})" 2952 2953 def interval_sql(self, expression: exp.Interval) -> str: 2954 unit = self.sql(expression, "unit") 2955 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 2956 unit = self.TIME_PART_SINGULARS.get(unit, unit) 2957 unit = f" {unit}" if unit else "" 2958 2959 if self.SINGLE_STRING_INTERVAL: 2960 this = expression.this.name if expression.this else "" 2961 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 2962 2963 this = self.sql(expression, "this") 2964 if this: 2965 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 2966 this = f" {this}" if unwrapped else f" ({this})" 2967 2968 return f"INTERVAL{this}{unit}" 2969 2970 def return_sql(self, expression: exp.Return) -> str: 2971 return f"RETURN {self.sql(expression, 'this')}" 2972 2973 def reference_sql(self, expression: exp.Reference) -> str: 2974 this = self.sql(expression, "this") 2975 expressions = self.expressions(expression, flat=True) 2976 expressions = f"({expressions})" if expressions else "" 2977 options = self.expressions(expression, key="options", flat=True, sep=" ") 2978 options = f" {options}" if options else "" 2979 return f"REFERENCES {this}{expressions}{options}" 2980 2981 def anonymous_sql(self, expression: exp.Anonymous) -> str: 2982 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 2983 parent = expression.parent 2984 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 2985 return self.func( 2986 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 2987 ) 2988 2989 def paren_sql(self, expression: exp.Paren) -> str: 2990 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 2991 return f"({sql}{self.seg(')', sep='')}" 2992 2993 def neg_sql(self, expression: exp.Neg) -> str: 2994 # This makes sure we don't convert "- - 5" to "--5", which is a comment 2995 this_sql = self.sql(expression, "this") 2996 sep = " " if this_sql[0] == "-" else "" 2997 return f"-{sep}{this_sql}" 2998 2999 def not_sql(self, expression: exp.Not) -> str: 3000 return f"NOT {self.sql(expression, 'this')}" 3001 3002 def alias_sql(self, expression: exp.Alias) -> str: 3003 alias = self.sql(expression, "alias") 3004 alias = f" AS {alias}" if alias else "" 3005 return f"{self.sql(expression, 'this')}{alias}" 3006 3007 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3008 alias = expression.args["alias"] 3009 3010 identifier_alias = isinstance(alias, exp.Identifier) 3011 literal_alias = isinstance(alias, exp.Literal) 3012 3013 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3014 alias.replace(exp.Literal.string(alias.output_name)) 3015 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3016 alias.replace(exp.to_identifier(alias.output_name)) 3017 3018 return self.alias_sql(expression) 3019 3020 def aliases_sql(self, expression: exp.Aliases) -> str: 3021 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3022 3023 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3024 this = self.sql(expression, "this") 3025 index = self.sql(expression, "expression") 3026 return f"{this} AT {index}" 3027 3028 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3029 this = self.sql(expression, "this") 3030 zone = self.sql(expression, "zone") 3031 return f"{this} AT TIME ZONE {zone}" 3032 3033 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3034 this = self.sql(expression, "this") 3035 zone = self.sql(expression, "zone") 3036 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3037 3038 def add_sql(self, expression: exp.Add) -> str: 3039 return self.binary(expression, "+") 3040 3041 def and_sql( 3042 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3043 ) -> str: 3044 return self.connector_sql(expression, "AND", stack) 3045 3046 def or_sql( 3047 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3048 ) -> str: 3049 return self.connector_sql(expression, "OR", stack) 3050 3051 def xor_sql( 3052 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3053 ) -> str: 3054 return self.connector_sql(expression, "XOR", stack) 3055 3056 def connector_sql( 3057 self, 3058 expression: exp.Connector, 3059 op: str, 3060 stack: t.Optional[t.List[str | exp.Expression]] = None, 3061 ) -> str: 3062 if stack is not None: 3063 if expression.expressions: 3064 stack.append(self.expressions(expression, sep=f" {op} ")) 3065 else: 3066 stack.append(expression.right) 3067 if expression.comments and self.comments: 3068 for comment in expression.comments: 3069 if comment: 3070 op += f" /*{self.pad_comment(comment)}*/" 3071 stack.extend((op, expression.left)) 3072 return op 3073 3074 stack = [expression] 3075 sqls: t.List[str] = [] 3076 ops = set() 3077 3078 while stack: 3079 node = stack.pop() 3080 if isinstance(node, exp.Connector): 3081 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3082 else: 3083 sql = self.sql(node) 3084 if sqls and sqls[-1] in ops: 3085 sqls[-1] += f" {sql}" 3086 else: 3087 sqls.append(sql) 3088 3089 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3090 return sep.join(sqls) 3091 3092 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3093 return self.binary(expression, "&") 3094 3095 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3096 return self.binary(expression, "<<") 3097 3098 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3099 return f"~{self.sql(expression, 'this')}" 3100 3101 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3102 return self.binary(expression, "|") 3103 3104 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3105 return self.binary(expression, ">>") 3106 3107 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3108 return self.binary(expression, "^") 3109 3110 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3111 format_sql = self.sql(expression, "format") 3112 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3113 to_sql = self.sql(expression, "to") 3114 to_sql = f" {to_sql}" if to_sql else "" 3115 action = self.sql(expression, "action") 3116 action = f" {action}" if action else "" 3117 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{format_sql}{action})" 3118 3119 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3120 zone = self.sql(expression, "this") 3121 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3122 3123 def collate_sql(self, expression: exp.Collate) -> str: 3124 if self.COLLATE_IS_FUNC: 3125 return self.function_fallback_sql(expression) 3126 return self.binary(expression, "COLLATE") 3127 3128 def command_sql(self, expression: exp.Command) -> str: 3129 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3130 3131 def comment_sql(self, expression: exp.Comment) -> str: 3132 this = self.sql(expression, "this") 3133 kind = expression.args["kind"] 3134 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3135 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3136 expression_sql = self.sql(expression, "expression") 3137 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3138 3139 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3140 this = self.sql(expression, "this") 3141 delete = " DELETE" if expression.args.get("delete") else "" 3142 recompress = self.sql(expression, "recompress") 3143 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3144 to_disk = self.sql(expression, "to_disk") 3145 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3146 to_volume = self.sql(expression, "to_volume") 3147 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3148 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3149 3150 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3151 where = self.sql(expression, "where") 3152 group = self.sql(expression, "group") 3153 aggregates = self.expressions(expression, key="aggregates") 3154 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3155 3156 if not (where or group or aggregates) and len(expression.expressions) == 1: 3157 return f"TTL {self.expressions(expression, flat=True)}" 3158 3159 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3160 3161 def transaction_sql(self, expression: exp.Transaction) -> str: 3162 return "BEGIN" 3163 3164 def commit_sql(self, expression: exp.Commit) -> str: 3165 chain = expression.args.get("chain") 3166 if chain is not None: 3167 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3168 3169 return f"COMMIT{chain or ''}" 3170 3171 def rollback_sql(self, expression: exp.Rollback) -> str: 3172 savepoint = expression.args.get("savepoint") 3173 savepoint = f" TO {savepoint}" if savepoint else "" 3174 return f"ROLLBACK{savepoint}" 3175 3176 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3177 this = self.sql(expression, "this") 3178 3179 dtype = self.sql(expression, "dtype") 3180 if dtype: 3181 collate = self.sql(expression, "collate") 3182 collate = f" COLLATE {collate}" if collate else "" 3183 using = self.sql(expression, "using") 3184 using = f" USING {using}" if using else "" 3185 return f"ALTER COLUMN {this} SET DATA TYPE {dtype}{collate}{using}" 3186 3187 default = self.sql(expression, "default") 3188 if default: 3189 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3190 3191 comment = self.sql(expression, "comment") 3192 if comment: 3193 return f"ALTER COLUMN {this} COMMENT {comment}" 3194 3195 allow_null = expression.args.get("allow_null") 3196 drop = expression.args.get("drop") 3197 3198 if not drop and not allow_null: 3199 self.unsupported("Unsupported ALTER COLUMN syntax") 3200 3201 if allow_null is not None: 3202 keyword = "DROP" if drop else "SET" 3203 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3204 3205 return f"ALTER COLUMN {this} DROP DEFAULT" 3206 3207 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3208 this = self.sql(expression, "this") 3209 if not isinstance(expression.this, exp.Var): 3210 this = f"KEY DISTKEY {this}" 3211 return f"ALTER DISTSTYLE {this}" 3212 3213 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3214 compound = " COMPOUND" if expression.args.get("compound") else "" 3215 this = self.sql(expression, "this") 3216 expressions = self.expressions(expression, flat=True) 3217 expressions = f"({expressions})" if expressions else "" 3218 return f"ALTER{compound} SORTKEY {this or expressions}" 3219 3220 def renametable_sql(self, expression: exp.RenameTable) -> str: 3221 if not self.RENAME_TABLE_WITH_DB: 3222 # Remove db from tables 3223 expression = expression.transform( 3224 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3225 ).assert_is(exp.RenameTable) 3226 this = self.sql(expression, "this") 3227 return f"RENAME TO {this}" 3228 3229 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3230 exists = " IF EXISTS" if expression.args.get("exists") else "" 3231 old_column = self.sql(expression, "this") 3232 new_column = self.sql(expression, "to") 3233 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3234 3235 def alterset_sql(self, expression: exp.AlterSet) -> str: 3236 exprs = self.expressions(expression, flat=True) 3237 return f"SET {exprs}" 3238 3239 def alter_sql(self, expression: exp.Alter) -> str: 3240 actions = expression.args["actions"] 3241 3242 if isinstance(actions[0], exp.ColumnDef): 3243 actions = self.add_column_sql(expression) 3244 elif isinstance(actions[0], exp.Schema): 3245 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3246 elif isinstance(actions[0], exp.Delete): 3247 actions = self.expressions(expression, key="actions", flat=True) 3248 elif isinstance(actions[0], exp.Query): 3249 actions = "AS " + self.expressions(expression, key="actions") 3250 else: 3251 actions = self.expressions(expression, key="actions", flat=True) 3252 3253 exists = " IF EXISTS" if expression.args.get("exists") else "" 3254 on_cluster = self.sql(expression, "cluster") 3255 on_cluster = f" {on_cluster}" if on_cluster else "" 3256 only = " ONLY" if expression.args.get("only") else "" 3257 options = self.expressions(expression, key="options") 3258 options = f", {options}" if options else "" 3259 kind = self.sql(expression, "kind") 3260 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3261 3262 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{not_valid}{options}" 3263 3264 def add_column_sql(self, expression: exp.Alter) -> str: 3265 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3266 return self.expressions( 3267 expression, 3268 key="actions", 3269 prefix="ADD COLUMN ", 3270 skip_first=True, 3271 ) 3272 return f"ADD {self.expressions(expression, key='actions', flat=True)}" 3273 3274 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3275 expressions = self.expressions(expression) 3276 exists = " IF EXISTS " if expression.args.get("exists") else " " 3277 return f"DROP{exists}{expressions}" 3278 3279 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3280 return f"ADD {self.expressions(expression)}" 3281 3282 def distinct_sql(self, expression: exp.Distinct) -> str: 3283 this = self.expressions(expression, flat=True) 3284 3285 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3286 case = exp.case() 3287 for arg in expression.expressions: 3288 case = case.when(arg.is_(exp.null()), exp.null()) 3289 this = self.sql(case.else_(f"({this})")) 3290 3291 this = f" {this}" if this else "" 3292 3293 on = self.sql(expression, "on") 3294 on = f" ON {on}" if on else "" 3295 return f"DISTINCT{this}{on}" 3296 3297 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3298 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3299 3300 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3301 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3302 3303 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3304 this_sql = self.sql(expression, "this") 3305 expression_sql = self.sql(expression, "expression") 3306 kind = "MAX" if expression.args.get("max") else "MIN" 3307 return f"{this_sql} HAVING {kind} {expression_sql}" 3308 3309 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3310 return self.sql( 3311 exp.Cast( 3312 this=exp.Div(this=expression.this, expression=expression.expression), 3313 to=exp.DataType(this=exp.DataType.Type.INT), 3314 ) 3315 ) 3316 3317 def dpipe_sql(self, expression: exp.DPipe) -> str: 3318 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3319 return self.func( 3320 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3321 ) 3322 return self.binary(expression, "||") 3323 3324 def div_sql(self, expression: exp.Div) -> str: 3325 l, r = expression.left, expression.right 3326 3327 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3328 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3329 3330 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3331 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3332 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3333 3334 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3335 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3336 return self.sql( 3337 exp.cast( 3338 l / r, 3339 to=exp.DataType.Type.BIGINT, 3340 ) 3341 ) 3342 3343 return self.binary(expression, "/") 3344 3345 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3346 return self.binary(expression, "OVERLAPS") 3347 3348 def distance_sql(self, expression: exp.Distance) -> str: 3349 return self.binary(expression, "<->") 3350 3351 def dot_sql(self, expression: exp.Dot) -> str: 3352 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3353 3354 def eq_sql(self, expression: exp.EQ) -> str: 3355 return self.binary(expression, "=") 3356 3357 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3358 return self.binary(expression, ":=") 3359 3360 def escape_sql(self, expression: exp.Escape) -> str: 3361 return self.binary(expression, "ESCAPE") 3362 3363 def glob_sql(self, expression: exp.Glob) -> str: 3364 return self.binary(expression, "GLOB") 3365 3366 def gt_sql(self, expression: exp.GT) -> str: 3367 return self.binary(expression, ">") 3368 3369 def gte_sql(self, expression: exp.GTE) -> str: 3370 return self.binary(expression, ">=") 3371 3372 def ilike_sql(self, expression: exp.ILike) -> str: 3373 return self.binary(expression, "ILIKE") 3374 3375 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3376 return self.binary(expression, "ILIKE ANY") 3377 3378 def is_sql(self, expression: exp.Is) -> str: 3379 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3380 return self.sql( 3381 expression.this if expression.expression.this else exp.not_(expression.this) 3382 ) 3383 return self.binary(expression, "IS") 3384 3385 def like_sql(self, expression: exp.Like) -> str: 3386 return self.binary(expression, "LIKE") 3387 3388 def likeany_sql(self, expression: exp.LikeAny) -> str: 3389 return self.binary(expression, "LIKE ANY") 3390 3391 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3392 return self.binary(expression, "SIMILAR TO") 3393 3394 def lt_sql(self, expression: exp.LT) -> str: 3395 return self.binary(expression, "<") 3396 3397 def lte_sql(self, expression: exp.LTE) -> str: 3398 return self.binary(expression, "<=") 3399 3400 def mod_sql(self, expression: exp.Mod) -> str: 3401 return self.binary(expression, "%") 3402 3403 def mul_sql(self, expression: exp.Mul) -> str: 3404 return self.binary(expression, "*") 3405 3406 def neq_sql(self, expression: exp.NEQ) -> str: 3407 return self.binary(expression, "<>") 3408 3409 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3410 return self.binary(expression, "IS NOT DISTINCT FROM") 3411 3412 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3413 return self.binary(expression, "IS DISTINCT FROM") 3414 3415 def slice_sql(self, expression: exp.Slice) -> str: 3416 return self.binary(expression, ":") 3417 3418 def sub_sql(self, expression: exp.Sub) -> str: 3419 return self.binary(expression, "-") 3420 3421 def trycast_sql(self, expression: exp.TryCast) -> str: 3422 return self.cast_sql(expression, safe_prefix="TRY_") 3423 3424 def try_sql(self, expression: exp.Try) -> str: 3425 if not self.TRY_SUPPORTED: 3426 self.unsupported("Unsupported TRY function") 3427 return self.sql(expression, "this") 3428 3429 return self.func("TRY", expression.this) 3430 3431 def log_sql(self, expression: exp.Log) -> str: 3432 this = expression.this 3433 expr = expression.expression 3434 3435 if self.dialect.LOG_BASE_FIRST is False: 3436 this, expr = expr, this 3437 elif self.dialect.LOG_BASE_FIRST is None and expr: 3438 if this.name in ("2", "10"): 3439 return self.func(f"LOG{this.name}", expr) 3440 3441 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3442 3443 return self.func("LOG", this, expr) 3444 3445 def use_sql(self, expression: exp.Use) -> str: 3446 kind = self.sql(expression, "kind") 3447 kind = f" {kind}" if kind else "" 3448 this = self.sql(expression, "this") 3449 this = f" {this}" if this else "" 3450 return f"USE{kind}{this}" 3451 3452 def binary(self, expression: exp.Binary, op: str) -> str: 3453 sqls: t.List[str] = [] 3454 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3455 binary_type = type(expression) 3456 3457 while stack: 3458 node = stack.pop() 3459 3460 if type(node) is binary_type: 3461 op_func = node.args.get("operator") 3462 if op_func: 3463 op = f"OPERATOR({self.sql(op_func)})" 3464 3465 stack.append(node.right) 3466 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3467 stack.append(node.left) 3468 else: 3469 sqls.append(self.sql(node)) 3470 3471 return "".join(sqls) 3472 3473 def function_fallback_sql(self, expression: exp.Func) -> str: 3474 args = [] 3475 3476 for key in expression.arg_types: 3477 arg_value = expression.args.get(key) 3478 3479 if isinstance(arg_value, list): 3480 for value in arg_value: 3481 args.append(value) 3482 elif arg_value is not None: 3483 args.append(arg_value) 3484 3485 if self.normalize_functions: 3486 name = expression.sql_name() 3487 else: 3488 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3489 3490 return self.func(name, *args) 3491 3492 def func( 3493 self, 3494 name: str, 3495 *args: t.Optional[exp.Expression | str], 3496 prefix: str = "(", 3497 suffix: str = ")", 3498 normalize: bool = True, 3499 ) -> str: 3500 name = self.normalize_func(name) if normalize else name 3501 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3502 3503 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str: 3504 arg_sqls = tuple( 3505 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3506 ) 3507 if self.pretty and self.too_wide(arg_sqls): 3508 return self.indent("\n" + ",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True) 3509 return ", ".join(arg_sqls) 3510 3511 def too_wide(self, args: t.Iterable) -> bool: 3512 return sum(len(arg) for arg in args) > self.max_text_width 3513 3514 def format_time( 3515 self, 3516 expression: exp.Expression, 3517 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3518 inverse_time_trie: t.Optional[t.Dict] = None, 3519 ) -> t.Optional[str]: 3520 return format_time( 3521 self.sql(expression, "format"), 3522 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3523 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3524 ) 3525 3526 def expressions( 3527 self, 3528 expression: t.Optional[exp.Expression] = None, 3529 key: t.Optional[str] = None, 3530 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3531 flat: bool = False, 3532 indent: bool = True, 3533 skip_first: bool = False, 3534 skip_last: bool = False, 3535 sep: str = ", ", 3536 prefix: str = "", 3537 dynamic: bool = False, 3538 new_line: bool = False, 3539 ) -> str: 3540 expressions = expression.args.get(key or "expressions") if expression else sqls 3541 3542 if not expressions: 3543 return "" 3544 3545 if flat: 3546 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3547 3548 num_sqls = len(expressions) 3549 result_sqls = [] 3550 3551 for i, e in enumerate(expressions): 3552 sql = self.sql(e, comment=False) 3553 if not sql: 3554 continue 3555 3556 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3557 3558 if self.pretty: 3559 if self.leading_comma: 3560 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3561 else: 3562 result_sqls.append( 3563 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3564 ) 3565 else: 3566 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3567 3568 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3569 if new_line: 3570 result_sqls.insert(0, "") 3571 result_sqls.append("") 3572 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3573 else: 3574 result_sql = "".join(result_sqls) 3575 3576 return ( 3577 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3578 if indent 3579 else result_sql 3580 ) 3581 3582 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3583 flat = flat or isinstance(expression.parent, exp.Properties) 3584 expressions_sql = self.expressions(expression, flat=flat) 3585 if flat: 3586 return f"{op} {expressions_sql}" 3587 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3588 3589 def naked_property(self, expression: exp.Property) -> str: 3590 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3591 if not property_name: 3592 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3593 return f"{property_name} {self.sql(expression, 'this')}" 3594 3595 def tag_sql(self, expression: exp.Tag) -> str: 3596 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3597 3598 def token_sql(self, token_type: TokenType) -> str: 3599 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3600 3601 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3602 this = self.sql(expression, "this") 3603 expressions = self.no_identify(self.expressions, expression) 3604 expressions = ( 3605 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3606 ) 3607 return f"{this}{expressions}" 3608 3609 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3610 this = self.sql(expression, "this") 3611 expressions = self.expressions(expression, flat=True) 3612 return f"{this}({expressions})" 3613 3614 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3615 return self.binary(expression, "=>") 3616 3617 def when_sql(self, expression: exp.When) -> str: 3618 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3619 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3620 condition = self.sql(expression, "condition") 3621 condition = f" AND {condition}" if condition else "" 3622 3623 then_expression = expression.args.get("then") 3624 if isinstance(then_expression, exp.Insert): 3625 this = self.sql(then_expression, "this") 3626 this = f"INSERT {this}" if this else "INSERT" 3627 then = self.sql(then_expression, "expression") 3628 then = f"{this} VALUES {then}" if then else this 3629 elif isinstance(then_expression, exp.Update): 3630 if isinstance(then_expression.args.get("expressions"), exp.Star): 3631 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3632 else: 3633 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 3634 else: 3635 then = self.sql(then_expression) 3636 return f"WHEN {matched}{source}{condition} THEN {then}" 3637 3638 def merge_sql(self, expression: exp.Merge) -> str: 3639 table = expression.this 3640 table_alias = "" 3641 3642 hints = table.args.get("hints") 3643 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3644 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3645 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3646 3647 this = self.sql(table) 3648 using = f"USING {self.sql(expression, 'using')}" 3649 on = f"ON {self.sql(expression, 'on')}" 3650 expressions = self.expressions(expression, sep=" ", indent=False) 3651 returning = self.sql(expression, "returning") 3652 if returning: 3653 expressions = f"{expressions}{returning}" 3654 3655 sep = self.sep() 3656 3657 return self.prepend_ctes( 3658 expression, 3659 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{expressions}", 3660 ) 3661 3662 @unsupported_args("format") 3663 def tochar_sql(self, expression: exp.ToChar) -> str: 3664 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3665 3666 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3667 if not self.SUPPORTS_TO_NUMBER: 3668 self.unsupported("Unsupported TO_NUMBER function") 3669 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3670 3671 fmt = expression.args.get("format") 3672 if not fmt: 3673 self.unsupported("Conversion format is required for TO_NUMBER") 3674 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3675 3676 return self.func("TO_NUMBER", expression.this, fmt) 3677 3678 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3679 this = self.sql(expression, "this") 3680 kind = self.sql(expression, "kind") 3681 settings_sql = self.expressions(expression, key="settings", sep=" ") 3682 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3683 return f"{this}({kind}{args})" 3684 3685 def dictrange_sql(self, expression: exp.DictRange) -> str: 3686 this = self.sql(expression, "this") 3687 max = self.sql(expression, "max") 3688 min = self.sql(expression, "min") 3689 return f"{this}(MIN {min} MAX {max})" 3690 3691 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3692 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3693 3694 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3695 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3696 3697 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3698 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3699 expressions = self.expressions(expression, flat=True) 3700 expressions = f" {self.wrap(expressions)}" if expressions else "" 3701 buckets = self.sql(expression, "buckets") 3702 kind = self.sql(expression, "kind") 3703 buckets = f" BUCKETS {buckets}" if buckets else "" 3704 order = self.sql(expression, "order") 3705 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 3706 3707 def oncluster_sql(self, expression: exp.OnCluster) -> str: 3708 return "" 3709 3710 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3711 expressions = self.expressions(expression, key="expressions", flat=True) 3712 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3713 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3714 buckets = self.sql(expression, "buckets") 3715 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 3716 3717 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3718 this = self.sql(expression, "this") 3719 having = self.sql(expression, "having") 3720 3721 if having: 3722 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3723 3724 return self.func("ANY_VALUE", this) 3725 3726 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3727 transform = self.func("TRANSFORM", *expression.expressions) 3728 row_format_before = self.sql(expression, "row_format_before") 3729 row_format_before = f" {row_format_before}" if row_format_before else "" 3730 record_writer = self.sql(expression, "record_writer") 3731 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3732 using = f" USING {self.sql(expression, 'command_script')}" 3733 schema = self.sql(expression, "schema") 3734 schema = f" AS {schema}" if schema else "" 3735 row_format_after = self.sql(expression, "row_format_after") 3736 row_format_after = f" {row_format_after}" if row_format_after else "" 3737 record_reader = self.sql(expression, "record_reader") 3738 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3739 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 3740 3741 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3742 key_block_size = self.sql(expression, "key_block_size") 3743 if key_block_size: 3744 return f"KEY_BLOCK_SIZE = {key_block_size}" 3745 3746 using = self.sql(expression, "using") 3747 if using: 3748 return f"USING {using}" 3749 3750 parser = self.sql(expression, "parser") 3751 if parser: 3752 return f"WITH PARSER {parser}" 3753 3754 comment = self.sql(expression, "comment") 3755 if comment: 3756 return f"COMMENT {comment}" 3757 3758 visible = expression.args.get("visible") 3759 if visible is not None: 3760 return "VISIBLE" if visible else "INVISIBLE" 3761 3762 engine_attr = self.sql(expression, "engine_attr") 3763 if engine_attr: 3764 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3765 3766 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3767 if secondary_engine_attr: 3768 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3769 3770 self.unsupported("Unsupported index constraint option.") 3771 return "" 3772 3773 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 3774 enforced = " ENFORCED" if expression.args.get("enforced") else "" 3775 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 3776 3777 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3778 kind = self.sql(expression, "kind") 3779 kind = f"{kind} INDEX" if kind else "INDEX" 3780 this = self.sql(expression, "this") 3781 this = f" {this}" if this else "" 3782 index_type = self.sql(expression, "index_type") 3783 index_type = f" USING {index_type}" if index_type else "" 3784 expressions = self.expressions(expression, flat=True) 3785 expressions = f" ({expressions})" if expressions else "" 3786 options = self.expressions(expression, key="options", sep=" ") 3787 options = f" {options}" if options else "" 3788 return f"{kind}{this}{index_type}{expressions}{options}" 3789 3790 def nvl2_sql(self, expression: exp.Nvl2) -> str: 3791 if self.NVL2_SUPPORTED: 3792 return self.function_fallback_sql(expression) 3793 3794 case = exp.Case().when( 3795 expression.this.is_(exp.null()).not_(copy=False), 3796 expression.args["true"], 3797 copy=False, 3798 ) 3799 else_cond = expression.args.get("false") 3800 if else_cond: 3801 case.else_(else_cond, copy=False) 3802 3803 return self.sql(case) 3804 3805 def comprehension_sql(self, expression: exp.Comprehension) -> str: 3806 this = self.sql(expression, "this") 3807 expr = self.sql(expression, "expression") 3808 iterator = self.sql(expression, "iterator") 3809 condition = self.sql(expression, "condition") 3810 condition = f" IF {condition}" if condition else "" 3811 return f"{this} FOR {expr} IN {iterator}{condition}" 3812 3813 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 3814 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 3815 3816 def opclass_sql(self, expression: exp.Opclass) -> str: 3817 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 3818 3819 def predict_sql(self, expression: exp.Predict) -> str: 3820 model = self.sql(expression, "this") 3821 model = f"MODEL {model}" 3822 table = self.sql(expression, "expression") 3823 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 3824 parameters = self.sql(expression, "params_struct") 3825 return self.func("PREDICT", model, table, parameters or None) 3826 3827 def forin_sql(self, expression: exp.ForIn) -> str: 3828 this = self.sql(expression, "this") 3829 expression_sql = self.sql(expression, "expression") 3830 return f"FOR {this} DO {expression_sql}" 3831 3832 def refresh_sql(self, expression: exp.Refresh) -> str: 3833 this = self.sql(expression, "this") 3834 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 3835 return f"REFRESH {table}{this}" 3836 3837 def toarray_sql(self, expression: exp.ToArray) -> str: 3838 arg = expression.this 3839 if not arg.type: 3840 from sqlglot.optimizer.annotate_types import annotate_types 3841 3842 arg = annotate_types(arg) 3843 3844 if arg.is_type(exp.DataType.Type.ARRAY): 3845 return self.sql(arg) 3846 3847 cond_for_null = arg.is_(exp.null()) 3848 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 3849 3850 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 3851 this = expression.this 3852 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 3853 return self.sql(this) 3854 3855 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 3856 3857 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 3858 this = expression.this 3859 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 3860 return self.sql(this) 3861 3862 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP)) 3863 3864 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 3865 this = expression.this 3866 time_format = self.format_time(expression) 3867 3868 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 3869 return self.sql( 3870 exp.cast( 3871 exp.StrToTime(this=this, format=expression.args["format"]), 3872 exp.DataType.Type.DATE, 3873 ) 3874 ) 3875 3876 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 3877 return self.sql(this) 3878 3879 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 3880 3881 def unixdate_sql(self, expression: exp.UnixDate) -> str: 3882 return self.sql( 3883 exp.func( 3884 "DATEDIFF", 3885 expression.this, 3886 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 3887 "day", 3888 ) 3889 ) 3890 3891 def lastday_sql(self, expression: exp.LastDay) -> str: 3892 if self.LAST_DAY_SUPPORTS_DATE_PART: 3893 return self.function_fallback_sql(expression) 3894 3895 unit = expression.text("unit") 3896 if unit and unit != "MONTH": 3897 self.unsupported("Date parts are not supported in LAST_DAY.") 3898 3899 return self.func("LAST_DAY", expression.this) 3900 3901 def dateadd_sql(self, expression: exp.DateAdd) -> str: 3902 from sqlglot.dialects.dialect import unit_to_str 3903 3904 return self.func( 3905 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 3906 ) 3907 3908 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 3909 if self.CAN_IMPLEMENT_ARRAY_ANY: 3910 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 3911 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 3912 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 3913 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 3914 3915 from sqlglot.dialects import Dialect 3916 3917 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 3918 if self.dialect.__class__ != Dialect: 3919 self.unsupported("ARRAY_ANY is unsupported") 3920 3921 return self.function_fallback_sql(expression) 3922 3923 def struct_sql(self, expression: exp.Struct) -> str: 3924 expression.set( 3925 "expressions", 3926 [ 3927 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 3928 if isinstance(e, exp.PropertyEQ) 3929 else e 3930 for e in expression.expressions 3931 ], 3932 ) 3933 3934 return self.function_fallback_sql(expression) 3935 3936 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 3937 low = self.sql(expression, "this") 3938 high = self.sql(expression, "expression") 3939 3940 return f"{low} TO {high}" 3941 3942 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 3943 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 3944 tables = f" {self.expressions(expression)}" 3945 3946 exists = " IF EXISTS" if expression.args.get("exists") else "" 3947 3948 on_cluster = self.sql(expression, "cluster") 3949 on_cluster = f" {on_cluster}" if on_cluster else "" 3950 3951 identity = self.sql(expression, "identity") 3952 identity = f" {identity} IDENTITY" if identity else "" 3953 3954 option = self.sql(expression, "option") 3955 option = f" {option}" if option else "" 3956 3957 partition = self.sql(expression, "partition") 3958 partition = f" {partition}" if partition else "" 3959 3960 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 3961 3962 # This transpiles T-SQL's CONVERT function 3963 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 3964 def convert_sql(self, expression: exp.Convert) -> str: 3965 to = expression.this 3966 value = expression.expression 3967 style = expression.args.get("style") 3968 safe = expression.args.get("safe") 3969 strict = expression.args.get("strict") 3970 3971 if not to or not value: 3972 return "" 3973 3974 # Retrieve length of datatype and override to default if not specified 3975 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 3976 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 3977 3978 transformed: t.Optional[exp.Expression] = None 3979 cast = exp.Cast if strict else exp.TryCast 3980 3981 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 3982 if isinstance(style, exp.Literal) and style.is_int: 3983 from sqlglot.dialects.tsql import TSQL 3984 3985 style_value = style.name 3986 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 3987 if not converted_style: 3988 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 3989 3990 fmt = exp.Literal.string(converted_style) 3991 3992 if to.this == exp.DataType.Type.DATE: 3993 transformed = exp.StrToDate(this=value, format=fmt) 3994 elif to.this == exp.DataType.Type.DATETIME: 3995 transformed = exp.StrToTime(this=value, format=fmt) 3996 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 3997 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 3998 elif to.this == exp.DataType.Type.TEXT: 3999 transformed = exp.TimeToStr(this=value, format=fmt) 4000 4001 if not transformed: 4002 transformed = cast(this=value, to=to, safe=safe) 4003 4004 return self.sql(transformed) 4005 4006 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4007 this = expression.this 4008 if isinstance(this, exp.JSONPathWildcard): 4009 this = self.json_path_part(this) 4010 return f".{this}" if this else "" 4011 4012 if exp.SAFE_IDENTIFIER_RE.match(this): 4013 return f".{this}" 4014 4015 this = self.json_path_part(this) 4016 return f"[{this}]" if self.JSON_PATH_BRACKETED_KEY_SUPPORTED else f".{this}" 4017 4018 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4019 this = self.json_path_part(expression.this) 4020 return f"[{this}]" if this else "" 4021 4022 def _simplify_unless_literal(self, expression: E) -> E: 4023 if not isinstance(expression, exp.Literal): 4024 from sqlglot.optimizer.simplify import simplify 4025 4026 expression = simplify(expression, dialect=self.dialect) 4027 4028 return expression 4029 4030 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4031 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4032 # The first modifier here will be the one closest to the AggFunc's arg 4033 mods = sorted( 4034 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4035 key=lambda x: 0 4036 if isinstance(x, exp.HavingMax) 4037 else (1 if isinstance(x, exp.Order) else 2), 4038 ) 4039 4040 if mods: 4041 mod = mods[0] 4042 this = expression.__class__(this=mod.this.copy()) 4043 this.meta["inline"] = True 4044 mod.this.replace(this) 4045 return self.sql(expression.this) 4046 4047 agg_func = expression.find(exp.AggFunc) 4048 4049 if agg_func: 4050 return self.sql(agg_func)[:-1] + f" {text})" 4051 4052 return f"{self.sql(expression, 'this')} {text}" 4053 4054 def _replace_line_breaks(self, string: str) -> str: 4055 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4056 if self.pretty: 4057 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4058 return string 4059 4060 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4061 option = self.sql(expression, "this") 4062 4063 if expression.expressions: 4064 upper = option.upper() 4065 4066 # Snowflake FILE_FORMAT options are separated by whitespace 4067 sep = " " if upper == "FILE_FORMAT" else ", " 4068 4069 # Databricks copy/format options do not set their list of values with EQ 4070 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4071 values = self.expressions(expression, flat=True, sep=sep) 4072 return f"{option}{op}({values})" 4073 4074 value = self.sql(expression, "expression") 4075 4076 if not value: 4077 return option 4078 4079 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4080 4081 return f"{option}{op}{value}" 4082 4083 def credentials_sql(self, expression: exp.Credentials) -> str: 4084 cred_expr = expression.args.get("credentials") 4085 if isinstance(cred_expr, exp.Literal): 4086 # Redshift case: CREDENTIALS <string> 4087 credentials = self.sql(expression, "credentials") 4088 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4089 else: 4090 # Snowflake case: CREDENTIALS = (...) 4091 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4092 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4093 4094 storage = self.sql(expression, "storage") 4095 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4096 4097 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4098 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4099 4100 iam_role = self.sql(expression, "iam_role") 4101 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4102 4103 region = self.sql(expression, "region") 4104 region = f" REGION {region}" if region else "" 4105 4106 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4107 4108 def copy_sql(self, expression: exp.Copy) -> str: 4109 this = self.sql(expression, "this") 4110 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4111 4112 credentials = self.sql(expression, "credentials") 4113 credentials = self.seg(credentials) if credentials else "" 4114 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4115 files = self.expressions(expression, key="files", flat=True) 4116 4117 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4118 params = self.expressions( 4119 expression, 4120 key="params", 4121 sep=sep, 4122 new_line=True, 4123 skip_last=True, 4124 skip_first=True, 4125 indent=self.COPY_PARAMS_ARE_WRAPPED, 4126 ) 4127 4128 if params: 4129 if self.COPY_PARAMS_ARE_WRAPPED: 4130 params = f" WITH ({params})" 4131 elif not self.pretty: 4132 params = f" {params}" 4133 4134 return f"COPY{this}{kind} {files}{credentials}{params}" 4135 4136 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4137 return "" 4138 4139 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4140 on_sql = "ON" if expression.args.get("on") else "OFF" 4141 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4142 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4143 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4144 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4145 4146 if filter_col or retention_period: 4147 on_sql = self.func("ON", filter_col, retention_period) 4148 4149 return f"DATA_DELETION={on_sql}" 4150 4151 def maskingpolicycolumnconstraint_sql( 4152 self, expression: exp.MaskingPolicyColumnConstraint 4153 ) -> str: 4154 this = self.sql(expression, "this") 4155 expressions = self.expressions(expression, flat=True) 4156 expressions = f" USING ({expressions})" if expressions else "" 4157 return f"MASKING POLICY {this}{expressions}" 4158 4159 def gapfill_sql(self, expression: exp.GapFill) -> str: 4160 this = self.sql(expression, "this") 4161 this = f"TABLE {this}" 4162 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4163 4164 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4165 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4166 4167 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4168 this = self.sql(expression, "this") 4169 expr = expression.expression 4170 4171 if isinstance(expr, exp.Func): 4172 # T-SQL's CLR functions are case sensitive 4173 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4174 else: 4175 expr = self.sql(expression, "expression") 4176 4177 return self.scope_resolution(expr, this) 4178 4179 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4180 if self.PARSE_JSON_NAME is None: 4181 return self.sql(expression.this) 4182 4183 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4184 4185 def rand_sql(self, expression: exp.Rand) -> str: 4186 lower = self.sql(expression, "lower") 4187 upper = self.sql(expression, "upper") 4188 4189 if lower and upper: 4190 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4191 return self.func("RAND", expression.this) 4192 4193 def changes_sql(self, expression: exp.Changes) -> str: 4194 information = self.sql(expression, "information") 4195 information = f"INFORMATION => {information}" 4196 at_before = self.sql(expression, "at_before") 4197 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4198 end = self.sql(expression, "end") 4199 end = f"{self.seg('')}{end}" if end else "" 4200 4201 return f"CHANGES ({information}){at_before}{end}" 4202 4203 def pad_sql(self, expression: exp.Pad) -> str: 4204 prefix = "L" if expression.args.get("is_left") else "R" 4205 4206 fill_pattern = self.sql(expression, "fill_pattern") or None 4207 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4208 fill_pattern = "' '" 4209 4210 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4211 4212 def summarize_sql(self, expression: exp.Summarize) -> str: 4213 table = " TABLE" if expression.args.get("table") else "" 4214 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4215 4216 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4217 generate_series = exp.GenerateSeries(**expression.args) 4218 4219 parent = expression.parent 4220 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4221 parent = parent.parent 4222 4223 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4224 return self.sql(exp.Unnest(expressions=[generate_series])) 4225 4226 if isinstance(parent, exp.Select): 4227 self.unsupported("GenerateSeries projection unnesting is not supported.") 4228 4229 return self.sql(generate_series) 4230 4231 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4232 exprs = expression.expressions 4233 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4234 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4235 else: 4236 rhs = self.expressions(expression) 4237 4238 return self.func(name, expression.this, rhs) 4239 4240 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4241 if self.SUPPORTS_CONVERT_TIMEZONE: 4242 return self.function_fallback_sql(expression) 4243 4244 source_tz = expression.args.get("source_tz") 4245 target_tz = expression.args.get("target_tz") 4246 timestamp = expression.args.get("timestamp") 4247 4248 if source_tz and timestamp: 4249 timestamp = exp.AtTimeZone( 4250 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4251 ) 4252 4253 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4254 4255 return self.sql(expr) 4256 4257 def json_sql(self, expression: exp.JSON) -> str: 4258 this = self.sql(expression, "this") 4259 this = f" {this}" if this else "" 4260 4261 _with = expression.args.get("with") 4262 4263 if _with is None: 4264 with_sql = "" 4265 elif not _with: 4266 with_sql = " WITHOUT" 4267 else: 4268 with_sql = " WITH" 4269 4270 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4271 4272 return f"JSON{this}{with_sql}{unique_sql}" 4273 4274 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4275 def _generate_on_options(arg: t.Any) -> str: 4276 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4277 4278 path = self.sql(expression, "path") 4279 returning = self.sql(expression, "returning") 4280 returning = f" RETURNING {returning}" if returning else "" 4281 4282 on_condition = self.sql(expression, "on_condition") 4283 on_condition = f" {on_condition}" if on_condition else "" 4284 4285 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4286 4287 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4288 else_ = "ELSE " if expression.args.get("else_") else "" 4289 condition = self.sql(expression, "expression") 4290 condition = f"WHEN {condition} THEN " if condition else else_ 4291 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4292 return f"{condition}{insert}" 4293 4294 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4295 kind = self.sql(expression, "kind") 4296 expressions = self.seg(self.expressions(expression, sep=" ")) 4297 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4298 return res 4299 4300 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4301 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4302 empty = expression.args.get("empty") 4303 empty = ( 4304 f"DEFAULT {empty} ON EMPTY" 4305 if isinstance(empty, exp.Expression) 4306 else self.sql(expression, "empty") 4307 ) 4308 4309 error = expression.args.get("error") 4310 error = ( 4311 f"DEFAULT {error} ON ERROR" 4312 if isinstance(error, exp.Expression) 4313 else self.sql(expression, "error") 4314 ) 4315 4316 if error and empty: 4317 error = ( 4318 f"{empty} {error}" 4319 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4320 else f"{error} {empty}" 4321 ) 4322 empty = "" 4323 4324 null = self.sql(expression, "null") 4325 4326 return f"{empty}{error}{null}" 4327 4328 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4329 this = self.sql(expression, "this") 4330 path = self.sql(expression, "path") 4331 4332 passing = self.expressions(expression, "passing") 4333 passing = f" PASSING {passing}" if passing else "" 4334 4335 on_condition = self.sql(expression, "on_condition") 4336 on_condition = f" {on_condition}" if on_condition else "" 4337 4338 path = f"{path}{passing}{on_condition}" 4339 4340 return self.func("JSON_EXISTS", this, path) 4341 4342 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4343 array_agg = self.function_fallback_sql(expression) 4344 4345 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4346 parent = expression.parent 4347 if isinstance(parent, exp.Filter): 4348 parent_cond = parent.expression.this 4349 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4350 else: 4351 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4352 this = expression.this 4353 this_sql = ( 4354 self.expressions(this) 4355 if isinstance(this, exp.Distinct) 4356 else self.sql(expression, "this") 4357 ) 4358 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4359 4360 return array_agg 4361 4362 def apply_sql(self, expression: exp.Apply) -> str: 4363 this = self.sql(expression, "this") 4364 expr = self.sql(expression, "expression") 4365 4366 return f"{this} APPLY({expr})" 4367 4368 def grant_sql(self, expression: exp.Grant) -> str: 4369 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4370 4371 kind = self.sql(expression, "kind") 4372 kind = f" {kind}" if kind else "" 4373 4374 securable = self.sql(expression, "securable") 4375 securable = f" {securable}" if securable else "" 4376 4377 principals = self.expressions(expression, key="principals", flat=True) 4378 4379 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4380 4381 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4382 4383 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4384 this = self.sql(expression, "this") 4385 columns = self.expressions(expression, flat=True) 4386 columns = f"({columns})" if columns else "" 4387 4388 return f"{this}{columns}" 4389 4390 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4391 this = self.sql(expression, "this") 4392 4393 kind = self.sql(expression, "kind") 4394 kind = f"{kind} " if kind else "" 4395 4396 return f"{kind}{this}" 4397 4398 def columns_sql(self, expression: exp.Columns): 4399 func = self.function_fallback_sql(expression) 4400 if expression.args.get("unpack"): 4401 func = f"*{func}" 4402 4403 return func 4404 4405 def overlay_sql(self, expression: exp.Overlay): 4406 this = self.sql(expression, "this") 4407 expr = self.sql(expression, "expression") 4408 from_sql = self.sql(expression, "from") 4409 for_sql = self.sql(expression, "for") 4410 for_sql = f" FOR {for_sql}" if for_sql else "" 4411 4412 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
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)
639 def __init__( 640 self, 641 pretty: t.Optional[bool] = None, 642 identify: str | bool = False, 643 normalize: bool = False, 644 pad: int = 2, 645 indent: int = 2, 646 normalize_functions: t.Optional[str | bool] = None, 647 unsupported_level: ErrorLevel = ErrorLevel.WARN, 648 max_unsupported: int = 3, 649 leading_comma: bool = False, 650 max_text_width: int = 80, 651 comments: bool = True, 652 dialect: DialectType = None, 653 ): 654 import sqlglot 655 from sqlglot.dialects import Dialect 656 657 self.pretty = pretty if pretty is not None else sqlglot.pretty 658 self.identify = identify 659 self.normalize = normalize 660 self.pad = pad 661 self._indent = indent 662 self.unsupported_level = unsupported_level 663 self.max_unsupported = max_unsupported 664 self.leading_comma = leading_comma 665 self.max_text_width = max_text_width 666 self.comments = comments 667 self.dialect = Dialect.get_or_raise(dialect) 668 669 # This is both a Dialect property and a Generator argument, so we prioritize the latter 670 self.normalize_functions = ( 671 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 672 ) 673 674 self.unsupported_messages: t.List[str] = [] 675 self._escaped_quote_end: str = ( 676 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 677 ) 678 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 679 680 self._next_name = name_sequence("_t") 681 682 self._identifier_start = self.dialect.IDENTIFIER_START 683 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.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.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>}
SUPPORTED_JSON_PATH_PARTS =
{<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'>, <class 'sqlglot.expressions.JSONPathWildcard'>, <class 'sqlglot.expressions.JSONPathRecursive'>, <class 'sqlglot.expressions.JSONPathKey'>}
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.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.NCHAR: 'NCHAR'>, <Type.CHAR: 'CHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>}
685 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 686 """ 687 Generates the SQL string corresponding to the given syntax tree. 688 689 Args: 690 expression: The syntax tree. 691 copy: Whether to copy the expression. The generator performs mutations so 692 it is safer to copy. 693 694 Returns: 695 The SQL string corresponding to `expression`. 696 """ 697 if copy: 698 expression = expression.copy() 699 700 expression = self.preprocess(expression) 701 702 self.unsupported_messages = [] 703 sql = self.sql(expression).strip() 704 705 if self.pretty: 706 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 707 708 if self.unsupported_level == ErrorLevel.IGNORE: 709 return sql 710 711 if self.unsupported_level == ErrorLevel.WARN: 712 for msg in self.unsupported_messages: 713 logger.warning(msg) 714 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 715 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 716 717 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:
719 def preprocess(self, expression: exp.Expression) -> exp.Expression: 720 """Apply generic preprocessing transformations to a given expression.""" 721 expression = self._move_ctes_to_top_level(expression) 722 723 if self.ENSURE_BOOLS: 724 from sqlglot.transforms import ensure_bools 725 726 expression = ensure_bools(expression) 727 728 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:
757 def maybe_comment( 758 self, 759 sql: str, 760 expression: t.Optional[exp.Expression] = None, 761 comments: t.Optional[t.List[str]] = None, 762 separated: bool = False, 763 ) -> str: 764 comments = ( 765 ((expression and expression.comments) if comments is None else comments) # type: ignore 766 if self.comments 767 else None 768 ) 769 770 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 771 return sql 772 773 comments_sql = " ".join( 774 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 775 ) 776 777 if not comments_sql: 778 return sql 779 780 comments_sql = self._replace_line_breaks(comments_sql) 781 782 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 783 return ( 784 f"{self.sep()}{comments_sql}{sql}" 785 if not sql or sql[0].isspace() 786 else f"{comments_sql}{self.sep()}{sql}" 787 ) 788 789 return f"{sql} {comments_sql}"
791 def wrap(self, expression: exp.Expression | str) -> str: 792 this_sql = ( 793 self.sql(expression) 794 if isinstance(expression, exp.UNWRAPPED_QUERIES) 795 else self.sql(expression, "this") 796 ) 797 if not this_sql: 798 return "()" 799 800 this_sql = self.indent(this_sql, level=1, pad=0) 801 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:
817 def indent( 818 self, 819 sql: str, 820 level: int = 0, 821 pad: t.Optional[int] = None, 822 skip_first: bool = False, 823 skip_last: bool = False, 824 ) -> str: 825 if not self.pretty or not sql: 826 return sql 827 828 pad = self.pad if pad is None else pad 829 lines = sql.split("\n") 830 831 return "\n".join( 832 ( 833 line 834 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 835 else f"{' ' * (level * self._indent + pad)}{line}" 836 ) 837 for i, line in enumerate(lines) 838 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
840 def sql( 841 self, 842 expression: t.Optional[str | exp.Expression], 843 key: t.Optional[str] = None, 844 comment: bool = True, 845 ) -> str: 846 if not expression: 847 return "" 848 849 if isinstance(expression, str): 850 return expression 851 852 if key: 853 value = expression.args.get(key) 854 if value: 855 return self.sql(value) 856 return "" 857 858 transform = self.TRANSFORMS.get(expression.__class__) 859 860 if callable(transform): 861 sql = transform(self, expression) 862 elif isinstance(expression, exp.Expression): 863 exp_handler_name = f"{expression.key}_sql" 864 865 if hasattr(self, exp_handler_name): 866 sql = getattr(self, exp_handler_name)(expression) 867 elif isinstance(expression, exp.Func): 868 sql = self.function_fallback_sql(expression) 869 elif isinstance(expression, exp.Property): 870 sql = self.property_sql(expression) 871 else: 872 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 873 else: 874 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 875 876 return self.maybe_comment(sql, expression) if self.comments and comment else sql
883 def cache_sql(self, expression: exp.Cache) -> str: 884 lazy = " LAZY" if expression.args.get("lazy") else "" 885 table = self.sql(expression, "this") 886 options = expression.args.get("options") 887 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 888 sql = self.sql(expression, "expression") 889 sql = f" AS{self.sep()}{sql}" if sql else "" 890 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 891 return self.prepend_ctes(expression, sql)
893 def characterset_sql(self, expression: exp.CharacterSet) -> str: 894 if isinstance(expression.parent, exp.Cast): 895 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 896 default = "DEFAULT " if expression.args.get("default") else "" 897 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
911 def column_sql(self, expression: exp.Column) -> str: 912 join_mark = " (+)" if expression.args.get("join_mark") else "" 913 914 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 915 join_mark = "" 916 self.unsupported("Outer join syntax using the (+) operator is not supported.") 917 918 return f"{self.column_parts(expression)}{join_mark}"
926 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 927 column = self.sql(expression, "this") 928 kind = self.sql(expression, "kind") 929 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 930 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 931 kind = f"{sep}{kind}" if kind else "" 932 constraints = f" {constraints}" if constraints else "" 933 position = self.sql(expression, "position") 934 position = f" {position}" if position else "" 935 936 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 937 kind = "" 938 939 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
946 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 947 this = self.sql(expression, "this") 948 if expression.args.get("not_null"): 949 persisted = " PERSISTED NOT NULL" 950 elif expression.args.get("persisted"): 951 persisted = " PERSISTED" 952 else: 953 persisted = "" 954 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
967 def generatedasidentitycolumnconstraint_sql( 968 self, expression: exp.GeneratedAsIdentityColumnConstraint 969 ) -> str: 970 this = "" 971 if expression.this is not None: 972 on_null = " ON NULL" if expression.args.get("on_null") else "" 973 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 974 975 start = expression.args.get("start") 976 start = f"START WITH {start}" if start else "" 977 increment = expression.args.get("increment") 978 increment = f" INCREMENT BY {increment}" if increment else "" 979 minvalue = expression.args.get("minvalue") 980 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 981 maxvalue = expression.args.get("maxvalue") 982 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 983 cycle = expression.args.get("cycle") 984 cycle_sql = "" 985 986 if cycle is not None: 987 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 988 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 989 990 sequence_opts = "" 991 if start or increment or cycle_sql: 992 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 993 sequence_opts = f" ({sequence_opts.strip()})" 994 995 expr = self.sql(expression, "expression") 996 expr = f"({expr})" if expr else "IDENTITY" 997 998 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1000 def generatedasrowcolumnconstraint_sql( 1001 self, expression: exp.GeneratedAsRowColumnConstraint 1002 ) -> str: 1003 start = "START" if expression.args.get("start") else "END" 1004 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1005 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:
1024 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1025 this = self.sql(expression, "this") 1026 this = f" {this}" if this else "" 1027 index_type = expression.args.get("index_type") 1028 index_type = f" USING {index_type}" if index_type else "" 1029 on_conflict = self.sql(expression, "on_conflict") 1030 on_conflict = f" {on_conflict}" if on_conflict else "" 1031 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1032 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}"
1037 def create_sql(self, expression: exp.Create) -> str: 1038 kind = self.sql(expression, "kind") 1039 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1040 properties = expression.args.get("properties") 1041 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1042 1043 this = self.createable_sql(expression, properties_locs) 1044 1045 properties_sql = "" 1046 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1047 exp.Properties.Location.POST_WITH 1048 ): 1049 properties_sql = self.sql( 1050 exp.Properties( 1051 expressions=[ 1052 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1053 *properties_locs[exp.Properties.Location.POST_WITH], 1054 ] 1055 ) 1056 ) 1057 1058 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1059 properties_sql = self.sep() + properties_sql 1060 elif not self.pretty: 1061 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1062 properties_sql = f" {properties_sql}" 1063 1064 begin = " BEGIN" if expression.args.get("begin") else "" 1065 end = " END" if expression.args.get("end") else "" 1066 1067 expression_sql = self.sql(expression, "expression") 1068 if expression_sql: 1069 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1070 1071 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1072 postalias_props_sql = "" 1073 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1074 postalias_props_sql = self.properties( 1075 exp.Properties( 1076 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1077 ), 1078 wrapped=False, 1079 ) 1080 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1081 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1082 1083 postindex_props_sql = "" 1084 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1085 postindex_props_sql = self.properties( 1086 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1087 wrapped=False, 1088 prefix=" ", 1089 ) 1090 1091 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1092 indexes = f" {indexes}" if indexes else "" 1093 index_sql = indexes + postindex_props_sql 1094 1095 replace = " OR REPLACE" if expression.args.get("replace") else "" 1096 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1097 unique = " UNIQUE" if expression.args.get("unique") else "" 1098 1099 clustered = expression.args.get("clustered") 1100 if clustered is None: 1101 clustered_sql = "" 1102 elif clustered: 1103 clustered_sql = " CLUSTERED COLUMNSTORE" 1104 else: 1105 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1106 1107 postcreate_props_sql = "" 1108 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1109 postcreate_props_sql = self.properties( 1110 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1111 sep=" ", 1112 prefix=" ", 1113 wrapped=False, 1114 ) 1115 1116 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1117 1118 postexpression_props_sql = "" 1119 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1120 postexpression_props_sql = self.properties( 1121 exp.Properties( 1122 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1123 ), 1124 sep=" ", 1125 prefix=" ", 1126 wrapped=False, 1127 ) 1128 1129 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1130 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1131 no_schema_binding = ( 1132 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1133 ) 1134 1135 clone = self.sql(expression, "clone") 1136 clone = f" {clone}" if clone else "" 1137 1138 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1139 return self.prepend_ctes(expression, expression_sql)
1141 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1142 start = self.sql(expression, "start") 1143 start = f"START WITH {start}" if start else "" 1144 increment = self.sql(expression, "increment") 1145 increment = f" INCREMENT BY {increment}" if increment else "" 1146 minvalue = self.sql(expression, "minvalue") 1147 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1148 maxvalue = self.sql(expression, "maxvalue") 1149 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1150 owned = self.sql(expression, "owned") 1151 owned = f" OWNED BY {owned}" if owned else "" 1152 1153 cache = expression.args.get("cache") 1154 if cache is None: 1155 cache_str = "" 1156 elif cache is True: 1157 cache_str = " CACHE" 1158 else: 1159 cache_str = f" CACHE {cache}" 1160 1161 options = self.expressions(expression, key="options", flat=True, sep=" ") 1162 options = f" {options}" if options else "" 1163 1164 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1166 def clone_sql(self, expression: exp.Clone) -> str: 1167 this = self.sql(expression, "this") 1168 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1169 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1170 return f"{shallow}{keyword} {this}"
1172 def describe_sql(self, expression: exp.Describe) -> str: 1173 style = expression.args.get("style") 1174 style = f" {style}" if style else "" 1175 partition = self.sql(expression, "partition") 1176 partition = f" {partition}" if partition else "" 1177 return f"DESCRIBE{style} {self.sql(expression, 'this')}{partition}"
1199 def cte_sql(self, expression: exp.CTE) -> str: 1200 alias = self.sql(expression, "alias") 1201 1202 materialized = expression.args.get("materialized") 1203 if materialized is False: 1204 materialized = "NOT MATERIALIZED " 1205 elif materialized: 1206 materialized = "MATERIALIZED " 1207 1208 return f"{alias} AS {materialized or ''}{self.wrap(expression)}"
1210 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1211 alias = self.sql(expression, "this") 1212 columns = self.expressions(expression, key="columns", flat=True) 1213 columns = f"({columns})" if columns else "" 1214 1215 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1216 columns = "" 1217 self.unsupported("Named columns are not supported in table alias.") 1218 1219 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1220 alias = self._next_name() 1221 1222 return f"{alias}{columns}"
1242 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1243 this = self.sql(expression, "this") 1244 escape = expression.args.get("escape") 1245 1246 if self.dialect.UNICODE_START: 1247 escape_substitute = r"\\\1" 1248 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1249 else: 1250 escape_substitute = r"\\u\1" 1251 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1252 1253 if escape: 1254 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1255 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1256 else: 1257 escape_pattern = ESCAPED_UNICODE_RE 1258 escape_sql = "" 1259 1260 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1261 this = escape_pattern.sub(escape_substitute, this) 1262 1263 return f"{left_quote}{this}{right_quote}{escape_sql}"
1275 def datatype_sql(self, expression: exp.DataType) -> str: 1276 nested = "" 1277 values = "" 1278 interior = self.expressions(expression, flat=True) 1279 1280 type_value = expression.this 1281 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1282 type_sql = self.sql(expression, "kind") 1283 else: 1284 type_sql = ( 1285 self.TYPE_MAPPING.get(type_value, type_value.value) 1286 if isinstance(type_value, exp.DataType.Type) 1287 else type_value 1288 ) 1289 1290 if interior: 1291 if expression.args.get("nested"): 1292 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1293 if expression.args.get("values") is not None: 1294 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1295 values = self.expressions(expression, key="values", flat=True) 1296 values = f"{delimiters[0]}{values}{delimiters[1]}" 1297 elif type_value == exp.DataType.Type.INTERVAL: 1298 nested = f" {interior}" 1299 else: 1300 nested = f"({interior})" 1301 1302 type_sql = f"{type_sql}{nested}{values}" 1303 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1304 exp.DataType.Type.TIMETZ, 1305 exp.DataType.Type.TIMESTAMPTZ, 1306 ): 1307 type_sql = f"{type_sql} WITH TIME ZONE" 1308 1309 return type_sql
1311 def directory_sql(self, expression: exp.Directory) -> str: 1312 local = "LOCAL " if expression.args.get("local") else "" 1313 row_format = self.sql(expression, "row_format") 1314 row_format = f" {row_format}" if row_format else "" 1315 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1317 def delete_sql(self, expression: exp.Delete) -> str: 1318 this = self.sql(expression, "this") 1319 this = f" FROM {this}" if this else "" 1320 using = self.sql(expression, "using") 1321 using = f" USING {using}" if using else "" 1322 cluster = self.sql(expression, "cluster") 1323 cluster = f" {cluster}" if cluster else "" 1324 where = self.sql(expression, "where") 1325 returning = self.sql(expression, "returning") 1326 limit = self.sql(expression, "limit") 1327 tables = self.expressions(expression, key="tables") 1328 tables = f" {tables}" if tables else "" 1329 if self.RETURNING_END: 1330 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1331 else: 1332 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1333 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1335 def drop_sql(self, expression: exp.Drop) -> str: 1336 this = self.sql(expression, "this") 1337 expressions = self.expressions(expression, flat=True) 1338 expressions = f" ({expressions})" if expressions else "" 1339 kind = expression.args["kind"] 1340 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1341 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1342 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1343 on_cluster = self.sql(expression, "cluster") 1344 on_cluster = f" {on_cluster}" if on_cluster else "" 1345 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1346 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1347 cascade = " CASCADE" if expression.args.get("cascade") else "" 1348 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1349 purge = " PURGE" if expression.args.get("purge") else "" 1350 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1352 def set_operation(self, expression: exp.SetOperation) -> str: 1353 op_type = type(expression) 1354 op_name = op_type.key.upper() 1355 1356 distinct = expression.args.get("distinct") 1357 if ( 1358 distinct is False 1359 and op_type in (exp.Except, exp.Intersect) 1360 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1361 ): 1362 self.unsupported(f"{op_name} ALL is not supported") 1363 1364 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1365 1366 if distinct is None: 1367 distinct = default_distinct 1368 if distinct is None: 1369 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1370 1371 if distinct is default_distinct: 1372 kind = "" 1373 else: 1374 kind = " DISTINCT" if distinct else " ALL" 1375 1376 by_name = " BY NAME" if expression.args.get("by_name") else "" 1377 return f"{op_name}{kind}{by_name}"
1379 def set_operations(self, expression: exp.SetOperation) -> str: 1380 if not self.SET_OP_MODIFIERS: 1381 limit = expression.args.get("limit") 1382 order = expression.args.get("order") 1383 1384 if limit or order: 1385 select = self._move_ctes_to_top_level( 1386 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1387 ) 1388 1389 if limit: 1390 select = select.limit(limit.pop(), copy=False) 1391 if order: 1392 select = select.order_by(order.pop(), copy=False) 1393 return self.sql(select) 1394 1395 sqls: t.List[str] = [] 1396 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1397 1398 while stack: 1399 node = stack.pop() 1400 1401 if isinstance(node, exp.SetOperation): 1402 stack.append(node.expression) 1403 stack.append( 1404 self.maybe_comment( 1405 self.set_operation(node), comments=node.comments, separated=True 1406 ) 1407 ) 1408 stack.append(node.this) 1409 else: 1410 sqls.append(self.sql(node)) 1411 1412 this = self.sep().join(sqls) 1413 this = self.query_modifiers(expression, this) 1414 return self.prepend_ctes(expression, this)
1416 def fetch_sql(self, expression: exp.Fetch) -> str: 1417 direction = expression.args.get("direction") 1418 direction = f" {direction}" if direction else "" 1419 count = self.sql(expression, "count") 1420 count = f" {count}" if count else "" 1421 if expression.args.get("percent"): 1422 count = f"{count} PERCENT" 1423 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY" 1424 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}"
1426 def filter_sql(self, expression: exp.Filter) -> str: 1427 if self.AGGREGATE_FILTER_SUPPORTED: 1428 this = self.sql(expression, "this") 1429 where = self.sql(expression, "expression").strip() 1430 return f"{this} FILTER({where})" 1431 1432 agg = expression.this 1433 agg_arg = agg.this 1434 cond = expression.expression.this 1435 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1436 return self.sql(agg)
1445 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1446 using = self.sql(expression, "using") 1447 using = f" USING {using}" if using else "" 1448 columns = self.expressions(expression, key="columns", flat=True) 1449 columns = f"({columns})" if columns else "" 1450 partition_by = self.expressions(expression, key="partition_by", flat=True) 1451 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1452 where = self.sql(expression, "where") 1453 include = self.expressions(expression, key="include", flat=True) 1454 if include: 1455 include = f" INCLUDE ({include})" 1456 with_storage = self.expressions(expression, key="with_storage", flat=True) 1457 with_storage = f" WITH ({with_storage})" if with_storage else "" 1458 tablespace = self.sql(expression, "tablespace") 1459 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1460 on = self.sql(expression, "on") 1461 on = f" ON {on}" if on else "" 1462 1463 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1465 def index_sql(self, expression: exp.Index) -> str: 1466 unique = "UNIQUE " if expression.args.get("unique") else "" 1467 primary = "PRIMARY " if expression.args.get("primary") else "" 1468 amp = "AMP " if expression.args.get("amp") else "" 1469 name = self.sql(expression, "this") 1470 name = f"{name} " if name else "" 1471 table = self.sql(expression, "table") 1472 table = f"{self.INDEX_ON} {table}" if table else "" 1473 1474 index = "INDEX " if not table else "" 1475 1476 params = self.sql(expression, "params") 1477 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1479 def identifier_sql(self, expression: exp.Identifier) -> str: 1480 text = expression.name 1481 lower = text.lower() 1482 text = lower if self.normalize and not expression.quoted else text 1483 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1484 if ( 1485 expression.quoted 1486 or self.dialect.can_identify(text, self.identify) 1487 or lower in self.RESERVED_KEYWORDS 1488 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1489 ): 1490 text = f"{self._identifier_start}{text}{self._identifier_end}" 1491 return text
1506 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1507 input_format = self.sql(expression, "input_format") 1508 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1509 output_format = self.sql(expression, "output_format") 1510 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1511 return self.sep().join((input_format, output_format))
1520 def properties_sql(self, expression: exp.Properties) -> str: 1521 root_properties = [] 1522 with_properties = [] 1523 1524 for p in expression.expressions: 1525 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1526 if p_loc == exp.Properties.Location.POST_WITH: 1527 with_properties.append(p) 1528 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1529 root_properties.append(p) 1530 1531 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1532 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1533 1534 if root_props and with_props and not self.pretty: 1535 with_props = " " + with_props 1536 1537 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1544 def properties( 1545 self, 1546 properties: exp.Properties, 1547 prefix: str = "", 1548 sep: str = ", ", 1549 suffix: str = "", 1550 wrapped: bool = True, 1551 ) -> str: 1552 if properties.expressions: 1553 expressions = self.expressions(properties, sep=sep, indent=False) 1554 if expressions: 1555 expressions = self.wrap(expressions) if wrapped else expressions 1556 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1557 return ""
1562 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1563 properties_locs = defaultdict(list) 1564 for p in properties.expressions: 1565 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1566 if p_loc != exp.Properties.Location.UNSUPPORTED: 1567 properties_locs[p_loc].append(p) 1568 else: 1569 self.unsupported(f"Unsupported property {p.key}") 1570 1571 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1578 def property_sql(self, expression: exp.Property) -> str: 1579 property_cls = expression.__class__ 1580 if property_cls == exp.Property: 1581 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1582 1583 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1584 if not property_name: 1585 self.unsupported(f"Unsupported property {expression.key}") 1586 1587 return f"{property_name}={self.sql(expression, 'this')}"
1589 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1590 if self.SUPPORTS_CREATE_TABLE_LIKE: 1591 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1592 options = f" {options}" if options else "" 1593 1594 like = f"LIKE {self.sql(expression, 'this')}{options}" 1595 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1596 like = f"({like})" 1597 1598 return like 1599 1600 if expression.expressions: 1601 self.unsupported("Transpilation of LIKE property options is unsupported") 1602 1603 select = exp.select("*").from_(expression.this).limit(0) 1604 return f"AS {self.sql(select)}"
1611 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1612 no = "NO " if expression.args.get("no") else "" 1613 local = expression.args.get("local") 1614 local = f"{local} " if local else "" 1615 dual = "DUAL " if expression.args.get("dual") else "" 1616 before = "BEFORE " if expression.args.get("before") else "" 1617 after = "AFTER " if expression.args.get("after") else "" 1618 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1634 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1635 if expression.args.get("no"): 1636 return "NO MERGEBLOCKRATIO" 1637 if expression.args.get("default"): 1638 return "DEFAULT MERGEBLOCKRATIO" 1639 1640 percent = " PERCENT" if expression.args.get("percent") else "" 1641 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1643 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1644 default = expression.args.get("default") 1645 minimum = expression.args.get("minimum") 1646 maximum = expression.args.get("maximum") 1647 if default or minimum or maximum: 1648 if default: 1649 prop = "DEFAULT" 1650 elif minimum: 1651 prop = "MINIMUM" 1652 else: 1653 prop = "MAXIMUM" 1654 return f"{prop} DATABLOCKSIZE" 1655 units = expression.args.get("units") 1656 units = f" {units}" if units else "" 1657 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1659 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1660 autotemp = expression.args.get("autotemp") 1661 always = expression.args.get("always") 1662 default = expression.args.get("default") 1663 manual = expression.args.get("manual") 1664 never = expression.args.get("never") 1665 1666 if autotemp is not None: 1667 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1668 elif always: 1669 prop = "ALWAYS" 1670 elif default: 1671 prop = "DEFAULT" 1672 elif manual: 1673 prop = "MANUAL" 1674 elif never: 1675 prop = "NEVER" 1676 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1678 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1679 no = expression.args.get("no") 1680 no = " NO" if no else "" 1681 concurrent = expression.args.get("concurrent") 1682 concurrent = " CONCURRENT" if concurrent else "" 1683 target = self.sql(expression, "target") 1684 target = f" {target}" if target else "" 1685 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1687 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1688 if isinstance(expression.this, list): 1689 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1690 if expression.this: 1691 modulus = self.sql(expression, "this") 1692 remainder = self.sql(expression, "expression") 1693 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1694 1695 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1696 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1697 return f"FROM ({from_expressions}) TO ({to_expressions})"
1699 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1700 this = self.sql(expression, "this") 1701 1702 for_values_or_default = expression.expression 1703 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1704 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1705 else: 1706 for_values_or_default = " DEFAULT" 1707 1708 return f"PARTITION OF {this}{for_values_or_default}"
1710 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1711 kind = expression.args.get("kind") 1712 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1713 for_or_in = expression.args.get("for_or_in") 1714 for_or_in = f" {for_or_in}" if for_or_in else "" 1715 lock_type = expression.args.get("lock_type") 1716 override = " OVERRIDE" if expression.args.get("override") else "" 1717 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1719 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1720 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1721 statistics = expression.args.get("statistics") 1722 statistics_sql = "" 1723 if statistics is not None: 1724 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1725 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1727 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1728 this = self.sql(expression, "this") 1729 this = f"HISTORY_TABLE={this}" if this else "" 1730 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1731 data_consistency = ( 1732 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1733 ) 1734 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1735 retention_period = ( 1736 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1737 ) 1738 1739 if this: 1740 on_sql = self.func("ON", this, data_consistency, retention_period) 1741 else: 1742 on_sql = "ON" if expression.args.get("on") else "OFF" 1743 1744 sql = f"SYSTEM_VERSIONING={on_sql}" 1745 1746 return f"WITH({sql})" if expression.args.get("with") else sql
1748 def insert_sql(self, expression: exp.Insert) -> str: 1749 hint = self.sql(expression, "hint") 1750 overwrite = expression.args.get("overwrite") 1751 1752 if isinstance(expression.this, exp.Directory): 1753 this = " OVERWRITE" if overwrite else " INTO" 1754 else: 1755 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1756 1757 stored = self.sql(expression, "stored") 1758 stored = f" {stored}" if stored else "" 1759 alternative = expression.args.get("alternative") 1760 alternative = f" OR {alternative}" if alternative else "" 1761 ignore = " IGNORE" if expression.args.get("ignore") else "" 1762 is_function = expression.args.get("is_function") 1763 if is_function: 1764 this = f"{this} FUNCTION" 1765 this = f"{this} {self.sql(expression, 'this')}" 1766 1767 exists = " IF EXISTS" if expression.args.get("exists") else "" 1768 where = self.sql(expression, "where") 1769 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1770 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1771 on_conflict = self.sql(expression, "conflict") 1772 on_conflict = f" {on_conflict}" if on_conflict else "" 1773 by_name = " BY NAME" if expression.args.get("by_name") else "" 1774 returning = self.sql(expression, "returning") 1775 1776 if self.RETURNING_END: 1777 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1778 else: 1779 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1780 1781 partition_by = self.sql(expression, "partition") 1782 partition_by = f" {partition_by}" if partition_by else "" 1783 settings = self.sql(expression, "settings") 1784 settings = f" {settings}" if settings else "" 1785 1786 source = self.sql(expression, "source") 1787 source = f"TABLE {source}" if source else "" 1788 1789 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1790 return self.prepend_ctes(expression, sql)
1808 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1809 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1810 1811 constraint = self.sql(expression, "constraint") 1812 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1813 1814 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1815 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1816 action = self.sql(expression, "action") 1817 1818 expressions = self.expressions(expression, flat=True) 1819 if expressions: 1820 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1821 expressions = f" {set_keyword}{expressions}" 1822 1823 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1828 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1829 fields = self.sql(expression, "fields") 1830 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1831 escaped = self.sql(expression, "escaped") 1832 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1833 items = self.sql(expression, "collection_items") 1834 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1835 keys = self.sql(expression, "map_keys") 1836 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1837 lines = self.sql(expression, "lines") 1838 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1839 null = self.sql(expression, "null") 1840 null = f" NULL DEFINED AS {null}" if null else "" 1841 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1869 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1870 table = self.table_parts(expression) 1871 only = "ONLY " if expression.args.get("only") else "" 1872 partition = self.sql(expression, "partition") 1873 partition = f" {partition}" if partition else "" 1874 version = self.sql(expression, "version") 1875 version = f" {version}" if version else "" 1876 alias = self.sql(expression, "alias") 1877 alias = f"{sep}{alias}" if alias else "" 1878 1879 sample = self.sql(expression, "sample") 1880 if self.dialect.ALIAS_POST_TABLESAMPLE: 1881 sample_pre_alias = sample 1882 sample_post_alias = "" 1883 else: 1884 sample_pre_alias = "" 1885 sample_post_alias = sample 1886 1887 hints = self.expressions(expression, key="hints", sep=" ") 1888 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 1889 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 1890 joins = self.indent( 1891 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 1892 ) 1893 laterals = self.expressions(expression, key="laterals", sep="") 1894 1895 file_format = self.sql(expression, "format") 1896 if file_format: 1897 pattern = self.sql(expression, "pattern") 1898 pattern = f", PATTERN => {pattern}" if pattern else "" 1899 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 1900 1901 ordinality = expression.args.get("ordinality") or "" 1902 if ordinality: 1903 ordinality = f" WITH ORDINALITY{alias}" 1904 alias = "" 1905 1906 when = self.sql(expression, "when") 1907 if when: 1908 table = f"{table} {when}" 1909 1910 changes = self.sql(expression, "changes") 1911 changes = f" {changes}" if changes else "" 1912 1913 rows_from = self.expressions(expression, key="rows_from") 1914 if rows_from: 1915 table = f"ROWS FROM {self.wrap(rows_from)}" 1916 1917 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:
1919 def tablesample_sql( 1920 self, 1921 expression: exp.TableSample, 1922 tablesample_keyword: t.Optional[str] = None, 1923 ) -> str: 1924 method = self.sql(expression, "method") 1925 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 1926 numerator = self.sql(expression, "bucket_numerator") 1927 denominator = self.sql(expression, "bucket_denominator") 1928 field = self.sql(expression, "bucket_field") 1929 field = f" ON {field}" if field else "" 1930 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 1931 seed = self.sql(expression, "seed") 1932 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 1933 1934 size = self.sql(expression, "size") 1935 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 1936 size = f"{size} ROWS" 1937 1938 percent = self.sql(expression, "percent") 1939 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 1940 percent = f"{percent} PERCENT" 1941 1942 expr = f"{bucket}{percent}{size}" 1943 if self.TABLESAMPLE_REQUIRES_PARENS: 1944 expr = f"({expr})" 1945 1946 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
1948 def pivot_sql(self, expression: exp.Pivot) -> str: 1949 expressions = self.expressions(expression, flat=True) 1950 1951 if expression.this: 1952 this = self.sql(expression, "this") 1953 if not expressions: 1954 return f"UNPIVOT {this}" 1955 1956 on = f"{self.seg('ON')} {expressions}" 1957 using = self.expressions(expression, key="using", flat=True) 1958 using = f"{self.seg('USING')} {using}" if using else "" 1959 group = self.sql(expression, "group") 1960 return f"PIVOT {this}{on}{using}{group}" 1961 1962 alias = self.sql(expression, "alias") 1963 alias = f" AS {alias}" if alias else "" 1964 direction = self.seg("UNPIVOT" if expression.unpivot else "PIVOT") 1965 1966 field = self.sql(expression, "field") 1967 1968 include_nulls = expression.args.get("include_nulls") 1969 if include_nulls is not None: 1970 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 1971 else: 1972 nulls = "" 1973 1974 default_on_null = self.sql(expression, "default_on_null") 1975 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 1976 return f"{direction}{nulls}({expressions} FOR {field}{default_on_null}){alias}"
1987 def update_sql(self, expression: exp.Update) -> str: 1988 this = self.sql(expression, "this") 1989 set_sql = self.expressions(expression, flat=True) 1990 from_sql = self.sql(expression, "from") 1991 where_sql = self.sql(expression, "where") 1992 returning = self.sql(expression, "returning") 1993 order = self.sql(expression, "order") 1994 limit = self.sql(expression, "limit") 1995 if self.RETURNING_END: 1996 expression_sql = f"{from_sql}{where_sql}{returning}" 1997 else: 1998 expression_sql = f"{returning}{from_sql}{where_sql}" 1999 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2000 return self.prepend_ctes(expression, sql)
2002 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2003 values_as_table = values_as_table and self.VALUES_AS_TABLE 2004 2005 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2006 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2007 args = self.expressions(expression) 2008 alias = self.sql(expression, "alias") 2009 values = f"VALUES{self.seg('')}{args}" 2010 values = ( 2011 f"({values})" 2012 if self.WRAP_DERIVED_VALUES 2013 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2014 else values 2015 ) 2016 return f"{values} AS {alias}" if alias else values 2017 2018 # Converts `VALUES...` expression into a series of select unions. 2019 alias_node = expression.args.get("alias") 2020 column_names = alias_node and alias_node.columns 2021 2022 selects: t.List[exp.Query] = [] 2023 2024 for i, tup in enumerate(expression.expressions): 2025 row = tup.expressions 2026 2027 if i == 0 and column_names: 2028 row = [ 2029 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2030 ] 2031 2032 selects.append(exp.Select(expressions=row)) 2033 2034 if self.pretty: 2035 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2036 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2037 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2038 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2039 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2040 2041 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2042 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2043 return f"({unions}){alias}"
2048 @unsupported_args("expressions") 2049 def into_sql(self, expression: exp.Into) -> str: 2050 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2051 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2052 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2069 def group_sql(self, expression: exp.Group) -> str: 2070 group_by_all = expression.args.get("all") 2071 if group_by_all is True: 2072 modifier = " ALL" 2073 elif group_by_all is False: 2074 modifier = " DISTINCT" 2075 else: 2076 modifier = "" 2077 2078 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2079 2080 grouping_sets = self.expressions(expression, key="grouping_sets") 2081 cube = self.expressions(expression, key="cube") 2082 rollup = self.expressions(expression, key="rollup") 2083 2084 groupings = csv( 2085 self.seg(grouping_sets) if grouping_sets else "", 2086 self.seg(cube) if cube else "", 2087 self.seg(rollup) if rollup else "", 2088 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2089 sep=self.GROUPINGS_SEP, 2090 ) 2091 2092 if ( 2093 expression.expressions 2094 and groupings 2095 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2096 ): 2097 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2098 2099 return f"{group_by}{groupings}"
2105 def connect_sql(self, expression: exp.Connect) -> str: 2106 start = self.sql(expression, "start") 2107 start = self.seg(f"START WITH {start}") if start else "" 2108 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2109 connect = self.sql(expression, "connect") 2110 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2111 return start + connect
2116 def join_sql(self, expression: exp.Join) -> str: 2117 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2118 side = None 2119 else: 2120 side = expression.side 2121 2122 op_sql = " ".join( 2123 op 2124 for op in ( 2125 expression.method, 2126 "GLOBAL" if expression.args.get("global") else None, 2127 side, 2128 expression.kind, 2129 expression.hint if self.JOIN_HINTS else None, 2130 ) 2131 if op 2132 ) 2133 match_cond = self.sql(expression, "match_condition") 2134 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2135 on_sql = self.sql(expression, "on") 2136 using = expression.args.get("using") 2137 2138 if not on_sql and using: 2139 on_sql = csv(*(self.sql(column) for column in using)) 2140 2141 this = expression.this 2142 this_sql = self.sql(this) 2143 2144 if on_sql: 2145 on_sql = self.indent(on_sql, skip_first=True) 2146 space = self.seg(" " * self.pad) if self.pretty else " " 2147 if using: 2148 on_sql = f"{space}USING ({on_sql})" 2149 else: 2150 on_sql = f"{space}ON {on_sql}" 2151 elif not op_sql: 2152 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2153 return f" {this_sql}" 2154 2155 return f", {this_sql}" 2156 2157 if op_sql != "STRAIGHT_JOIN": 2158 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2159 2160 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}"
2167 def lateral_op(self, expression: exp.Lateral) -> str: 2168 cross_apply = expression.args.get("cross_apply") 2169 2170 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2171 if cross_apply is True: 2172 op = "INNER JOIN " 2173 elif cross_apply is False: 2174 op = "LEFT JOIN " 2175 else: 2176 op = "" 2177 2178 return f"{op}LATERAL"
2180 def lateral_sql(self, expression: exp.Lateral) -> str: 2181 this = self.sql(expression, "this") 2182 2183 if expression.args.get("view"): 2184 alias = expression.args["alias"] 2185 columns = self.expressions(alias, key="columns", flat=True) 2186 table = f" {alias.name}" if alias.name else "" 2187 columns = f" AS {columns}" if columns else "" 2188 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2189 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2190 2191 alias = self.sql(expression, "alias") 2192 alias = f" AS {alias}" if alias else "" 2193 return f"{self.lateral_op(expression)} {this}{alias}"
2195 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2196 this = self.sql(expression, "this") 2197 2198 args = [ 2199 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2200 for e in (expression.args.get(k) for k in ("offset", "expression")) 2201 if e 2202 ] 2203 2204 args_sql = ", ".join(self.sql(e) for e in args) 2205 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2206 expressions = self.expressions(expression, flat=True) 2207 expressions = f" BY {expressions}" if expressions else "" 2208 2209 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{expressions}"
2211 def offset_sql(self, expression: exp.Offset) -> str: 2212 this = self.sql(expression, "this") 2213 value = expression.expression 2214 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2215 expressions = self.expressions(expression, flat=True) 2216 expressions = f" BY {expressions}" if expressions else "" 2217 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2219 def setitem_sql(self, expression: exp.SetItem) -> str: 2220 kind = self.sql(expression, "kind") 2221 kind = f"{kind} " if kind else "" 2222 this = self.sql(expression, "this") 2223 expressions = self.expressions(expression) 2224 collate = self.sql(expression, "collate") 2225 collate = f" COLLATE {collate}" if collate else "" 2226 global_ = "GLOBAL " if expression.args.get("global") else "" 2227 return f"{global_}{kind}{this}{expressions}{collate}"
2229 def set_sql(self, expression: exp.Set) -> str: 2230 expressions = ( 2231 f" {self.expressions(expression, flat=True)}" if expression.expressions else "" 2232 ) 2233 tag = " TAG" if expression.args.get("tag") else "" 2234 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
2239 def lock_sql(self, expression: exp.Lock) -> str: 2240 if not self.LOCKING_READS_SUPPORTED: 2241 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2242 return "" 2243 2244 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2245 expressions = self.expressions(expression, flat=True) 2246 expressions = f" OF {expressions}" if expressions else "" 2247 wait = expression.args.get("wait") 2248 2249 if wait is not None: 2250 if isinstance(wait, exp.Literal): 2251 wait = f" WAIT {self.sql(wait)}" 2252 else: 2253 wait = " NOWAIT" if wait else " SKIP LOCKED" 2254 2255 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2263 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2264 if self.dialect.ESCAPED_SEQUENCES: 2265 to_escaped = self.dialect.ESCAPED_SEQUENCES 2266 text = "".join( 2267 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2268 ) 2269 2270 return self._replace_line_breaks(text).replace( 2271 self.dialect.QUOTE_END, self._escaped_quote_end 2272 )
2274 def loaddata_sql(self, expression: exp.LoadData) -> str: 2275 local = " LOCAL" if expression.args.get("local") else "" 2276 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2277 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2278 this = f" INTO TABLE {self.sql(expression, 'this')}" 2279 partition = self.sql(expression, "partition") 2280 partition = f" {partition}" if partition else "" 2281 input_format = self.sql(expression, "input_format") 2282 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2283 serde = self.sql(expression, "serde") 2284 serde = f" SERDE {serde}" if serde else "" 2285 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2293 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2294 this = self.sql(expression, "this") 2295 this = f"{this} " if this else this 2296 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2297 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2299 def withfill_sql(self, expression: exp.WithFill) -> str: 2300 from_sql = self.sql(expression, "from") 2301 from_sql = f" FROM {from_sql}" if from_sql else "" 2302 to_sql = self.sql(expression, "to") 2303 to_sql = f" TO {to_sql}" if to_sql else "" 2304 step_sql = self.sql(expression, "step") 2305 step_sql = f" STEP {step_sql}" if step_sql else "" 2306 interpolated_values = [ 2307 f"{self.sql(named_expression, 'alias')} AS {self.sql(named_expression, 'this')}" 2308 for named_expression in expression.args.get("interpolate") or [] 2309 ] 2310 interpolate = ( 2311 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2312 ) 2313 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2324 def ordered_sql(self, expression: exp.Ordered) -> str: 2325 desc = expression.args.get("desc") 2326 asc = not desc 2327 2328 nulls_first = expression.args.get("nulls_first") 2329 nulls_last = not nulls_first 2330 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2331 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2332 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2333 2334 this = self.sql(expression, "this") 2335 2336 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2337 nulls_sort_change = "" 2338 if nulls_first and ( 2339 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2340 ): 2341 nulls_sort_change = " NULLS FIRST" 2342 elif ( 2343 nulls_last 2344 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2345 and not nulls_are_last 2346 ): 2347 nulls_sort_change = " NULLS LAST" 2348 2349 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2350 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2351 window = expression.find_ancestor(exp.Window, exp.Select) 2352 if isinstance(window, exp.Window) and window.args.get("spec"): 2353 self.unsupported( 2354 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2355 ) 2356 nulls_sort_change = "" 2357 elif ( 2358 self.NULL_ORDERING_SUPPORTED is False 2359 and (isinstance(expression.find_ancestor(exp.AggFunc, exp.Select), exp.AggFunc)) 2360 and ( 2361 (asc and nulls_sort_change == " NULLS LAST") 2362 or (desc and nulls_sort_change == " NULLS FIRST") 2363 ) 2364 ): 2365 self.unsupported( 2366 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2367 ) 2368 nulls_sort_change = "" 2369 elif self.NULL_ORDERING_SUPPORTED is None: 2370 if expression.this.is_int: 2371 self.unsupported( 2372 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2373 ) 2374 elif not isinstance(expression.this, exp.Rand): 2375 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2376 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2377 nulls_sort_change = "" 2378 2379 with_fill = self.sql(expression, "with_fill") 2380 with_fill = f" {with_fill}" if with_fill else "" 2381 2382 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2392 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2393 partition = self.partition_by_sql(expression) 2394 order = self.sql(expression, "order") 2395 measures = self.expressions(expression, key="measures") 2396 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2397 rows = self.sql(expression, "rows") 2398 rows = self.seg(rows) if rows else "" 2399 after = self.sql(expression, "after") 2400 after = self.seg(after) if after else "" 2401 pattern = self.sql(expression, "pattern") 2402 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2403 definition_sqls = [ 2404 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2405 for definition in expression.args.get("define", []) 2406 ] 2407 definitions = self.expressions(sqls=definition_sqls) 2408 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2409 body = "".join( 2410 ( 2411 partition, 2412 order, 2413 measures, 2414 rows, 2415 after, 2416 pattern, 2417 define, 2418 ) 2419 ) 2420 alias = self.sql(expression, "alias") 2421 alias = f" {alias}" if alias else "" 2422 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2424 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2425 limit = expression.args.get("limit") 2426 2427 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2428 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2429 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2430 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2431 2432 return csv( 2433 *sqls, 2434 *[self.sql(join) for join in expression.args.get("joins") or []], 2435 self.sql(expression, "connect"), 2436 self.sql(expression, "match"), 2437 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2438 self.sql(expression, "prewhere"), 2439 self.sql(expression, "where"), 2440 self.sql(expression, "group"), 2441 self.sql(expression, "having"), 2442 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2443 self.sql(expression, "order"), 2444 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2445 *self.after_limit_modifiers(expression), 2446 self.options_modifier(expression), 2447 sep="", 2448 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2457 def offset_limit_modifiers( 2458 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2459 ) -> t.List[str]: 2460 return [ 2461 self.sql(expression, "offset") if fetch else self.sql(limit), 2462 self.sql(limit) if fetch else self.sql(expression, "offset"), 2463 ]
2470 def select_sql(self, expression: exp.Select) -> str: 2471 into = expression.args.get("into") 2472 if not self.SUPPORTS_SELECT_INTO and into: 2473 into.pop() 2474 2475 hint = self.sql(expression, "hint") 2476 distinct = self.sql(expression, "distinct") 2477 distinct = f" {distinct}" if distinct else "" 2478 kind = self.sql(expression, "kind") 2479 2480 limit = expression.args.get("limit") 2481 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2482 top = self.limit_sql(limit, top=True) 2483 limit.pop() 2484 else: 2485 top = "" 2486 2487 expressions = self.expressions(expression) 2488 2489 if kind: 2490 if kind in self.SELECT_KINDS: 2491 kind = f" AS {kind}" 2492 else: 2493 if kind == "STRUCT": 2494 expressions = self.expressions( 2495 sqls=[ 2496 self.sql( 2497 exp.Struct( 2498 expressions=[ 2499 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2500 if isinstance(e, exp.Alias) 2501 else e 2502 for e in expression.expressions 2503 ] 2504 ) 2505 ) 2506 ] 2507 ) 2508 kind = "" 2509 2510 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2511 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2512 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2513 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2514 sql = self.query_modifiers( 2515 expression, 2516 f"SELECT{top_distinct}{kind}{expressions}", 2517 self.sql(expression, "into", comment=False), 2518 self.sql(expression, "from", comment=False), 2519 ) 2520 2521 sql = self.prepend_ctes(expression, sql) 2522 2523 if not self.SUPPORTS_SELECT_INTO and into: 2524 if into.args.get("temporary"): 2525 table_kind = " TEMPORARY" 2526 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2527 table_kind = " UNLOGGED" 2528 else: 2529 table_kind = "" 2530 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2531 2532 return sql
2544 def star_sql(self, expression: exp.Star) -> str: 2545 except_ = self.expressions(expression, key="except", flat=True) 2546 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2547 replace = self.expressions(expression, key="replace", flat=True) 2548 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2549 rename = self.expressions(expression, key="rename", flat=True) 2550 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2551 return f"*{except_}{replace}{rename}"
2567 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2568 alias = self.sql(expression, "alias") 2569 alias = f"{sep}{alias}" if alias else "" 2570 sample = self.sql(expression, "sample") 2571 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2572 alias = f"{sample}{alias}" 2573 2574 # Set to None so it's not generated again by self.query_modifiers() 2575 expression.set("sample", None) 2576 2577 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2578 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2579 return self.prepend_ctes(expression, sql)
2585 def unnest_sql(self, expression: exp.Unnest) -> str: 2586 args = self.expressions(expression, flat=True) 2587 2588 alias = expression.args.get("alias") 2589 offset = expression.args.get("offset") 2590 2591 if self.UNNEST_WITH_ORDINALITY: 2592 if alias and isinstance(offset, exp.Expression): 2593 alias.append("columns", offset) 2594 2595 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2596 columns = alias.columns 2597 alias = self.sql(columns[0]) if columns else "" 2598 else: 2599 alias = self.sql(alias) 2600 2601 alias = f" AS {alias}" if alias else alias 2602 if self.UNNEST_WITH_ORDINALITY: 2603 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2604 else: 2605 if isinstance(offset, exp.Expression): 2606 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2607 elif offset: 2608 suffix = f"{alias} WITH OFFSET" 2609 else: 2610 suffix = alias 2611 2612 return f"UNNEST({args}){suffix}"
2621 def window_sql(self, expression: exp.Window) -> str: 2622 this = self.sql(expression, "this") 2623 partition = self.partition_by_sql(expression) 2624 order = expression.args.get("order") 2625 order = self.order_sql(order, flat=True) if order else "" 2626 spec = self.sql(expression, "spec") 2627 alias = self.sql(expression, "alias") 2628 over = self.sql(expression, "over") or "OVER" 2629 2630 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2631 2632 first = expression.args.get("first") 2633 if first is None: 2634 first = "" 2635 else: 2636 first = "FIRST" if first else "LAST" 2637 2638 if not partition and not order and not spec and alias: 2639 return f"{this} {alias}" 2640 2641 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg) 2642 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2648 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2649 kind = self.sql(expression, "kind") 2650 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2651 end = ( 2652 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2653 or "CURRENT ROW" 2654 ) 2655 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]:
2668 def bracket_offset_expressions( 2669 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2670 ) -> t.List[exp.Expression]: 2671 return apply_index_offset( 2672 expression.this, 2673 expression.expressions, 2674 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2675 )
2685 def any_sql(self, expression: exp.Any) -> str: 2686 this = self.sql(expression, "this") 2687 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2688 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2689 this = self.wrap(this) 2690 return f"ANY{this}" 2691 return f"ANY {this}"
2696 def case_sql(self, expression: exp.Case) -> str: 2697 this = self.sql(expression, "this") 2698 statements = [f"CASE {this}" if this else "CASE"] 2699 2700 for e in expression.args["ifs"]: 2701 statements.append(f"WHEN {self.sql(e, 'this')}") 2702 statements.append(f"THEN {self.sql(e, 'true')}") 2703 2704 default = self.sql(expression, "default") 2705 2706 if default: 2707 statements.append(f"ELSE {default}") 2708 2709 statements.append("END") 2710 2711 if self.pretty and self.too_wide(statements): 2712 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2713 2714 return " ".join(statements)
2731 def trim_sql(self, expression: exp.Trim) -> str: 2732 trim_type = self.sql(expression, "position") 2733 2734 if trim_type == "LEADING": 2735 func_name = "LTRIM" 2736 elif trim_type == "TRAILING": 2737 func_name = "RTRIM" 2738 else: 2739 func_name = "TRIM" 2740 2741 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]:
2743 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2744 args = expression.expressions 2745 if isinstance(expression, exp.ConcatWs): 2746 args = args[1:] # Skip the delimiter 2747 2748 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2749 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2750 2751 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2752 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2753 2754 return args
2756 def concat_sql(self, expression: exp.Concat) -> str: 2757 expressions = self.convert_concat_args(expression) 2758 2759 # Some dialects don't allow a single-argument CONCAT call 2760 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2761 return self.sql(expressions[0]) 2762 2763 return self.func("CONCAT", *expressions)
2774 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2775 expressions = self.expressions(expression, flat=True) 2776 reference = self.sql(expression, "reference") 2777 reference = f" {reference}" if reference else "" 2778 delete = self.sql(expression, "delete") 2779 delete = f" ON DELETE {delete}" if delete else "" 2780 update = self.sql(expression, "update") 2781 update = f" ON UPDATE {update}" if update else "" 2782 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}"
2784 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2785 expressions = self.expressions(expression, flat=True) 2786 options = self.expressions(expression, key="options", flat=True, sep=" ") 2787 options = f" {options}" if options else "" 2788 return f"PRIMARY KEY ({expressions}){options}"
2801 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2802 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2803 2804 if expression.args.get("escape"): 2805 path = self.escape_str(path) 2806 2807 if self.QUOTE_JSON_PATH: 2808 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2809 2810 return path
2812 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2813 if isinstance(expression, exp.JSONPathPart): 2814 transform = self.TRANSFORMS.get(expression.__class__) 2815 if not callable(transform): 2816 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2817 return "" 2818 2819 return transform(self, expression) 2820 2821 if isinstance(expression, int): 2822 return str(expression) 2823 2824 if self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 2825 escaped = expression.replace("'", "\\'") 2826 escaped = f"\\'{expression}\\'" 2827 else: 2828 escaped = expression.replace('"', '\\"') 2829 escaped = f'"{escaped}"' 2830 2831 return escaped
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
2836 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 2837 null_handling = expression.args.get("null_handling") 2838 null_handling = f" {null_handling}" if null_handling else "" 2839 2840 unique_keys = expression.args.get("unique_keys") 2841 if unique_keys is not None: 2842 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 2843 else: 2844 unique_keys = "" 2845 2846 return_type = self.sql(expression, "return_type") 2847 return_type = f" RETURNING {return_type}" if return_type else "" 2848 encoding = self.sql(expression, "encoding") 2849 encoding = f" ENCODING {encoding}" if encoding else "" 2850 2851 return self.func( 2852 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 2853 *expression.expressions, 2854 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 2855 )
2860 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 2861 null_handling = expression.args.get("null_handling") 2862 null_handling = f" {null_handling}" if null_handling else "" 2863 return_type = self.sql(expression, "return_type") 2864 return_type = f" RETURNING {return_type}" if return_type else "" 2865 strict = " STRICT" if expression.args.get("strict") else "" 2866 return self.func( 2867 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 2868 )
2870 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 2871 this = self.sql(expression, "this") 2872 order = self.sql(expression, "order") 2873 null_handling = expression.args.get("null_handling") 2874 null_handling = f" {null_handling}" if null_handling else "" 2875 return_type = self.sql(expression, "return_type") 2876 return_type = f" RETURNING {return_type}" if return_type else "" 2877 strict = " STRICT" if expression.args.get("strict") else "" 2878 return self.func( 2879 "JSON_ARRAYAGG", 2880 this, 2881 suffix=f"{order}{null_handling}{return_type}{strict})", 2882 )
2884 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 2885 path = self.sql(expression, "path") 2886 path = f" PATH {path}" if path else "" 2887 nested_schema = self.sql(expression, "nested_schema") 2888 2889 if nested_schema: 2890 return f"NESTED{path} {nested_schema}" 2891 2892 this = self.sql(expression, "this") 2893 kind = self.sql(expression, "kind") 2894 kind = f" {kind}" if kind else "" 2895 return f"{this}{kind}{path}"
2900 def jsontable_sql(self, expression: exp.JSONTable) -> str: 2901 this = self.sql(expression, "this") 2902 path = self.sql(expression, "path") 2903 path = f", {path}" if path else "" 2904 error_handling = expression.args.get("error_handling") 2905 error_handling = f" {error_handling}" if error_handling else "" 2906 empty_handling = expression.args.get("empty_handling") 2907 empty_handling = f" {empty_handling}" if empty_handling else "" 2908 schema = self.sql(expression, "schema") 2909 return self.func( 2910 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 2911 )
2913 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 2914 this = self.sql(expression, "this") 2915 kind = self.sql(expression, "kind") 2916 path = self.sql(expression, "path") 2917 path = f" {path}" if path else "" 2918 as_json = " AS JSON" if expression.args.get("as_json") else "" 2919 return f"{this} {kind}{path}{as_json}"
2921 def openjson_sql(self, expression: exp.OpenJSON) -> str: 2922 this = self.sql(expression, "this") 2923 path = self.sql(expression, "path") 2924 path = f", {path}" if path else "" 2925 expressions = self.expressions(expression) 2926 with_ = ( 2927 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 2928 if expressions 2929 else "" 2930 ) 2931 return f"OPENJSON({this}{path}){with_}"
2933 def in_sql(self, expression: exp.In) -> str: 2934 query = expression.args.get("query") 2935 unnest = expression.args.get("unnest") 2936 field = expression.args.get("field") 2937 is_global = " GLOBAL" if expression.args.get("is_global") else "" 2938 2939 if query: 2940 in_sql = self.sql(query) 2941 elif unnest: 2942 in_sql = self.in_unnest_op(unnest) 2943 elif field: 2944 in_sql = self.sql(field) 2945 else: 2946 in_sql = f"({self.expressions(expression, flat=True)})" 2947 2948 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
2953 def interval_sql(self, expression: exp.Interval) -> str: 2954 unit = self.sql(expression, "unit") 2955 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 2956 unit = self.TIME_PART_SINGULARS.get(unit, unit) 2957 unit = f" {unit}" if unit else "" 2958 2959 if self.SINGLE_STRING_INTERVAL: 2960 this = expression.this.name if expression.this else "" 2961 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 2962 2963 this = self.sql(expression, "this") 2964 if this: 2965 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 2966 this = f" {this}" if unwrapped else f" ({this})" 2967 2968 return f"INTERVAL{this}{unit}"
2973 def reference_sql(self, expression: exp.Reference) -> str: 2974 this = self.sql(expression, "this") 2975 expressions = self.expressions(expression, flat=True) 2976 expressions = f"({expressions})" if expressions else "" 2977 options = self.expressions(expression, key="options", flat=True, sep=" ") 2978 options = f" {options}" if options else "" 2979 return f"REFERENCES {this}{expressions}{options}"
2981 def anonymous_sql(self, expression: exp.Anonymous) -> str: 2982 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 2983 parent = expression.parent 2984 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 2985 return self.func( 2986 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 2987 )
3007 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3008 alias = expression.args["alias"] 3009 3010 identifier_alias = isinstance(alias, exp.Identifier) 3011 literal_alias = isinstance(alias, exp.Literal) 3012 3013 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3014 alias.replace(exp.Literal.string(alias.output_name)) 3015 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3016 alias.replace(exp.to_identifier(alias.output_name)) 3017 3018 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:
3056 def connector_sql( 3057 self, 3058 expression: exp.Connector, 3059 op: str, 3060 stack: t.Optional[t.List[str | exp.Expression]] = None, 3061 ) -> str: 3062 if stack is not None: 3063 if expression.expressions: 3064 stack.append(self.expressions(expression, sep=f" {op} ")) 3065 else: 3066 stack.append(expression.right) 3067 if expression.comments and self.comments: 3068 for comment in expression.comments: 3069 if comment: 3070 op += f" /*{self.pad_comment(comment)}*/" 3071 stack.extend((op, expression.left)) 3072 return op 3073 3074 stack = [expression] 3075 sqls: t.List[str] = [] 3076 ops = set() 3077 3078 while stack: 3079 node = stack.pop() 3080 if isinstance(node, exp.Connector): 3081 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3082 else: 3083 sql = self.sql(node) 3084 if sqls and sqls[-1] in ops: 3085 sqls[-1] += f" {sql}" 3086 else: 3087 sqls.append(sql) 3088 3089 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3090 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3110 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3111 format_sql = self.sql(expression, "format") 3112 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3113 to_sql = self.sql(expression, "to") 3114 to_sql = f" {to_sql}" if to_sql else "" 3115 action = self.sql(expression, "action") 3116 action = f" {action}" if action else "" 3117 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{format_sql}{action})"
3131 def comment_sql(self, expression: exp.Comment) -> str: 3132 this = self.sql(expression, "this") 3133 kind = expression.args["kind"] 3134 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3135 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3136 expression_sql = self.sql(expression, "expression") 3137 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3139 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3140 this = self.sql(expression, "this") 3141 delete = " DELETE" if expression.args.get("delete") else "" 3142 recompress = self.sql(expression, "recompress") 3143 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3144 to_disk = self.sql(expression, "to_disk") 3145 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3146 to_volume = self.sql(expression, "to_volume") 3147 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3148 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3150 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3151 where = self.sql(expression, "where") 3152 group = self.sql(expression, "group") 3153 aggregates = self.expressions(expression, key="aggregates") 3154 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3155 3156 if not (where or group or aggregates) and len(expression.expressions) == 1: 3157 return f"TTL {self.expressions(expression, flat=True)}" 3158 3159 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3176 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3177 this = self.sql(expression, "this") 3178 3179 dtype = self.sql(expression, "dtype") 3180 if dtype: 3181 collate = self.sql(expression, "collate") 3182 collate = f" COLLATE {collate}" if collate else "" 3183 using = self.sql(expression, "using") 3184 using = f" USING {using}" if using else "" 3185 return f"ALTER COLUMN {this} SET DATA TYPE {dtype}{collate}{using}" 3186 3187 default = self.sql(expression, "default") 3188 if default: 3189 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3190 3191 comment = self.sql(expression, "comment") 3192 if comment: 3193 return f"ALTER COLUMN {this} COMMENT {comment}" 3194 3195 allow_null = expression.args.get("allow_null") 3196 drop = expression.args.get("drop") 3197 3198 if not drop and not allow_null: 3199 self.unsupported("Unsupported ALTER COLUMN syntax") 3200 3201 if allow_null is not None: 3202 keyword = "DROP" if drop else "SET" 3203 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3204 3205 return f"ALTER COLUMN {this} DROP DEFAULT"
3213 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3214 compound = " COMPOUND" if expression.args.get("compound") else "" 3215 this = self.sql(expression, "this") 3216 expressions = self.expressions(expression, flat=True) 3217 expressions = f"({expressions})" if expressions else "" 3218 return f"ALTER{compound} SORTKEY {this or expressions}"
3220 def renametable_sql(self, expression: exp.RenameTable) -> str: 3221 if not self.RENAME_TABLE_WITH_DB: 3222 # Remove db from tables 3223 expression = expression.transform( 3224 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3225 ).assert_is(exp.RenameTable) 3226 this = self.sql(expression, "this") 3227 return f"RENAME TO {this}"
3239 def alter_sql(self, expression: exp.Alter) -> str: 3240 actions = expression.args["actions"] 3241 3242 if isinstance(actions[0], exp.ColumnDef): 3243 actions = self.add_column_sql(expression) 3244 elif isinstance(actions[0], exp.Schema): 3245 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3246 elif isinstance(actions[0], exp.Delete): 3247 actions = self.expressions(expression, key="actions", flat=True) 3248 elif isinstance(actions[0], exp.Query): 3249 actions = "AS " + self.expressions(expression, key="actions") 3250 else: 3251 actions = self.expressions(expression, key="actions", flat=True) 3252 3253 exists = " IF EXISTS" if expression.args.get("exists") else "" 3254 on_cluster = self.sql(expression, "cluster") 3255 on_cluster = f" {on_cluster}" if on_cluster else "" 3256 only = " ONLY" if expression.args.get("only") else "" 3257 options = self.expressions(expression, key="options") 3258 options = f", {options}" if options else "" 3259 kind = self.sql(expression, "kind") 3260 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3261 3262 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{not_valid}{options}"
3264 def add_column_sql(self, expression: exp.Alter) -> str: 3265 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3266 return self.expressions( 3267 expression, 3268 key="actions", 3269 prefix="ADD COLUMN ", 3270 skip_first=True, 3271 ) 3272 return f"ADD {self.expressions(expression, key='actions', flat=True)}"
3282 def distinct_sql(self, expression: exp.Distinct) -> str: 3283 this = self.expressions(expression, flat=True) 3284 3285 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3286 case = exp.case() 3287 for arg in expression.expressions: 3288 case = case.when(arg.is_(exp.null()), exp.null()) 3289 this = self.sql(case.else_(f"({this})")) 3290 3291 this = f" {this}" if this else "" 3292 3293 on = self.sql(expression, "on") 3294 on = f" ON {on}" if on else "" 3295 return f"DISTINCT{this}{on}"
3324 def div_sql(self, expression: exp.Div) -> str: 3325 l, r = expression.left, expression.right 3326 3327 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3328 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3329 3330 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3331 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3332 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3333 3334 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3335 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3336 return self.sql( 3337 exp.cast( 3338 l / r, 3339 to=exp.DataType.Type.BIGINT, 3340 ) 3341 ) 3342 3343 return self.binary(expression, "/")
3431 def log_sql(self, expression: exp.Log) -> str: 3432 this = expression.this 3433 expr = expression.expression 3434 3435 if self.dialect.LOG_BASE_FIRST is False: 3436 this, expr = expr, this 3437 elif self.dialect.LOG_BASE_FIRST is None and expr: 3438 if this.name in ("2", "10"): 3439 return self.func(f"LOG{this.name}", expr) 3440 3441 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3442 3443 return self.func("LOG", this, expr)
3452 def binary(self, expression: exp.Binary, op: str) -> str: 3453 sqls: t.List[str] = [] 3454 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3455 binary_type = type(expression) 3456 3457 while stack: 3458 node = stack.pop() 3459 3460 if type(node) is binary_type: 3461 op_func = node.args.get("operator") 3462 if op_func: 3463 op = f"OPERATOR({self.sql(op_func)})" 3464 3465 stack.append(node.right) 3466 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3467 stack.append(node.left) 3468 else: 3469 sqls.append(self.sql(node)) 3470 3471 return "".join(sqls)
3473 def function_fallback_sql(self, expression: exp.Func) -> str: 3474 args = [] 3475 3476 for key in expression.arg_types: 3477 arg_value = expression.args.get(key) 3478 3479 if isinstance(arg_value, list): 3480 for value in arg_value: 3481 args.append(value) 3482 elif arg_value is not None: 3483 args.append(arg_value) 3484 3485 if self.normalize_functions: 3486 name = expression.sql_name() 3487 else: 3488 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3489 3490 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:
3492 def func( 3493 self, 3494 name: str, 3495 *args: t.Optional[exp.Expression | str], 3496 prefix: str = "(", 3497 suffix: str = ")", 3498 normalize: bool = True, 3499 ) -> str: 3500 name = self.normalize_func(name) if normalize else name 3501 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
3503 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str: 3504 arg_sqls = tuple( 3505 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3506 ) 3507 if self.pretty and self.too_wide(arg_sqls): 3508 return self.indent("\n" + ",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True) 3509 return ", ".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]:
3514 def format_time( 3515 self, 3516 expression: exp.Expression, 3517 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3518 inverse_time_trie: t.Optional[t.Dict] = None, 3519 ) -> t.Optional[str]: 3520 return format_time( 3521 self.sql(expression, "format"), 3522 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3523 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3524 )
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:
3526 def expressions( 3527 self, 3528 expression: t.Optional[exp.Expression] = None, 3529 key: t.Optional[str] = None, 3530 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3531 flat: bool = False, 3532 indent: bool = True, 3533 skip_first: bool = False, 3534 skip_last: bool = False, 3535 sep: str = ", ", 3536 prefix: str = "", 3537 dynamic: bool = False, 3538 new_line: bool = False, 3539 ) -> str: 3540 expressions = expression.args.get(key or "expressions") if expression else sqls 3541 3542 if not expressions: 3543 return "" 3544 3545 if flat: 3546 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3547 3548 num_sqls = len(expressions) 3549 result_sqls = [] 3550 3551 for i, e in enumerate(expressions): 3552 sql = self.sql(e, comment=False) 3553 if not sql: 3554 continue 3555 3556 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3557 3558 if self.pretty: 3559 if self.leading_comma: 3560 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3561 else: 3562 result_sqls.append( 3563 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3564 ) 3565 else: 3566 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3567 3568 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3569 if new_line: 3570 result_sqls.insert(0, "") 3571 result_sqls.append("") 3572 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3573 else: 3574 result_sql = "".join(result_sqls) 3575 3576 return ( 3577 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3578 if indent 3579 else result_sql 3580 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3582 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3583 flat = flat or isinstance(expression.parent, exp.Properties) 3584 expressions_sql = self.expressions(expression, flat=flat) 3585 if flat: 3586 return f"{op} {expressions_sql}" 3587 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3589 def naked_property(self, expression: exp.Property) -> str: 3590 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3591 if not property_name: 3592 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3593 return f"{property_name} {self.sql(expression, 'this')}"
3601 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3602 this = self.sql(expression, "this") 3603 expressions = self.no_identify(self.expressions, expression) 3604 expressions = ( 3605 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3606 ) 3607 return f"{this}{expressions}"
3617 def when_sql(self, expression: exp.When) -> str: 3618 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3619 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3620 condition = self.sql(expression, "condition") 3621 condition = f" AND {condition}" if condition else "" 3622 3623 then_expression = expression.args.get("then") 3624 if isinstance(then_expression, exp.Insert): 3625 this = self.sql(then_expression, "this") 3626 this = f"INSERT {this}" if this else "INSERT" 3627 then = self.sql(then_expression, "expression") 3628 then = f"{this} VALUES {then}" if then else this 3629 elif isinstance(then_expression, exp.Update): 3630 if isinstance(then_expression.args.get("expressions"), exp.Star): 3631 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3632 else: 3633 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}" 3634 else: 3635 then = self.sql(then_expression) 3636 return f"WHEN {matched}{source}{condition} THEN {then}"
3638 def merge_sql(self, expression: exp.Merge) -> str: 3639 table = expression.this 3640 table_alias = "" 3641 3642 hints = table.args.get("hints") 3643 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3644 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3645 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3646 3647 this = self.sql(table) 3648 using = f"USING {self.sql(expression, 'using')}" 3649 on = f"ON {self.sql(expression, 'on')}" 3650 expressions = self.expressions(expression, sep=" ", indent=False) 3651 returning = self.sql(expression, "returning") 3652 if returning: 3653 expressions = f"{expressions}{returning}" 3654 3655 sep = self.sep() 3656 3657 return self.prepend_ctes( 3658 expression, 3659 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{expressions}", 3660 )
3666 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3667 if not self.SUPPORTS_TO_NUMBER: 3668 self.unsupported("Unsupported TO_NUMBER function") 3669 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3670 3671 fmt = expression.args.get("format") 3672 if not fmt: 3673 self.unsupported("Conversion format is required for TO_NUMBER") 3674 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3675 3676 return self.func("TO_NUMBER", expression.this, fmt)
3678 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3679 this = self.sql(expression, "this") 3680 kind = self.sql(expression, "kind") 3681 settings_sql = self.expressions(expression, key="settings", sep=" ") 3682 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3683 return f"{this}({kind}{args})"
3698 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3699 expressions = self.expressions(expression, flat=True) 3700 expressions = f" {self.wrap(expressions)}" if expressions else "" 3701 buckets = self.sql(expression, "buckets") 3702 kind = self.sql(expression, "kind") 3703 buckets = f" BUCKETS {buckets}" if buckets else "" 3704 order = self.sql(expression, "order") 3705 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
3710 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3711 expressions = self.expressions(expression, key="expressions", flat=True) 3712 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3713 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3714 buckets = self.sql(expression, "buckets") 3715 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
3717 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3718 this = self.sql(expression, "this") 3719 having = self.sql(expression, "having") 3720 3721 if having: 3722 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3723 3724 return self.func("ANY_VALUE", this)
3726 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3727 transform = self.func("TRANSFORM", *expression.expressions) 3728 row_format_before = self.sql(expression, "row_format_before") 3729 row_format_before = f" {row_format_before}" if row_format_before else "" 3730 record_writer = self.sql(expression, "record_writer") 3731 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3732 using = f" USING {self.sql(expression, 'command_script')}" 3733 schema = self.sql(expression, "schema") 3734 schema = f" AS {schema}" if schema else "" 3735 row_format_after = self.sql(expression, "row_format_after") 3736 row_format_after = f" {row_format_after}" if row_format_after else "" 3737 record_reader = self.sql(expression, "record_reader") 3738 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3739 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
3741 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3742 key_block_size = self.sql(expression, "key_block_size") 3743 if key_block_size: 3744 return f"KEY_BLOCK_SIZE = {key_block_size}" 3745 3746 using = self.sql(expression, "using") 3747 if using: 3748 return f"USING {using}" 3749 3750 parser = self.sql(expression, "parser") 3751 if parser: 3752 return f"WITH PARSER {parser}" 3753 3754 comment = self.sql(expression, "comment") 3755 if comment: 3756 return f"COMMENT {comment}" 3757 3758 visible = expression.args.get("visible") 3759 if visible is not None: 3760 return "VISIBLE" if visible else "INVISIBLE" 3761 3762 engine_attr = self.sql(expression, "engine_attr") 3763 if engine_attr: 3764 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3765 3766 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3767 if secondary_engine_attr: 3768 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3769 3770 self.unsupported("Unsupported index constraint option.") 3771 return ""
3777 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3778 kind = self.sql(expression, "kind") 3779 kind = f"{kind} INDEX" if kind else "INDEX" 3780 this = self.sql(expression, "this") 3781 this = f" {this}" if this else "" 3782 index_type = self.sql(expression, "index_type") 3783 index_type = f" USING {index_type}" if index_type else "" 3784 expressions = self.expressions(expression, flat=True) 3785 expressions = f" ({expressions})" if expressions else "" 3786 options = self.expressions(expression, key="options", sep=" ") 3787 options = f" {options}" if options else "" 3788 return f"{kind}{this}{index_type}{expressions}{options}"
3790 def nvl2_sql(self, expression: exp.Nvl2) -> str: 3791 if self.NVL2_SUPPORTED: 3792 return self.function_fallback_sql(expression) 3793 3794 case = exp.Case().when( 3795 expression.this.is_(exp.null()).not_(copy=False), 3796 expression.args["true"], 3797 copy=False, 3798 ) 3799 else_cond = expression.args.get("false") 3800 if else_cond: 3801 case.else_(else_cond, copy=False) 3802 3803 return self.sql(case)
3805 def comprehension_sql(self, expression: exp.Comprehension) -> str: 3806 this = self.sql(expression, "this") 3807 expr = self.sql(expression, "expression") 3808 iterator = self.sql(expression, "iterator") 3809 condition = self.sql(expression, "condition") 3810 condition = f" IF {condition}" if condition else "" 3811 return f"{this} FOR {expr} IN {iterator}{condition}"
3819 def predict_sql(self, expression: exp.Predict) -> str: 3820 model = self.sql(expression, "this") 3821 model = f"MODEL {model}" 3822 table = self.sql(expression, "expression") 3823 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 3824 parameters = self.sql(expression, "params_struct") 3825 return self.func("PREDICT", model, table, parameters or None)
3837 def toarray_sql(self, expression: exp.ToArray) -> str: 3838 arg = expression.this 3839 if not arg.type: 3840 from sqlglot.optimizer.annotate_types import annotate_types 3841 3842 arg = annotate_types(arg) 3843 3844 if arg.is_type(exp.DataType.Type.ARRAY): 3845 return self.sql(arg) 3846 3847 cond_for_null = arg.is_(exp.null()) 3848 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
3864 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 3865 this = expression.this 3866 time_format = self.format_time(expression) 3867 3868 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 3869 return self.sql( 3870 exp.cast( 3871 exp.StrToTime(this=this, format=expression.args["format"]), 3872 exp.DataType.Type.DATE, 3873 ) 3874 ) 3875 3876 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 3877 return self.sql(this) 3878 3879 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
3891 def lastday_sql(self, expression: exp.LastDay) -> str: 3892 if self.LAST_DAY_SUPPORTS_DATE_PART: 3893 return self.function_fallback_sql(expression) 3894 3895 unit = expression.text("unit") 3896 if unit and unit != "MONTH": 3897 self.unsupported("Date parts are not supported in LAST_DAY.") 3898 3899 return self.func("LAST_DAY", expression.this)
3908 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 3909 if self.CAN_IMPLEMENT_ARRAY_ANY: 3910 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 3911 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 3912 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 3913 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 3914 3915 from sqlglot.dialects import Dialect 3916 3917 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 3918 if self.dialect.__class__ != Dialect: 3919 self.unsupported("ARRAY_ANY is unsupported") 3920 3921 return self.function_fallback_sql(expression)
3923 def struct_sql(self, expression: exp.Struct) -> str: 3924 expression.set( 3925 "expressions", 3926 [ 3927 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 3928 if isinstance(e, exp.PropertyEQ) 3929 else e 3930 for e in expression.expressions 3931 ], 3932 ) 3933 3934 return self.function_fallback_sql(expression)
3942 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 3943 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 3944 tables = f" {self.expressions(expression)}" 3945 3946 exists = " IF EXISTS" if expression.args.get("exists") else "" 3947 3948 on_cluster = self.sql(expression, "cluster") 3949 on_cluster = f" {on_cluster}" if on_cluster else "" 3950 3951 identity = self.sql(expression, "identity") 3952 identity = f" {identity} IDENTITY" if identity else "" 3953 3954 option = self.sql(expression, "option") 3955 option = f" {option}" if option else "" 3956 3957 partition = self.sql(expression, "partition") 3958 partition = f" {partition}" if partition else "" 3959 3960 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
3964 def convert_sql(self, expression: exp.Convert) -> str: 3965 to = expression.this 3966 value = expression.expression 3967 style = expression.args.get("style") 3968 safe = expression.args.get("safe") 3969 strict = expression.args.get("strict") 3970 3971 if not to or not value: 3972 return "" 3973 3974 # Retrieve length of datatype and override to default if not specified 3975 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 3976 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 3977 3978 transformed: t.Optional[exp.Expression] = None 3979 cast = exp.Cast if strict else exp.TryCast 3980 3981 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 3982 if isinstance(style, exp.Literal) and style.is_int: 3983 from sqlglot.dialects.tsql import TSQL 3984 3985 style_value = style.name 3986 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 3987 if not converted_style: 3988 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 3989 3990 fmt = exp.Literal.string(converted_style) 3991 3992 if to.this == exp.DataType.Type.DATE: 3993 transformed = exp.StrToDate(this=value, format=fmt) 3994 elif to.this == exp.DataType.Type.DATETIME: 3995 transformed = exp.StrToTime(this=value, format=fmt) 3996 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 3997 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 3998 elif to.this == exp.DataType.Type.TEXT: 3999 transformed = exp.TimeToStr(this=value, format=fmt) 4000 4001 if not transformed: 4002 transformed = cast(this=value, to=to, safe=safe) 4003 4004 return self.sql(transformed)
4060 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4061 option = self.sql(expression, "this") 4062 4063 if expression.expressions: 4064 upper = option.upper() 4065 4066 # Snowflake FILE_FORMAT options are separated by whitespace 4067 sep = " " if upper == "FILE_FORMAT" else ", " 4068 4069 # Databricks copy/format options do not set their list of values with EQ 4070 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4071 values = self.expressions(expression, flat=True, sep=sep) 4072 return f"{option}{op}({values})" 4073 4074 value = self.sql(expression, "expression") 4075 4076 if not value: 4077 return option 4078 4079 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4080 4081 return f"{option}{op}{value}"
4083 def credentials_sql(self, expression: exp.Credentials) -> str: 4084 cred_expr = expression.args.get("credentials") 4085 if isinstance(cred_expr, exp.Literal): 4086 # Redshift case: CREDENTIALS <string> 4087 credentials = self.sql(expression, "credentials") 4088 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4089 else: 4090 # Snowflake case: CREDENTIALS = (...) 4091 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4092 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4093 4094 storage = self.sql(expression, "storage") 4095 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4096 4097 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4098 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4099 4100 iam_role = self.sql(expression, "iam_role") 4101 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4102 4103 region = self.sql(expression, "region") 4104 region = f" REGION {region}" if region else "" 4105 4106 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4108 def copy_sql(self, expression: exp.Copy) -> str: 4109 this = self.sql(expression, "this") 4110 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4111 4112 credentials = self.sql(expression, "credentials") 4113 credentials = self.seg(credentials) if credentials else "" 4114 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4115 files = self.expressions(expression, key="files", flat=True) 4116 4117 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4118 params = self.expressions( 4119 expression, 4120 key="params", 4121 sep=sep, 4122 new_line=True, 4123 skip_last=True, 4124 skip_first=True, 4125 indent=self.COPY_PARAMS_ARE_WRAPPED, 4126 ) 4127 4128 if params: 4129 if self.COPY_PARAMS_ARE_WRAPPED: 4130 params = f" WITH ({params})" 4131 elif not self.pretty: 4132 params = f" {params}" 4133 4134 return f"COPY{this}{kind} {files}{credentials}{params}"
4139 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4140 on_sql = "ON" if expression.args.get("on") else "OFF" 4141 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4142 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4143 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4144 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4145 4146 if filter_col or retention_period: 4147 on_sql = self.func("ON", filter_col, retention_period) 4148 4149 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4151 def maskingpolicycolumnconstraint_sql( 4152 self, expression: exp.MaskingPolicyColumnConstraint 4153 ) -> str: 4154 this = self.sql(expression, "this") 4155 expressions = self.expressions(expression, flat=True) 4156 expressions = f" USING ({expressions})" if expressions else "" 4157 return f"MASKING POLICY {this}{expressions}"
4167 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4168 this = self.sql(expression, "this") 4169 expr = expression.expression 4170 4171 if isinstance(expr, exp.Func): 4172 # T-SQL's CLR functions are case sensitive 4173 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4174 else: 4175 expr = self.sql(expression, "expression") 4176 4177 return self.scope_resolution(expr, this)
4185 def rand_sql(self, expression: exp.Rand) -> str: 4186 lower = self.sql(expression, "lower") 4187 upper = self.sql(expression, "upper") 4188 4189 if lower and upper: 4190 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4191 return self.func("RAND", expression.this)
4193 def changes_sql(self, expression: exp.Changes) -> str: 4194 information = self.sql(expression, "information") 4195 information = f"INFORMATION => {information}" 4196 at_before = self.sql(expression, "at_before") 4197 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4198 end = self.sql(expression, "end") 4199 end = f"{self.seg('')}{end}" if end else "" 4200 4201 return f"CHANGES ({information}){at_before}{end}"
4203 def pad_sql(self, expression: exp.Pad) -> str: 4204 prefix = "L" if expression.args.get("is_left") else "R" 4205 4206 fill_pattern = self.sql(expression, "fill_pattern") or None 4207 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4208 fill_pattern = "' '" 4209 4210 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4216 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4217 generate_series = exp.GenerateSeries(**expression.args) 4218 4219 parent = expression.parent 4220 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4221 parent = parent.parent 4222 4223 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4224 return self.sql(exp.Unnest(expressions=[generate_series])) 4225 4226 if isinstance(parent, exp.Select): 4227 self.unsupported("GenerateSeries projection unnesting is not supported.") 4228 4229 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4231 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4232 exprs = expression.expressions 4233 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4234 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4235 else: 4236 rhs = self.expressions(expression) 4237 4238 return self.func(name, expression.this, rhs)
4240 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4241 if self.SUPPORTS_CONVERT_TIMEZONE: 4242 return self.function_fallback_sql(expression) 4243 4244 source_tz = expression.args.get("source_tz") 4245 target_tz = expression.args.get("target_tz") 4246 timestamp = expression.args.get("timestamp") 4247 4248 if source_tz and timestamp: 4249 timestamp = exp.AtTimeZone( 4250 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4251 ) 4252 4253 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4254 4255 return self.sql(expr)
4257 def json_sql(self, expression: exp.JSON) -> str: 4258 this = self.sql(expression, "this") 4259 this = f" {this}" if this else "" 4260 4261 _with = expression.args.get("with") 4262 4263 if _with is None: 4264 with_sql = "" 4265 elif not _with: 4266 with_sql = " WITHOUT" 4267 else: 4268 with_sql = " WITH" 4269 4270 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4271 4272 return f"JSON{this}{with_sql}{unique_sql}"
4274 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4275 def _generate_on_options(arg: t.Any) -> str: 4276 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4277 4278 path = self.sql(expression, "path") 4279 returning = self.sql(expression, "returning") 4280 returning = f" RETURNING {returning}" if returning else "" 4281 4282 on_condition = self.sql(expression, "on_condition") 4283 on_condition = f" {on_condition}" if on_condition else "" 4284 4285 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4287 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4288 else_ = "ELSE " if expression.args.get("else_") else "" 4289 condition = self.sql(expression, "expression") 4290 condition = f"WHEN {condition} THEN " if condition else else_ 4291 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4292 return f"{condition}{insert}"
4300 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4301 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4302 empty = expression.args.get("empty") 4303 empty = ( 4304 f"DEFAULT {empty} ON EMPTY" 4305 if isinstance(empty, exp.Expression) 4306 else self.sql(expression, "empty") 4307 ) 4308 4309 error = expression.args.get("error") 4310 error = ( 4311 f"DEFAULT {error} ON ERROR" 4312 if isinstance(error, exp.Expression) 4313 else self.sql(expression, "error") 4314 ) 4315 4316 if error and empty: 4317 error = ( 4318 f"{empty} {error}" 4319 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4320 else f"{error} {empty}" 4321 ) 4322 empty = "" 4323 4324 null = self.sql(expression, "null") 4325 4326 return f"{empty}{error}{null}"
4328 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4329 this = self.sql(expression, "this") 4330 path = self.sql(expression, "path") 4331 4332 passing = self.expressions(expression, "passing") 4333 passing = f" PASSING {passing}" if passing else "" 4334 4335 on_condition = self.sql(expression, "on_condition") 4336 on_condition = f" {on_condition}" if on_condition else "" 4337 4338 path = f"{path}{passing}{on_condition}" 4339 4340 return self.func("JSON_EXISTS", this, path)
4342 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4343 array_agg = self.function_fallback_sql(expression) 4344 4345 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4346 parent = expression.parent 4347 if isinstance(parent, exp.Filter): 4348 parent_cond = parent.expression.this 4349 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4350 else: 4351 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4352 this = expression.this 4353 this_sql = ( 4354 self.expressions(this) 4355 if isinstance(this, exp.Distinct) 4356 else self.sql(expression, "this") 4357 ) 4358 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4359 4360 return array_agg
4368 def grant_sql(self, expression: exp.Grant) -> str: 4369 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4370 4371 kind = self.sql(expression, "kind") 4372 kind = f" {kind}" if kind else "" 4373 4374 securable = self.sql(expression, "securable") 4375 securable = f" {securable}" if securable else "" 4376 4377 principals = self.expressions(expression, key="principals", flat=True) 4378 4379 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4380 4381 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4405 def overlay_sql(self, expression: exp.Overlay): 4406 this = self.sql(expression, "this") 4407 expr = self.sql(expression, "expression") 4408 from_sql = self.sql(expression, "from") 4409 for_sql = self.sql(expression, "for") 4410 for_sql = f" FOR {for_sql}" if for_sql else "" 4411 4412 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"