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: rename_func("BITWISE_LEFT_SHIFT"),
283        exp.BitwiseNot: lambda self, e: self.func("BITWISE_NOT", e.this),
284        exp.BitwiseOr: lambda self, e: self.func("BITWISE_OR", e.this, e.expression),
285        exp.BitwiseRightShift: rename_func("BITWISE_RIGHT_SHIFT"),
286        exp.BitwiseXor: lambda self, e: self.func("BITWISE_XOR", e.this, e.expression),
287        exp.Cast: transforms.preprocess([transforms.epoch_cast_to_ts]),
288        exp.CurrentTime: lambda *_: "CURRENT_TIME",
289        exp.CurrentTimestamp: lambda *_: "CURRENT_TIMESTAMP",
290        exp.CurrentUser: lambda *_: "CURRENT_USER",
291        exp.DateAdd: _date_delta_sql("DATE_ADD"),
292        exp.DateDiff: lambda self, e: self.func("DATE_DIFF", unit_to_str(e), e.expression, e.this),
293        exp.DateStrToDate: datestrtodate_sql,
294        exp.DateToDi: lambda self, e: (
295            f"CAST(DATE_FORMAT({self.sql(e, 'this')}, {type(self.dialect).DATEINT_FORMAT}) AS INT)"
296        ),
297        exp.DateSub: _date_delta_sql("DATE_ADD", negate_interval=True),
298        exp.DayOfWeek: lambda self, e: f"(({self.func('DAY_OF_WEEK', e.this)} % 7) + 1)",
299        exp.DayOfWeekIso: rename_func("DAY_OF_WEEK"),
300        exp.Decode: lambda self, e: encode_decode_sql(self, e, "FROM_UTF8"),
301        exp.DiToDate: lambda self, e: (
302            f"CAST(DATE_PARSE(CAST({self.sql(e, 'this')} AS VARCHAR), {type(self.dialect).DATEINT_FORMAT}) AS DATE)"
303        ),
304        exp.Encode: lambda self, e: encode_decode_sql(self, e, "TO_UTF8"),
305        exp.FileFormatProperty: lambda self, e: f"format={self.sql(exp.Literal.string(e.name))}",
306        exp.First: _first_last_sql,
307        exp.FromISO8601Date: rename_func("FROM_ISO8601_DATE"),
308        exp.FromISO8601Timestamp: rename_func("FROM_ISO8601_TIMESTAMP"),
309        exp.FromTimeZone: lambda self, e: (
310            f"WITH_TIMEZONE({self.sql(e, 'this')}, {self.sql(e, 'zone')}) AT TIME ZONE 'UTC'"
311        ),
312        exp.GenerateSeries: sequence_sql,
313        exp.GenerateDateArray: sequence_sql,
314        exp.If: if_sql(),
315        exp.ILike: no_ilike_sql,
316        exp.Initcap: _initcap_sql,
317        exp.Last: _first_last_sql,
318        exp.LastDay: lambda self, e: self.func("LAST_DAY_OF_MONTH", e.this),
319        exp.Lateral: _explode_to_unnest_sql,
320        exp.Left: left_to_substring_sql,
321        exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost", "max_dist")(
322            rename_func("LEVENSHTEIN_DISTANCE")
323        ),
324        exp.LogicalAnd: rename_func("BOOL_AND"),
325        exp.LogicalOr: rename_func("BOOL_OR"),
326        exp.Pivot: no_pivot_sql,
327        exp.Quantile: _quantile_sql,
328        exp.RegexpExtract: regexp_extract_sql,
329        exp.RegexpExtractAll: regexp_extract_sql,
330        exp.Right: right_to_substring_sql,
331        exp.Schema: _schema_sql,
332        exp.SchemaCommentProperty: lambda self, e: self.naked_property(e),
333        exp.Select: transforms.preprocess(
334            [
335                transforms.eliminate_window_clause,
336                transforms.eliminate_qualify,
337                transforms.eliminate_distinct_on,
338                transforms.explode_projection_to_unnest(1),
339                transforms.eliminate_semi_and_anti_joins,
340                amend_exploded_column_table,
341            ]
342        ),
343        exp.SortArray: _no_sort_array,
344        exp.SqlSecurityProperty: lambda self, e: f"SECURITY {self.sql(e.this)}",
345        exp.StrPosition: lambda self, e: strposition_sql(self, e, supports_occurrence=True),
346        exp.StrToDate: lambda self, e: f"CAST({_str_to_time_sql(self, e)} AS DATE)",
347        exp.StrToMap: rename_func("SPLIT_TO_MAP"),
348        exp.StrToTime: _str_to_time_sql,
349        exp.StructExtract: struct_extract_sql,
350        exp.Table: transforms.preprocess([transforms.unnest_generate_series]),
351        exp.Timestamp: no_timestamp_sql,
352        exp.TimestampAdd: _date_delta_sql("DATE_ADD"),
353        exp.TimestampTrunc: timestamptrunc_sql(),
354        exp.TimeStrToDate: timestrtotime_sql,
355        exp.TimeStrToTime: timestrtotime_sql,
356        exp.TimeStrToUnix: lambda self, e: self.func(
357            "TO_UNIXTIME", self.func("DATE_PARSE", e.this, type(self.dialect).TIME_FORMAT)
358        ),
359        exp.TimeToStr: lambda self, e: self.func("DATE_FORMAT", e.this, self.format_time(e)),
360        exp.TimeToUnix: rename_func("TO_UNIXTIME"),
361        exp.ToChar: lambda self, e: self.func("DATE_FORMAT", e.this, self.format_time(e)),
362        exp.TryCast: transforms.preprocess([transforms.epoch_cast_to_ts]),
363        exp.TsOrDiToDi: lambda self, e: (
364            f"CAST(SUBSTR(REPLACE(CAST({self.sql(e, 'this')} AS VARCHAR), '-', ''), 1, 8) AS INT)"
365        ),
366        exp.TsOrDsAdd: _ts_or_ds_add_sql,
367        exp.TsOrDsDiff: _ts_or_ds_diff_sql,
368        exp.TsOrDsToDate: _ts_or_ds_to_date_sql,
369        exp.Unhex: rename_func("FROM_HEX"),
370        exp.UnixToStr: lambda self, e: (
371            f"DATE_FORMAT(FROM_UNIXTIME({self.sql(e, 'this')}), {self.format_time(e)})"
372        ),
373        exp.UnixToTime: _unix_to_time_sql,
374        exp.UnixToTimeStr: lambda self, e: f"CAST(FROM_UNIXTIME({self.sql(e, 'this')}) AS VARCHAR)",
375        exp.VariancePop: rename_func("VAR_POP"),
376        exp.With: transforms.preprocess([transforms.add_recursive_cte_column_names]),
377        exp.WithinGroup: transforms.preprocess([transforms.remove_within_group_for_percentiles]),
378        # Note: Presto's TRUNCATE always returns DOUBLE, even with decimals=0, whereas
379        # most dialects return INT (SQLite also returns REAL, see sqlite.py). This creates
380        # a bidirectional transpilation gap: Presto→Other may change float division to int
381        # division, and vice versa. Modeling precisely would require exp.FloatTrunc or
382        # similar, deemed overengineering for this subtle semantic difference.
383        exp.Trunc: rename_func("TRUNCATE"),
384        exp.Xor: bool_xor_sql,
385        exp.MD5Digest: rename_func("MD5"),
386        exp.SHA: rename_func("SHA1"),
387        exp.SHA1Digest: rename_func("SHA1"),
388        exp.SHA2: sha256_sql,
389        exp.SHA2Digest: sha2_digest_sql,
390        exp.Substring: rename_func("SUBSTR"),
391    }
392
393    RESERVED_KEYWORDS = {
394        "alter",
395        "and",
396        "as",
397        "between",
398        "by",
399        "case",
400        "cast",
401        "constraint",
402        "create",
403        "cross",
404        "current_time",
405        "current_timestamp",
406        "deallocate",
407        "delete",
408        "describe",
409        "distinct",
410        "drop",
411        "else",
412        "end",
413        "escape",
414        "except",
415        "execute",
416        "exists",
417        "extract",
418        "false",
419        "for",
420        "from",
421        "full",
422        "group",
423        "having",
424        "in",
425        "inner",
426        "insert",
427        "intersect",
428        "into",
429        "is",
430        "join",
431        "left",
432        "like",
433        "natural",
434        "not",
435        "null",
436        "on",
437        "or",
438        "order",
439        "outer",
440        "prepare",
441        "right",
442        "select",
443        "table",
444        "then",
445        "true",
446        "union",
447        "using",
448        "values",
449        "when",
450        "where",
451        "with",
452    }
453
454    def extract_sql(self, expression: exp.Extract) -> str:
455        date_part = expression.name
456
457        if not date_part.startswith("EPOCH"):
458            return super().extract_sql(expression)
459
460        if date_part == "EPOCH_MILLISECOND":
461            scale = 10**3
462        elif date_part == "EPOCH_MICROSECOND":
463            scale = 10**6
464        elif date_part == "EPOCH_NANOSECOND":
465            scale = 10**9
466        else:
467            scale = None
468
469        value = expression.expression
470
471        ts = exp.cast(value, to=exp.DType.TIMESTAMP.into_expr())
472        to_unix: exp.Expr = exp.TimeToUnix(this=ts)
473
474        if scale:
475            to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale))
476
477        return self.sql(to_unix)
478
479    def jsonformat_sql(self, expression: exp.JSONFormat) -> str:
480        this = expression.this
481        is_json = expression.args.get("is_json")
482
483        if this and not (is_json or this.type):
484            from sqlglot.optimizer.annotate_types import annotate_types
485
486            this = annotate_types(this, dialect=self.dialect)
487
488        if not (is_json or this.is_type(exp.DType.JSON)):
489            this.replace(exp.cast(this, exp.DType.JSON))
490
491        return self.function_fallback_sql(expression)
492
493    def md5_sql(self, expression: exp.MD5) -> str:
494        this = expression.this
495
496        if not this.type:
497            from sqlglot.optimizer.annotate_types import annotate_types
498
499            this = annotate_types(this, dialect=self.dialect)
500
501        if this.is_type(*exp.DataType.TEXT_TYPES):
502            this = exp.Encode(this=this, charset=exp.Literal.string("utf-8"))
503
504        return self.func("LOWER", self.func("TO_HEX", self.func("MD5", self.sql(this))))
505
506    def strtounix_sql(self, expression: exp.StrToUnix) -> str:
507        # Since `TO_UNIXTIME` requires a `TIMESTAMP`, we need to parse the argument into one.
508        # To do this, we first try to `DATE_PARSE` it, but since this can fail when there's a
509        # timezone involved, we wrap it in a `TRY` call and use `PARSE_DATETIME` as a fallback,
510        # which seems to be using the same time mapping as Hive, as per:
511        # https://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html
512        this = expression.this
513        value_as_text = exp.cast(this, exp.DType.TEXT)
514        value_as_timestamp = exp.cast(this, exp.DType.TIMESTAMP) if this.is_string else this
515
516        parse_without_tz = self.func("DATE_PARSE", value_as_text, self.format_time(expression))
517
518        formatted_value = self.func("DATE_FORMAT", value_as_timestamp, self.format_time(expression))
519        parse_with_tz = self.func(
520            "PARSE_DATETIME",
521            formatted_value,
522            self.format_time(expression, Hive.INVERSE_TIME_MAPPING, Hive.INVERSE_TIME_TRIE),
523        )
524        coalesced = self.func("COALESCE", self.func("TRY", parse_without_tz), parse_with_tz)
525        return self.func("TO_UNIXTIME", coalesced)
526
527    def bracket_sql(self, expression: exp.Bracket) -> str:
528        if expression.args.get("safe"):
529            return bracket_to_element_at_sql(self, expression)
530        return super().bracket_sql(expression)
531
532    def struct_sql(self, expression: exp.Struct) -> str:
533        if not expression.type:
534            from sqlglot.optimizer.annotate_types import annotate_types
535
536            annotate_types(expression, dialect=self.dialect)
537
538        values: list[str] = []
539        schema: list[str] = []
540        unknown_type = False
541
542        for e in expression.expressions:
543            if isinstance(e, exp.PropertyEQ):
544                if e.type and e.type.is_type(exp.DType.UNKNOWN):
545                    unknown_type = True
546                else:
547                    schema.append(f"{self.sql(e, 'this')} {self.sql(e.type)}")
548                values.append(self.sql(e, "expression"))
549            else:
550                values.append(self.sql(e))
551
552        size = len(expression.expressions)
553
554        if not size or len(schema) != size:
555            if unknown_type:
556                self.unsupported(
557                    "Cannot convert untyped key-value definitions (try annotate_types)."
558                )
559            return self.func("ROW", *values)
560        return f"CAST(ROW({', '.join(values)}) AS ROW({', '.join(schema)}))"
561
562    def interval_sql(self, expression: exp.Interval) -> str:
563        if expression.this and expression.text("unit").upper().startswith("WEEK"):
564            return f"({expression.this.name} * INTERVAL '7' DAY)"
565        return super().interval_sql(expression)
566
567    def transaction_sql(self, expression: exp.Transaction) -> str:
568        modes = expression.args.get("modes")
569        modes = f" {', '.join(modes)}" if modes else ""
570        return f"START TRANSACTION{modes}"
571
572    def offset_limit_modifiers(
573        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
574    ) -> list[str]:
575        return [
576            self.sql(expression, "offset"),
577            self.sql(limit),
578        ]
579
580    def create_sql(self, expression: exp.Create) -> str:
581        """
582        Presto doesn't support CREATE VIEW with expressions (ex: `CREATE VIEW x (cola)` then `(cola)` is the expression),
583        so we need to remove them
584        """
585        kind = expression.args["kind"]
586        schema = expression.this
587        if kind == "VIEW" and schema.expressions:
588            expression.this.set("expressions", None)
589        return super().create_sql(expression)
590
591    def delete_sql(self, expression: exp.Delete) -> str:
592        """
593        Presto only supports DELETE FROM for a single table without an alias, so we need
594        to remove the unnecessary parts. If the original DELETE statement contains more
595        than one table to be deleted, we can't safely map it 1-1 to a Presto statement.
596        """
597        tables = expression.args.get("tables") or [expression.this]
598        if len(tables) > 1:
599            return super().delete_sql(expression)
600
601        table = tables[0]
602        expression.set("this", table)
603        expression.set("tables", None)
604
605        if isinstance(table, exp.Table):
606            table_alias = table.args.get("alias")
607            if table_alias:
608                table_alias.pop()
609                expression = t.cast(exp.Delete, expression.transform(unqualify_columns))
610
611        return super().delete_sql(expression)
612
613    def jsonextract_sql(self, expression: exp.JSONExtract) -> str:
614        is_json_extract = self.dialect.settings.get("variant_extract_is_json_extract", True)
615
616        # Generate JSON_EXTRACT unless the user has configured that a Snowflake / Databricks
617        # VARIANT extract (e.g. col:x.y) should map to dot notation (i.e ROW access) in Presto/Trino
618        if not expression.args.get("variant_extract") or is_json_extract:
619            return self.func(
620                "JSON_EXTRACT", expression.this, expression.expression, *expression.expressions
621            )
622
623        this = self.sql(expression, "this")
624
625        # Convert the JSONPath extraction `JSON_EXTRACT(col, '$.x.y) to a ROW access col.x.y
626        segments = []
627        for path_key in expression.expression.expressions[1:]:
628            if not isinstance(path_key, exp.JSONPathKey):
629                # Cannot transpile subscripts, wildcards etc to dot notation
630                self.unsupported(f"Cannot transpile JSONPath segment '{path_key}' to ROW access")
631                continue
632            key = path_key.this
633            if not exp.SAFE_IDENTIFIER_RE.match(key):
634                key = f'"{key}"'
635            segments.append(f".{key}")
636
637        expr = "".join(segments)
638
639        return f"{this}{expr}"
640
641    def groupconcat_sql(self, expression: exp.GroupConcat) -> str:
642        return self.func(
643            "ARRAY_JOIN",
644            self.func("ARRAY_AGG", expression.this),
645            expression.args.get("separator"),
646        )
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: rename_func("BITWISE_LEFT_SHIFT"),
284        exp.BitwiseNot: lambda self, e: self.func("BITWISE_NOT", e.this),
285        exp.BitwiseOr: lambda self, e: self.func("BITWISE_OR", e.this, e.expression),
286        exp.BitwiseRightShift: rename_func("BITWISE_RIGHT_SHIFT"),
287        exp.BitwiseXor: lambda self, e: self.func("BITWISE_XOR", e.this, e.expression),
288        exp.Cast: transforms.preprocess([transforms.epoch_cast_to_ts]),
289        exp.CurrentTime: lambda *_: "CURRENT_TIME",
290        exp.CurrentTimestamp: lambda *_: "CURRENT_TIMESTAMP",
291        exp.CurrentUser: lambda *_: "CURRENT_USER",
292        exp.DateAdd: _date_delta_sql("DATE_ADD"),
293        exp.DateDiff: lambda self, e: self.func("DATE_DIFF", unit_to_str(e), e.expression, e.this),
294        exp.DateStrToDate: datestrtodate_sql,
295        exp.DateToDi: lambda self, e: (
296            f"CAST(DATE_FORMAT({self.sql(e, 'this')}, {type(self.dialect).DATEINT_FORMAT}) AS INT)"
297        ),
298        exp.DateSub: _date_delta_sql("DATE_ADD", negate_interval=True),
299        exp.DayOfWeek: lambda self, e: f"(({self.func('DAY_OF_WEEK', e.this)} % 7) + 1)",
300        exp.DayOfWeekIso: rename_func("DAY_OF_WEEK"),
301        exp.Decode: lambda self, e: encode_decode_sql(self, e, "FROM_UTF8"),
302        exp.DiToDate: lambda self, e: (
303            f"CAST(DATE_PARSE(CAST({self.sql(e, 'this')} AS VARCHAR), {type(self.dialect).DATEINT_FORMAT}) AS DATE)"
304        ),
305        exp.Encode: lambda self, e: encode_decode_sql(self, e, "TO_UTF8"),
306        exp.FileFormatProperty: lambda self, e: f"format={self.sql(exp.Literal.string(e.name))}",
307        exp.First: _first_last_sql,
308        exp.FromISO8601Date: rename_func("FROM_ISO8601_DATE"),
309        exp.FromISO8601Timestamp: rename_func("FROM_ISO8601_TIMESTAMP"),
310        exp.FromTimeZone: lambda self, e: (
311            f"WITH_TIMEZONE({self.sql(e, 'this')}, {self.sql(e, 'zone')}) AT TIME ZONE 'UTC'"
312        ),
313        exp.GenerateSeries: sequence_sql,
314        exp.GenerateDateArray: sequence_sql,
315        exp.If: if_sql(),
316        exp.ILike: no_ilike_sql,
317        exp.Initcap: _initcap_sql,
318        exp.Last: _first_last_sql,
319        exp.LastDay: lambda self, e: self.func("LAST_DAY_OF_MONTH", e.this),
320        exp.Lateral: _explode_to_unnest_sql,
321        exp.Left: left_to_substring_sql,
322        exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost", "max_dist")(
323            rename_func("LEVENSHTEIN_DISTANCE")
324        ),
325        exp.LogicalAnd: rename_func("BOOL_AND"),
326        exp.LogicalOr: rename_func("BOOL_OR"),
327        exp.Pivot: no_pivot_sql,
328        exp.Quantile: _quantile_sql,
329        exp.RegexpExtract: regexp_extract_sql,
330        exp.RegexpExtractAll: regexp_extract_sql,
331        exp.Right: right_to_substring_sql,
332        exp.Schema: _schema_sql,
333        exp.SchemaCommentProperty: lambda self, e: self.naked_property(e),
334        exp.Select: transforms.preprocess(
335            [
336                transforms.eliminate_window_clause,
337                transforms.eliminate_qualify,
338                transforms.eliminate_distinct_on,
339                transforms.explode_projection_to_unnest(1),
340                transforms.eliminate_semi_and_anti_joins,
341                amend_exploded_column_table,
342            ]
343        ),
344        exp.SortArray: _no_sort_array,
345        exp.SqlSecurityProperty: lambda self, e: f"SECURITY {self.sql(e.this)}",
346        exp.StrPosition: lambda self, e: strposition_sql(self, e, supports_occurrence=True),
347        exp.StrToDate: lambda self, e: f"CAST({_str_to_time_sql(self, e)} AS DATE)",
348        exp.StrToMap: rename_func("SPLIT_TO_MAP"),
349        exp.StrToTime: _str_to_time_sql,
350        exp.StructExtract: struct_extract_sql,
351        exp.Table: transforms.preprocess([transforms.unnest_generate_series]),
352        exp.Timestamp: no_timestamp_sql,
353        exp.TimestampAdd: _date_delta_sql("DATE_ADD"),
354        exp.TimestampTrunc: timestamptrunc_sql(),
355        exp.TimeStrToDate: timestrtotime_sql,
356        exp.TimeStrToTime: timestrtotime_sql,
357        exp.TimeStrToUnix: lambda self, e: self.func(
358            "TO_UNIXTIME", self.func("DATE_PARSE", e.this, type(self.dialect).TIME_FORMAT)
359        ),
360        exp.TimeToStr: lambda self, e: self.func("DATE_FORMAT", e.this, self.format_time(e)),
361        exp.TimeToUnix: rename_func("TO_UNIXTIME"),
362        exp.ToChar: lambda self, e: self.func("DATE_FORMAT", e.this, self.format_time(e)),
363        exp.TryCast: transforms.preprocess([transforms.epoch_cast_to_ts]),
364        exp.TsOrDiToDi: lambda self, e: (
365            f"CAST(SUBSTR(REPLACE(CAST({self.sql(e, 'this')} AS VARCHAR), '-', ''), 1, 8) AS INT)"
366        ),
367        exp.TsOrDsAdd: _ts_or_ds_add_sql,
368        exp.TsOrDsDiff: _ts_or_ds_diff_sql,
369        exp.TsOrDsToDate: _ts_or_ds_to_date_sql,
370        exp.Unhex: rename_func("FROM_HEX"),
371        exp.UnixToStr: lambda self, e: (
372            f"DATE_FORMAT(FROM_UNIXTIME({self.sql(e, 'this')}), {self.format_time(e)})"
373        ),
374        exp.UnixToTime: _unix_to_time_sql,
375        exp.UnixToTimeStr: lambda self, e: f"CAST(FROM_UNIXTIME({self.sql(e, 'this')}) AS VARCHAR)",
376        exp.VariancePop: rename_func("VAR_POP"),
377        exp.With: transforms.preprocess([transforms.add_recursive_cte_column_names]),
378        exp.WithinGroup: transforms.preprocess([transforms.remove_within_group_for_percentiles]),
379        # Note: Presto's TRUNCATE always returns DOUBLE, even with decimals=0, whereas
380        # most dialects return INT (SQLite also returns REAL, see sqlite.py). This creates
381        # a bidirectional transpilation gap: Presto→Other may change float division to int
382        # division, and vice versa. Modeling precisely would require exp.FloatTrunc or
383        # similar, deemed overengineering for this subtle semantic difference.
384        exp.Trunc: rename_func("TRUNCATE"),
385        exp.Xor: bool_xor_sql,
386        exp.MD5Digest: rename_func("MD5"),
387        exp.SHA: rename_func("SHA1"),
388        exp.SHA1Digest: rename_func("SHA1"),
389        exp.SHA2: sha256_sql,
390        exp.SHA2Digest: sha2_digest_sql,
391        exp.Substring: rename_func("SUBSTR"),
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        )

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.CalledOnNullInputProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <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.ClusterProperty'>: <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.ArrayContainedBy'>: <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.properties.CalledOnNullInputProperty'>: <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.JSONBPathExists'>: <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 rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.BitwiseNot'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.core.BitwiseOr'>: <function PrestoGenerator.<lambda>>, <class 'sqlglot.expressions.core.BitwiseRightShift'>: <function rename_func.<locals>.<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.temporal.FromISO8601Date'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.FromISO8601Timestamp'>: <function rename_func.<locals>.<lambda>>, <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>, <class 'sqlglot.expressions.string.Substring'>: <function rename_func.<locals>.<lambda>>}
RESERVED_KEYWORDS = {'case', 'with', 'between', 'prepare', 'values', 'except', 'end', 'union', 'escape', 'true', 'group', 'intersect', 'having', 'false', 'on', 'insert', 'exists', 'right', 'for', 'select', 'create', 'inner', 'when', 'into', 'constraint', 'in', 'distinct', 'left', 'or', 'from', 'join', 'full', 'delete', 'is', 'then', 'else', 'extract', 'natural', 'current_time', 'order', 'table', 'using', 'deallocate', 'and', 'like', 'alter', 'by', 'describe', 'drop', 'cross', 'not', 'cast', 'execute', 'null', 'as', 'where', 'current_timestamp', 'outer'}
def extract_sql(self, expression: sqlglot.expressions.temporal.Extract) -> str:
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)
def jsonformat_sql(self, expression: sqlglot.expressions.json.JSONFormat) -> str:
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)
def md5_sql(self, expression: sqlglot.expressions.string.MD5) -> str:
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))))
def strtounix_sql(self, expression: sqlglot.expressions.temporal.StrToUnix) -> str:
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)
def bracket_sql(self, expression: sqlglot.expressions.core.Bracket) -> str:
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)
def struct_sql(self, expression: sqlglot.expressions.array.Struct) -> str:
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)}))"
def interval_sql(self, expression: sqlglot.expressions.datatypes.Interval) -> str:
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)
def transaction_sql(self, expression: sqlglot.expressions.ddl.Transaction) -> str:
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}"
def offset_limit_modifiers( self, expression: sqlglot.expressions.core.Expr, fetch: bool, limit: sqlglot.expressions.query.Fetch | sqlglot.expressions.query.Limit | None) -> list[str]:
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        ]
def create_sql(self, expression: sqlglot.expressions.ddl.Create) -> str:
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)

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:
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)

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:
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}"
def groupconcat_sql(self, expression: sqlglot.expressions.aggregate.GroupConcat) -> str:
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        )
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
SUPPORTS_NAMED_CTE_COLUMNS
UNPIVOT_ALIASES_ARE_IDENTIFIERS
JSON_KEY_VALUE_PAIR_SEP
INSERT_OVERWRITE
SUPPORTS_SELECT_INTO
SUPPORTS_UNLOGGED_TABLES
SUPPORTS_CREATE_TABLE_LIKE
SUPPORTS_MODIFY_COLUMN
SUPPORTS_CHANGE_COLUMN
JSON_TYPE_REQUIRED_FOR_EXTRACTION
JSON_PATH_BRACKETED_KEY_SUPPORTED
JSON_PATH_SINGLE_QUOTE_ESCAPE
JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
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
TYPE_PARAM_SETTINGS
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_param_bound_limiter
datatype_sql
directory_sql
drop_sql
set_operation
set_operations
fetch_sql
limitoptions_sql
filter_sql
hint_sql
indexparameters_sql
index_sql
dynamicidentifier_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
clusterproperty_sql
distribute_sql
sort_sql
ordered_sql
matchrecognizemeasure_sql
matchrecognize_sql
query_modifiers
options_modifier
forclause_sql
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
fromiso8601date_sql
fromiso8601timestamp_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
parsedatetime_sql
currentdate_sql
collate_sql
command_sql
comment_sql
mergetreettlaction_sql
mergetreettl_sql
commit_sql
rollback_sql
altercolumn_sql
modifycolumn_sql
alterindex_sql
alterdiststyle_sql
altersortkey_sql
alterrename_sql
renamecolumn_sql
alterset_sql
alter_sql
altersession_sql
add_column_sql
droppartition_sql
dropprimarykey_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
distancend_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
macrooverloads_sql
macrooverload_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