1from __future__ import annotations
2
3import logging
4import typing as t
5from collections import defaultdict
6from functools import reduce
7
8from sqlglot import exp
9from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages
10from sqlglot.helper import apply_index_offset, csv, seq_get
11from sqlglot.time import format_time
12from sqlglot.tokens import Tokenizer, TokenType
13
14if t.TYPE_CHECKING:
15 from sqlglot._typing import E
16
17logger = logging.getLogger("sqlglot")
18
19
20class Generator:
21 """
22 Generator converts a given syntax tree to the corresponding SQL string.
23
24 Args:
25 pretty: Whether or not to format the produced SQL string.
26 Default: False.
27 identify: Determines when an identifier should be quoted. Possible values are:
28 False (default): Never quote, except in cases where it's mandatory by the dialect.
29 True or 'always': Always quote.
30 'safe': Only quote identifiers that are case insensitive.
31 normalize: Whether or not to normalize identifiers to lowercase.
32 Default: False.
33 pad: Determines the pad size in a formatted string.
34 Default: 2.
35 indent: Determines the indentation size in a formatted string.
36 Default: 2.
37 normalize_functions: Whether or not to normalize all function names. Possible values are:
38 "upper" or True (default): Convert names to uppercase.
39 "lower": Convert names to lowercase.
40 False: Disables function name normalization.
41 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.
42 Default ErrorLevel.WARN.
43 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.
44 This is only relevant if unsupported_level is ErrorLevel.RAISE.
45 Default: 3
46 leading_comma: Determines whether or not the comma is leading or trailing in select expressions.
47 This is only relevant when generating in pretty mode.
48 Default: False
49 max_text_width: The max number of characters in a segment before creating new lines in pretty mode.
50 The default is on the smaller end because the length only represents a segment and not the true
51 line length.
52 Default: 80
53 comments: Whether or not to preserve comments in the output SQL code.
54 Default: True
55 """
56
57 TRANSFORMS = {
58 exp.DateAdd: lambda self, e: self.func(
59 "DATE_ADD", e.this, e.expression, exp.Literal.string(e.text("unit"))
60 ),
61 exp.CaseSpecificColumnConstraint: lambda self, e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC",
62 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}",
63 exp.CharacterSetProperty: lambda self, e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}",
64 exp.CheckColumnConstraint: lambda self, e: f"CHECK ({self.sql(e, 'this')})",
65 exp.ClusteredColumnConstraint: lambda self, e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})",
66 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}",
67 exp.CopyGrantsProperty: lambda self, e: "COPY GRANTS",
68 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}",
69 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}",
70 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}",
71 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}",
72 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),
73 exp.ExternalProperty: lambda self, e: "EXTERNAL",
74 exp.HeapProperty: lambda self, e: "HEAP",
75 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
76 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}",
77 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}",
78 exp.LanguageProperty: lambda self, e: self.naked_property(e),
79 exp.LocationProperty: lambda self, e: self.naked_property(e),
80 exp.LogProperty: lambda self, e: f"{'NO ' if e.args.get('no') else ''}LOG",
81 exp.MaterializedProperty: lambda self, e: "MATERIALIZED",
82 exp.NoPrimaryIndexProperty: lambda self, e: "NO PRIMARY INDEX",
83 exp.NonClusteredColumnConstraint: lambda self, e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})",
84 exp.NotForReplicationColumnConstraint: lambda self, e: "NOT FOR REPLICATION",
85 exp.OnCommitProperty: lambda self, e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS",
86 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}",
87 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}",
88 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}",
89 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
90 exp.RemoteWithConnectionModelProperty: lambda self, e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
91 exp.ReturnsProperty: lambda self, e: self.naked_property(e),
92 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
93 exp.SetProperty: lambda self, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
94 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}",
95 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}",
96 exp.StabilityProperty: lambda self, e: e.name,
97 exp.TemporaryProperty: lambda self, e: f"TEMPORARY",
98 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}",
99 exp.TransientProperty: lambda self, e: "TRANSIENT",
100 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions),
101 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
102 exp.UppercaseColumnConstraint: lambda self, e: f"UPPERCASE",
103 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]),
104 exp.VolatileProperty: lambda self, e: "VOLATILE",
105 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}",
106 }
107
108 # Whether the base comes first
109 LOG_BASE_FIRST = True
110
111 # Whether or not null ordering is supported in order by
112 NULL_ORDERING_SUPPORTED = True
113
114 # Whether or not locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported
115 LOCKING_READS_SUPPORTED = False
116
117 # Always do union distinct or union all
118 EXPLICIT_UNION = False
119
120 # Wrap derived values in parens, usually standard but spark doesn't support it
121 WRAP_DERIVED_VALUES = True
122
123 # Whether or not create function uses an AS before the RETURN
124 CREATE_FUNCTION_RETURN_AS = True
125
126 # Whether or not MERGE ... WHEN MATCHED BY SOURCE is allowed
127 MATCHED_BY_SOURCE = True
128
129 # Whether or not the INTERVAL expression works only with values like '1 day'
130 SINGLE_STRING_INTERVAL = False
131
132 # Whether or not the plural form of date parts like day (i.e. "days") is supported in INTERVALs
133 INTERVAL_ALLOWS_PLURAL_FORM = True
134
135 # Whether or not the TABLESAMPLE clause supports a method name, like BERNOULLI
136 TABLESAMPLE_WITH_METHOD = True
137
138 # Whether or not to treat the number in TABLESAMPLE (50) as a percentage
139 TABLESAMPLE_SIZE_IS_PERCENT = False
140
141 # Whether or not limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH")
142 LIMIT_FETCH = "ALL"
143
144 # Whether or not limit and fetch allows expresions or just limits
145 LIMIT_ONLY_LITERALS = False
146
147 # Whether or not a table is allowed to be renamed with a db
148 RENAME_TABLE_WITH_DB = True
149
150 # The separator for grouping sets and rollups
151 GROUPINGS_SEP = ","
152
153 # The string used for creating an index on a table
154 INDEX_ON = "ON"
155
156 # Whether or not join hints should be generated
157 JOIN_HINTS = True
158
159 # Whether or not table hints should be generated
160 TABLE_HINTS = True
161
162 # Whether or not query hints should be generated
163 QUERY_HINTS = True
164
165 # What kind of separator to use for query hints
166 QUERY_HINT_SEP = ", "
167
168 # Whether or not comparing against booleans (e.g. x IS TRUE) is supported
169 IS_BOOL_ALLOWED = True
170
171 # Whether or not to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement
172 DUPLICATE_KEY_UPDATE_WITH_SET = True
173
174 # Whether or not to generate the limit as TOP <value> instead of LIMIT <value>
175 LIMIT_IS_TOP = False
176
177 # Whether or not to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
178 RETURNING_END = True
179
180 # Whether or not to generate the (+) suffix for columns used in old-style join conditions
181 COLUMN_JOIN_MARKS_SUPPORTED = False
182
183 # Whether or not to generate an unquoted value for EXTRACT's date part argument
184 EXTRACT_ALLOWS_QUOTES = True
185
186 # Whether or not TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax
187 TZ_TO_WITH_TIME_ZONE = False
188
189 # Whether or not the NVL2 function is supported
190 NVL2_SUPPORTED = True
191
192 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
193 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE")
194
195 # Whether or not VALUES statements can be used as derived tables.
196 # MySQL 5 and Redshift do not allow this, so when False, it will convert
197 # SELECT * VALUES into SELECT UNION
198 VALUES_AS_TABLE = True
199
200 # Whether or not the word COLUMN is included when adding a column with ALTER TABLE
201 ALTER_TABLE_ADD_COLUMN_KEYWORD = True
202
203 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
204 UNNEST_WITH_ORDINALITY = True
205
206 # Whether or not FILTER (WHERE cond) can be used for conditional aggregation
207 AGGREGATE_FILTER_SUPPORTED = True
208
209 # Whether or not JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds
210 SEMI_ANTI_JOIN_WITH_SIDE = True
211
212 # Whether or not session variables / parameters are supported, e.g. @x in T-SQL
213 SUPPORTS_PARAMETERS = True
214
215 # Whether or not to include the type of a computed column in the CREATE DDL
216 COMPUTED_COLUMN_WITH_TYPE = True
217
218 # Whether or not CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY
219 SUPPORTS_TABLE_COPY = True
220
221 # Whether or not parentheses are required around the table sample's expression
222 TABLESAMPLE_REQUIRES_PARENS = True
223
224 # Whether or not COLLATE is a function instead of a binary operator
225 COLLATE_IS_FUNC = False
226
227 # Whether or not data types support additional specifiers like e.g. CHAR or BYTE (oracle)
228 DATA_TYPE_SPECIFIERS_ALLOWED = False
229
230 # Whether or not conditions require booleans WHERE x = 0 vs WHERE x
231 ENSURE_BOOLS = False
232
233 # Whether or not the "RECURSIVE" keyword is required when defining recursive CTEs
234 CTE_RECURSIVE_KEYWORD_REQUIRED = True
235
236 # Whether the behavior of a / b depends on the types of a and b.
237 # False means a / b is always float division.
238 # True means a / b is integer division if both a and b are integers.
239 TYPED_DIVISION = False
240
241 # False means 1 / 0 throws an error.
242 # True means 1 / 0 returns null.
243 SAFE_DIVISION = False
244
245 TYPE_MAPPING = {
246 exp.DataType.Type.NCHAR: "CHAR",
247 exp.DataType.Type.NVARCHAR: "VARCHAR",
248 exp.DataType.Type.MEDIUMTEXT: "TEXT",
249 exp.DataType.Type.LONGTEXT: "TEXT",
250 exp.DataType.Type.TINYTEXT: "TEXT",
251 exp.DataType.Type.MEDIUMBLOB: "BLOB",
252 exp.DataType.Type.LONGBLOB: "BLOB",
253 exp.DataType.Type.TINYBLOB: "BLOB",
254 exp.DataType.Type.INET: "INET",
255 }
256
257 STAR_MAPPING = {
258 "except": "EXCEPT",
259 "replace": "REPLACE",
260 }
261
262 TIME_PART_SINGULARS = {
263 "microseconds": "microsecond",
264 "seconds": "second",
265 "minutes": "minute",
266 "hours": "hour",
267 "days": "day",
268 "weeks": "week",
269 "months": "month",
270 "quarters": "quarter",
271 "years": "year",
272 }
273
274 TOKEN_MAPPING: t.Dict[TokenType, str] = {}
275
276 STRUCT_DELIMITER = ("<", ">")
277
278 PARAMETER_TOKEN = "@"
279
280 PROPERTIES_LOCATION = {
281 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
282 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
283 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,
284 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,
285 exp.ChecksumProperty: exp.Properties.Location.POST_NAME,
286 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,
287 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,
288 exp.Cluster: exp.Properties.Location.POST_SCHEMA,
289 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
290 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
291 exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
292 exp.DictRange: exp.Properties.Location.POST_SCHEMA,
293 exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
294 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,
295 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,
296 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,
297 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,
298 exp.ExternalProperty: exp.Properties.Location.POST_CREATE,
299 exp.FallbackProperty: exp.Properties.Location.POST_NAME,
300 exp.FileFormatProperty: exp.Properties.Location.POST_WITH,
301 exp.FreespaceProperty: exp.Properties.Location.POST_NAME,
302 exp.HeapProperty: exp.Properties.Location.POST_WITH,
303 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,
304 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,
305 exp.JournalProperty: exp.Properties.Location.POST_NAME,
306 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,
307 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,
308 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,
309 exp.LockingProperty: exp.Properties.Location.POST_ALIAS,
310 exp.LogProperty: exp.Properties.Location.POST_NAME,
311 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,
312 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,
313 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,
314 exp.OnProperty: exp.Properties.Location.POST_SCHEMA,
315 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,
316 exp.Order: exp.Properties.Location.POST_SCHEMA,
317 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,
318 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,
319 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,
320 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,
321 exp.Property: exp.Properties.Location.POST_WITH,
322 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,
323 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,
324 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,
325 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,
326 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,
327 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,
328 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,
329 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,
330 exp.Set: exp.Properties.Location.POST_SCHEMA,
331 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,
332 exp.SetProperty: exp.Properties.Location.POST_CREATE,
333 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,
334 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
335 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
336 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
337 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
338 exp.TransientProperty: exp.Properties.Location.POST_CREATE,
339 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
340 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
341 exp.VolatileProperty: exp.Properties.Location.POST_CREATE,
342 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,
343 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,
344 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,
345 }
346
347 # Keywords that can't be used as unquoted identifier names
348 RESERVED_KEYWORDS: t.Set[str] = set()
349
350 # Expressions whose comments are separated from them for better formatting
351 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
352 exp.Create,
353 exp.Delete,
354 exp.Drop,
355 exp.From,
356 exp.Insert,
357 exp.Join,
358 exp.Select,
359 exp.Update,
360 exp.Where,
361 exp.With,
362 )
363
364 # Expressions that should not have their comments generated in maybe_comment
365 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = (
366 exp.Binary,
367 exp.Union,
368 )
369
370 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL
371 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = (
372 exp.Column,
373 exp.Literal,
374 exp.Neg,
375 exp.Paren,
376 )
377
378 # Expressions that need to have all CTEs under them bubbled up to them
379 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set()
380
381 KEY_VALUE_DEFINITONS = (exp.Bracket, exp.EQ, exp.PropertyEQ, exp.Slice)
382
383 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__"
384
385 # Autofilled
386 INVERSE_TIME_MAPPING: t.Dict[str, str] = {}
387 INVERSE_TIME_TRIE: t.Dict = {}
388 INVERSE_ESCAPE_SEQUENCES: t.Dict[str, str] = {}
389 INDEX_OFFSET = 0
390 UNNEST_COLUMN_ONLY = False
391 ALIAS_POST_TABLESAMPLE = False
392 IDENTIFIERS_CAN_START_WITH_DIGIT = False
393 STRICT_STRING_CONCAT = False
394 NORMALIZE_FUNCTIONS: bool | str = "upper"
395 NULL_ORDERING = "nulls_are_small"
396
397 can_identify: t.Callable[[str, str | bool], bool]
398
399 # Delimiters for quotes, identifiers and the corresponding escape characters
400 QUOTE_START = "'"
401 QUOTE_END = "'"
402 IDENTIFIER_START = '"'
403 IDENTIFIER_END = '"'
404 TOKENIZER_CLASS = Tokenizer
405
406 # Delimiters for bit, hex, byte and raw literals
407 BIT_START: t.Optional[str] = None
408 BIT_END: t.Optional[str] = None
409 HEX_START: t.Optional[str] = None
410 HEX_END: t.Optional[str] = None
411 BYTE_START: t.Optional[str] = None
412 BYTE_END: t.Optional[str] = None
413
414 __slots__ = (
415 "pretty",
416 "identify",
417 "normalize",
418 "pad",
419 "_indent",
420 "normalize_functions",
421 "unsupported_level",
422 "max_unsupported",
423 "leading_comma",
424 "max_text_width",
425 "comments",
426 "unsupported_messages",
427 "_escaped_quote_end",
428 "_escaped_identifier_end",
429 )
430
431 def __init__(
432 self,
433 pretty: t.Optional[bool] = None,
434 identify: str | bool = False,
435 normalize: bool = False,
436 pad: int = 2,
437 indent: int = 2,
438 normalize_functions: t.Optional[str | bool] = None,
439 unsupported_level: ErrorLevel = ErrorLevel.WARN,
440 max_unsupported: int = 3,
441 leading_comma: bool = False,
442 max_text_width: int = 80,
443 comments: bool = True,
444 ):
445 import sqlglot
446
447 self.pretty = pretty if pretty is not None else sqlglot.pretty
448 self.identify = identify
449 self.normalize = normalize
450 self.pad = pad
451 self._indent = indent
452 self.unsupported_level = unsupported_level
453 self.max_unsupported = max_unsupported
454 self.leading_comma = leading_comma
455 self.max_text_width = max_text_width
456 self.comments = comments
457
458 # This is both a Dialect property and a Generator argument, so we prioritize the latter
459 self.normalize_functions = (
460 self.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions
461 )
462
463 self.unsupported_messages: t.List[str] = []
464 self._escaped_quote_end: str = self.TOKENIZER_CLASS.STRING_ESCAPES[0] + self.QUOTE_END
465 self._escaped_identifier_end: str = (
466 self.TOKENIZER_CLASS.IDENTIFIER_ESCAPES[0] + self.IDENTIFIER_END
467 )
468
469 def generate(self, expression: exp.Expression, copy: bool = True) -> str:
470 """
471 Generates the SQL string corresponding to the given syntax tree.
472
473 Args:
474 expression: The syntax tree.
475 copy: Whether or not to copy the expression. The generator performs mutations so
476 it is safer to copy.
477
478 Returns:
479 The SQL string corresponding to `expression`.
480 """
481 if copy:
482 expression = expression.copy()
483
484 expression = self.preprocess(expression)
485
486 self.unsupported_messages = []
487 sql = self.sql(expression).strip()
488
489 if self.pretty:
490 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n")
491
492 if self.unsupported_level == ErrorLevel.IGNORE:
493 return sql
494
495 if self.unsupported_level == ErrorLevel.WARN:
496 for msg in self.unsupported_messages:
497 logger.warning(msg)
498 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:
499 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))
500
501 return sql
502
503 def preprocess(self, expression: exp.Expression) -> exp.Expression:
504 """Apply generic preprocessing transformations to a given expression."""
505 if (
506 not expression.parent
507 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES
508 and any(node.parent is not expression for node in expression.find_all(exp.With))
509 ):
510 from sqlglot.transforms import move_ctes_to_top_level
511
512 expression = move_ctes_to_top_level(expression)
513
514 if self.ENSURE_BOOLS:
515 from sqlglot.transforms import ensure_bools
516
517 expression = ensure_bools(expression)
518
519 return expression
520
521 def unsupported(self, message: str) -> None:
522 if self.unsupported_level == ErrorLevel.IMMEDIATE:
523 raise UnsupportedError(message)
524 self.unsupported_messages.append(message)
525
526 def sep(self, sep: str = " ") -> str:
527 return f"{sep.strip()}\n" if self.pretty else sep
528
529 def seg(self, sql: str, sep: str = " ") -> str:
530 return f"{self.sep(sep)}{sql}"
531
532 def pad_comment(self, comment: str) -> str:
533 comment = " " + comment if comment[0].strip() else comment
534 comment = comment + " " if comment[-1].strip() else comment
535 return comment
536
537 def maybe_comment(
538 self,
539 sql: str,
540 expression: t.Optional[exp.Expression] = None,
541 comments: t.Optional[t.List[str]] = None,
542 ) -> str:
543 comments = (
544 ((expression and expression.comments) if comments is None else comments) # type: ignore
545 if self.comments
546 else None
547 )
548
549 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):
550 return sql
551
552 comments_sql = " ".join(
553 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment
554 )
555
556 if not comments_sql:
557 return sql
558
559 if isinstance(expression, self.WITH_SEPARATED_COMMENTS):
560 return (
561 f"{self.sep()}{comments_sql}{sql}"
562 if sql[0].isspace()
563 else f"{comments_sql}{self.sep()}{sql}"
564 )
565
566 return f"{sql} {comments_sql}"
567
568 def wrap(self, expression: exp.Expression | str) -> str:
569 this_sql = self.indent(
570 self.sql(expression)
571 if isinstance(expression, (exp.Select, exp.Union))
572 else self.sql(expression, "this"),
573 level=1,
574 pad=0,
575 )
576 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
577
578 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
579 original = self.identify
580 self.identify = False
581 result = func(*args, **kwargs)
582 self.identify = original
583 return result
584
585 def normalize_func(self, name: str) -> str:
586 if self.normalize_functions == "upper" or self.normalize_functions is True:
587 return name.upper()
588 if self.normalize_functions == "lower":
589 return name.lower()
590 return name
591
592 def indent(
593 self,
594 sql: str,
595 level: int = 0,
596 pad: t.Optional[int] = None,
597 skip_first: bool = False,
598 skip_last: bool = False,
599 ) -> str:
600 if not self.pretty:
601 return sql
602
603 pad = self.pad if pad is None else pad
604 lines = sql.split("\n")
605
606 return "\n".join(
607 line
608 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)
609 else f"{' ' * (level * self._indent + pad)}{line}"
610 for i, line in enumerate(lines)
611 )
612
613 def sql(
614 self,
615 expression: t.Optional[str | exp.Expression],
616 key: t.Optional[str] = None,
617 comment: bool = True,
618 ) -> str:
619 if not expression:
620 return ""
621
622 if isinstance(expression, str):
623 return expression
624
625 if key:
626 value = expression.args.get(key)
627 if value:
628 return self.sql(value)
629 return ""
630
631 transform = self.TRANSFORMS.get(expression.__class__)
632
633 if callable(transform):
634 sql = transform(self, expression)
635 elif transform:
636 sql = transform
637 elif isinstance(expression, exp.Expression):
638 exp_handler_name = f"{expression.key}_sql"
639
640 if hasattr(self, exp_handler_name):
641 sql = getattr(self, exp_handler_name)(expression)
642 elif isinstance(expression, exp.Func):
643 sql = self.function_fallback_sql(expression)
644 elif isinstance(expression, exp.Property):
645 sql = self.property_sql(expression)
646 else:
647 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}")
648 else:
649 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}")
650
651 return self.maybe_comment(sql, expression) if self.comments and comment else sql
652
653 def uncache_sql(self, expression: exp.Uncache) -> str:
654 table = self.sql(expression, "this")
655 exists_sql = " IF EXISTS" if expression.args.get("exists") else ""
656 return f"UNCACHE TABLE{exists_sql} {table}"
657
658 def cache_sql(self, expression: exp.Cache) -> str:
659 lazy = " LAZY" if expression.args.get("lazy") else ""
660 table = self.sql(expression, "this")
661 options = expression.args.get("options")
662 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else ""
663 sql = self.sql(expression, "expression")
664 sql = f" AS{self.sep()}{sql}" if sql else ""
665 sql = f"CACHE{lazy} TABLE {table}{options}{sql}"
666 return self.prepend_ctes(expression, sql)
667
668 def characterset_sql(self, expression: exp.CharacterSet) -> str:
669 if isinstance(expression.parent, exp.Cast):
670 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}"
671 default = "DEFAULT " if expression.args.get("default") else ""
672 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
673
674 def column_sql(self, expression: exp.Column) -> str:
675 join_mark = " (+)" if expression.args.get("join_mark") else ""
676
677 if join_mark and not self.COLUMN_JOIN_MARKS_SUPPORTED:
678 join_mark = ""
679 self.unsupported("Outer join syntax using the (+) operator is not supported.")
680
681 column = ".".join(
682 self.sql(part)
683 for part in (
684 expression.args.get("catalog"),
685 expression.args.get("db"),
686 expression.args.get("table"),
687 expression.args.get("this"),
688 )
689 if part
690 )
691
692 return f"{column}{join_mark}"
693
694 def columnposition_sql(self, expression: exp.ColumnPosition) -> str:
695 this = self.sql(expression, "this")
696 this = f" {this}" if this else ""
697 position = self.sql(expression, "position")
698 return f"{position}{this}"
699
700 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str:
701 column = self.sql(expression, "this")
702 kind = self.sql(expression, "kind")
703 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True)
704 exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
705 kind = f"{sep}{kind}" if kind else ""
706 constraints = f" {constraints}" if constraints else ""
707 position = self.sql(expression, "position")
708 position = f" {position}" if position else ""
709
710 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:
711 kind = ""
712
713 return f"{exists}{column}{kind}{constraints}{position}"
714
715 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:
716 this = self.sql(expression, "this")
717 kind_sql = self.sql(expression, "kind").strip()
718 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql
719
720 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
721 this = self.sql(expression, "this")
722 if expression.args.get("not_null"):
723 persisted = " PERSISTED NOT NULL"
724 elif expression.args.get("persisted"):
725 persisted = " PERSISTED"
726 else:
727 persisted = ""
728 return f"AS {this}{persisted}"
729
730 def autoincrementcolumnconstraint_sql(self, _) -> str:
731 return self.token_sql(TokenType.AUTO_INCREMENT)
732
733 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:
734 if isinstance(expression.this, list):
735 this = self.wrap(self.expressions(expression, key="this", flat=True))
736 else:
737 this = self.sql(expression, "this")
738
739 return f"COMPRESS {this}"
740
741 def generatedasidentitycolumnconstraint_sql(
742 self, expression: exp.GeneratedAsIdentityColumnConstraint
743 ) -> str:
744 this = ""
745 if expression.this is not None:
746 on_null = " ON NULL" if expression.args.get("on_null") else ""
747 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}"
748
749 start = expression.args.get("start")
750 start = f"START WITH {start}" if start else ""
751 increment = expression.args.get("increment")
752 increment = f" INCREMENT BY {increment}" if increment else ""
753 minvalue = expression.args.get("minvalue")
754 minvalue = f" MINVALUE {minvalue}" if minvalue else ""
755 maxvalue = expression.args.get("maxvalue")
756 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else ""
757 cycle = expression.args.get("cycle")
758 cycle_sql = ""
759
760 if cycle is not None:
761 cycle_sql = f"{' NO' if not cycle else ''} CYCLE"
762 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql
763
764 sequence_opts = ""
765 if start or increment or cycle_sql:
766 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}"
767 sequence_opts = f" ({sequence_opts.strip()})"
768
769 expr = self.sql(expression, "expression")
770 expr = f"({expr})" if expr else "IDENTITY"
771
772 return f"GENERATED{this} AS {expr}{sequence_opts}"
773
774 def generatedasrowcolumnconstraint_sql(
775 self, expression: exp.GeneratedAsRowColumnConstraint
776 ) -> str:
777 start = "START" if expression.args["start"] else "END"
778 hidden = " HIDDEN" if expression.args.get("hidden") else ""
779 return f"GENERATED ALWAYS AS ROW {start}{hidden}"
780
781 def periodforsystemtimeconstraint_sql(
782 self, expression: exp.PeriodForSystemTimeConstraint
783 ) -> str:
784 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})"
785
786 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:
787 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL"
788
789 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:
790 desc = expression.args.get("desc")
791 if desc is not None:
792 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}"
793 return f"PRIMARY KEY"
794
795 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:
796 this = self.sql(expression, "this")
797 this = f" {this}" if this else ""
798 index_type = expression.args.get("index_type")
799 index_type = f" USING {index_type}" if index_type else ""
800 return f"UNIQUE{this}{index_type}"
801
802 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:
803 return self.sql(expression, "this")
804
805 def create_sql(self, expression: exp.Create) -> str:
806 kind = self.sql(expression, "kind").upper()
807 properties = expression.args.get("properties")
808 properties_locs = self.locate_properties(properties) if properties else defaultdict()
809
810 this = self.createable_sql(expression, properties_locs)
811
812 properties_sql = ""
813 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(
814 exp.Properties.Location.POST_WITH
815 ):
816 properties_sql = self.sql(
817 exp.Properties(
818 expressions=[
819 *properties_locs[exp.Properties.Location.POST_SCHEMA],
820 *properties_locs[exp.Properties.Location.POST_WITH],
821 ]
822 )
823 )
824
825 begin = " BEGIN" if expression.args.get("begin") else ""
826 end = " END" if expression.args.get("end") else ""
827
828 expression_sql = self.sql(expression, "expression")
829 if expression_sql:
830 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}"
831
832 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):
833 if properties_locs.get(exp.Properties.Location.POST_ALIAS):
834 postalias_props_sql = self.properties(
835 exp.Properties(
836 expressions=properties_locs[exp.Properties.Location.POST_ALIAS]
837 ),
838 wrapped=False,
839 )
840 expression_sql = f" AS {postalias_props_sql}{expression_sql}"
841 else:
842 expression_sql = f" AS{expression_sql}"
843
844 postindex_props_sql = ""
845 if properties_locs.get(exp.Properties.Location.POST_INDEX):
846 postindex_props_sql = self.properties(
847 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),
848 wrapped=False,
849 prefix=" ",
850 )
851
852 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ")
853 indexes = f" {indexes}" if indexes else ""
854 index_sql = indexes + postindex_props_sql
855
856 replace = " OR REPLACE" if expression.args.get("replace") else ""
857 unique = " UNIQUE" if expression.args.get("unique") else ""
858
859 postcreate_props_sql = ""
860 if properties_locs.get(exp.Properties.Location.POST_CREATE):
861 postcreate_props_sql = self.properties(
862 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),
863 sep=" ",
864 prefix=" ",
865 wrapped=False,
866 )
867
868 modifiers = "".join((replace, unique, postcreate_props_sql))
869
870 postexpression_props_sql = ""
871 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):
872 postexpression_props_sql = self.properties(
873 exp.Properties(
874 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]
875 ),
876 sep=" ",
877 prefix=" ",
878 wrapped=False,
879 )
880
881 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else ""
882 no_schema_binding = (
883 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else ""
884 )
885
886 clone = self.sql(expression, "clone")
887 clone = f" {clone}" if clone else ""
888
889 expression_sql = f"CREATE{modifiers} {kind}{exists_sql} {this}{properties_sql}{expression_sql}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}"
890 return self.prepend_ctes(expression, expression_sql)
891
892 def clone_sql(self, expression: exp.Clone) -> str:
893 this = self.sql(expression, "this")
894 shallow = "SHALLOW " if expression.args.get("shallow") else ""
895 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE"
896 this = f"{shallow}{keyword} {this}"
897 when = self.sql(expression, "when")
898
899 if when:
900 kind = self.sql(expression, "kind")
901 expr = self.sql(expression, "expression")
902 return f"{this} {when} ({kind} => {expr})"
903
904 return this
905
906 def describe_sql(self, expression: exp.Describe) -> str:
907 return f"DESCRIBE {self.sql(expression, 'this')}"
908
909 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str:
910 with_ = self.sql(expression, "with")
911 if with_:
912 sql = f"{with_}{self.sep()}{sql}"
913 return sql
914
915 def with_sql(self, expression: exp.With) -> str:
916 sql = self.expressions(expression, flat=True)
917 recursive = (
918 "RECURSIVE "
919 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive")
920 else ""
921 )
922
923 return f"WITH {recursive}{sql}"
924
925 def cte_sql(self, expression: exp.CTE) -> str:
926 alias = self.sql(expression, "alias")
927 return f"{alias} AS {self.wrap(expression)}"
928
929 def tablealias_sql(self, expression: exp.TableAlias) -> str:
930 alias = self.sql(expression, "this")
931 columns = self.expressions(expression, key="columns", flat=True)
932 columns = f"({columns})" if columns else ""
933
934 if not alias and not self.UNNEST_COLUMN_ONLY:
935 alias = "_t"
936
937 return f"{alias}{columns}"
938
939 def bitstring_sql(self, expression: exp.BitString) -> str:
940 this = self.sql(expression, "this")
941 if self.BIT_START:
942 return f"{self.BIT_START}{this}{self.BIT_END}"
943 return f"{int(this, 2)}"
944
945 def hexstring_sql(self, expression: exp.HexString) -> str:
946 this = self.sql(expression, "this")
947 if self.HEX_START:
948 return f"{self.HEX_START}{this}{self.HEX_END}"
949 return f"{int(this, 16)}"
950
951 def bytestring_sql(self, expression: exp.ByteString) -> str:
952 this = self.sql(expression, "this")
953 if self.BYTE_START:
954 return f"{self.BYTE_START}{this}{self.BYTE_END}"
955 return this
956
957 def rawstring_sql(self, expression: exp.RawString) -> str:
958 string = self.escape_str(expression.this.replace("\\", "\\\\"))
959 return f"{self.QUOTE_START}{string}{self.QUOTE_END}"
960
961 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:
962 this = self.sql(expression, "this")
963 specifier = self.sql(expression, "expression")
964 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else ""
965 return f"{this}{specifier}"
966
967 def datatype_sql(self, expression: exp.DataType) -> str:
968 type_value = expression.this
969
970 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"):
971 type_sql = self.sql(expression, "kind")
972 else:
973 type_sql = (
974 self.TYPE_MAPPING.get(type_value, type_value.value)
975 if isinstance(type_value, exp.DataType.Type)
976 else type_value
977 )
978
979 nested = ""
980 interior = self.expressions(expression, flat=True)
981 values = ""
982
983 if interior:
984 if expression.args.get("nested"):
985 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
986 if expression.args.get("values") is not None:
987 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
988 values = self.expressions(expression, key="values", flat=True)
989 values = f"{delimiters[0]}{values}{delimiters[1]}"
990 elif type_value == exp.DataType.Type.INTERVAL:
991 nested = f" {interior}"
992 else:
993 nested = f"({interior})"
994
995 type_sql = f"{type_sql}{nested}{values}"
996 if self.TZ_TO_WITH_TIME_ZONE and type_value in (
997 exp.DataType.Type.TIMETZ,
998 exp.DataType.Type.TIMESTAMPTZ,
999 ):
1000 type_sql = f"{type_sql} WITH TIME ZONE"
1001
1002 return type_sql
1003
1004 def directory_sql(self, expression: exp.Directory) -> str:
1005 local = "LOCAL " if expression.args.get("local") else ""
1006 row_format = self.sql(expression, "row_format")
1007 row_format = f" {row_format}" if row_format else ""
1008 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1009
1010 def delete_sql(self, expression: exp.Delete) -> str:
1011 this = self.sql(expression, "this")
1012 this = f" FROM {this}" if this else ""
1013 using = self.sql(expression, "using")
1014 using = f" USING {using}" if using else ""
1015 where = self.sql(expression, "where")
1016 returning = self.sql(expression, "returning")
1017 limit = self.sql(expression, "limit")
1018 tables = self.expressions(expression, key="tables")
1019 tables = f" {tables}" if tables else ""
1020 if self.RETURNING_END:
1021 expression_sql = f"{this}{using}{where}{returning}{limit}"
1022 else:
1023 expression_sql = f"{returning}{this}{using}{where}{limit}"
1024 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1025
1026 def drop_sql(self, expression: exp.Drop) -> str:
1027 this = self.sql(expression, "this")
1028 kind = expression.args["kind"]
1029 exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
1030 temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1031 materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
1032 cascade = " CASCADE" if expression.args.get("cascade") else ""
1033 constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
1034 purge = " PURGE" if expression.args.get("purge") else ""
1035 return (
1036 f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{cascade}{constraints}{purge}"
1037 )
1038
1039 def except_sql(self, expression: exp.Except) -> str:
1040 return self.prepend_ctes(
1041 expression,
1042 self.set_operation(expression, self.except_op(expression)),
1043 )
1044
1045 def except_op(self, expression: exp.Except) -> str:
1046 return f"EXCEPT{'' if expression.args.get('distinct') else ' ALL'}"
1047
1048 def fetch_sql(self, expression: exp.Fetch) -> str:
1049 direction = expression.args.get("direction")
1050 direction = f" {direction.upper()}" if direction else ""
1051 count = expression.args.get("count")
1052 count = f" {count}" if count else ""
1053 if expression.args.get("percent"):
1054 count = f"{count} PERCENT"
1055 with_ties_or_only = "WITH TIES" if expression.args.get("with_ties") else "ONLY"
1056 return f"{self.seg('FETCH')}{direction}{count} ROWS {with_ties_or_only}"
1057
1058 def filter_sql(self, expression: exp.Filter) -> str:
1059 if self.AGGREGATE_FILTER_SUPPORTED:
1060 this = self.sql(expression, "this")
1061 where = self.sql(expression, "expression").strip()
1062 return f"{this} FILTER({where})"
1063
1064 agg = expression.this
1065 agg_arg = agg.this
1066 cond = expression.expression.this
1067 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))
1068 return self.sql(agg)
1069
1070 def hint_sql(self, expression: exp.Hint) -> str:
1071 if not self.QUERY_HINTS:
1072 self.unsupported("Hints are not supported")
1073 return ""
1074
1075 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */"
1076
1077 def index_sql(self, expression: exp.Index) -> str:
1078 unique = "UNIQUE " if expression.args.get("unique") else ""
1079 primary = "PRIMARY " if expression.args.get("primary") else ""
1080 amp = "AMP " if expression.args.get("amp") else ""
1081 name = self.sql(expression, "this")
1082 name = f"{name} " if name else ""
1083 table = self.sql(expression, "table")
1084 table = f"{self.INDEX_ON} {table}" if table else ""
1085 using = self.sql(expression, "using")
1086 using = f" USING {using}" if using else ""
1087 index = "INDEX " if not table else ""
1088 columns = self.expressions(expression, key="columns", flat=True)
1089 columns = f"({columns})" if columns else ""
1090 partition_by = self.expressions(expression, key="partition_by", flat=True)
1091 partition_by = f" PARTITION BY {partition_by}" if partition_by else ""
1092 where = self.sql(expression, "where")
1093 return f"{unique}{primary}{amp}{index}{name}{table}{using}{columns}{partition_by}{where}"
1094
1095 def identifier_sql(self, expression: exp.Identifier) -> str:
1096 text = expression.name
1097 lower = text.lower()
1098 text = lower if self.normalize and not expression.quoted else text
1099 text = text.replace(self.IDENTIFIER_END, self._escaped_identifier_end)
1100 if (
1101 expression.quoted
1102 or self.can_identify(text, self.identify)
1103 or lower in self.RESERVED_KEYWORDS
1104 or (not self.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())
1105 ):
1106 text = f"{self.IDENTIFIER_START}{text}{self.IDENTIFIER_END}"
1107 return text
1108
1109 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
1110 input_format = self.sql(expression, "input_format")
1111 input_format = f"INPUTFORMAT {input_format}" if input_format else ""
1112 output_format = self.sql(expression, "output_format")
1113 output_format = f"OUTPUTFORMAT {output_format}" if output_format else ""
1114 return self.sep().join((input_format, output_format))
1115
1116 def national_sql(self, expression: exp.National, prefix: str = "N") -> str:
1117 string = self.sql(exp.Literal.string(expression.name))
1118 return f"{prefix}{string}"
1119
1120 def partition_sql(self, expression: exp.Partition) -> str:
1121 return f"PARTITION({self.expressions(expression, flat=True)})"
1122
1123 def properties_sql(self, expression: exp.Properties) -> str:
1124 root_properties = []
1125 with_properties = []
1126
1127 for p in expression.expressions:
1128 p_loc = self.PROPERTIES_LOCATION[p.__class__]
1129 if p_loc == exp.Properties.Location.POST_WITH:
1130 with_properties.append(p)
1131 elif p_loc == exp.Properties.Location.POST_SCHEMA:
1132 root_properties.append(p)
1133
1134 return self.root_properties(
1135 exp.Properties(expressions=root_properties)
1136 ) + self.with_properties(exp.Properties(expressions=with_properties))
1137
1138 def root_properties(self, properties: exp.Properties) -> str:
1139 if properties.expressions:
1140 return self.sep() + self.expressions(properties, indent=False, sep=" ")
1141 return ""
1142
1143 def properties(
1144 self,
1145 properties: exp.Properties,
1146 prefix: str = "",
1147 sep: str = ", ",
1148 suffix: str = "",
1149 wrapped: bool = True,
1150 ) -> str:
1151 if properties.expressions:
1152 expressions = self.expressions(properties, sep=sep, indent=False)
1153 if expressions:
1154 expressions = self.wrap(expressions) if wrapped else expressions
1155 return f"{prefix}{' ' if prefix and prefix != ' ' else ''}{expressions}{suffix}"
1156 return ""
1157
1158 def with_properties(self, properties: exp.Properties) -> str:
1159 return self.properties(properties, prefix=self.seg("WITH"))
1160
1161 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
1162 properties_locs = defaultdict(list)
1163 for p in properties.expressions:
1164 p_loc = self.PROPERTIES_LOCATION[p.__class__]
1165 if p_loc != exp.Properties.Location.UNSUPPORTED:
1166 properties_locs[p_loc].append(p)
1167 else:
1168 self.unsupported(f"Unsupported property {p.key}")
1169
1170 return properties_locs
1171
1172 def property_name(self, expression: exp.Property, string_key: bool = False) -> str:
1173 if isinstance(expression.this, exp.Dot):
1174 return self.sql(expression, "this")
1175 return f"'{expression.name}'" if string_key else expression.name
1176
1177 def property_sql(self, expression: exp.Property) -> str:
1178 property_cls = expression.__class__
1179 if property_cls == exp.Property:
1180 return f"{self.property_name(expression)}={self.sql(expression, 'value')}"
1181
1182 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)
1183 if not property_name:
1184 self.unsupported(f"Unsupported property {expression.key}")
1185
1186 return f"{property_name}={self.sql(expression, 'this')}"
1187
1188 def likeproperty_sql(self, expression: exp.LikeProperty) -> str:
1189 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions)
1190 options = f" {options}" if options else ""
1191 return f"LIKE {self.sql(expression, 'this')}{options}"
1192
1193 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:
1194 no = "NO " if expression.args.get("no") else ""
1195 protection = " PROTECTION" if expression.args.get("protection") else ""
1196 return f"{no}FALLBACK{protection}"
1197
1198 def journalproperty_sql(self, expression: exp.JournalProperty) -> str:
1199 no = "NO " if expression.args.get("no") else ""
1200 local = expression.args.get("local")
1201 local = f"{local} " if local else ""
1202 dual = "DUAL " if expression.args.get("dual") else ""
1203 before = "BEFORE " if expression.args.get("before") else ""
1204 after = "AFTER " if expression.args.get("after") else ""
1205 return f"{no}{local}{dual}{before}{after}JOURNAL"
1206
1207 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:
1208 freespace = self.sql(expression, "this")
1209 percent = " PERCENT" if expression.args.get("percent") else ""
1210 return f"FREESPACE={freespace}{percent}"
1211
1212 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:
1213 if expression.args.get("default"):
1214 property = "DEFAULT"
1215 elif expression.args.get("on"):
1216 property = "ON"
1217 else:
1218 property = "OFF"
1219 return f"CHECKSUM={property}"
1220
1221 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:
1222 if expression.args.get("no"):
1223 return "NO MERGEBLOCKRATIO"
1224 if expression.args.get("default"):
1225 return "DEFAULT MERGEBLOCKRATIO"
1226
1227 percent = " PERCENT" if expression.args.get("percent") else ""
1228 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1229
1230 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:
1231 default = expression.args.get("default")
1232 minimum = expression.args.get("minimum")
1233 maximum = expression.args.get("maximum")
1234 if default or minimum or maximum:
1235 if default:
1236 prop = "DEFAULT"
1237 elif minimum:
1238 prop = "MINIMUM"
1239 else:
1240 prop = "MAXIMUM"
1241 return f"{prop} DATABLOCKSIZE"
1242 units = expression.args.get("units")
1243 units = f" {units}" if units else ""
1244 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
1245
1246 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:
1247 autotemp = expression.args.get("autotemp")
1248 always = expression.args.get("always")
1249 default = expression.args.get("default")
1250 manual = expression.args.get("manual")
1251 never = expression.args.get("never")
1252
1253 if autotemp is not None:
1254 prop = f"AUTOTEMP({self.expressions(autotemp)})"
1255 elif always:
1256 prop = "ALWAYS"
1257 elif default:
1258 prop = "DEFAULT"
1259 elif manual:
1260 prop = "MANUAL"
1261 elif never:
1262 prop = "NEVER"
1263 return f"BLOCKCOMPRESSION={prop}"
1264
1265 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:
1266 no = expression.args.get("no")
1267 no = " NO" if no else ""
1268 concurrent = expression.args.get("concurrent")
1269 concurrent = " CONCURRENT" if concurrent else ""
1270
1271 for_ = ""
1272 if expression.args.get("for_all"):
1273 for_ = " FOR ALL"
1274 elif expression.args.get("for_insert"):
1275 for_ = " FOR INSERT"
1276 elif expression.args.get("for_none"):
1277 for_ = " FOR NONE"
1278 return f"WITH{no}{concurrent} ISOLATED LOADING{for_}"
1279
1280 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:
1281 if isinstance(expression.this, list):
1282 return f"IN ({self.expressions(expression, key='this', flat=True)})"
1283 if expression.this:
1284 modulus = self.sql(expression, "this")
1285 remainder = self.sql(expression, "expression")
1286 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})"
1287
1288 from_expressions = self.expressions(expression, key="from_expressions", flat=True)
1289 to_expressions = self.expressions(expression, key="to_expressions", flat=True)
1290 return f"FROM ({from_expressions}) TO ({to_expressions})"
1291
1292 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:
1293 this = self.sql(expression, "this")
1294
1295 for_values_or_default = expression.expression
1296 if isinstance(for_values_or_default, exp.PartitionBoundSpec):
1297 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}"
1298 else:
1299 for_values_or_default = " DEFAULT"
1300
1301 return f"PARTITION OF {this}{for_values_or_default}"
1302
1303 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:
1304 kind = expression.args.get("kind")
1305 this = f" {self.sql(expression, 'this')}" if expression.this else ""
1306 for_or_in = expression.args.get("for_or_in")
1307 for_or_in = f" {for_or_in}" if for_or_in else ""
1308 lock_type = expression.args.get("lock_type")
1309 override = " OVERRIDE" if expression.args.get("override") else ""
1310 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1311
1312 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:
1313 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
1314 statistics = expression.args.get("statistics")
1315 statistics_sql = ""
1316 if statistics is not None:
1317 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS"
1318 return f"{data_sql}{statistics_sql}"
1319
1320 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
1321 sql = "WITH(SYSTEM_VERSIONING=ON"
1322
1323 if expression.this:
1324 history_table = self.sql(expression, "this")
1325 sql = f"{sql}(HISTORY_TABLE={history_table}"
1326
1327 if expression.expression:
1328 data_consistency_check = self.sql(expression, "expression")
1329 sql = f"{sql}, DATA_CONSISTENCY_CHECK={data_consistency_check}"
1330
1331 sql = f"{sql})"
1332
1333 return f"{sql})"
1334
1335 def insert_sql(self, expression: exp.Insert) -> str:
1336 overwrite = expression.args.get("overwrite")
1337
1338 if isinstance(expression.this, exp.Directory):
1339 this = " OVERWRITE" if overwrite else " INTO"
1340 else:
1341 this = " OVERWRITE TABLE" if overwrite else " INTO"
1342
1343 alternative = expression.args.get("alternative")
1344 alternative = f" OR {alternative}" if alternative else ""
1345 ignore = " IGNORE" if expression.args.get("ignore") else ""
1346
1347 this = f"{this} {self.sql(expression, 'this')}"
1348
1349 exists = " IF EXISTS" if expression.args.get("exists") else ""
1350 partition_sql = (
1351 f" {self.sql(expression, 'partition')}" if expression.args.get("partition") else ""
1352 )
1353 where = self.sql(expression, "where")
1354 where = f"{self.sep()}REPLACE WHERE {where}" if where else ""
1355 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}"
1356 conflict = self.sql(expression, "conflict")
1357 by_name = " BY NAME" if expression.args.get("by_name") else ""
1358 returning = self.sql(expression, "returning")
1359
1360 if self.RETURNING_END:
1361 expression_sql = f"{expression_sql}{conflict}{returning}"
1362 else:
1363 expression_sql = f"{returning}{expression_sql}{conflict}"
1364
1365 sql = f"INSERT{alternative}{ignore}{this}{by_name}{exists}{partition_sql}{where}{expression_sql}"
1366 return self.prepend_ctes(expression, sql)
1367
1368 def intersect_sql(self, expression: exp.Intersect) -> str:
1369 return self.prepend_ctes(
1370 expression,
1371 self.set_operation(expression, self.intersect_op(expression)),
1372 )
1373
1374 def intersect_op(self, expression: exp.Intersect) -> str:
1375 return f"INTERSECT{'' if expression.args.get('distinct') else ' ALL'}"
1376
1377 def introducer_sql(self, expression: exp.Introducer) -> str:
1378 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}"
1379
1380 def kill_sql(self, expression: exp.Kill) -> str:
1381 kind = self.sql(expression, "kind")
1382 kind = f" {kind}" if kind else ""
1383 this = self.sql(expression, "this")
1384 this = f" {this}" if this else ""
1385 return f"KILL{kind}{this}"
1386
1387 def pseudotype_sql(self, expression: exp.PseudoType) -> str:
1388 return expression.name.upper()
1389
1390 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:
1391 return expression.name.upper()
1392
1393 def onconflict_sql(self, expression: exp.OnConflict) -> str:
1394 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT"
1395 constraint = self.sql(expression, "constraint")
1396 if constraint:
1397 constraint = f"ON CONSTRAINT {constraint}"
1398 key = self.expressions(expression, key="key", flat=True)
1399 do = "" if expression.args.get("duplicate") else " DO "
1400 nothing = "NOTHING" if expression.args.get("nothing") else ""
1401 expressions = self.expressions(expression, flat=True)
1402 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else ""
1403 if expressions:
1404 expressions = f"UPDATE {set_keyword}{expressions}"
1405 return f"{self.seg(conflict)} {constraint}{key}{do}{nothing}{expressions}"
1406
1407 def returning_sql(self, expression: exp.Returning) -> str:
1408 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}"
1409
1410 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:
1411 fields = expression.args.get("fields")
1412 fields = f" FIELDS TERMINATED BY {fields}" if fields else ""
1413 escaped = expression.args.get("escaped")
1414 escaped = f" ESCAPED BY {escaped}" if escaped else ""
1415 items = expression.args.get("collection_items")
1416 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else ""
1417 keys = expression.args.get("map_keys")
1418 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else ""
1419 lines = expression.args.get("lines")
1420 lines = f" LINES TERMINATED BY {lines}" if lines else ""
1421 null = expression.args.get("null")
1422 null = f" NULL DEFINED AS {null}" if null else ""
1423 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1424
1425 def withtablehint_sql(self, expression: exp.WithTableHint) -> str:
1426 return f"WITH ({self.expressions(expression, flat=True)})"
1427
1428 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:
1429 this = f"{self.sql(expression, 'this')} INDEX"
1430 target = self.sql(expression, "target")
1431 target = f" FOR {target}" if target else ""
1432 return f"{this}{target} ({self.expressions(expression, flat=True)})"
1433
1434 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str:
1435 table = ".".join(
1436 self.sql(part)
1437 for part in (
1438 expression.args.get("catalog"),
1439 expression.args.get("db"),
1440 expression.args.get("this"),
1441 )
1442 if part is not None
1443 )
1444
1445 version = self.sql(expression, "version")
1446 version = f" {version}" if version else ""
1447 alias = self.sql(expression, "alias")
1448 alias = f"{sep}{alias}" if alias else ""
1449 hints = self.expressions(expression, key="hints", sep=" ")
1450 hints = f" {hints}" if hints and self.TABLE_HINTS else ""
1451 pivots = self.expressions(expression, key="pivots", sep=" ", flat=True)
1452 pivots = f" {pivots}" if pivots else ""
1453 joins = self.expressions(expression, key="joins", sep="", skip_first=True)
1454 laterals = self.expressions(expression, key="laterals", sep="")
1455
1456 file_format = self.sql(expression, "format")
1457 if file_format:
1458 pattern = self.sql(expression, "pattern")
1459 pattern = f", PATTERN => {pattern}" if pattern else ""
1460 file_format = f" (FILE_FORMAT => {file_format}{pattern})"
1461
1462 index = self.sql(expression, "index")
1463 index = f" AT {index}" if index else ""
1464
1465 ordinality = expression.args.get("ordinality") or ""
1466 if ordinality:
1467 ordinality = f" WITH ORDINALITY{alias}"
1468 alias = ""
1469
1470 return f"{table}{version}{file_format}{alias}{index}{hints}{pivots}{joins}{laterals}{ordinality}"
1471
1472 def tablesample_sql(
1473 self, expression: exp.TableSample, seed_prefix: str = "SEED", sep=" AS "
1474 ) -> str:
1475 if self.ALIAS_POST_TABLESAMPLE and expression.this and expression.this.alias:
1476 table = expression.this.copy()
1477 table.set("alias", None)
1478 this = self.sql(table)
1479 alias = f"{sep}{self.sql(expression.this, 'alias')}"
1480 else:
1481 this = self.sql(expression, "this")
1482 alias = ""
1483
1484 method = self.sql(expression, "method")
1485 method = f"{method.upper()} " if method and self.TABLESAMPLE_WITH_METHOD else ""
1486 numerator = self.sql(expression, "bucket_numerator")
1487 denominator = self.sql(expression, "bucket_denominator")
1488 field = self.sql(expression, "bucket_field")
1489 field = f" ON {field}" if field else ""
1490 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else ""
1491 percent = self.sql(expression, "percent")
1492 percent = f"{percent} PERCENT" if percent else ""
1493 rows = self.sql(expression, "rows")
1494 rows = f"{rows} ROWS" if rows else ""
1495
1496 size = self.sql(expression, "size")
1497 if size and self.TABLESAMPLE_SIZE_IS_PERCENT:
1498 size = f"{size} PERCENT"
1499
1500 seed = self.sql(expression, "seed")
1501 seed = f" {seed_prefix} ({seed})" if seed else ""
1502 kind = expression.args.get("kind", "TABLESAMPLE")
1503
1504 expr = f"{bucket}{percent}{rows}{size}"
1505 if self.TABLESAMPLE_REQUIRES_PARENS:
1506 expr = f"({expr})"
1507
1508 return f"{this} {kind} {method}{expr}{seed}{alias}"
1509
1510 def pivot_sql(self, expression: exp.Pivot) -> str:
1511 expressions = self.expressions(expression, flat=True)
1512
1513 if expression.this:
1514 this = self.sql(expression, "this")
1515 if not expressions:
1516 return f"UNPIVOT {this}"
1517
1518 on = f"{self.seg('ON')} {expressions}"
1519 using = self.expressions(expression, key="using", flat=True)
1520 using = f"{self.seg('USING')} {using}" if using else ""
1521 group = self.sql(expression, "group")
1522 return f"PIVOT {this}{on}{using}{group}"
1523
1524 alias = self.sql(expression, "alias")
1525 alias = f" AS {alias}" if alias else ""
1526 unpivot = expression.args.get("unpivot")
1527 direction = "UNPIVOT" if unpivot else "PIVOT"
1528 field = self.sql(expression, "field")
1529 include_nulls = expression.args.get("include_nulls")
1530 if include_nulls is not None:
1531 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS "
1532 else:
1533 nulls = ""
1534 return f"{direction}{nulls}({expressions} FOR {field}){alias}"
1535
1536 def version_sql(self, expression: exp.Version) -> str:
1537 this = f"FOR {expression.name}"
1538 kind = expression.text("kind")
1539 expr = self.sql(expression, "expression")
1540 return f"{this} {kind} {expr}"
1541
1542 def tuple_sql(self, expression: exp.Tuple) -> str:
1543 return f"({self.expressions(expression, flat=True)})"
1544
1545 def update_sql(self, expression: exp.Update) -> str:
1546 this = self.sql(expression, "this")
1547 set_sql = self.expressions(expression, flat=True)
1548 from_sql = self.sql(expression, "from")
1549 where_sql = self.sql(expression, "where")
1550 returning = self.sql(expression, "returning")
1551 order = self.sql(expression, "order")
1552 limit = self.sql(expression, "limit")
1553 if self.RETURNING_END:
1554 expression_sql = f"{from_sql}{where_sql}{returning}"
1555 else:
1556 expression_sql = f"{returning}{from_sql}{where_sql}"
1557 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}"
1558 return self.prepend_ctes(expression, sql)
1559
1560 def values_sql(self, expression: exp.Values) -> str:
1561 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example
1562 if self.VALUES_AS_TABLE or not expression.find_ancestor(exp.From, exp.Join):
1563 args = self.expressions(expression)
1564 alias = self.sql(expression, "alias")
1565 values = f"VALUES{self.seg('')}{args}"
1566 values = (
1567 f"({values})"
1568 if self.WRAP_DERIVED_VALUES and (alias or isinstance(expression.parent, exp.From))
1569 else values
1570 )
1571 return f"{values} AS {alias}" if alias else values
1572
1573 # Converts `VALUES...` expression into a series of select unions.
1574 alias_node = expression.args.get("alias")
1575 column_names = alias_node and alias_node.columns
1576
1577 selects: t.List[exp.Subqueryable] = []
1578
1579 for i, tup in enumerate(expression.expressions):
1580 row = tup.expressions
1581
1582 if i == 0 and column_names:
1583 row = [
1584 exp.alias_(value, column_name) for value, column_name in zip(row, column_names)
1585 ]
1586
1587 selects.append(exp.Select(expressions=row))
1588
1589 if self.pretty:
1590 # This may result in poor performance for large-cardinality `VALUES` tables, due to
1591 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase
1592 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.
1593 subqueryable = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)
1594 return self.subquery_sql(
1595 subqueryable.subquery(alias_node and alias_node.this, copy=False)
1596 )
1597
1598 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else ""
1599 unions = " UNION ALL ".join(self.sql(select) for select in selects)
1600 return f"({unions}){alias}"
1601
1602 def var_sql(self, expression: exp.Var) -> str:
1603 return self.sql(expression, "this")
1604
1605 def into_sql(self, expression: exp.Into) -> str:
1606 temporary = " TEMPORARY" if expression.args.get("temporary") else ""
1607 unlogged = " UNLOGGED" if expression.args.get("unlogged") else ""
1608 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
1609
1610 def from_sql(self, expression: exp.From) -> str:
1611 return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
1612
1613 def group_sql(self, expression: exp.Group) -> str:
1614 group_by = self.op_expressions("GROUP BY", expression)
1615
1616 if expression.args.get("all"):
1617 return f"{group_by} ALL"
1618
1619 grouping_sets = self.expressions(expression, key="grouping_sets", indent=False)
1620 grouping_sets = (
1621 f"{self.seg('GROUPING SETS')} {self.wrap(grouping_sets)}" if grouping_sets else ""
1622 )
1623
1624 cube = expression.args.get("cube", [])
1625 if seq_get(cube, 0) is True:
1626 return f"{group_by}{self.seg('WITH CUBE')}"
1627 else:
1628 cube_sql = self.expressions(expression, key="cube", indent=False)
1629 cube_sql = f"{self.seg('CUBE')} {self.wrap(cube_sql)}" if cube_sql else ""
1630
1631 rollup = expression.args.get("rollup", [])
1632 if seq_get(rollup, 0) is True:
1633 return f"{group_by}{self.seg('WITH ROLLUP')}"
1634 else:
1635 rollup_sql = self.expressions(expression, key="rollup", indent=False)
1636 rollup_sql = f"{self.seg('ROLLUP')} {self.wrap(rollup_sql)}" if rollup_sql else ""
1637
1638 groupings = csv(
1639 grouping_sets,
1640 cube_sql,
1641 rollup_sql,
1642 self.seg("WITH TOTALS") if expression.args.get("totals") else "",
1643 sep=self.GROUPINGS_SEP,
1644 )
1645
1646 if expression.args.get("expressions") and groupings:
1647 group_by = f"{group_by}{self.GROUPINGS_SEP}"
1648
1649 return f"{group_by}{groupings}"
1650
1651 def having_sql(self, expression: exp.Having) -> str:
1652 this = self.indent(self.sql(expression, "this"))
1653 return f"{self.seg('HAVING')}{self.sep()}{this}"
1654
1655 def connect_sql(self, expression: exp.Connect) -> str:
1656 start = self.sql(expression, "start")
1657 start = self.seg(f"START WITH {start}") if start else ""
1658 connect = self.sql(expression, "connect")
1659 connect = self.seg(f"CONNECT BY {connect}")
1660 return start + connect
1661
1662 def prior_sql(self, expression: exp.Prior) -> str:
1663 return f"PRIOR {self.sql(expression, 'this')}"
1664
1665 def join_sql(self, expression: exp.Join) -> str:
1666 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"):
1667 side = None
1668 else:
1669 side = expression.side
1670
1671 op_sql = " ".join(
1672 op
1673 for op in (
1674 expression.method,
1675 "GLOBAL" if expression.args.get("global") else None,
1676 side,
1677 expression.kind,
1678 expression.hint if self.JOIN_HINTS else None,
1679 )
1680 if op
1681 )
1682 on_sql = self.sql(expression, "on")
1683 using = expression.args.get("using")
1684
1685 if not on_sql and using:
1686 on_sql = csv(*(self.sql(column) for column in using))
1687
1688 this_sql = self.sql(expression, "this")
1689
1690 if on_sql:
1691 on_sql = self.indent(on_sql, skip_first=True)
1692 space = self.seg(" " * self.pad) if self.pretty else " "
1693 if using:
1694 on_sql = f"{space}USING ({on_sql})"
1695 else:
1696 on_sql = f"{space}ON {on_sql}"
1697 elif not op_sql:
1698 return f", {this_sql}"
1699
1700 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN"
1701 return f"{self.seg(op_sql)} {this_sql}{on_sql}"
1702
1703 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str:
1704 args = self.expressions(expression, flat=True)
1705 args = f"({args})" if len(args.split(",")) > 1 else args
1706 return f"{args} {arrow_sep} {self.sql(expression, 'this')}"
1707
1708 def lateral_sql(self, expression: exp.Lateral) -> str:
1709 this = self.sql(expression, "this")
1710
1711 if expression.args.get("view"):
1712 alias = expression.args["alias"]
1713 columns = self.expressions(alias, key="columns", flat=True)
1714 table = f" {alias.name}" if alias.name else ""
1715 columns = f" AS {columns}" if columns else ""
1716 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}")
1717 return f"{op_sql}{self.sep()}{this}{table}{columns}"
1718
1719 alias = self.sql(expression, "alias")
1720 alias = f" AS {alias}" if alias else ""
1721 return f"LATERAL {this}{alias}"
1722
1723 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:
1724 this = self.sql(expression, "this")
1725
1726 args = [
1727 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e
1728 for e in (expression.args.get(k) for k in ("offset", "expression"))
1729 if e
1730 ]
1731
1732 args_sql = ", ".join(self.sql(e) for e in args)
1733 args_sql = f"({args_sql})" if any(top and not e.is_number for e in args) else args_sql
1734 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}"
1735
1736 def offset_sql(self, expression: exp.Offset) -> str:
1737 this = self.sql(expression, "this")
1738 expression = expression.expression
1739 expression = (
1740 self._simplify_unless_literal(expression) if self.LIMIT_ONLY_LITERALS else expression
1741 )
1742 return f"{this}{self.seg('OFFSET')} {self.sql(expression)}"
1743
1744 def setitem_sql(self, expression: exp.SetItem) -> str:
1745 kind = self.sql(expression, "kind")
1746 kind = f"{kind} " if kind else ""
1747 this = self.sql(expression, "this")
1748 expressions = self.expressions(expression)
1749 collate = self.sql(expression, "collate")
1750 collate = f" COLLATE {collate}" if collate else ""
1751 global_ = "GLOBAL " if expression.args.get("global") else ""
1752 return f"{global_}{kind}{this}{expressions}{collate}"
1753
1754 def set_sql(self, expression: exp.Set) -> str:
1755 expressions = (
1756 f" {self.expressions(expression, flat=True)}" if expression.expressions else ""
1757 )
1758 tag = " TAG" if expression.args.get("tag") else ""
1759 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}"
1760
1761 def pragma_sql(self, expression: exp.Pragma) -> str:
1762 return f"PRAGMA {self.sql(expression, 'this')}"
1763
1764 def lock_sql(self, expression: exp.Lock) -> str:
1765 if not self.LOCKING_READS_SUPPORTED:
1766 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
1767 return ""
1768
1769 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE"
1770 expressions = self.expressions(expression, flat=True)
1771 expressions = f" OF {expressions}" if expressions else ""
1772 wait = expression.args.get("wait")
1773
1774 if wait is not None:
1775 if isinstance(wait, exp.Literal):
1776 wait = f" WAIT {self.sql(wait)}"
1777 else:
1778 wait = " NOWAIT" if wait else " SKIP LOCKED"
1779
1780 return f"{lock_type}{expressions}{wait or ''}"
1781
1782 def literal_sql(self, expression: exp.Literal) -> str:
1783 text = expression.this or ""
1784 if expression.is_string:
1785 text = f"{self.QUOTE_START}{self.escape_str(text)}{self.QUOTE_END}"
1786 return text
1787
1788 def escape_str(self, text: str) -> str:
1789 text = text.replace(self.QUOTE_END, self._escaped_quote_end)
1790 if self.INVERSE_ESCAPE_SEQUENCES:
1791 text = "".join(self.INVERSE_ESCAPE_SEQUENCES.get(ch, ch) for ch in text)
1792 elif self.pretty:
1793 text = text.replace("\n", self.SENTINEL_LINE_BREAK)
1794 return text
1795
1796 def loaddata_sql(self, expression: exp.LoadData) -> str:
1797 local = " LOCAL" if expression.args.get("local") else ""
1798 inpath = f" INPATH {self.sql(expression, 'inpath')}"
1799 overwrite = " OVERWRITE" if expression.args.get("overwrite") else ""
1800 this = f" INTO TABLE {self.sql(expression, 'this')}"
1801 partition = self.sql(expression, "partition")
1802 partition = f" {partition}" if partition else ""
1803 input_format = self.sql(expression, "input_format")
1804 input_format = f" INPUTFORMAT {input_format}" if input_format else ""
1805 serde = self.sql(expression, "serde")
1806 serde = f" SERDE {serde}" if serde else ""
1807 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
1808
1809 def null_sql(self, *_) -> str:
1810 return "NULL"
1811
1812 def boolean_sql(self, expression: exp.Boolean) -> str:
1813 return "TRUE" if expression.this else "FALSE"
1814
1815 def order_sql(self, expression: exp.Order, flat: bool = False) -> str:
1816 this = self.sql(expression, "this")
1817 this = f"{this} " if this else this
1818 return self.op_expressions(f"{this}ORDER BY", expression, flat=this or flat) # type: ignore
1819
1820 def cluster_sql(self, expression: exp.Cluster) -> str:
1821 return self.op_expressions("CLUSTER BY", expression)
1822
1823 def distribute_sql(self, expression: exp.Distribute) -> str:
1824 return self.op_expressions("DISTRIBUTE BY", expression)
1825
1826 def sort_sql(self, expression: exp.Sort) -> str:
1827 return self.op_expressions("SORT BY", expression)
1828
1829 def ordered_sql(self, expression: exp.Ordered) -> str:
1830 desc = expression.args.get("desc")
1831 asc = not desc
1832
1833 nulls_first = expression.args.get("nulls_first")
1834 nulls_last = not nulls_first
1835 nulls_are_large = self.NULL_ORDERING == "nulls_are_large"
1836 nulls_are_small = self.NULL_ORDERING == "nulls_are_small"
1837 nulls_are_last = self.NULL_ORDERING == "nulls_are_last"
1838
1839 this = self.sql(expression, "this")
1840
1841 sort_order = " DESC" if desc else (" ASC" if desc is False else "")
1842 nulls_sort_change = ""
1843 if nulls_first and (
1844 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last
1845 ):
1846 nulls_sort_change = " NULLS FIRST"
1847 elif (
1848 nulls_last
1849 and ((asc and nulls_are_small) or (desc and nulls_are_large))
1850 and not nulls_are_last
1851 ):
1852 nulls_sort_change = " NULLS LAST"
1853
1854 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it
1855 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:
1856 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else ""
1857 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}"
1858 nulls_sort_change = ""
1859
1860 return f"{this}{sort_order}{nulls_sort_change}"
1861
1862 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:
1863 partition = self.partition_by_sql(expression)
1864 order = self.sql(expression, "order")
1865 measures = self.expressions(expression, key="measures")
1866 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else ""
1867 rows = self.sql(expression, "rows")
1868 rows = self.seg(rows) if rows else ""
1869 after = self.sql(expression, "after")
1870 after = self.seg(after) if after else ""
1871 pattern = self.sql(expression, "pattern")
1872 pattern = self.seg(f"PATTERN ({pattern})") if pattern else ""
1873 definition_sqls = [
1874 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}"
1875 for definition in expression.args.get("define", [])
1876 ]
1877 definitions = self.expressions(sqls=definition_sqls)
1878 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else ""
1879 body = "".join(
1880 (
1881 partition,
1882 order,
1883 measures,
1884 rows,
1885 after,
1886 pattern,
1887 define,
1888 )
1889 )
1890 alias = self.sql(expression, "alias")
1891 alias = f" {alias}" if alias else ""
1892 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
1893
1894 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
1895 limit: t.Optional[exp.Fetch | exp.Limit] = expression.args.get("limit")
1896
1897 # If the limit is generated as TOP, we need to ensure it's not generated twice
1898 with_offset_limit_modifiers = not isinstance(limit, exp.Limit) or not self.LIMIT_IS_TOP
1899
1900 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch):
1901 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count")))
1902 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit):
1903 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression))
1904
1905 fetch = isinstance(limit, exp.Fetch)
1906
1907 offset_limit_modifiers = (
1908 self.offset_limit_modifiers(expression, fetch, limit)
1909 if with_offset_limit_modifiers
1910 else []
1911 )
1912
1913 return csv(
1914 *sqls,
1915 *[self.sql(join) for join in expression.args.get("joins") or []],
1916 self.sql(expression, "connect"),
1917 self.sql(expression, "match"),
1918 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []],
1919 self.sql(expression, "where"),
1920 self.sql(expression, "group"),
1921 self.sql(expression, "having"),
1922 *self.after_having_modifiers(expression),
1923 self.sql(expression, "order"),
1924 *offset_limit_modifiers,
1925 *self.after_limit_modifiers(expression),
1926 sep="",
1927 )
1928
1929 def offset_limit_modifiers(
1930 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]
1931 ) -> t.List[str]:
1932 return [
1933 self.sql(expression, "offset") if fetch else self.sql(limit),
1934 self.sql(limit) if fetch else self.sql(expression, "offset"),
1935 ]
1936
1937 def after_having_modifiers(self, expression: exp.Expression) -> t.List[str]:
1938 return [
1939 self.sql(expression, "qualify"),
1940 self.seg("WINDOW ") + self.expressions(expression, key="windows", flat=True)
1941 if expression.args.get("windows")
1942 else "",
1943 self.sql(expression, "distribute"),
1944 self.sql(expression, "sort"),
1945 self.sql(expression, "cluster"),
1946 ]
1947
1948 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]:
1949 locks = self.expressions(expression, key="locks", sep=" ")
1950 locks = f" {locks}" if locks else ""
1951 return [locks, self.sql(expression, "sample")]
1952
1953 def select_sql(self, expression: exp.Select) -> str:
1954 hint = self.sql(expression, "hint")
1955 distinct = self.sql(expression, "distinct")
1956 distinct = f" {distinct}" if distinct else ""
1957 kind = self.sql(expression, "kind").upper()
1958 limit = expression.args.get("limit")
1959 top = (
1960 self.limit_sql(limit, top=True)
1961 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP
1962 else ""
1963 )
1964
1965 expressions = self.expressions(expression)
1966
1967 if kind:
1968 if kind in self.SELECT_KINDS:
1969 kind = f" AS {kind}"
1970 else:
1971 if kind == "STRUCT":
1972 expressions = self.expressions(
1973 sqls=[
1974 self.sql(
1975 exp.Struct(
1976 expressions=[
1977 exp.column(e.output_name).eq(
1978 e.this if isinstance(e, exp.Alias) else e
1979 )
1980 for e in expression.expressions
1981 ]
1982 )
1983 )
1984 ]
1985 )
1986 kind = ""
1987
1988 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata
1989 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.
1990 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}"
1991 expressions = f"{self.sep()}{expressions}" if expressions else expressions
1992 sql = self.query_modifiers(
1993 expression,
1994 f"SELECT{top_distinct}{kind}{expressions}",
1995 self.sql(expression, "into", comment=False),
1996 self.sql(expression, "from", comment=False),
1997 )
1998 return self.prepend_ctes(expression, sql)
1999
2000 def schema_sql(self, expression: exp.Schema) -> str:
2001 this = self.sql(expression, "this")
2002 sql = self.schema_columns_sql(expression)
2003 return f"{this} {sql}" if this and sql else this or sql
2004
2005 def schema_columns_sql(self, expression: exp.Schema) -> str:
2006 if expression.expressions:
2007 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}"
2008 return ""
2009
2010 def star_sql(self, expression: exp.Star) -> str:
2011 except_ = self.expressions(expression, key="except", flat=True)
2012 except_ = f"{self.seg(self.STAR_MAPPING['except'])} ({except_})" if except_ else ""
2013 replace = self.expressions(expression, key="replace", flat=True)
2014 replace = f"{self.seg(self.STAR_MAPPING['replace'])} ({replace})" if replace else ""
2015 return f"*{except_}{replace}"
2016
2017 def parameter_sql(self, expression: exp.Parameter) -> str:
2018 this = self.sql(expression, "this")
2019 return f"{self.PARAMETER_TOKEN}{this}" if self.SUPPORTS_PARAMETERS else this
2020
2021 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:
2022 this = self.sql(expression, "this")
2023 kind = expression.text("kind")
2024 if kind:
2025 kind = f"{kind}."
2026 return f"@@{kind}{this}"
2027
2028 def placeholder_sql(self, expression: exp.Placeholder) -> str:
2029 return f":{expression.name}" if expression.name else "?"
2030
2031 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str:
2032 alias = self.sql(expression, "alias")
2033 alias = f"{sep}{alias}" if alias else ""
2034
2035 pivots = self.expressions(expression, key="pivots", sep=" ", flat=True)
2036 pivots = f" {pivots}" if pivots else ""
2037
2038 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)
2039 return self.prepend_ctes(expression, sql)
2040
2041 def qualify_sql(self, expression: exp.Qualify) -> str:
2042 this = self.indent(self.sql(expression, "this"))
2043 return f"{self.seg('QUALIFY')}{self.sep()}{this}"
2044
2045 def union_sql(self, expression: exp.Union) -> str:
2046 return self.prepend_ctes(
2047 expression,
2048 self.set_operation(expression, self.union_op(expression)),
2049 )
2050
2051 def union_op(self, expression: exp.Union) -> str:
2052 kind = " DISTINCT" if self.EXPLICIT_UNION else ""
2053 kind = kind if expression.args.get("distinct") else " ALL"
2054 by_name = " BY NAME" if expression.args.get("by_name") else ""
2055 return f"UNION{kind}{by_name}"
2056
2057 def unnest_sql(self, expression: exp.Unnest) -> str:
2058 args = self.expressions(expression, flat=True)
2059
2060 alias = expression.args.get("alias")
2061 offset = expression.args.get("offset")
2062
2063 if self.UNNEST_WITH_ORDINALITY:
2064 if alias and isinstance(offset, exp.Expression):
2065 alias.append("columns", offset)
2066
2067 if alias and self.UNNEST_COLUMN_ONLY:
2068 columns = alias.columns
2069 alias = self.sql(columns[0]) if columns else ""
2070 else:
2071 alias = self.sql(alias)
2072
2073 alias = f" AS {alias}" if alias else alias
2074 if self.UNNEST_WITH_ORDINALITY:
2075 suffix = f" WITH ORDINALITY{alias}" if offset else alias
2076 else:
2077 if isinstance(offset, exp.Expression):
2078 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}"
2079 elif offset:
2080 suffix = f"{alias} WITH OFFSET"
2081 else:
2082 suffix = alias
2083
2084 return f"UNNEST({args}){suffix}"
2085
2086 def where_sql(self, expression: exp.Where) -> str:
2087 this = self.indent(self.sql(expression, "this"))
2088 return f"{self.seg('WHERE')}{self.sep()}{this}"
2089
2090 def window_sql(self, expression: exp.Window) -> str:
2091 this = self.sql(expression, "this")
2092 partition = self.partition_by_sql(expression)
2093 order = expression.args.get("order")
2094 order = self.order_sql(order, flat=True) if order else ""
2095 spec = self.sql(expression, "spec")
2096 alias = self.sql(expression, "alias")
2097 over = self.sql(expression, "over") or "OVER"
2098
2099 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}"
2100
2101 first = expression.args.get("first")
2102 if first is None:
2103 first = ""
2104 else:
2105 first = "FIRST" if first else "LAST"
2106
2107 if not partition and not order and not spec and alias:
2108 return f"{this} {alias}"
2109
2110 args = " ".join(arg for arg in (alias, first, partition, order, spec) if arg)
2111 return f"{this} ({args})"
2112
2113 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:
2114 partition = self.expressions(expression, key="partition_by", flat=True)
2115 return f"PARTITION BY {partition}" if partition else ""
2116
2117 def windowspec_sql(self, expression: exp.WindowSpec) -> str:
2118 kind = self.sql(expression, "kind")
2119 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ")
2120 end = (
2121 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ")
2122 or "CURRENT ROW"
2123 )
2124 return f"{kind} BETWEEN {start} AND {end}"
2125
2126 def withingroup_sql(self, expression: exp.WithinGroup) -> str:
2127 this = self.sql(expression, "this")
2128 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space
2129 return f"{this} WITHIN GROUP ({expression_sql})"
2130
2131 def between_sql(self, expression: exp.Between) -> str:
2132 this = self.sql(expression, "this")
2133 low = self.sql(expression, "low")
2134 high = self.sql(expression, "high")
2135 return f"{this} BETWEEN {low} AND {high}"
2136
2137 def bracket_sql(self, expression: exp.Bracket) -> str:
2138 expressions = apply_index_offset(expression.this, expression.expressions, self.INDEX_OFFSET)
2139 expressions_sql = ", ".join(self.sql(e) for e in expressions)
2140
2141 return f"{self.sql(expression, 'this')}[{expressions_sql}]"
2142
2143 def safebracket_sql(self, expression: exp.SafeBracket) -> str:
2144 return self.bracket_sql(expression)
2145
2146 def all_sql(self, expression: exp.All) -> str:
2147 return f"ALL {self.wrap(expression)}"
2148
2149 def any_sql(self, expression: exp.Any) -> str:
2150 this = self.sql(expression, "this")
2151 if isinstance(expression.this, exp.Subqueryable):
2152 this = self.wrap(this)
2153 return f"ANY {this}"
2154
2155 def exists_sql(self, expression: exp.Exists) -> str:
2156 return f"EXISTS{self.wrap(expression)}"
2157
2158 def case_sql(self, expression: exp.Case) -> str:
2159 this = self.sql(expression, "this")
2160 statements = [f"CASE {this}" if this else "CASE"]
2161
2162 for e in expression.args["ifs"]:
2163 statements.append(f"WHEN {self.sql(e, 'this')}")
2164 statements.append(f"THEN {self.sql(e, 'true')}")
2165
2166 default = self.sql(expression, "default")
2167
2168 if default:
2169 statements.append(f"ELSE {default}")
2170
2171 statements.append("END")
2172
2173 if self.pretty and self.text_width(statements) > self.max_text_width:
2174 return self.indent("\n".join(statements), skip_first=True, skip_last=True)
2175
2176 return " ".join(statements)
2177
2178 def constraint_sql(self, expression: exp.Constraint) -> str:
2179 this = self.sql(expression, "this")
2180 expressions = self.expressions(expression, flat=True)
2181 return f"CONSTRAINT {this} {expressions}"
2182
2183 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:
2184 order = expression.args.get("order")
2185 order = f" OVER ({self.order_sql(order, flat=True)})" if order else ""
2186 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}"
2187
2188 def extract_sql(self, expression: exp.Extract) -> str:
2189 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name
2190 expression_sql = self.sql(expression, "expression")
2191 return f"EXTRACT({this} FROM {expression_sql})"
2192
2193 def trim_sql(self, expression: exp.Trim) -> str:
2194 trim_type = self.sql(expression, "position")
2195
2196 if trim_type == "LEADING":
2197 return self.func("LTRIM", expression.this)
2198 elif trim_type == "TRAILING":
2199 return self.func("RTRIM", expression.this)
2200 else:
2201 return self.func("TRIM", expression.this, expression.expression)
2202
2203 def safeconcat_sql(self, expression: exp.SafeConcat) -> str:
2204 expressions = expression.expressions
2205 if self.STRICT_STRING_CONCAT:
2206 expressions = [exp.cast(e, "text") for e in expressions]
2207 return self.func("CONCAT", *expressions)
2208
2209 def check_sql(self, expression: exp.Check) -> str:
2210 this = self.sql(expression, key="this")
2211 return f"CHECK ({this})"
2212
2213 def foreignkey_sql(self, expression: exp.ForeignKey) -> str:
2214 expressions = self.expressions(expression, flat=True)
2215 reference = self.sql(expression, "reference")
2216 reference = f" {reference}" if reference else ""
2217 delete = self.sql(expression, "delete")
2218 delete = f" ON DELETE {delete}" if delete else ""
2219 update = self.sql(expression, "update")
2220 update = f" ON UPDATE {update}" if update else ""
2221 return f"FOREIGN KEY ({expressions}){reference}{delete}{update}"
2222
2223 def primarykey_sql(self, expression: exp.ForeignKey) -> str:
2224 expressions = self.expressions(expression, flat=True)
2225 options = self.expressions(expression, key="options", flat=True, sep=" ")
2226 options = f" {options}" if options else ""
2227 return f"PRIMARY KEY ({expressions}){options}"
2228
2229 def if_sql(self, expression: exp.If) -> str:
2230 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false")))
2231
2232 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:
2233 modifier = expression.args.get("modifier")
2234 modifier = f" {modifier}" if modifier else ""
2235 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})"
2236
2237 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
2238 return f"{self.sql(expression, 'this')}: {self.sql(expression, 'expression')}"
2239
2240 def formatjson_sql(self, expression: exp.FormatJson) -> str:
2241 return f"{self.sql(expression, 'this')} FORMAT JSON"
2242
2243 def jsonobject_sql(self, expression: exp.JSONObject) -> str:
2244 null_handling = expression.args.get("null_handling")
2245 null_handling = f" {null_handling}" if null_handling else ""
2246 unique_keys = expression.args.get("unique_keys")
2247 if unique_keys is not None:
2248 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
2249 else:
2250 unique_keys = ""
2251 return_type = self.sql(expression, "return_type")
2252 return_type = f" RETURNING {return_type}" if return_type else ""
2253 encoding = self.sql(expression, "encoding")
2254 encoding = f" ENCODING {encoding}" if encoding else ""
2255 return self.func(
2256 "JSON_OBJECT",
2257 *expression.expressions,
2258 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})",
2259 )
2260
2261 def jsonarray_sql(self, expression: exp.JSONArray) -> str:
2262 null_handling = expression.args.get("null_handling")
2263 null_handling = f" {null_handling}" if null_handling else ""
2264 return_type = self.sql(expression, "return_type")
2265 return_type = f" RETURNING {return_type}" if return_type else ""
2266 strict = " STRICT" if expression.args.get("strict") else ""
2267 return self.func(
2268 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})"
2269 )
2270
2271 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:
2272 this = self.sql(expression, "this")
2273 order = self.sql(expression, "order")
2274 null_handling = expression.args.get("null_handling")
2275 null_handling = f" {null_handling}" if null_handling else ""
2276 return_type = self.sql(expression, "return_type")
2277 return_type = f" RETURNING {return_type}" if return_type else ""
2278 strict = " STRICT" if expression.args.get("strict") else ""
2279 return self.func(
2280 "JSON_ARRAYAGG",
2281 this,
2282 suffix=f"{order}{null_handling}{return_type}{strict})",
2283 )
2284
2285 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:
2286 path = self.sql(expression, "path")
2287 path = f" PATH {path}" if path else ""
2288 nested_schema = self.sql(expression, "nested_schema")
2289
2290 if nested_schema:
2291 return f"NESTED{path} {nested_schema}"
2292
2293 this = self.sql(expression, "this")
2294 kind = self.sql(expression, "kind")
2295 kind = f" {kind}" if kind else ""
2296 return f"{this}{kind}{path}"
2297
2298 def jsonschema_sql(self, expression: exp.JSONSchema) -> str:
2299 return self.func("COLUMNS", *expression.expressions)
2300
2301 def jsontable_sql(self, expression: exp.JSONTable) -> str:
2302 this = self.sql(expression, "this")
2303 path = self.sql(expression, "path")
2304 path = f", {path}" if path else ""
2305 error_handling = expression.args.get("error_handling")
2306 error_handling = f" {error_handling}" if error_handling else ""
2307 empty_handling = expression.args.get("empty_handling")
2308 empty_handling = f" {empty_handling}" if empty_handling else ""
2309 schema = self.sql(expression, "schema")
2310 return self.func(
2311 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})"
2312 )
2313
2314 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:
2315 this = self.sql(expression, "this")
2316 kind = self.sql(expression, "kind")
2317 path = self.sql(expression, "path")
2318 path = f" {path}" if path else ""
2319 as_json = " AS JSON" if expression.args.get("as_json") else ""
2320 return f"{this} {kind}{path}{as_json}"
2321
2322 def openjson_sql(self, expression: exp.OpenJSON) -> str:
2323 this = self.sql(expression, "this")
2324 path = self.sql(expression, "path")
2325 path = f", {path}" if path else ""
2326 expressions = self.expressions(expression)
2327 with_ = (
2328 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}"
2329 if expressions
2330 else ""
2331 )
2332 return f"OPENJSON({this}{path}){with_}"
2333
2334 def in_sql(self, expression: exp.In) -> str:
2335 query = expression.args.get("query")
2336 unnest = expression.args.get("unnest")
2337 field = expression.args.get("field")
2338 is_global = " GLOBAL" if expression.args.get("is_global") else ""
2339
2340 if query:
2341 in_sql = self.wrap(query)
2342 elif unnest:
2343 in_sql = self.in_unnest_op(unnest)
2344 elif field:
2345 in_sql = self.sql(field)
2346 else:
2347 in_sql = f"({self.expressions(expression, flat=True)})"
2348
2349 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
2350
2351 def in_unnest_op(self, unnest: exp.Unnest) -> str:
2352 return f"(SELECT {self.sql(unnest)})"
2353
2354 def interval_sql(self, expression: exp.Interval) -> str:
2355 unit = self.sql(expression, "unit")
2356 if not self.INTERVAL_ALLOWS_PLURAL_FORM:
2357 unit = self.TIME_PART_SINGULARS.get(unit.lower(), unit)
2358 unit = f" {unit}" if unit else ""
2359
2360 if self.SINGLE_STRING_INTERVAL:
2361 this = expression.this.name if expression.this else ""
2362 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}"
2363
2364 this = self.sql(expression, "this")
2365 if this:
2366 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)
2367 this = f" {this}" if unwrapped else f" ({this})"
2368
2369 return f"INTERVAL{this}{unit}"
2370
2371 def return_sql(self, expression: exp.Return) -> str:
2372 return f"RETURN {self.sql(expression, 'this')}"
2373
2374 def reference_sql(self, expression: exp.Reference) -> str:
2375 this = self.sql(expression, "this")
2376 expressions = self.expressions(expression, flat=True)
2377 expressions = f"({expressions})" if expressions else ""
2378 options = self.expressions(expression, key="options", flat=True, sep=" ")
2379 options = f" {options}" if options else ""
2380 return f"REFERENCES {this}{expressions}{options}"
2381
2382 def anonymous_sql(self, expression: exp.Anonymous) -> str:
2383 return self.func(expression.name, *expression.expressions)
2384
2385 def paren_sql(self, expression: exp.Paren) -> str:
2386 if isinstance(expression.unnest(), exp.Select):
2387 sql = self.wrap(expression)
2388 else:
2389 sql = self.seg(self.indent(self.sql(expression, "this")), sep="")
2390 sql = f"({sql}{self.seg(')', sep='')}"
2391
2392 return self.prepend_ctes(expression, sql)
2393
2394 def neg_sql(self, expression: exp.Neg) -> str:
2395 # This makes sure we don't convert "- - 5" to "--5", which is a comment
2396 this_sql = self.sql(expression, "this")
2397 sep = " " if this_sql[0] == "-" else ""
2398 return f"-{sep}{this_sql}"
2399
2400 def not_sql(self, expression: exp.Not) -> str:
2401 return f"NOT {self.sql(expression, 'this')}"
2402
2403 def alias_sql(self, expression: exp.Alias) -> str:
2404 alias = self.sql(expression, "alias")
2405 alias = f" AS {alias}" if alias else ""
2406 return f"{self.sql(expression, 'this')}{alias}"
2407
2408 def aliases_sql(self, expression: exp.Aliases) -> str:
2409 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
2410
2411 def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
2412 this = self.sql(expression, "this")
2413 zone = self.sql(expression, "zone")
2414 return f"{this} AT TIME ZONE {zone}"
2415
2416 def add_sql(self, expression: exp.Add) -> str:
2417 return self.binary(expression, "+")
2418
2419 def and_sql(self, expression: exp.And) -> str:
2420 return self.connector_sql(expression, "AND")
2421
2422 def xor_sql(self, expression: exp.Xor) -> str:
2423 return self.connector_sql(expression, "XOR")
2424
2425 def connector_sql(self, expression: exp.Connector, op: str) -> str:
2426 if not self.pretty:
2427 return self.binary(expression, op)
2428
2429 sqls = tuple(
2430 self.maybe_comment(self.sql(e), e, e.parent.comments or []) if i != 1 else self.sql(e)
2431 for i, e in enumerate(expression.flatten(unnest=False))
2432 )
2433
2434 sep = "\n" if self.text_width(sqls) > self.max_text_width else " "
2435 return f"{sep}{op} ".join(sqls)
2436
2437 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:
2438 return self.binary(expression, "&")
2439
2440 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:
2441 return self.binary(expression, "<<")
2442
2443 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:
2444 return f"~{self.sql(expression, 'this')}"
2445
2446 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:
2447 return self.binary(expression, "|")
2448
2449 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:
2450 return self.binary(expression, ">>")
2451
2452 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:
2453 return self.binary(expression, "^")
2454
2455 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
2456 format_sql = self.sql(expression, "format")
2457 format_sql = f" FORMAT {format_sql}" if format_sql else ""
2458 to_sql = self.sql(expression, "to")
2459 to_sql = f" {to_sql}" if to_sql else ""
2460 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{format_sql})"
2461
2462 def currentdate_sql(self, expression: exp.CurrentDate) -> str:
2463 zone = self.sql(expression, "this")
2464 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE"
2465
2466 def collate_sql(self, expression: exp.Collate) -> str:
2467 if self.COLLATE_IS_FUNC:
2468 return self.function_fallback_sql(expression)
2469 return self.binary(expression, "COLLATE")
2470
2471 def command_sql(self, expression: exp.Command) -> str:
2472 return f"{self.sql(expression, 'this').upper()} {expression.text('expression').strip()}"
2473
2474 def comment_sql(self, expression: exp.Comment) -> str:
2475 this = self.sql(expression, "this")
2476 kind = expression.args["kind"]
2477 exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
2478 expression_sql = self.sql(expression, "expression")
2479 return f"COMMENT{exists_sql}ON {kind} {this} IS {expression_sql}"
2480
2481 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:
2482 this = self.sql(expression, "this")
2483 delete = " DELETE" if expression.args.get("delete") else ""
2484 recompress = self.sql(expression, "recompress")
2485 recompress = f" RECOMPRESS {recompress}" if recompress else ""
2486 to_disk = self.sql(expression, "to_disk")
2487 to_disk = f" TO DISK {to_disk}" if to_disk else ""
2488 to_volume = self.sql(expression, "to_volume")
2489 to_volume = f" TO VOLUME {to_volume}" if to_volume else ""
2490 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
2491
2492 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:
2493 where = self.sql(expression, "where")
2494 group = self.sql(expression, "group")
2495 aggregates = self.expressions(expression, key="aggregates")
2496 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else ""
2497
2498 if not (where or group or aggregates) and len(expression.expressions) == 1:
2499 return f"TTL {self.expressions(expression, flat=True)}"
2500
2501 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
2502
2503 def transaction_sql(self, expression: exp.Transaction) -> str:
2504 return "BEGIN"
2505
2506 def commit_sql(self, expression: exp.Commit) -> str:
2507 chain = expression.args.get("chain")
2508 if chain is not None:
2509 chain = " AND CHAIN" if chain else " AND NO CHAIN"
2510
2511 return f"COMMIT{chain or ''}"
2512
2513 def rollback_sql(self, expression: exp.Rollback) -> str:
2514 savepoint = expression.args.get("savepoint")
2515 savepoint = f" TO {savepoint}" if savepoint else ""
2516 return f"ROLLBACK{savepoint}"
2517
2518 def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
2519 this = self.sql(expression, "this")
2520
2521 dtype = self.sql(expression, "dtype")
2522 if dtype:
2523 collate = self.sql(expression, "collate")
2524 collate = f" COLLATE {collate}" if collate else ""
2525 using = self.sql(expression, "using")
2526 using = f" USING {using}" if using else ""
2527 return f"ALTER COLUMN {this} SET DATA TYPE {dtype}{collate}{using}"
2528
2529 default = self.sql(expression, "default")
2530 if default:
2531 return f"ALTER COLUMN {this} SET DEFAULT {default}"
2532
2533 if not expression.args.get("drop"):
2534 self.unsupported("Unsupported ALTER COLUMN syntax")
2535
2536 return f"ALTER COLUMN {this} DROP DEFAULT"
2537
2538 def renametable_sql(self, expression: exp.RenameTable) -> str:
2539 if not self.RENAME_TABLE_WITH_DB:
2540 # Remove db from tables
2541 expression = expression.transform(
2542 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n
2543 )
2544 this = self.sql(expression, "this")
2545 return f"RENAME TO {this}"
2546
2547 def altertable_sql(self, expression: exp.AlterTable) -> str:
2548 actions = expression.args["actions"]
2549
2550 if isinstance(actions[0], exp.ColumnDef):
2551 actions = self.add_column_sql(expression)
2552 elif isinstance(actions[0], exp.Schema):
2553 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ")
2554 elif isinstance(actions[0], exp.Delete):
2555 actions = self.expressions(expression, key="actions", flat=True)
2556 else:
2557 actions = self.expressions(expression, key="actions", flat=True)
2558
2559 exists = " IF EXISTS" if expression.args.get("exists") else ""
2560 only = " ONLY" if expression.args.get("only") else ""
2561 return f"ALTER TABLE{exists}{only} {self.sql(expression, 'this')} {actions}"
2562
2563 def add_column_sql(self, expression: exp.AlterTable) -> str:
2564 if self.ALTER_TABLE_ADD_COLUMN_KEYWORD:
2565 return self.expressions(
2566 expression,
2567 key="actions",
2568 prefix="ADD COLUMN ",
2569 )
2570 return f"ADD {self.expressions(expression, key='actions', flat=True)}"
2571
2572 def droppartition_sql(self, expression: exp.DropPartition) -> str:
2573 expressions = self.expressions(expression)
2574 exists = " IF EXISTS " if expression.args.get("exists") else " "
2575 return f"DROP{exists}{expressions}"
2576
2577 def addconstraint_sql(self, expression: exp.AddConstraint) -> str:
2578 this = self.sql(expression, "this")
2579 expression_ = self.sql(expression, "expression")
2580 add_constraint = f"ADD CONSTRAINT {this}" if this else "ADD"
2581
2582 enforced = expression.args.get("enforced")
2583 if enforced is not None:
2584 return f"{add_constraint} CHECK ({expression_}){' ENFORCED' if enforced else ''}"
2585
2586 return f"{add_constraint} {expression_}"
2587
2588 def distinct_sql(self, expression: exp.Distinct) -> str:
2589 this = self.expressions(expression, flat=True)
2590 this = f" {this}" if this else ""
2591
2592 on = self.sql(expression, "on")
2593 on = f" ON {on}" if on else ""
2594 return f"DISTINCT{this}{on}"
2595
2596 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:
2597 return f"{self.sql(expression, 'this')} IGNORE NULLS"
2598
2599 def respectnulls_sql(self, expression: exp.RespectNulls) -> str:
2600 return f"{self.sql(expression, 'this')} RESPECT NULLS"
2601
2602 def intdiv_sql(self, expression: exp.IntDiv) -> str:
2603 return self.sql(
2604 exp.Cast(
2605 this=exp.Div(this=expression.this, expression=expression.expression),
2606 to=exp.DataType(this=exp.DataType.Type.INT),
2607 )
2608 )
2609
2610 def dpipe_sql(self, expression: exp.DPipe) -> str:
2611 return self.binary(expression, "||")
2612
2613 def safedpipe_sql(self, expression: exp.SafeDPipe) -> str:
2614 if self.STRICT_STRING_CONCAT:
2615 return self.func("CONCAT", *(exp.cast(e, "text") for e in expression.flatten()))
2616 return self.dpipe_sql(expression)
2617
2618 def div_sql(self, expression: exp.Div) -> str:
2619 l, r = expression.left, expression.right
2620
2621 if not self.SAFE_DIVISION and expression.args.get("safe"):
2622 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))
2623
2624 if self.TYPED_DIVISION and not expression.args.get("typed"):
2625 if not l.is_type(*exp.DataType.FLOAT_TYPES) and not r.is_type(
2626 *exp.DataType.FLOAT_TYPES
2627 ):
2628 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE))
2629
2630 elif not self.TYPED_DIVISION and expression.args.get("typed"):
2631 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):
2632 return self.sql(
2633 exp.cast(
2634 l / r,
2635 to=exp.DataType.Type.BIGINT,
2636 )
2637 )
2638
2639 return self.binary(expression, "/")
2640
2641 def overlaps_sql(self, expression: exp.Overlaps) -> str:
2642 return self.binary(expression, "OVERLAPS")
2643
2644 def distance_sql(self, expression: exp.Distance) -> str:
2645 return self.binary(expression, "<->")
2646
2647 def dot_sql(self, expression: exp.Dot) -> str:
2648 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}"
2649
2650 def eq_sql(self, expression: exp.EQ) -> str:
2651 return self.binary(expression, "=")
2652
2653 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
2654 return self.binary(expression, ":=")
2655
2656 def escape_sql(self, expression: exp.Escape) -> str:
2657 return self.binary(expression, "ESCAPE")
2658
2659 def glob_sql(self, expression: exp.Glob) -> str:
2660 return self.binary(expression, "GLOB")
2661
2662 def gt_sql(self, expression: exp.GT) -> str:
2663 return self.binary(expression, ">")
2664
2665 def gte_sql(self, expression: exp.GTE) -> str:
2666 return self.binary(expression, ">=")
2667
2668 def ilike_sql(self, expression: exp.ILike) -> str:
2669 return self.binary(expression, "ILIKE")
2670
2671 def ilikeany_sql(self, expression: exp.ILikeAny) -> str:
2672 return self.binary(expression, "ILIKE ANY")
2673
2674 def is_sql(self, expression: exp.Is) -> str:
2675 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):
2676 return self.sql(
2677 expression.this if expression.expression.this else exp.not_(expression.this)
2678 )
2679 return self.binary(expression, "IS")
2680
2681 def like_sql(self, expression: exp.Like) -> str:
2682 return self.binary(expression, "LIKE")
2683
2684 def likeany_sql(self, expression: exp.LikeAny) -> str:
2685 return self.binary(expression, "LIKE ANY")
2686
2687 def similarto_sql(self, expression: exp.SimilarTo) -> str:
2688 return self.binary(expression, "SIMILAR TO")
2689
2690 def lt_sql(self, expression: exp.LT) -> str:
2691 return self.binary(expression, "<")
2692
2693 def lte_sql(self, expression: exp.LTE) -> str:
2694 return self.binary(expression, "<=")
2695
2696 def mod_sql(self, expression: exp.Mod) -> str:
2697 return self.binary(expression, "%")
2698
2699 def mul_sql(self, expression: exp.Mul) -> str:
2700 return self.binary(expression, "*")
2701
2702 def neq_sql(self, expression: exp.NEQ) -> str:
2703 return self.binary(expression, "<>")
2704
2705 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:
2706 return self.binary(expression, "IS NOT DISTINCT FROM")
2707
2708 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:
2709 return self.binary(expression, "IS DISTINCT FROM")
2710
2711 def or_sql(self, expression: exp.Or) -> str:
2712 return self.connector_sql(expression, "OR")
2713
2714 def slice_sql(self, expression: exp.Slice) -> str:
2715 return self.binary(expression, ":")
2716
2717 def sub_sql(self, expression: exp.Sub) -> str:
2718 return self.binary(expression, "-")
2719
2720 def trycast_sql(self, expression: exp.TryCast) -> str:
2721 return self.cast_sql(expression, safe_prefix="TRY_")
2722
2723 def log_sql(self, expression: exp.Log) -> str:
2724 args = list(expression.args.values())
2725 if not self.LOG_BASE_FIRST:
2726 args.reverse()
2727 return self.func("LOG", *args)
2728
2729 def use_sql(self, expression: exp.Use) -> str:
2730 kind = self.sql(expression, "kind")
2731 kind = f" {kind}" if kind else ""
2732 this = self.sql(expression, "this")
2733 this = f" {this}" if this else ""
2734 return f"USE{kind}{this}"
2735
2736 def binary(self, expression: exp.Binary, op: str) -> str:
2737 op = self.maybe_comment(op, comments=expression.comments)
2738 return f"{self.sql(expression, 'this')} {op} {self.sql(expression, 'expression')}"
2739
2740 def function_fallback_sql(self, expression: exp.Func) -> str:
2741 args = []
2742
2743 for key in expression.arg_types:
2744 arg_value = expression.args.get(key)
2745
2746 if isinstance(arg_value, list):
2747 for value in arg_value:
2748 args.append(value)
2749 elif arg_value is not None:
2750 args.append(arg_value)
2751
2752 if self.normalize_functions:
2753 name = expression.sql_name()
2754 else:
2755 name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
2756
2757 return self.func(name, *args)
2758
2759 def func(
2760 self,
2761 name: str,
2762 *args: t.Optional[exp.Expression | str],
2763 prefix: str = "(",
2764 suffix: str = ")",
2765 ) -> str:
2766 return f"{self.normalize_func(name)}{prefix}{self.format_args(*args)}{suffix}"
2767
2768 def format_args(self, *args: t.Optional[str | exp.Expression]) -> str:
2769 arg_sqls = tuple(self.sql(arg) for arg in args if arg is not None)
2770 if self.pretty and self.text_width(arg_sqls) > self.max_text_width:
2771 return self.indent("\n" + f",\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True)
2772 return ", ".join(arg_sqls)
2773
2774 def text_width(self, args: t.Iterable) -> int:
2775 return sum(len(arg) for arg in args)
2776
2777 def format_time(self, expression: exp.Expression) -> t.Optional[str]:
2778 return format_time(
2779 self.sql(expression, "format"), self.INVERSE_TIME_MAPPING, self.INVERSE_TIME_TRIE
2780 )
2781
2782 def expressions(
2783 self,
2784 expression: t.Optional[exp.Expression] = None,
2785 key: t.Optional[str] = None,
2786 sqls: t.Optional[t.Collection[str | exp.Expression]] = None,
2787 flat: bool = False,
2788 indent: bool = True,
2789 skip_first: bool = False,
2790 sep: str = ", ",
2791 prefix: str = "",
2792 ) -> str:
2793 expressions = expression.args.get(key or "expressions") if expression else sqls
2794
2795 if not expressions:
2796 return ""
2797
2798 if flat:
2799 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)
2800
2801 num_sqls = len(expressions)
2802
2803 # These are calculated once in case we have the leading_comma / pretty option set, correspondingly
2804 pad = " " * self.pad
2805 stripped_sep = sep.strip()
2806
2807 result_sqls = []
2808 for i, e in enumerate(expressions):
2809 sql = self.sql(e, comment=False)
2810 if not sql:
2811 continue
2812
2813 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else ""
2814
2815 if self.pretty:
2816 if self.leading_comma:
2817 result_sqls.append(f"{sep if i > 0 else pad}{prefix}{sql}{comments}")
2818 else:
2819 result_sqls.append(
2820 f"{prefix}{sql}{stripped_sep if i + 1 < num_sqls else ''}{comments}"
2821 )
2822 else:
2823 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}")
2824
2825 result_sql = "\n".join(result_sqls) if self.pretty else "".join(result_sqls)
2826 return self.indent(result_sql, skip_first=skip_first) if indent else result_sql
2827
2828 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str:
2829 flat = flat or isinstance(expression.parent, exp.Properties)
2830 expressions_sql = self.expressions(expression, flat=flat)
2831 if flat:
2832 return f"{op} {expressions_sql}"
2833 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
2834
2835 def naked_property(self, expression: exp.Property) -> str:
2836 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)
2837 if not property_name:
2838 self.unsupported(f"Unsupported property {expression.__class__.__name__}")
2839 return f"{property_name} {self.sql(expression, 'this')}"
2840
2841 def set_operation(self, expression: exp.Union, op: str) -> str:
2842 this = self.maybe_comment(self.sql(expression, "this"), comments=expression.comments)
2843 op = self.seg(op)
2844 return self.query_modifiers(
2845 expression, f"{this}{op}{self.sep()}{self.sql(expression, 'expression')}"
2846 )
2847
2848 def tag_sql(self, expression: exp.Tag) -> str:
2849 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}"
2850
2851 def token_sql(self, token_type: TokenType) -> str:
2852 return self.TOKEN_MAPPING.get(token_type, token_type.name)
2853
2854 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:
2855 this = self.sql(expression, "this")
2856 expressions = self.no_identify(self.expressions, expression)
2857 expressions = (
2858 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}"
2859 )
2860 return f"{this}{expressions}"
2861
2862 def joinhint_sql(self, expression: exp.JoinHint) -> str:
2863 this = self.sql(expression, "this")
2864 expressions = self.expressions(expression, flat=True)
2865 return f"{this}({expressions})"
2866
2867 def kwarg_sql(self, expression: exp.Kwarg) -> str:
2868 return self.binary(expression, "=>")
2869
2870 def when_sql(self, expression: exp.When) -> str:
2871 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
2872 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else ""
2873 condition = self.sql(expression, "condition")
2874 condition = f" AND {condition}" if condition else ""
2875
2876 then_expression = expression.args.get("then")
2877 if isinstance(then_expression, exp.Insert):
2878 then = f"INSERT {self.sql(then_expression, 'this')}"
2879 if "expression" in then_expression.args:
2880 then += f" VALUES {self.sql(then_expression, 'expression')}"
2881 elif isinstance(then_expression, exp.Update):
2882 if isinstance(then_expression.args.get("expressions"), exp.Star):
2883 then = f"UPDATE {self.sql(then_expression, 'expressions')}"
2884 else:
2885 then = f"UPDATE SET {self.expressions(then_expression, flat=True)}"
2886 else:
2887 then = self.sql(then_expression)
2888 return f"WHEN {matched}{source}{condition} THEN {then}"
2889
2890 def merge_sql(self, expression: exp.Merge) -> str:
2891 table = expression.this
2892 table_alias = ""
2893
2894 hints = table.args.get("hints")
2895 if hints and table.alias and isinstance(hints[0], exp.WithTableHint):
2896 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]
2897 table_alias = f" AS {self.sql(table.args['alias'].pop())}"
2898
2899 this = self.sql(table)
2900 using = f"USING {self.sql(expression, 'using')}"
2901 on = f"ON {self.sql(expression, 'on')}"
2902 expressions = self.expressions(expression, sep=" ")
2903
2904 return self.prepend_ctes(
2905 expression, f"MERGE INTO {this}{table_alias} {using} {on} {expressions}"
2906 )
2907
2908 def tochar_sql(self, expression: exp.ToChar) -> str:
2909 if expression.args.get("format"):
2910 self.unsupported("Format argument unsupported for TO_CHAR/TO_VARCHAR function")
2911
2912 return self.sql(exp.cast(expression.this, "text"))
2913
2914 def dictproperty_sql(self, expression: exp.DictProperty) -> str:
2915 this = self.sql(expression, "this")
2916 kind = self.sql(expression, "kind")
2917 settings_sql = self.expressions(expression, key="settings", sep=" ")
2918 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()"
2919 return f"{this}({kind}{args})"
2920
2921 def dictrange_sql(self, expression: exp.DictRange) -> str:
2922 this = self.sql(expression, "this")
2923 max = self.sql(expression, "max")
2924 min = self.sql(expression, "min")
2925 return f"{this}(MIN {min} MAX {max})"
2926
2927 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:
2928 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}"
2929
2930 def oncluster_sql(self, expression: exp.OnCluster) -> str:
2931 return ""
2932
2933 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:
2934 expressions = self.expressions(expression, key="expressions", flat=True)
2935 sorted_by = self.expressions(expression, key="sorted_by", flat=True)
2936 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else ""
2937 buckets = self.sql(expression, "buckets")
2938 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
2939
2940 def anyvalue_sql(self, expression: exp.AnyValue) -> str:
2941 this = self.sql(expression, "this")
2942 having = self.sql(expression, "having")
2943
2944 if having:
2945 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}"
2946
2947 return self.func("ANY_VALUE", this)
2948
2949 def querytransform_sql(self, expression: exp.QueryTransform) -> str:
2950 transform = self.func("TRANSFORM", *expression.expressions)
2951 row_format_before = self.sql(expression, "row_format_before")
2952 row_format_before = f" {row_format_before}" if row_format_before else ""
2953 record_writer = self.sql(expression, "record_writer")
2954 record_writer = f" RECORDWRITER {record_writer}" if record_writer else ""
2955 using = f" USING {self.sql(expression, 'command_script')}"
2956 schema = self.sql(expression, "schema")
2957 schema = f" AS {schema}" if schema else ""
2958 row_format_after = self.sql(expression, "row_format_after")
2959 row_format_after = f" {row_format_after}" if row_format_after else ""
2960 record_reader = self.sql(expression, "record_reader")
2961 record_reader = f" RECORDREADER {record_reader}" if record_reader else ""
2962 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
2963
2964 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:
2965 key_block_size = self.sql(expression, "key_block_size")
2966 if key_block_size:
2967 return f"KEY_BLOCK_SIZE = {key_block_size}"
2968
2969 using = self.sql(expression, "using")
2970 if using:
2971 return f"USING {using}"
2972
2973 parser = self.sql(expression, "parser")
2974 if parser:
2975 return f"WITH PARSER {parser}"
2976
2977 comment = self.sql(expression, "comment")
2978 if comment:
2979 return f"COMMENT {comment}"
2980
2981 visible = expression.args.get("visible")
2982 if visible is not None:
2983 return "VISIBLE" if visible else "INVISIBLE"
2984
2985 engine_attr = self.sql(expression, "engine_attr")
2986 if engine_attr:
2987 return f"ENGINE_ATTRIBUTE = {engine_attr}"
2988
2989 secondary_engine_attr = self.sql(expression, "secondary_engine_attr")
2990 if secondary_engine_attr:
2991 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}"
2992
2993 self.unsupported("Unsupported index constraint option.")
2994 return ""
2995
2996 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:
2997 kind = self.sql(expression, "kind")
2998 kind = f"{kind} INDEX" if kind else "INDEX"
2999 this = self.sql(expression, "this")
3000 this = f" {this}" if this else ""
3001 index_type = self.sql(expression, "index_type")
3002 index_type = f" USING {index_type}" if index_type else ""
3003 schema = self.sql(expression, "schema")
3004 schema = f" {schema}" if schema else ""
3005 options = self.expressions(expression, key="options", sep=" ")
3006 options = f" {options}" if options else ""
3007 return f"{kind}{this}{index_type}{schema}{options}"
3008
3009 def nvl2_sql(self, expression: exp.Nvl2) -> str:
3010 if self.NVL2_SUPPORTED:
3011 return self.function_fallback_sql(expression)
3012
3013 case = exp.Case().when(
3014 expression.this.is_(exp.null()).not_(copy=False),
3015 expression.args["true"],
3016 copy=False,
3017 )
3018 else_cond = expression.args.get("false")
3019 if else_cond:
3020 case.else_(else_cond, copy=False)
3021
3022 return self.sql(case)
3023
3024 def comprehension_sql(self, expression: exp.Comprehension) -> str:
3025 this = self.sql(expression, "this")
3026 expr = self.sql(expression, "expression")
3027 iterator = self.sql(expression, "iterator")
3028 condition = self.sql(expression, "condition")
3029 condition = f" IF {condition}" if condition else ""
3030 return f"{this} FOR {expr} IN {iterator}{condition}"
3031
3032 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:
3033 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})"
3034
3035 def opclass_sql(self, expression: exp.Opclass) -> str: