Edit on GitHub

sqlglot.generators.presto

  1from __future__ import annotations
  2
  3import typing as t
  4
  5from sqlglot import exp, generator, transforms
  6from sqlglot.dialects.dialect import (
  7    bool_xor_sql,
  8    bracket_to_element_at_sql,
  9    datestrtodate_sql,
 10    encode_decode_sql,
 11    if_sql,
 12    left_to_substring_sql,
 13    no_ilike_sql,
 14    no_pivot_sql,
 15    no_timestamp_sql,
 16    regexp_extract_sql,
 17    rename_func,
 18    right_to_substring_sql,
 19    sha256_sql,
 20    strposition_sql,
 21    struct_extract_sql,
 22    timestamptrunc_sql,
 23    timestrtotime_sql,
 24    ts_or_ds_add_cast,
 25    unit_to_str,
 26    sequence_sql,
 27    explode_to_unnest_sql,
 28    sha2_digest_sql,
 29)
 30from sqlglot.dialects.hive import Hive
 31from sqlglot.generator import unsupported_args
 32from sqlglot.optimizer.scope import find_all_in_scope
 33from sqlglot.transforms import unqualify_columns
 34
 35DATE_ADD_OR_SUB = t.Union[exp.DateAdd, exp.TimestampAdd, exp.DateSub]
 36
 37
 38def _initcap_sql(self: PrestoGenerator, expression: exp.Initcap) -> str:
 39    delimiters = expression.expression
 40    if delimiters and not (
 41        delimiters.is_string and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS
 42    ):
 43        self.unsupported("INITCAP does not support custom delimiters")
 44
 45    regex = r"(\w)(\w*)"
 46    return f"REGEXP_REPLACE({self.sql(expression, 'this')}, '{regex}', x -> UPPER(x[1]) || LOWER(x[2]))"
 47
 48
 49def _no_sort_array(self: PrestoGenerator, expression: exp.SortArray) -> str:
 50    if expression.args.get("asc") == exp.false():
 51        comparator = "(a, b) -> CASE WHEN a < b THEN 1 WHEN a > b THEN -1 ELSE 0 END"
 52    else:
 53        comparator = None
 54    return self.func("ARRAY_SORT", expression.this, comparator)
 55
 56
 57def _schema_sql(self: PrestoGenerator, expression: exp.Schema) -> str:
 58    if isinstance(expression.parent, exp.PartitionedByProperty):
 59        # Any columns in the ARRAY[] string literals should not be quoted
 60        expression.transform(lambda n: n.name if isinstance(n, exp.Identifier) else n, copy=False)
 61
 62        partition_exprs = [
 63            self.sql(c) if isinstance(c, (exp.Func, exp.Property)) else self.sql(c, "this")
 64            for c in expression.expressions
 65        ]
 66        return self.sql(exp.Array(expressions=[exp.Literal.string(c) for c in partition_exprs]))
 67
 68    if expression.parent:
 69        for schema in expression.parent.find_all(exp.Schema):
 70            if schema is expression:
 71                continue
 72
 73            column_defs = schema.find_all(exp.ColumnDef)
 74            if column_defs and isinstance(schema.parent, exp.Property):
 75                expression.expressions.extend(column_defs)
 76
 77    return self.schema_sql(expression)
 78
 79
 80def _quantile_sql(self: PrestoGenerator, expression: exp.Quantile) -> str:
 81    self.unsupported("Presto does not support exact quantiles")
 82    return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile"))
 83
 84
 85def _str_to_time_sql(
 86    self: PrestoGenerator, expression: exp.StrToDate | exp.StrToTime | exp.TsOrDsToDate
 87) -> str:
 88    return self.func("DATE_PARSE", expression.this, self.format_time(expression))
 89
 90
 91def _ts_or_ds_to_date_sql(self: PrestoGenerator, expression: exp.TsOrDsToDate) -> str:
 92    time_format = self.format_time(expression)
 93    dialect_class = type(self.dialect)
 94    if time_format and time_format not in (dialect_class.TIME_FORMAT, dialect_class.DATE_FORMAT):
 95        return self.sql(exp.cast(_str_to_time_sql(self, expression), exp.DType.DATE))
 96    return self.sql(exp.cast(exp.cast(expression.this, exp.DType.TIMESTAMP), exp.DType.DATE))
 97
 98
 99def _ts_or_ds_add_sql(self: PrestoGenerator, expression: exp.TsOrDsAdd) -> str:
100    expression = ts_or_ds_add_cast(expression)
101    unit = unit_to_str(expression)
102    return self.func("DATE_ADD", unit, expression.expression, expression.this)
103
104
105def _ts_or_ds_diff_sql(self: PrestoGenerator, expression: exp.TsOrDsDiff) -> str:
106    this = exp.cast(expression.this, exp.DType.TIMESTAMP)
107    expr = exp.cast(expression.expression, exp.DType.TIMESTAMP)
108    unit = unit_to_str(expression)
109    return self.func("DATE_DIFF", unit, expr, this)
110
111
112def _first_last_sql(self: PrestoGenerator, expression: exp.Func) -> str:
113    """
114    Trino doesn't support FIRST / LAST as functions, but they're valid in the context
115    of MATCH_RECOGNIZE, so we need to preserve them in that case. In all other cases
116    they're converted into an ARBITRARY call.
117
118    Reference: https://trino.io/docs/current/sql/match-recognize.html#logical-navigation-functions
119    """
120    if isinstance(expression.find_ancestor(exp.MatchRecognize, exp.Select), exp.MatchRecognize):
121        return self.function_fallback_sql(expression)
122
123    return rename_func("ARBITRARY")(self, expression)
124
125
126def _unix_to_time_sql(self: PrestoGenerator, expression: exp.UnixToTime) -> str:
127    scale = expression.args.get("scale")
128    timestamp = self.sql(expression, "this")
129    if scale in (None, exp.UnixToTime.SECONDS):
130        return rename_func("FROM_UNIXTIME")(self, expression)
131
132    return f"FROM_UNIXTIME(CAST({timestamp} AS DOUBLE) / POW(10, {scale}))"
133
134
135def _to_int(self: PrestoGenerator, expression: exp.Expr) -> exp.Expr:
136    if not expression.type:
137        from sqlglot.optimizer.annotate_types import annotate_types
138
139        annotate_types(expression, dialect=self.dialect)
140    if expression.type and expression.type.this not in exp.DataType.INTEGER_TYPES:
141        return exp.cast(expression, to=exp.DType.BIGINT)
142    return expression
143
144
145def _date_delta_sql(
146    name: str, negate_interval: bool = False
147) -> t.Callable[[PrestoGenerator, DATE_ADD_OR_SUB], str]:
148    def _delta_sql(self: PrestoGenerator, expression: DATE_ADD_OR_SUB) -> str:
149        interval = _to_int(self, expression.expression)
150        return self.func(
151            name,
152            unit_to_str(expression),
153            interval * (-1) if negate_interval else interval,
154            expression.this,
155        )
156
157    return _delta_sql
158
159
160def _explode_to_unnest_sql(self: PrestoGenerator, expression: exp.Lateral) -> str:
161    explode = expression.this
162    if isinstance(explode, exp.Explode):
163        exploded_type = explode.this.type
164        alias = expression.args.get("alias")
165
166        # This attempts a best-effort transpilation of LATERAL VIEW EXPLODE on a struct array
167        if (
168            isinstance(alias, exp.TableAlias)
169            and isinstance(exploded_type, exp.DataType)
170            and exploded_type.is_type(exp.DType.ARRAY)
171            and exploded_type.expressions
172            and exploded_type.expressions[0].is_type(exp.DType.STRUCT)
173        ):
174            # When unnesting a ROW in Presto, it produces N columns, so we need to fix the alias
175            alias.set("columns", [c.this.copy() for c in exploded_type.expressions[0].expressions])
176    elif isinstance(explode, exp.Inline):
177        explode.replace(exp.Explode(this=explode.this.copy()))
178
179    return explode_to_unnest_sql(self, expression)
180
181
182def amend_exploded_column_table(expression: exp.Expr) -> exp.Expr:
183    # We check for expression.type because the columns can be amended only if types were inferred
184    if isinstance(expression, exp.Select) and expression.type:
185        for lateral in expression.args.get("laterals") or []:
186            alias = lateral.args.get("alias")
187            if (
188                not isinstance(lateral.this, exp.Explode)
189                or not isinstance(alias, exp.TableAlias)
190                or len(alias.columns) != 1
191            ):
192                continue
193
194            new_table = alias.this
195            old_table = alias.columns[0].name.lower()
196
197            # When transpiling a LATERAL VIEW EXPLODE Spark query, the exploded fields may be qualified
198            # with the struct column, resulting in invalid Presto references that need to be amended
199            for column in find_all_in_scope(expression, exp.Column):
200                if column.db.lower() == old_table:
201                    column.set("table", column.args["db"].pop())
202                elif column.table.lower() == old_table:
203                    column.set("table", new_table.copy())
204                elif column.name.lower() == old_table and isinstance(column.parent, exp.Dot):
205                    column.parent.replace(exp.column(column.parent.expression, table=new_table))
206
207    return expression
208
209
210class PrestoGenerator(generator.Generator):
211    SELECT_KINDS: tuple[str, ...] = ()
212    SUPPORTS_DECODE_CASE = False
213
214    AFTER_HAVING_MODIFIER_TRANSFORMS = generator.AFTER_HAVING_MODIFIER_TRANSFORMS
215
216    INTERVAL_ALLOWS_PLURAL_FORM = False
217    JOIN_HINTS = False
218    TABLE_HINTS = False
219    QUERY_HINTS = False
220    IS_BOOL_ALLOWED = False
221    TZ_TO_WITH_TIME_ZONE = True
222    NVL2_SUPPORTED = False
223    STRUCT_DELIMITER = ("(", ")")
224    LIMIT_ONLY_LITERALS = True
225    SUPPORTS_SINGLE_ARG_CONCAT = False
226    LIKE_PROPERTY_INSIDE_SCHEMA = True
227    MULTI_ARG_DISTINCT = False
228    SUPPORTS_TO_NUMBER = False
229    HEX_FUNC = "TO_HEX"
230    PARSE_JSON_NAME: str | None = "JSON_PARSE"
231    PAD_FILL_PATTERN_IS_REQUIRED = True
232    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
233    SUPPORTS_MEDIAN = False
234    ARRAY_SIZE_NAME = "CARDINALITY"
235
236    PROPERTIES_LOCATION = {
237        **generator.Generator.PROPERTIES_LOCATION,
238        exp.LocationProperty: exp.Properties.Location.UNSUPPORTED,
239        exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
240    }
241
242    TYPE_MAPPING = {
243        **generator.Generator.TYPE_MAPPING,
244        exp.DType.BINARY: "VARBINARY",
245        exp.DType.BIT: "BOOLEAN",
246        exp.DType.DATETIME: "TIMESTAMP",
247        exp.DType.DATETIME64: "TIMESTAMP",
248        exp.DType.FLOAT: "REAL",
249        exp.DType.HLLSKETCH: "HYPERLOGLOG",
250        exp.DType.INT: "INTEGER",
251        exp.DType.STRUCT: "ROW",
252        exp.DType.TEXT: "VARCHAR",
253        exp.DType.TIMESTAMPTZ: "TIMESTAMP",
254        exp.DType.TIMESTAMPNTZ: "TIMESTAMP",
255        exp.DType.TIMETZ: "TIME",
256    }
257
258    TRANSFORMS = {
259        **generator.Generator.TRANSFORMS,
260        exp.AnyValue: rename_func("ARBITRARY"),
261        exp.ApproxQuantile: lambda self, e: self.func(
262            "APPROX_PERCENTILE",
263            e.this,
264            e.args.get("weight"),
265            e.args.get("quantile"),
266            e.args.get("accuracy"),
267        ),
268        exp.ArgMax: rename_func("MAX_BY"),
269        exp.ArgMin: rename_func("MIN_BY"),
270        exp.Array: transforms.preprocess(
271            [transforms.inherit_struct_field_names],
272            generator=lambda self, e: f"ARRAY[{self.expressions(e, flat=True)}]",
273        ),
274        exp.ArrayAny: rename_func("ANY_MATCH"),
275        exp.ArrayConcat: rename_func("CONCAT"),
276        exp.ArrayContains: rename_func("CONTAINS"),
277        exp.ArrayToString: rename_func("ARRAY_JOIN"),
278        exp.ArrayUniqueAgg: rename_func("SET_AGG"),
279        exp.ArraySlice: rename_func("SLICE"),
280        exp.AtTimeZone: rename_func("AT_TIMEZONE"),
281        exp.BitwiseAnd: lambda self, e: self.func("BITWISE_AND", e.this, e.expression),
282        exp.BitwiseLeftShift: lambda self, e: self.func(
283            "BITWISE_ARITHMETIC_SHIFT_LEFT", e.this, e.expression
284        ),
285        exp.BitwiseNot: lambda self, e: self.func("BITWISE_NOT", e.this),
286        exp.BitwiseOr: lambda self, e: self.func("BITWISE_OR", e.this, e.expression),
287        exp.BitwiseRightShift: lambda self, e: self.func(
288            "BITWISE_ARITHMETIC_SHIFT_RIGHT", e.this, e.expression
289        ),
290        exp.BitwiseXor: lambda self, e: self.func("BITWISE_XOR", e.this, e.expression),
291        exp.Cast: transforms.preprocess([transforms.epoch_cast_to_ts]),
292        exp.CurrentTime: lambda *_: "CURRENT_TIME",
293        exp.CurrentTimestamp: lambda *_: "CURRENT_TIMESTAMP",
294        exp.CurrentUser: lambda *_: "CURRENT_USER",
295        exp.DateAdd: _date_delta_sql("DATE_ADD"),
296        exp.DateDiff: lambda self, e: self.func("DATE_DIFF", unit_to_str(e), e.expression, e.this),
297        exp.DateStrToDate: datestrtodate_sql,
298        exp.DateToDi: lambda self, e: (
299            f"CAST(DATE_FORMAT({self.sql(e, 'this')}, {type(self.dialect).DATEINT_FORMAT}) AS INT)"
300        ),
301        exp.DateSub: _date_delta_sql("DATE_ADD", negate_interval=True),
302        exp.DayOfWeek: lambda self, e: f"(({self.func('DAY_OF_WEEK', e.this)} % 7) + 1)",
303        exp.DayOfWeekIso: rename_func("DAY_OF_WEEK"),
304        exp.Decode: lambda self, e: encode_decode_sql(self, e, "FROM_UTF8"),
305        exp.DiToDate: lambda self, e: (
306            f"CAST(DATE_PARSE(CAST({self.sql(e, 'this')} AS VARCHAR), {type(self.dialect).DATEINT_FORMAT}) AS DATE)"
307        ),
308        exp.Encode: lambda self, e: encode_decode_sql(self, e, "TO_UTF8"),
309        exp.FileFormatProperty: lambda self, e: f"format={self.sql(exp.Literal.string(e.name))}",
310        exp.First: _first_last_sql,
311        exp.FromTimeZone: lambda self, e: (
312            f"WITH_TIMEZONE({self.sql(e, 'this')}, {self.sql(e, 'zone')}) AT TIME ZONE 'UTC'"
313        ),
314        exp.GenerateSeries: sequence_sql,
315        exp.GenerateDateArray: sequence_sql,
316        exp.If: if_sql(),
317        exp.ILike: no_ilike_sql,
318        exp.Initcap: _initcap_sql,
319        exp.Last: _first_last_sql,
320        exp.LastDay: lambda self, e: self.func("LAST_DAY_OF_MONTH", e.this),
321        exp.Lateral: _explode_to_unnest_sql,
322        exp.Left: left_to_substring_sql,
323        exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost", "max_dist")(
324            rename_func("LEVENSHTEIN_DISTANCE")
325        ),
326        exp.LogicalAnd: rename_func("BOOL_AND"),
327        exp.LogicalOr: rename_func("BOOL_OR"),
328        exp.Pivot: no_pivot_sql,
329        exp.Quantile: _quantile_sql,
330        exp.RegexpExtract: regexp_extract_sql,
331        exp.RegexpExtractAll: regexp_extract_sql,
332        exp.Right: right_to_substring_sql,
333        exp.Schema: _schema_sql,
334        exp.SchemaCommentProperty: lambda self, e: self.naked_property(e),
335        exp.Select: transforms.preprocess(
336            [
337                transforms.eliminate_window_clause,
338                transforms.eliminate_qualify,
339                transforms.eliminate_distinct_on,
340                transforms.explode_projection_to_unnest(1),
341                transforms.eliminate_semi_and_anti_joins,
342                amend_exploded_column_table,
343            ]
344        ),
345        exp.SortArray: _no_sort_array,
346        exp.SqlSecurityProperty: lambda self, e: f"SECURITY {self.sql(e.this)}",
347        exp.StrPosition: lambda self, e: strposition_sql(self, e, supports_occurrence=True),
348        exp.StrToDate: lambda self, e: f"CAST({_str_to_time_sql(self, e)} AS DATE)",
349        exp.StrToMap: rename_func("SPLIT_TO_MAP"),
350        exp.StrToTime: _str_to_time_sql,
351        exp.StructExtract: struct_extract_sql,
352        exp.Table: transforms.preprocess([transforms.unnest_generate_series]),
353        exp.Timestamp: no_timestamp_sql,
354        exp.TimestampAdd: _date_delta_sql("DATE_ADD"),
355        exp.TimestampTrunc: timestamptrunc_sql(),
356        exp.TimeStrToDate: timestrtotime_sql,
357        exp.TimeStrToTime: timestrtotime_sql,
358        exp.TimeStrToUnix: lambda self, e: self.func(
359            "TO_UNIXTIME", self.func("DATE_PARSE", e.this, type(self.dialect).TIME_FORMAT)
360        ),
361        exp.TimeToStr: lambda self, e: self.func("DATE_FORMAT", e.this, self.format_time(e)),
362        exp.TimeToUnix: rename_func("TO_UNIXTIME"),
363        exp.ToChar: lambda self, e: self.func("DATE_FORMAT", e.this, self.format_time(e)),
364        exp.TryCast: transforms.preprocess([transforms.epoch_cast_to_ts]),
365        exp.TsOrDiToDi: lambda self, e: (
366            f"CAST(SUBSTR(REPLACE(CAST({self.sql(e, 'this')} AS VARCHAR), '-', ''), 1, 8) AS INT)"
367        ),
368        exp.TsOrDsAdd: _ts_or_ds_add_sql,
369        exp.TsOrDsDiff: _ts_or_ds_diff_sql,
370        exp.TsOrDsToDate: _ts_or_ds_to_date_sql,
371        exp.Unhex: rename_func("FROM_HEX"),
372        exp.UnixToStr: lambda self, e: (
373            f"DATE_FORMAT(FROM_UNIXTIME({self.sql(e, 'this')}), {self.format_time(e)})"
374        ),
375        exp.UnixToTime: _unix_to_time_sql,
376        exp.UnixToTimeStr: lambda self, e: f"CAST(FROM_UNIXTIME({self.sql(e, 'this')}) AS VARCHAR)",
377        exp.VariancePop: rename_func("VAR_POP"),
378        exp.With: transforms.preprocess([transforms.add_recursive_cte_column_names]),
379        exp.WithinGroup: transforms.preprocess([transforms.remove_within_group_for_percentiles]),
380        # Note: Presto's TRUNCATE always returns DOUBLE, even with decimals=0, whereas
381        # most dialects return INT (SQLite also returns REAL, see sqlite.py). This creates
382        # a bidirectional transpilation gap: Presto→Other may change float division to int
383        # division, and vice versa. Modeling precisely would require exp.FloatTrunc or
384        # similar, deemed overengineering for this subtle semantic difference.
385        exp.Trunc: rename_func("TRUNCATE"),
386        exp.Xor: bool_xor_sql,
387        exp.MD5Digest: rename_func("MD5"),
388        exp.SHA: rename_func("SHA1"),
389        exp.SHA1Digest: rename_func("SHA1"),
390        exp.SHA2: sha256_sql,
391        exp.SHA2Digest: sha2_digest_sql,
392    }
393
394    RESERVED_KEYWORDS = {
395        "alter",
396        "and",
397        "as",
398        "between",
399        "by",
400        "case",
401        "cast",
402        "constraint",
403        "create",
404        "cross",
405        "current_time",
406        "current_timestamp",
407        "deallocate",
408        "delete",
409        "describe",
410        "distinct",
411        "drop",
412        "else",
413        "end",
414        "escape",
415        "except",
416        "execute",
417        "exists",
418        "extract",
419        "false",
420        "for",
421        "from",
422        "full",
423        "group",
424        "having",
425        "in",
426        "inner",
427        "insert",
428        "intersect",
429        "into",
430        "is",
431        "join",
432        "left",
433        "like",
434        "natural",
435        "not",
436        "null",
437        "on",
438        "or",
439        "order",
440        "outer",
441        "prepare",
442        "right",
443        "select",
444        "table",
445        "then",
446        "true",
447        "union",
448        "using",
449        "values",
450        "when",
451        "where",
452        "with",
453    }
454
455    def extract_sql(self, expression: exp.Extract) -> str:
456        date_part = expression.name
457
458        if not date_part.startswith("EPOCH"):
459            return super().extract_sql(expression)
460
461        if date_part == "EPOCH_MILLISECOND":
462            scale = 10**3
463        elif date_part == "EPOCH_MICROSECOND":
464            scale = 10**6
465        elif date_part == "EPOCH_NANOSECOND":
466            scale = 10**9
467        else:
468            scale = None
469
470        value = expression.expression
471
472        ts = exp.cast(value, to=exp.DType.TIMESTAMP.into_expr())
473        to_unix: exp.Expr = exp.TimeToUnix(this=ts)
474
475        if scale:
476            to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale))
477
478        return self.sql(to_unix)
479
480    def jsonformat_sql(self, expression: exp.JSONFormat) -> str:
481        this = expression.this
482        is_json = expression.args.get("is_json")
483
484        if this and not (is_json or this.type):
485            from sqlglot.optimizer.annotate_types import annotate_types
486
487            this = annotate_types(this, dialect=self.dialect)
488
489        if not (is_json or this.is_type(exp.DType.JSON)):
490            this.replace(exp.cast(this, exp.DType.JSON))
491
492        return self.function_fallback_sql(expression)
493
494    def md5_sql(self, expression: exp.MD5) -> str:
495        this = expression.this
496
497        if not this.type:
498            from sqlglot.optimizer.annotate_types import annotate_types
499
500            this = annotate_types(this, dialect=self.dialect)
501
502        if this.is_type(*exp.DataType.TEXT_TYPES):
503            this = exp.Encode(this=this, charset=exp.Literal.string("utf-8"))
504
505        return self.func("LOWER", self.func("TO_HEX", self.func("MD5", self.sql(this))))
506
507    def strtounix_sql(self, expression: exp.StrToUnix) -> str:
508        # Since `TO_UNIXTIME` requires a `TIMESTAMP`, we need to parse the argument into one.
509        # To do this, we first try to `DATE_PARSE` it, but since this can fail when there's a
510        # timezone involved, we wrap it in a `TRY` call and use `PARSE_DATETIME` as a fallback,
511        # which seems to be using the same time mapping as Hive, as per:
512        # https://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html
513        this = expression.this
514        value_as_text = exp.cast(this, exp.DType.TEXT)
515        value_as_timestamp = exp.cast(this, exp.DType.TIMESTAMP) if this.is_string else this
516
517        parse_without_tz = self.func("DATE_PARSE", value_as_text, self.format_time(expression))
518
519        formatted_value = self.func("DATE_FORMAT", value_as_timestamp, self.format_time(expression))
520        parse_with_tz = self.func(
521            "PARSE_DATETIME",
522            formatted_value,
523            self.format_time(expression, Hive.INVERSE_TIME_MAPPING, Hive.INVERSE_TIME_TRIE),
524        )
525        coalesced = self.func("COALESCE", self.func("TRY", parse_without_tz), parse_with_tz)
526        return self.func("TO_UNIXTIME", coalesced)
527
528    def bracket_sql(self, expression: exp.Bracket) -> str:
529        if expression.args.get("safe"):
530            return bracket_to_element_at_sql(self, expression)
531        return super().bracket_sql(expression)
532
533    def struct_sql(self, expression: exp.Struct) -> str:
534        if not expression.type:
535            from sqlglot.optimizer.annotate_types import annotate_types
536
537            annotate_types(expression, dialect=self.dialect)
538
539        values: list[str] = []
540        schema: list[str] = []
541        unknown_type = False
542
543        for e in expression.expressions:
544            if isinstance(e, exp.PropertyEQ):
545                if e.type and e.type.is_type(exp.DType.UNKNOWN):
546                    unknown_type = True
547                else:
548                    schema.append(f"{self.sql(e, 'this')} {self.sql(e.type)}")
549                values.append(self.sql(e, "expression"))
550            else:
551                values.append(self.sql(e))
552
553        size = len(expression.expressions)
554
555        if not size or len(schema) != size:
556            if unknown_type:
557                self.unsupported(
558                    "Cannot convert untyped key-value definitions (try annotate_types)."
559                )
560            return self.func("ROW", *values)
561        return f"CAST(ROW({', '.join(values)}) AS ROW({', '.join(schema)}))"
562
563    def interval_sql(self, expression: exp.Interval) -> str:
564        if expression.this and expression.text("unit").upper().startswith("WEEK"):
565            return f"({expression.this.name} * INTERVAL '7' DAY)"
566        return super().interval_sql(expression)
567
568    def transaction_sql(self, expression: exp.Transaction) -> str:
569        modes = expression.args.get("modes")
570        modes = f" {', '.join(modes)}" if modes else ""
571        return f"START TRANSACTION{modes}"
572
573    def offset_limit_modifiers(
574        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
575    ) -> list[str]:
576        return [
577            self.sql(expression, "offset"),
578            self.sql(limit),
579        ]
580
581    def create_sql(self, expression: exp.Create) -> str:
582        """
583        Presto doesn't support CREATE VIEW with expressions (ex: `CREATE VIEW x (cola)` then `(cola)` is the expression),
584        so we need to remove them
585        """
586        kind = expression.args["kind"]
587        schema = expression.this
588        if kind == "VIEW" and schema.expressions:
589            expression.this.set("expressions", None)
590        return super().create_sql(expression)
591
592    def delete_sql(self, expression: exp.Delete) -> str:
593        """
594        Presto only supports DELETE FROM for a single table without an alias, so we need
595        to remove the unnecessary parts. If the original DELETE statement contains more
596        than one table to be deleted, we can't safely map it 1-1 to a Presto statement.
597        """
598        tables = expression.args.get("tables") or [expression.this]
599        if len(tables) > 1:
600            return super().delete_sql(expression)
601
602        table = tables[0]
603        expression.set("this", table)
604        expression.set("tables", None)
605
606        if isinstance(table, exp.Table):
607            table_alias = table.args.get("alias")
608            if table_alias:
609                table_alias.pop()
610                expression = t.cast(exp.Delete, expression.transform(unqualify_columns))
611
612        return super().delete_sql(expression)
613
614    def jsonextract_sql(self, expression: exp.JSONExtract) -> str:
615        is_json_extract = self.dialect.settings.get("variant_extract_is_json_extract", True)
616
617        # Generate JSON_EXTRACT unless the user has configured that a Snowflake / Databricks
618        # VARIANT extract (e.g. col:x.y) should map to dot notation (i.e ROW access) in Presto/Trino
619        if not expression.args.get("variant_extract") or is_json_extract:
620            return self.func(
621                "JSON_EXTRACT", expression.this, expression.expression, *expression.expressions
622            )
623
624        this = self.sql(expression, "this")
625
626        # Convert the JSONPath extraction `JSON_EXTRACT(col, '$.x.y) to a ROW access col.x.y
627        segments = []
628        for path_key in expression.expression.expressions[1:]:
629            if not isinstance(path_key, exp.JSONPathKey):
630                # Cannot transpile subscripts, wildcards etc to dot notation
631                self.unsupported(f"Cannot transpile JSONPath segment '{path_key}' to ROW access")
632                continue
633            key = path_key.this
634            if not exp.SAFE_IDENTIFIER_RE.match(key):
635                key = f'"{key}"'
636            segments.append(f".{key}")
637
638        expr = "".join(segments)
639
640        return f"{this}{expr}"
641
642    def groupconcat_sql(self, expression: exp.GroupConcat) -> str:
643        return self.func(
644            "ARRAY_JOIN",
645            self.func("ARRAY_AGG", expression.this),
646            expression.args.get("separator"),
647        )
def amend_exploded_column_table( expression: sqlglot.expressions.core.Expr) -> sqlglot.expressions.core.Expr:
183def amend_exploded_column_table(expression: exp.Expr) -> exp.Expr:
184    # We check for expression.type because the columns can be amended only if types were inferred
185    if isinstance(expression, exp.Select) and expression.type:
186        for lateral in expression.args.get("laterals") or []:
187            alias = lateral.args.get("alias")
188            if (
189                not isinstance(lateral.this, exp.Explode)
190                or not isinstance(alias, exp.TableAlias)
191                or len(alias.columns) != 1
192            ):
193                continue
194
195            new_table = alias.this
196            old_table = alias.columns[0].name.lower()
197
198            # When transpiling a LATERAL VIEW EXPLODE Spark query, the exploded fields may be qualified
199            # with the struct column, resulting in invalid Presto references that need to be amended
200            for column in find_all_in_scope(expression, exp.Column):
201                if column.db.lower() == old_table:
202                    column.set("table", column.args["db"].pop())
203                elif column.table.lower() == old_table:
204                    column.set("table", new_table.copy())
205                elif column.name.lower() == old_table and isinstance(column.parent, exp.Dot):
206                    column.parent.replace(exp.column(column.parent.expression, table=new_table))
207
208    return expression
class PrestoGenerator(sqlglot.generator.Generator):
211class PrestoGenerator(generator.Generator):
212    SELECT_KINDS: tuple[str, ...] = ()
213    SUPPORTS_DECODE_CASE = False
214
215    AFTER_HAVING_MODIFIER_TRANSFORMS = generator.AFTER_HAVING_MODIFIER_TRANSFORMS
216
217    INTERVAL_ALLOWS_PLURAL_FORM = False
218    JOIN_HINTS = False
219    TABLE_HINTS = False
220    QUERY_HINTS = False
221    IS_BOOL_ALLOWED = False
222    TZ_TO_WITH_TIME_ZONE = True
223    NVL2_SUPPORTED = False
224    STRUCT_DELIMITER = ("(", ")")
225    LIMIT_ONLY_LITERALS = True
226    SUPPORTS_SINGLE_ARG_CONCAT = False
227    LIKE_PROPERTY_INSIDE_SCHEMA = True
228    MULTI_ARG_DISTINCT = False
229    SUPPORTS_TO_NUMBER = False
230    HEX_FUNC = "TO_HEX"
231    PARSE_JSON_NAME: str | None = "JSON_PARSE"
232    PAD_FILL_PATTERN_IS_REQUIRED = True
233    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
234    SUPPORTS_MEDIAN = False
235    ARRAY_SIZE_NAME = "CARDINALITY"
236
237    PROPERTIES_LOCATION = {
238        **generator.Generator.PROPERTIES_LOCATION,
239        exp.LocationProperty: exp.Properties.Location.UNSUPPORTED,
240        exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
241    }
242
243    TYPE_MAPPING = {
244        **generator.Generator.TYPE_MAPPING,
245        exp.DType.BINARY: "VARBINARY",
246        exp.DType.BIT: "BOOLEAN",
247        exp.DType.DATETIME: "TIMESTAMP",
248        exp.DType.DATETIME64: "TIMESTAMP",
249        exp.DType.FLOAT: "REAL",
250        exp.DType.HLLSKETCH: "HYPERLOGLOG",
251        exp.DType.INT: "INTEGER",
252        exp.DType.STRUCT: "ROW",
253        exp.DType.TEXT: "VARCHAR",
254        exp.DType.TIMESTAMPTZ: "TIMESTAMP",
255        exp.DType.TIMESTAMPNTZ: "TIMESTAMP",
256        exp.DType.TIMETZ: "TIME",
257    }
258
259    TRANSFORMS = {
260        **generator.Generator.TRANSFORMS,
261        exp.AnyValue: rename_func("ARBITRARY"),
262        exp.ApproxQuantile: lambda self, e: self.func(
263            "APPROX_PERCENTILE",
264            e.this,
265            e.args.get("weight"),
266            e.args.get("quantile"),
267            e.args.get("accuracy"),
268        ),
269        exp.ArgMax: rename_func("MAX_BY"),
270        exp.ArgMin: rename_func("MIN_BY"),
271        exp.Array: transforms.preprocess(
272            [transforms.inherit_struct_field_names],
273            generator=lambda self, e: f"ARRAY[{self.expressions(e, flat=True)}]",
274        ),
275        exp.ArrayAny: rename_func("ANY_MATCH"),
276        exp.ArrayConcat: rename_func("CONCAT"),
277        exp.ArrayContains: rename_func("CONTAINS"),
278        exp.ArrayToString: rename_func("ARRAY_JOIN"),
279        exp.ArrayUniqueAgg: rename_func("SET_AGG"),
280        exp.ArraySlice: rename_func("SLICE"),
281        exp.AtTimeZone: rename_func("AT_TIMEZONE"),
282        exp.BitwiseAnd: lambda self, e: self.func("BITWISE_AND", e.this, e.expression),
283        exp.BitwiseLeftShift: lambda self, e: self.func(
284            "BITWISE_ARITHMETIC_SHIFT_LEFT", e.this, e.expression
285        ),
286        exp.BitwiseNot: lambda self, e: self.func("BITWISE_NOT", e.this),
287        exp.BitwiseOr: lambda self, e: self.func("BITWISE_OR", e.this, e.expression),
288        exp.BitwiseRightShift: lambda self, e: self.func(
289            "BITWISE_ARITHMETIC_SHIFT_RIGHT", e.this, e.expression
290        ),
291        exp.BitwiseXor: lambda self, e: self.func("BITWISE_XOR", e.this, e.expression),
292        exp.Cast: transforms.preprocess([transforms.epoch_cast_to_ts]),
293        exp.CurrentTime: lambda *_: "CURRENT_TIME",
294        exp.CurrentTimestamp: lambda *_: "CURRENT_TIMESTAMP",
295        exp.CurrentUser: lambda *_: "CURRENT_USER",
296        exp.DateAdd: _date_delta_sql("DATE_ADD"),
297        exp.DateDiff: lambda self, e: self.func("DATE_DIFF", unit_to_str(e), e.expression, e.this),
298        exp.DateStrToDate: datestrtodate_sql,
299        exp.DateToDi: lambda self, e: (
300            f"CAST(DATE_FORMAT({self.sql(e, 'this')}, {type(self.dialect).DATEINT_FORMAT}) AS INT)"
301        ),
302        exp.DateSub: _date_delta_sql("DATE_ADD", negate_interval=True),
303        exp.DayOfWeek: lambda self, e: f"(({self.func('DAY_OF_WEEK', e.this)} % 7) + 1)",
304        exp.DayOfWeekIso: rename_func("DAY_OF_WEEK"),
305        exp.Decode: lambda self, e: encode_decode_sql(self, e, "FROM_UTF8"),
306        exp.DiToDate: lambda self, e: (
307            f"CAST(DATE_PARSE(CAST({self.sql(e, 'this')} AS VARCHAR), {type(self.dialect).DATEINT_FORMAT}) AS DATE)"
308        ),
309        exp.Encode: lambda self, e: encode_decode_sql(self, e, "TO_UTF8"),
310        exp.FileFormatProperty: lambda self, e: f"format={self.sql(exp.Literal.string(e.name))}",
311        exp.First: _first_last_sql,
312        exp.FromTimeZone: lambda self, e: (
313            f"WITH_TIMEZONE({self.sql(e, 'this')}, {self.sql(e, 'zone')}) AT TIME ZONE 'UTC'"
314        ),
315        exp.GenerateSeries: sequence_sql,
316        exp.GenerateDateArray: sequence_sql,
317        exp.If: if_sql(),
318        exp.ILike: no_ilike_sql,
319        exp.Initcap: _initcap_sql,
320        exp.Last: _first_last_sql,
321        exp.LastDay: lambda self, e: self.func("LAST_DAY_OF_MONTH", e.this),
322        exp.Lateral: _explode_to_unnest_sql,
323        exp.Left: left_to_substring_sql,
324        exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost", "max_dist")(
325            rename_func("LEVENSHTEIN_DISTANCE")
326        ),
327        exp.LogicalAnd: rename_func("BOOL_AND"),
328        exp.LogicalOr: rename_func("BOOL_OR"),
329        exp.Pivot: no_pivot_sql,
330        exp.Quantile: _quantile_sql,
331        exp.RegexpExtract: regexp_extract_sql,
332        exp.RegexpExtractAll: regexp_extract_sql,
333        exp.Right: right_to_substring_sql,
334        exp.Schema: _schema_sql,
335        exp.SchemaCommentProperty: lambda self, e: self.naked_property(e),
336        exp.Select: transforms.preprocess(
337            [
338                transforms.eliminate_window_clause,
339                transforms.eliminate_qualify,
340                transforms.eliminate_distinct_on,
341                transforms.explode_projection_to_unnest(1),
342                transforms.eliminate_semi_and_anti_joins,
343                amend_exploded_column_table,
344            ]
345        ),
346        exp.SortArray: _no_sort_array,
347        exp.SqlSecurityProperty: lambda self, e: f"SECURITY {self.sql(e.this)}",
348        exp.StrPosition: lambda self, e: strposition_sql(self, e, supports_occurrence=True),
349        exp.StrToDate: lambda self, e: f"CAST({_str_to_time_sql(self, e)} AS DATE)",
350        exp.StrToMap: rename_func("SPLIT_TO_MAP"),
351        exp.StrToTime: _str_to_time_sql,
352        exp.StructExtract: struct_extract_sql,
353        exp.Table: transforms.preprocess([transforms.unnest_generate_series]),
354        exp.Timestamp: no_timestamp_sql,
355        exp.TimestampAdd: _date_delta_sql("DATE_ADD"),
356        exp.TimestampTrunc: timestamptrunc_sql(),
357        exp.TimeStrToDate: timestrtotime_sql,
358        exp.TimeStrToTime: timestrtotime_sql,
359        exp.TimeStrToUnix: lambda self, e: self.func(
360            "TO_UNIXTIME", self.func("DATE_PARSE", e.this, type(self.dialect).TIME_FORMAT)
361        ),
362        exp.TimeToStr: lambda self, e: self.func("DATE_FORMAT", e.this, self.format_time(e)),
363        exp.TimeToUnix: rename_func("TO_UNIXTIME"),
364        exp.ToChar: lambda self, e: self.func("DATE_FORMAT", e.this, self.format_time(e)),
365        exp.TryCast: transforms.preprocess([transforms.epoch_cast_to_ts]),
366        exp.TsOrDiToDi: lambda self, e: (
367            f"CAST(SUBSTR(REPLACE(CAST({self.sql(e, 'this')} AS VARCHAR), '-', ''), 1, 8) AS INT)"
368        ),
369        exp.TsOrDsAdd: _ts_or_ds_add_sql,
370        exp.TsOrDsDiff: _ts_or_ds_diff_sql,
371        exp.TsOrDsToDate: _ts_or_ds_to_date_sql,
372        exp.Unhex: rename_func("FROM_HEX"),
373        exp.UnixToStr: lambda self, e: (
374            f"DATE_FORMAT(FROM_UNIXTIME({self.sql(e, 'this')}), {self.format_time(e)})"
375        ),
376        exp.UnixToTime: _unix_to_time_sql,
377        exp.UnixToTimeStr: lambda self, e: f"CAST(FROM_UNIXTIME({self.sql(e, 'this')}) AS VARCHAR)",
378        exp.VariancePop: rename_func("VAR_POP"),
379        exp.With: transforms.preprocess([transforms.add_recursive_cte_column_names]),
380        exp.WithinGroup: transforms.preprocess([transforms.remove_within_group_for_percentiles]),
381        # Note: Presto's TRUNCATE always returns DOUBLE, even with decimals=0, whereas
382        # most dialects return INT (SQLite also returns REAL, see sqlite.py). This creates
383        # a bidirectional transpilation gap: Presto→Other may change float division to int
384        # division, and vice versa. Modeling precisely would require exp.FloatTrunc or
385        # similar, deemed overengineering for this subtle semantic difference.
386        exp.Trunc: rename_func("TRUNCATE"),
387        exp.Xor: bool_xor_sql,
388        exp.MD5Digest: rename_func("MD5"),
389        exp.SHA: rename_func("SHA1"),
390        exp.SHA1Digest: rename_func("SHA1"),
391        exp.SHA2: sha256_sql,
392        exp.SHA2Digest: sha2_digest_sql,
393    }
394
395    RESERVED_KEYWORDS = {
396        "alter",
397        "and",
398        "as",
399        "between",
400        "by",
401        "case",
402        "cast",
403        "constraint",
404        "create",
405        "cross",
406        "current_time",
407        "current_timestamp",
408        "deallocate",
409        "delete",
410        "describe",
411        "distinct",
412        "drop",
413        "else",
414        "end",
415        "escape",
416        "except",
417        "execute",
418        "exists",
419        "extract",
420        "false",
421        "for",
422        "from",
423        "full",
424        "group",
425        "having",
426        "in",
427        "inner",
428        "insert",
429        "intersect",
430        "into",
431        "is",
432        "join",
433        "left",
434        "like",
435        "natural",
436        "not",
437        "null",
438        "on",
439        "or",
440        "order",
441        "outer",
442        "prepare",
443        "right",
444        "select",
445        "table",
446        "then",
447        "true",
448        "union",
449        "using",
450        "values",
451        "when",
452        "where",
453        "with",
454    }
455
456    def extract_sql(self, expression: exp.Extract) -> str:
457        date_part = expression.name
458
459        if not date_part.startswith("EPOCH"):
460            return super().extract_sql(expression)
461
462        if date_part == "EPOCH_MILLISECOND":
463            scale = 10**3
464        elif date_part == "EPOCH_MICROSECOND":
465            scale = 10**6
466        elif date_part == "EPOCH_NANOSECOND":
467            scale = 10**9
468        else:
469            scale = None
470
471        value = expression.expression
472
473        ts = exp.cast(value, to=exp.DType.TIMESTAMP.into_expr())
474        to_unix: exp.Expr = exp.TimeToUnix(this=ts)
475
476        if scale:
477            to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale))
478
479        return self.sql(to_unix)
480
481    def jsonformat_sql(self, expression: exp.JSONFormat) -> str:
482        this = expression.this
483        is_json = expression.args.get("is_json")
484
485        if this and not (is_json or this.type):
486            from sqlglot.optimizer.annotate_types import annotate_types
487
488            this = annotate_types(this, dialect=self.dialect)
489
490        if not (is_json or this.is_type(exp.DType.JSON)):
491            this.replace(exp.cast(this, exp.DType.JSON))
492
493        return self.function_fallback_sql(expression)
494
495    def md5_sql(self, expression: exp.MD5) -> str:
496        this = expression.this
497
498        if not this.type:
499            from sqlglot.optimizer.annotate_types import annotate_types
500
501            this = annotate_types(this, dialect=self.dialect)
502
503        if this.is_type(*exp.DataType.TEXT_TYPES):
504            this = exp.Encode(this=this, charset=exp.Literal.string("utf-8"))
505
506        return self.func("LOWER", self.func("TO_HEX", self.func("MD5", self.sql(this))))
507
508    def strtounix_sql(self, expression: exp.StrToUnix) -> str:
509        # Since `TO_UNIXTIME` requires a `TIMESTAMP`, we need to parse the argument into one.
510        # To do this, we first try to `DATE_PARSE` it, but since this can fail when there's a
511        # timezone involved, we wrap it in a `TRY` call and use `PARSE_DATETIME` as a fallback,
512        # which seems to be using the same time mapping as Hive, as per:
513        # https://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html
514        this = expression.this
515        value_as_text = exp.cast(this, exp.DType.TEXT)
516        value_as_timestamp = exp.cast(this, exp.DType.TIMESTAMP) if this.is_string else this
517
518        parse_without_tz = self.func("DATE_PARSE", value_as_text, self.format_time(expression))
519
520        formatted_value = self.func("DATE_FORMAT", value_as_timestamp, self.format_time(expression))
521        parse_with_tz = self.func(
522            "PARSE_DATETIME",
523            formatted_value,
524            self.format_time(expression, Hive.INVERSE_TIME_MAPPING, Hive.INVERSE_TIME_TRIE),
525        )
526        coalesced = self.func("COALESCE", self.func("TRY", parse_without_tz), parse_with_tz)
527        return self.func("TO_UNIXTIME", coalesced)
528
529    def bracket_sql(self, expression: exp.Bracket) -> str:
530        if expression.args.get("safe"):
531            return bracket_to_element_at_sql(self, expression)
532        return super().bracket_sql(expression)
533
534    def struct_sql(self, expression: exp.Struct) -> str:
535        if not expression.type:
536            from sqlglot.optimizer.annotate_types import annotate_types
537
538            annotate_types(expression, dialect=self.dialect)
539
540        values: list[str] = []
541        schema: list[str] = []
542        unknown_type = False
543
544        for e in expression.expressions:
545            if isinstance(e, exp.PropertyEQ):
546                if e.type and e.type.is_type(exp.DType.UNKNOWN):
547                    unknown_type = True
548                else:
549                    schema.append(f"{self.sql(e, 'this')} {self.sql(e.type)}")
550                values.append(self.sql(e, "expression"))
551            else:
552                values.append(self.sql(e))
553
554        size = len(expression.expressions)
555
556        if not size or len(schema) != size:
557            if unknown_type:
558                self.unsupported(
559                    "Cannot convert untyped key-value definitions (try annotate_types)."
560                )
561            return self.func("ROW", *values)
562        return f"CAST(ROW({', '.join(values)}) AS ROW({', '.join(schema)}))"
563
564    def interval_sql(self, expression: exp.Interval) -> str:
565        if expression.this and expression.text("unit").upper().startswith("WEEK"):
566            return f"({expression.this.name} * INTERVAL '7' DAY)"
567        return super().interval_sql(expression)
568
569    def transaction_sql(self, expression: exp.Transaction) -> str:
570        modes = expression.args.get("modes")
571        modes = f" {', '.join(modes)}" if modes else ""
572        return f"START TRANSACTION{modes}"
573
574    def offset_limit_modifiers(
575        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
576    ) -> list[str]:
577        return [
578            self.sql(expression, "offset"),
579            self.sql(limit),
580        ]
581
582    def create_sql(self, expression: exp.Create) -> str:
583        """
584        Presto doesn't support CREATE VIEW with expressions (ex: `CREATE VIEW x (cola)` then `(cola)` is the expression),
585        so we need to remove them
586        """
587        kind = expression.args["kind"]
588        schema = expression.this
589        if kind == "VIEW" and schema.expressions:
590            expression.this.set("expressions", None)
591        return super().create_sql(expression)
592
593    def delete_sql(self, expression: exp.Delete) -> str:
594        """
595        Presto only supports DELETE FROM for a single table without an alias, so we need
596        to remove the unnecessary parts. If the original DELETE statement contains more
597        than one table to be deleted, we can't safely map it 1-1 to a Presto statement.
598        """
599        tables = expression.args.get("tables") or [expression.this]
600        if len(tables) > 1:
601            return super().delete_sql(expression)
602
603        table = tables[0]
604        expression.set("this", table)
605        expression.set("tables", None)
606
607        if isinstance(table, exp.Table):
608            table_alias = table.args.get("alias")
609            if table_alias:
610                table_alias.pop()
611                expression = t.cast(exp.Delete, expression.transform(unqualify_columns))
612
613        return super().delete_sql(expression)
614
615    def jsonextract_sql(self, expression: exp.JSONExtract) -> str:
616        is_json_extract = self.dialect.settings.get("variant_extract_is_json_extract", True)
617
618        # Generate JSON_EXTRACT unless the user has configured that a Snowflake / Databricks
619        # VARIANT extract (e.g. col:x.y) should map to dot notation (i.e ROW access) in Presto/Trino
620        if not expression.args.get("variant_extract") or is_json_extract:
621            return self.func(
622                "JSON_EXTRACT", expression.this, expression.expression, *expression.expressions
623            )
624
625        this = self.sql(expression, "this")
626
627        # Convert the JSONPath extraction `JSON_EXTRACT(col, '$.x.y) to a ROW access col.x.y
628        segments = []
629        for path_key in expression.expression.expressions[1:]:
630            if not isinstance(path_key, exp.JSONPathKey):
631                # Cannot transpile subscripts, wildcards etc to dot notation
632                self.unsupported(f"Cannot transpile JSONPath segment '{path_key}' to ROW access")
633                continue
634            key = path_key.this
635            if not exp.SAFE_IDENTIFIER_RE.match(key):
636                key = f'"{key}"'
637            segments.append(f".{key}")
638
639        expr = "".join(segments)
640
641        return f"{this}{expr}"
642
643    def groupconcat_sql(self, expression: exp.GroupConcat) -> str:
644        return self.func(
645            "ARRAY_JOIN",
646            self.func("ARRAY_AGG", expression.this),
647            expression.args.get("separator"),
648        )

Generator converts a given syntax tree to the corresponding SQL string.

Arguments:
  • pretty: Whether to format the produced SQL string. Default: False.
  • identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True: Always quote except for specials cases. 'safe': Only quote identifiers that are case insensitive.
  • normalize: Whether to normalize identifiers to lowercase. Default: False.
  • pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
  • indent: The indentation size in a formatted string. For example, this affects the indentation of subqueries and filters under a WHERE clause. Default: 2.
  • normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
  • unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
  • max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
  • leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
  • max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
  • comments: Whether to preserve comments in the output SQL code. Default: True
SELECT_KINDS: tuple[str, ...] = ()
SUPPORTS_DECODE_CASE = False
AFTER_HAVING_MODIFIER_TRANSFORMS = {'windows': <function <lambda>>, 'qualify': <function <lambda>>}
INTERVAL_ALLOWS_PLURAL_FORM = False
JOIN_HINTS = False
TABLE_HINTS = False
QUERY_HINTS = False
IS_BOOL_ALLOWED = False
TZ_TO_WITH_TIME_ZONE = True
NVL2_SUPPORTED = False
STRUCT_DELIMITER = ('(', ')')
LIMIT_ONLY_LITERALS = True
SUPPORTS_SINGLE_ARG_CONCAT = False
LIKE_PROPERTY_INSIDE_SCHEMA = True
MULTI_ARG_DISTINCT = False
SUPPORTS_TO_NUMBER = False
HEX_FUNC = 'TO_HEX'
PARSE_JSON_NAME: str | None = 'JSON_PARSE'
PAD_FILL_PATTERN_IS_REQUIRED = True
EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
SUPPORTS_MEDIAN = False
ARRAY_SIZE_NAME = 'CARDINALITY'
PROPERTIES_LOCATION = {<class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AlgorithmProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApiProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.AutoIncrementProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BackupProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BlockCompressionProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ChecksumProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CollateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.query.Cluster'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusteredByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistributedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DuplicateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DataBlocksizeProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DataDeletionProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DefinerProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DictRange'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DistKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EncodeProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.EngineProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.FallbackProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.FreespaceProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.HeapProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.HybridProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.IncludeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IsolatedLoadingProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.JournalProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LikeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LocationProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.LockProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockingProperty'>: <PropertiesLocation.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.properties.LogProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MergeBlockRatioProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.ModuleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.OnProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.query.Order'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.PartitionedOfProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.StrictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Tags'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
TYPE_MAPPING = {<DType.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <DType.NCHAR: 'NCHAR'>: 'CHAR', <DType.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'VARBINARY', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP', <DType.BINARY: 'BINARY'>: 'VARBINARY', <DType.BIT: 'BIT'>: 'BOOLEAN', <DType.DATETIME: 'DATETIME'>: 'TIMESTAMP', <DType.DATETIME64: 'DATETIME64'>: 'TIMESTAMP', <DType.FLOAT: 'FLOAT'>: 'REAL', <DType.HLLSKETCH: 'HLLSKETCH'>: 'HYPERLOGLOG', <DType.INT: 'INT'>: 'INTEGER', <DType.STRUCT: 'STRUCT'>: 'ROW', <DType.TEXT: 'TEXT'>: 'VARCHAR', <DType.TIMESTAMPTZ: 'TIMESTAMPTZ'>: 'TIMESTAMP', <DType.TIMESTAMPNTZ: 'TIMESTAMPNTZ'>: 'TIMESTAMP', <DType.TIMETZ: 'TIMETZ'>: 'TIME'}
TRANSFORMS = {<class 'sqlglot.expressions.query.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.core.Adjacent'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.AssumeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.string.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.SessionUser'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApiProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.EndStatement'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HybridProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.datatypes.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObject'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObjectAgg'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.NetFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InvisibleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.SafeFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.TriggerExecute'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ForceProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.aggregate.AnyValue'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.ApproxQuantile'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.aggregate.ArgMax'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.ArgMin'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.Array'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.array.ArrayAny'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.ArrayConcat'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.ArrayContains'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.ArrayToString'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.ArrayUniqueAgg'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.ArraySlice'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.AtTimeZone'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.BitwiseAnd'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.core.BitwiseLeftShift'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.core.BitwiseNot'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.core.BitwiseOr'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.core.BitwiseRightShift'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.core.BitwiseXor'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.functions.Cast'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.temporal.CurrentTime'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.CurrentTimestamp'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentUser'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.DateAdd'>: <function _date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.DateDiff'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.DateStrToDate'>: <function datestrtodate_sql>, <class 'sqlglot.expressions.temporal.DateToDi'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.DateSub'>: <function _date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.DayOfWeek'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.DayOfWeekIso'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.Decode'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.DiToDate'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.string.Encode'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.aggregate.First'>: <function _first_last_sql>, <class 'sqlglot.expressions.core.FromTimeZone'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.array.GenerateSeries'>: <function sequence_sql>, <class 'sqlglot.expressions.temporal.GenerateDateArray'>: <function sequence_sql>, <class 'sqlglot.expressions.functions.If'>: <function if_sql.<locals>._if_sql>, <class 'sqlglot.expressions.core.ILike'>: <function no_ilike_sql>, <class 'sqlglot.expressions.string.Initcap'>: <function _initcap_sql>, <class 'sqlglot.expressions.aggregate.Last'>: <function _first_last_sql>, <class 'sqlglot.expressions.temporal.LastDay'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.query.Lateral'>: <function _explode_to_unnest_sql>, <class 'sqlglot.expressions.string.Left'>: <function left_to_substring_sql>, <class 'sqlglot.expressions.string.Levenshtein'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.LogicalAnd'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.LogicalOr'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.query.Pivot'>: <function no_pivot_sql>, <class 'sqlglot.expressions.aggregate.Quantile'>: <function _quantile_sql>, <class 'sqlglot.expressions.string.RegexpExtract'>: <function regexp_extract_sql>, <class 'sqlglot.expressions.string.RegexpExtractAll'>: <function regexp_extract_sql>, <class 'sqlglot.expressions.string.Right'>: <function right_to_substring_sql>, <class 'sqlglot.expressions.query.Schema'>: <function _schema_sql>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.query.Select'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.array.SortArray'>: <function _no_sort_array>, <class 'sqlglot.expressions.string.StrPosition'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.StrToDate'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.string.StrToMap'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.StrToTime'>: <function _str_to_time_sql>, <class 'sqlglot.expressions.array.StructExtract'>: <function struct_extract_sql>, <class 'sqlglot.expressions.query.Table'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.temporal.Timestamp'>: <function no_timestamp_sql>, <class 'sqlglot.expressions.temporal.TimestampAdd'>: <function _date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.TimestampTrunc'>: <function timestamptrunc_sql.<locals>._timestamptrunc_sql>, <class 'sqlglot.expressions.temporal.TimeStrToDate'>: <function timestrtotime_sql>, <class 'sqlglot.expressions.temporal.TimeStrToTime'>: <function timestrtotime_sql>, <class 'sqlglot.expressions.temporal.TimeStrToUnix'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.TimeToStr'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.TimeToUnix'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.ToChar'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.functions.TryCast'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.temporal.TsOrDiToDi'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.TsOrDsAdd'>: <function _ts_or_ds_add_sql>, <class 'sqlglot.expressions.temporal.TsOrDsDiff'>: <function _ts_or_ds_diff_sql>, <class 'sqlglot.expressions.temporal.TsOrDsToDate'>: <function _ts_or_ds_to_date_sql>, <class 'sqlglot.expressions.string.Unhex'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.UnixToStr'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.UnixToTime'>: <function _unix_to_time_sql>, <class 'sqlglot.expressions.temporal.UnixToTimeStr'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.aggregate.VariancePop'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.query.With'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.core.WithinGroup'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.math.Trunc'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.Xor'>: <function bool_xor_sql>, <class 'sqlglot.expressions.string.MD5Digest'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.SHA'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.SHA1Digest'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.SHA2'>: <function sha256_sql>, <class 'sqlglot.expressions.string.SHA2Digest'>: <function sha2_digest_sql>}
RESERVED_KEYWORDS = {'deallocate', 'on', 'insert', 'true', 'exists', 'select', 'inner', 'natural', 'cross', 'in', 'join', 'escape', 'else', 'as', 'create', 'drop', 'delete', 'not', 'current_timestamp', 'cast', 'with', 'table', 'is', 'right', 'constraint', 'current_time', 'into', 'where', 'order', 'full', 'for', 'alter', 'having', 'left', 'intersect', 'execute', 'and', 'case', 'union', 'extract', 'by', 'between', 'group', 'describe', 'using', 'when', 'distinct', 'values', 'like', 'prepare', 'null', 'false', 'from', 'except', 'then', 'outer', 'or', 'end'}
def extract_sql(self, expression: sqlglot.expressions.temporal.Extract) -> str:
456    def extract_sql(self, expression: exp.Extract) -> str:
457        date_part = expression.name
458
459        if not date_part.startswith("EPOCH"):
460            return super().extract_sql(expression)
461
462        if date_part == "EPOCH_MILLISECOND":
463            scale = 10**3
464        elif date_part == "EPOCH_MICROSECOND":
465            scale = 10**6
466        elif date_part == "EPOCH_NANOSECOND":
467            scale = 10**9
468        else:
469            scale = None
470
471        value = expression.expression
472
473        ts = exp.cast(value, to=exp.DType.TIMESTAMP.into_expr())
474        to_unix: exp.Expr = exp.TimeToUnix(this=ts)
475
476        if scale:
477            to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale))
478
479        return self.sql(to_unix)
def jsonformat_sql(self, expression: sqlglot.expressions.json.JSONFormat) -> str:
481    def jsonformat_sql(self, expression: exp.JSONFormat) -> str:
482        this = expression.this
483        is_json = expression.args.get("is_json")
484
485        if this and not (is_json or this.type):
486            from sqlglot.optimizer.annotate_types import annotate_types
487
488            this = annotate_types(this, dialect=self.dialect)
489
490        if not (is_json or this.is_type(exp.DType.JSON)):
491            this.replace(exp.cast(this, exp.DType.JSON))
492
493        return self.function_fallback_sql(expression)
def md5_sql(self, expression: sqlglot.expressions.string.MD5) -> str:
495    def md5_sql(self, expression: exp.MD5) -> str:
496        this = expression.this
497
498        if not this.type:
499            from sqlglot.optimizer.annotate_types import annotate_types
500
501            this = annotate_types(this, dialect=self.dialect)
502
503        if this.is_type(*exp.DataType.TEXT_TYPES):
504            this = exp.Encode(this=this, charset=exp.Literal.string("utf-8"))
505
506        return self.func("LOWER", self.func("TO_HEX", self.func("MD5", self.sql(this))))
def strtounix_sql(self, expression: sqlglot.expressions.temporal.StrToUnix) -> str:
508    def strtounix_sql(self, expression: exp.StrToUnix) -> str:
509        # Since `TO_UNIXTIME` requires a `TIMESTAMP`, we need to parse the argument into one.
510        # To do this, we first try to `DATE_PARSE` it, but since this can fail when there's a
511        # timezone involved, we wrap it in a `TRY` call and use `PARSE_DATETIME` as a fallback,
512        # which seems to be using the same time mapping as Hive, as per:
513        # https://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html
514        this = expression.this
515        value_as_text = exp.cast(this, exp.DType.TEXT)
516        value_as_timestamp = exp.cast(this, exp.DType.TIMESTAMP) if this.is_string else this
517
518        parse_without_tz = self.func("DATE_PARSE", value_as_text, self.format_time(expression))
519
520        formatted_value = self.func("DATE_FORMAT", value_as_timestamp, self.format_time(expression))
521        parse_with_tz = self.func(
522            "PARSE_DATETIME",
523            formatted_value,
524            self.format_time(expression, Hive.INVERSE_TIME_MAPPING, Hive.INVERSE_TIME_TRIE),
525        )
526        coalesced = self.func("COALESCE", self.func("TRY", parse_without_tz), parse_with_tz)
527        return self.func("TO_UNIXTIME", coalesced)
def bracket_sql(self, expression: sqlglot.expressions.core.Bracket) -> str:
529    def bracket_sql(self, expression: exp.Bracket) -> str:
530        if expression.args.get("safe"):
531            return bracket_to_element_at_sql(self, expression)
532        return super().bracket_sql(expression)
def struct_sql(self, expression: sqlglot.expressions.array.Struct) -> str:
534    def struct_sql(self, expression: exp.Struct) -> str:
535        if not expression.type:
536            from sqlglot.optimizer.annotate_types import annotate_types
537
538            annotate_types(expression, dialect=self.dialect)
539
540        values: list[str] = []
541        schema: list[str] = []
542        unknown_type = False
543
544        for e in expression.expressions:
545            if isinstance(e, exp.PropertyEQ):
546                if e.type and e.type.is_type(exp.DType.UNKNOWN):
547                    unknown_type = True
548                else:
549                    schema.append(f"{self.sql(e, 'this')} {self.sql(e.type)}")
550                values.append(self.sql(e, "expression"))
551            else:
552                values.append(self.sql(e))
553
554        size = len(expression.expressions)
555
556        if not size or len(schema) != size:
557            if unknown_type:
558                self.unsupported(
559                    "Cannot convert untyped key-value definitions (try annotate_types)."
560                )
561            return self.func("ROW", *values)
562        return f"CAST(ROW({', '.join(values)}) AS ROW({', '.join(schema)}))"
def interval_sql(self, expression: sqlglot.expressions.datatypes.Interval) -> str:
564    def interval_sql(self, expression: exp.Interval) -> str:
565        if expression.this and expression.text("unit").upper().startswith("WEEK"):
566            return f"({expression.this.name} * INTERVAL '7' DAY)"
567        return super().interval_sql(expression)
def transaction_sql(self, expression: sqlglot.expressions.ddl.Transaction) -> str:
569    def transaction_sql(self, expression: exp.Transaction) -> str:
570        modes = expression.args.get("modes")
571        modes = f" {', '.join(modes)}" if modes else ""
572        return f"START TRANSACTION{modes}"
def offset_limit_modifiers( self, expression: sqlglot.expressions.core.Expr, fetch: bool, limit: sqlglot.expressions.query.Fetch | sqlglot.expressions.query.Limit | None) -> list[str]:
574    def offset_limit_modifiers(
575        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
576    ) -> list[str]:
577        return [
578            self.sql(expression, "offset"),
579            self.sql(limit),
580        ]
def create_sql(self, expression: sqlglot.expressions.ddl.Create) -> str:
582    def create_sql(self, expression: exp.Create) -> str:
583        """
584        Presto doesn't support CREATE VIEW with expressions (ex: `CREATE VIEW x (cola)` then `(cola)` is the expression),
585        so we need to remove them
586        """
587        kind = expression.args["kind"]
588        schema = expression.this
589        if kind == "VIEW" and schema.expressions:
590            expression.this.set("expressions", None)
591        return super().create_sql(expression)

Presto doesn't support CREATE VIEW with expressions (ex: CREATE VIEW x (cola) then (cola) is the expression), so we need to remove them

def delete_sql(self, expression: sqlglot.expressions.dml.Delete) -> str:
593    def delete_sql(self, expression: exp.Delete) -> str:
594        """
595        Presto only supports DELETE FROM for a single table without an alias, so we need
596        to remove the unnecessary parts. If the original DELETE statement contains more
597        than one table to be deleted, we can't safely map it 1-1 to a Presto statement.
598        """
599        tables = expression.args.get("tables") or [expression.this]
600        if len(tables) > 1:
601            return super().delete_sql(expression)
602
603        table = tables[0]
604        expression.set("this", table)
605        expression.set("tables", None)
606
607        if isinstance(table, exp.Table):
608            table_alias = table.args.get("alias")
609            if table_alias:
610                table_alias.pop()
611                expression = t.cast(exp.Delete, expression.transform(unqualify_columns))
612
613        return super().delete_sql(expression)

Presto only supports DELETE FROM for a single table without an alias, so we need to remove the unnecessary parts. If the original DELETE statement contains more than one table to be deleted, we can't safely map it 1-1 to a Presto statement.

def jsonextract_sql(self, expression: sqlglot.expressions.json.JSONExtract) -> str:
615    def jsonextract_sql(self, expression: exp.JSONExtract) -> str:
616        is_json_extract = self.dialect.settings.get("variant_extract_is_json_extract", True)
617
618        # Generate JSON_EXTRACT unless the user has configured that a Snowflake / Databricks
619        # VARIANT extract (e.g. col:x.y) should map to dot notation (i.e ROW access) in Presto/Trino
620        if not expression.args.get("variant_extract") or is_json_extract:
621            return self.func(
622                "JSON_EXTRACT", expression.this, expression.expression, *expression.expressions
623            )
624
625        this = self.sql(expression, "this")
626
627        # Convert the JSONPath extraction `JSON_EXTRACT(col, '$.x.y) to a ROW access col.x.y
628        segments = []
629        for path_key in expression.expression.expressions[1:]:
630            if not isinstance(path_key, exp.JSONPathKey):
631                # Cannot transpile subscripts, wildcards etc to dot notation
632                self.unsupported(f"Cannot transpile JSONPath segment '{path_key}' to ROW access")
633                continue
634            key = path_key.this
635            if not exp.SAFE_IDENTIFIER_RE.match(key):
636                key = f'"{key}"'
637            segments.append(f".{key}")
638
639        expr = "".join(segments)
640
641        return f"{this}{expr}"
def groupconcat_sql(self, expression: sqlglot.expressions.aggregate.GroupConcat) -> str:
643    def groupconcat_sql(self, expression: exp.GroupConcat) -> str:
644        return self.func(
645            "ARRAY_JOIN",
646            self.func("ARRAY_AGG", expression.this),
647            expression.args.get("separator"),
648        )
Inherited Members
sqlglot.generator.Generator
Generator
NULL_ORDERING_SUPPORTED
WINDOW_FUNCS_WITH_NULL_ORDERING
IGNORE_NULLS_IN_FUNC
IGNORE_NULLS_BEFORE_ORDER
LOCKING_READS_SUPPORTED
WRAP_DERIVED_VALUES
CREATE_FUNCTION_RETURN_AS
MATCHED_BY_SOURCE
SUPPORTS_MERGE_WHERE
SINGLE_STRING_INTERVAL
LIMIT_FETCH
RENAME_TABLE_WITH_DB
GROUPINGS_SEP
INDEX_ON
INOUT_SEPARATOR
DIRECTED_JOINS
QUERY_HINT_SEP
DUPLICATE_KEY_UPDATE_WITH_SET
LIMIT_IS_TOP
RETURNING_END
EXTRACT_ALLOWS_QUOTES
VALUES_AS_TABLE
ALTER_TABLE_INCLUDE_COLUMN_KEYWORD
UNNEST_WITH_ORDINALITY
AGGREGATE_FILTER_SUPPORTED
SEMI_ANTI_JOIN_WITH_SIDE
COMPUTED_COLUMN_WITH_TYPE
SUPPORTS_TABLE_COPY
TABLESAMPLE_REQUIRES_PARENS
TABLESAMPLE_SIZE_IS_ROWS
TABLESAMPLE_KEYWORDS
TABLESAMPLE_WITH_METHOD
TABLESAMPLE_SEED_KEYWORD
COLLATE_IS_FUNC
DATA_TYPE_SPECIFIERS_ALLOWED
ENSURE_BOOLS
CTE_RECURSIVE_KEYWORD_REQUIRED
LAST_DAY_SUPPORTS_DATE_PART
SUPPORTS_TABLE_ALIAS_COLUMNS
UNPIVOT_ALIASES_ARE_IDENTIFIERS
JSON_KEY_VALUE_PAIR_SEP
INSERT_OVERWRITE
SUPPORTS_SELECT_INTO
SUPPORTS_UNLOGGED_TABLES
SUPPORTS_CREATE_TABLE_LIKE
JSON_TYPE_REQUIRED_FOR_EXTRACTION
JSON_PATH_BRACKETED_KEY_SUPPORTED
JSON_PATH_SINGLE_QUOTE_ESCAPE
SUPPORTED_JSON_PATH_PARTS
CAN_IMPLEMENT_ARRAY_ANY
SUPPORTS_WINDOW_EXCLUDE
SET_OP_MODIFIERS
COPY_PARAMS_ARE_WRAPPED
COPY_PARAMS_EQ_REQUIRED
COPY_HAS_INTO_KEYWORD
TRY_SUPPORTED
SUPPORTS_UESCAPE
UNICODE_SUBSTITUTE
STAR_EXCEPT
WITH_PROPERTIES_PREFIX
QUOTE_JSON_PATH
SUPPORTS_EXPLODING_PROJECTIONS
ARRAY_CONCAT_IS_VAR_LEN
SUPPORTS_CONVERT_TIMEZONE
SUPPORTS_UNIX_SECONDS
ALTER_SET_WRAPPED
NORMALIZE_EXTRACT_DATE_PARTS
ALTER_SET_TYPE
ARRAY_SIZE_DIM_REQUIRED
SUPPORTS_BETWEEN_FLAGS
SUPPORTS_LIKE_QUANTIFIERS
MATCH_AGAINST_TABLE_PREFIX
SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD
DECLARE_DEFAULT_ASSIGNMENT
UPDATE_STATEMENT_SUPPORTS_FROM
STAR_EXCLUDE_REQUIRES_DERIVED_TABLE
SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
UNSUPPORTED_TYPES
TIME_PART_SINGULARS
TOKEN_MAPPING
PARAMETER_TOKEN
NAMED_PLACEHOLDER_TOKEN
EXPRESSION_PRECEDES_PROPERTIES_CREATABLES
WITH_SEPARATED_COMMENTS
EXCLUDE_COMMENTS
UNWRAPPED_INTERVAL_VALUES
PARAMETERIZABLE_TEXT_TYPES
EXPRESSIONS_WITHOUT_NESTED_CTES
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS
SAFE_JSON_PATH_KEY_RE
SENTINEL_LINE_BREAK
pretty
identify
normalize
pad
unsupported_level
max_unsupported
leading_comma
max_text_width
comments
dialect
normalize_functions
unsupported_messages
generate
preprocess
unsupported
sep
seg
sanitize_comment
maybe_comment
wrap
no_identify
normalize_func
indent
sql
uncache_sql
cache_sql
characterset_sql
column_parts
column_sql
pseudocolumn_sql
columnposition_sql
columndef_sql
columnconstraint_sql
computedcolumnconstraint_sql
autoincrementcolumnconstraint_sql
compresscolumnconstraint_sql
generatedasidentitycolumnconstraint_sql
generatedasrowcolumnconstraint_sql
periodforsystemtimeconstraint_sql
notnullcolumnconstraint_sql
primarykeycolumnconstraint_sql
uniquecolumnconstraint_sql
inoutcolumnconstraint_sql
createable_sql
sequenceproperties_sql
triggerproperties_sql
triggerreferencing_sql
triggerevent_sql
clone_sql
describe_sql
heredoc_sql
prepend_ctes
with_sql
cte_sql
tablealias_sql
bitstring_sql
hexstring_sql
bytestring_sql
unicodestring_sql
rawstring_sql
datatypeparam_sql
datatype_sql
directory_sql
drop_sql
set_operation
set_operations
fetch_sql
limitoptions_sql
filter_sql
hint_sql
indexparameters_sql
index_sql
identifier_sql
hex_sql
lowerhex_sql
inputoutputformat_sql
national_sql
partition_sql
properties_sql
root_properties
properties
with_properties
locate_properties
property_name
property_sql
uuidproperty_sql
likeproperty_sql
fallbackproperty_sql
journalproperty_sql
freespaceproperty_sql
checksumproperty_sql
mergeblockratioproperty_sql
moduleproperty_sql
datablocksizeproperty_sql
blockcompressionproperty_sql
isolatedloadingproperty_sql
partitionboundspec_sql
partitionedofproperty_sql
lockingproperty_sql
withdataproperty_sql
withsystemversioningproperty_sql
insert_sql
introducer_sql
kill_sql
pseudotype_sql
objectidentifier_sql
onconflict_sql
returning_sql
rowformatdelimitedproperty_sql
withtablehint_sql
indextablehint_sql
historicaldata_sql
table_parts
table_sql
tablefromrows_sql
tablesample_sql
pivot_sql
version_sql
tuple_sql
update_sql
values_sql
var_sql
into_sql
from_sql
groupingsets_sql
rollup_sql
rollupindex_sql
rollupproperty_sql
cube_sql
group_sql
having_sql
connect_sql
prior_sql
join_sql
lambda_sql
lateral_op
lateral_sql
limit_sql
offset_sql
setitem_sql
set_sql
queryband_sql
pragma_sql
lock_sql
literal_sql
escape_str
loaddata_sql
null_sql
boolean_sql
booland_sql
boolor_sql
order_sql
withfill_sql
cluster_sql
distribute_sql
sort_sql
ordered_sql
matchrecognizemeasure_sql
matchrecognize_sql
query_modifiers
options_modifier
for_modifiers
queryoption_sql
after_limit_modifiers
select_sql
schema_sql
schema_columns_sql
star_sql
parameter_sql
sessionparameter_sql
placeholder_sql
subquery_sql
qualify_sql
unnest_sql
prewhere_sql
where_sql
window_sql
partition_by_sql
windowspec_sql
withingroup_sql
between_sql
bracket_offset_expressions
all_sql
any_sql
exists_sql
case_sql
constraint_sql
nextvaluefor_sql
trim_sql
convert_concat_args
concat_sql
concatws_sql
check_sql
foreignkey_sql
primarykey_sql
if_sql
matchagainst_sql
jsonkeyvalue_sql
jsonpath_sql
json_path_part
formatjson_sql
formatphrase_sql
jsonarray_sql
jsonarrayagg_sql
jsoncolumndef_sql
jsonschema_sql
jsontable_sql
openjsoncolumndef_sql
openjson_sql
in_sql
in_unnest_op
return_sql
reference_sql
anonymous_sql
paren_sql
neg_sql
not_sql
alias_sql
pivotalias_sql
aliases_sql
atindex_sql
attimezone_sql
fromtimezone_sql
add_sql
and_sql
or_sql
xor_sql
connector_sql
bitwiseand_sql
bitwiseleftshift_sql
bitwisenot_sql
bitwiseor_sql
bitwiserightshift_sql
bitwisexor_sql
cast_sql
strtotime_sql
currentdate_sql
collate_sql
command_sql
comment_sql
mergetreettlaction_sql
mergetreettl_sql
commit_sql
rollback_sql
altercolumn_sql
alterindex_sql
alterdiststyle_sql
altersortkey_sql
alterrename_sql
renamecolumn_sql
alterset_sql
alter_sql
altersession_sql
add_column_sql
droppartition_sql
addconstraint_sql
addpartition_sql
distinct_sql
ignorenulls_sql
respectnulls_sql
havingmax_sql
intdiv_sql
dpipe_sql
div_sql
safedivide_sql
overlaps_sql
distance_sql
dot_sql
eq_sql
propertyeq_sql
escape_sql
glob_sql
gt_sql
gte_sql
is_sql
like_sql
ilike_sql
match_sql
similarto_sql
lt_sql
lte_sql
mod_sql
mul_sql
neq_sql
nullsafeeq_sql
nullsafeneq_sql
sub_sql
trycast_sql
jsoncast_sql
try_sql
log_sql
use_sql
binary
ceil_floor
function_fallback_sql
func
format_args
too_wide
format_time
expressions
op_expressions
naked_property
tag_sql
token_sql
userdefinedfunction_sql
joinhint_sql
kwarg_sql
when_sql
whens_sql
merge_sql
tochar_sql
tonumber_sql
dictproperty_sql
dictrange_sql
dictsubproperty_sql
duplicatekeyproperty_sql
uniquekeyproperty_sql
distributedbyproperty_sql
oncluster_sql
clusteredbyproperty_sql
anyvalue_sql
querytransform_sql
indexconstraintoption_sql
checkcolumnconstraint_sql
indexcolumnconstraint_sql
nvl2_sql
comprehension_sql
columnprefix_sql
opclass_sql
predict_sql
generateembedding_sql
generatetext_sql
generatetable_sql
generatebool_sql
generateint_sql
generatedouble_sql
mltranslate_sql
mlforecast_sql
aiforecast_sql
featuresattime_sql
vectorsearch_sql
forin_sql
refresh_sql
toarray_sql
tsordstotime_sql
tsordstotimestamp_sql
tsordstodatetime_sql
tsordstodate_sql
unixdate_sql
lastday_sql
dateadd_sql
arrayany_sql
partitionrange_sql
truncatetable_sql
convert_sql
copyparameter_sql
credentials_sql
copy_sql
semicolon_sql
datadeletionproperty_sql
maskingpolicycolumnconstraint_sql
gapfill_sql
scope_resolution
scoperesolution_sql
parsejson_sql
rand_sql
changes_sql
pad_sql
summarize_sql
explodinggenerateseries_sql
converttimezone_sql
json_sql
jsonvalue_sql
skipjsoncolumn_sql
conditionalinsert_sql
multitableinserts_sql
oncondition_sql
jsonextractquote_sql
jsonexists_sql
arrayagg_sql
slice_sql
apply_sql
grant_sql
revoke_sql
grantprivilege_sql
grantprincipal_sql
columns_sql
overlay_sql
todouble_sql
string_sql
median_sql
overflowtruncatebehavior_sql
unixseconds_sql
arraysize_sql
attach_sql
detach_sql
attachoption_sql
watermarkcolumnconstraint_sql
encodeproperty_sql
includeproperty_sql
xmlelement_sql
xmlkeyvalueoption_sql
partitionbyrangeproperty_sql
partitionbyrangepropertydynamic_sql
unpivotcolumns_sql
analyzesample_sql
analyzestatistics_sql
analyzehistogram_sql
analyzedelete_sql
analyzelistchainedrows_sql
analyzevalidate_sql
analyze_sql
xmltable_sql
xmlnamespace_sql
export_sql
declare_sql
declareitem_sql
recursivewithsearch_sql
parameterizedagg_sql
anonymousaggfunc_sql
combinedaggfunc_sql
combinedparameterizedagg_sql
show_sql
install_sql
get_put_sql
translatecharacters_sql
decodecase_sql
semanticview_sql
getextract_sql
datefromunixdate_sql
space_sql
buildproperty_sql
refreshtriggerproperty_sql
modelattribute_sql
directorystage_sql
uuid_sql
initcap_sql
localtime_sql
localtimestamp_sql
weekstart_sql
chr_sql
block_sql
storedprocedure_sql
ifblock_sql
whileblock_sql
execute_sql
executesql_sql
altermodifysqlsecurity_sql
usingproperty_sql
renameindex_sql