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

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 _date_diff_sql>, <class 'sqlglot.expressions.temporal.DatetimeDiff'>: <function _date_diff_sql>, <class 'sqlglot.expressions.temporal.TimestampDiff'>: <function _date_diff_sql>, <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.SHA2Digest'>: <function _sha2_digest_sql>, <class 'sqlglot.expressions.string.Substring'>: <function rename_func.<locals>.<lambda>>}
RESERVED_KEYWORDS = {'from', 'cast', 'having', 'and', 'delete', 'natural', 'for', 'left', 'else', 'prepare', 'in', 'order', 'between', 'right', 'as', 'drop', 'group', 'false', 'constraint', 'outer', 'exists', 'escape', 'alter', 'describe', 'create', 'not', 'end', 'current_timestamp', 'null', 'cross', 'extract', 'on', 'where', 'is', 'when', 'intersect', 'distinct', 'join', 'insert', 'execute', 'full', 'values', 'current_time', 'into', 'table', 'union', 'case', 'with', 'using', 'like', 'then', 'true', 'or', 'except', 'deallocate', 'select', 'inner', 'by'}
def extract_sql(self, expression: sqlglot.expressions.temporal.Extract) -> str:
502    def extract_sql(self, expression: exp.Extract) -> str:
503        date_part = expression.name
504
505        if not date_part.startswith("EPOCH"):
506            return super().extract_sql(expression)
507
508        if date_part == "EPOCH_MILLISECOND":
509            scale = 10**3
510        elif date_part == "EPOCH_MICROSECOND":
511            scale = 10**6
512        elif date_part == "EPOCH_NANOSECOND":
513            scale = 10**9
514        else:
515            scale = None
516
517        value = expression.expression
518
519        ts = exp.cast(value, to=exp.DType.TIMESTAMP.into_expr())
520        to_unix: exp.Expr = exp.TimeToUnix(this=ts)
521
522        if scale:
523            to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale))
524
525        return self.sql(to_unix)
def jsonformat_sql(self, expression: sqlglot.expressions.json.JSONFormat) -> str:
527    def jsonformat_sql(self, expression: exp.JSONFormat) -> str:
528        this = expression.this
529        is_json = expression.args.get("is_json")
530
531        if this and not (is_json or this.type):
532            from sqlglot.optimizer.annotate_types import annotate_types
533
534            this = annotate_types(this, dialect=self.dialect)
535
536        if not (is_json or this.is_type(exp.DType.JSON)):
537            this.replace(exp.cast(this, exp.DType.JSON))
538
539        return self.function_fallback_sql(expression)
def md5_sql(self, expression: sqlglot.expressions.string.MD5) -> str:
541    def md5_sql(self, expression: exp.MD5) -> str:
542        this = expression.this
543
544        if not this.type:
545            from sqlglot.optimizer.annotate_types import annotate_types
546
547            this = annotate_types(this, dialect=self.dialect)
548
549        if this.is_type(*exp.DataType.TEXT_TYPES):
550            this = exp.Encode(this=this, charset=exp.Literal.string("utf-8"))
551
552        return self.func("LOWER", self.func("TO_HEX", self.func("MD5", self.sql(this))))
def sha2_sql(self, expression: sqlglot.expressions.string.SHA2) -> str:
554    def sha2_sql(self, expression: exp.SHA2) -> str:
555        length = expression.text("length") or "256"
556        if length not in ("256", "512"):
557            self.unsupported(f"SHA{length} is not supported in Presto")
558
559        this = expression.this
560
561        if this.is_type(*exp.DataType.TEXT_TYPES):
562            this = exp.Encode(this=this, charset=exp.Literal.string("utf-8"))
563
564        return self.func("LOWER", self.func("TO_HEX", self.func(f"SHA{length}", self.sql(this))))
def strtounix_sql(self, expression: sqlglot.expressions.temporal.StrToUnix) -> str:
566    def strtounix_sql(self, expression: exp.StrToUnix) -> str:
567        # Since `TO_UNIXTIME` requires a `TIMESTAMP`, we need to parse the argument into one.
568        # To do this, we first try to `DATE_PARSE` it, but since this can fail when there's a
569        # timezone involved, we wrap it in a `TRY` call and use `PARSE_DATETIME` as a fallback,
570        # which seems to be using the same time mapping as Hive, as per:
571        # https://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html
572        this = expression.this
573        value_as_text = exp.cast(this, exp.DType.TEXT)
574        value_as_timestamp = exp.cast(this, exp.DType.TIMESTAMP) if this.is_string else this
575
576        parse_without_tz = self.func("DATE_PARSE", value_as_text, self.format_time(expression))
577
578        formatted_value = self.func("DATE_FORMAT", value_as_timestamp, self.format_time(expression))
579        parse_with_tz = self.func(
580            "PARSE_DATETIME",
581            formatted_value,
582            self.format_time(expression, Hive.INVERSE_TIME_MAPPING, Hive.INVERSE_TIME_TRIE),
583        )
584        coalesced = self.func("COALESCE", self.func("TRY", parse_without_tz), parse_with_tz)
585        return self.func("TO_UNIXTIME", coalesced)
def bracket_sql(self, expression: sqlglot.expressions.core.Bracket) -> str:
587    def bracket_sql(self, expression: exp.Bracket) -> str:
588        if expression.args.get("safe"):
589            return bracket_to_element_at_sql(self, expression)
590        return super().bracket_sql(expression)
def struct_sql(self, expression: sqlglot.expressions.array.Struct) -> str:
592    def struct_sql(self, expression: exp.Struct) -> str:
593        if not expression.type:
594            from sqlglot.optimizer.annotate_types import annotate_types
595
596            annotate_types(expression, dialect=self.dialect)
597
598        values: list[str] = []
599        schema: list[str] = []
600        unknown_type = False
601
602        for e in expression.expressions:
603            if isinstance(e, exp.PropertyEQ):
604                if e.type and e.type.is_type(exp.DType.UNKNOWN):
605                    unknown_type = True
606                else:
607                    schema.append(f"{self.sql(e, 'this')} {self.sql(e.type)}")
608                values.append(self.sql(e, "expression"))
609            else:
610                values.append(self.sql(e))
611
612        size = len(expression.expressions)
613
614        if not size or len(schema) != size:
615            if unknown_type:
616                self.unsupported(
617                    "Cannot convert untyped key-value definitions (try annotate_types)."
618                )
619            return self.func("ROW", *values)
620        return f"CAST(ROW({', '.join(values)}) AS ROW({', '.join(schema)}))"
def interval_sql(self, expression: sqlglot.expressions.datatypes.Interval) -> str:
622    def interval_sql(self, expression: exp.Interval) -> str:
623        if expression.this and expression.text("unit").upper().startswith("WEEK"):
624            return f"({expression.this.name} * INTERVAL '7' DAY)"
625        return super().interval_sql(expression)
def transaction_sql(self, expression: sqlglot.expressions.ddl.Transaction) -> str:
627    def transaction_sql(self, expression: exp.Transaction) -> str:
628        modes = expression.args.get("modes")
629        modes = f" {', '.join(modes)}" if modes else ""
630        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]:
632    def offset_limit_modifiers(
633        self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None
634    ) -> list[str]:
635        return [
636            self.sql(expression, "offset"),
637            self.sql(limit),
638        ]
def create_sql(self, expression: sqlglot.expressions.ddl.Create) -> str:
640    def create_sql(self, expression: exp.Create) -> str:
641        """
642        Presto doesn't support CREATE VIEW with expressions (ex: `CREATE VIEW x (cola)` then `(cola)` is the expression),
643        so we need to remove them
644        """
645        kind = expression.args["kind"]
646        schema = expression.this
647        if kind == "VIEW" and schema.expressions:
648            expression.this.set("expressions", None)
649        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:
651    def delete_sql(self, expression: exp.Delete) -> str:
652        """
653        Presto only supports DELETE FROM for a single table without an alias, so we need
654        to remove the unnecessary parts. If the original DELETE statement contains more
655        than one table to be deleted, we can't safely map it 1-1 to a Presto statement.
656        """
657        tables = expression.args.get("tables") or [expression.this]
658        if len(tables) > 1:
659            return super().delete_sql(expression)
660
661        table = tables[0]
662        expression.set("this", table)
663        expression.set("tables", None)
664
665        if isinstance(table, exp.Table):
666            table_alias = table.args.get("alias")
667            if table_alias:
668                table_alias.pop()
669                expression = t.cast(exp.Delete, expression.transform(unqualify_columns))
670
671        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:
673    def jsonextract_sql(self, expression: exp.JSONExtract) -> str:
674        is_json_extract = self.dialect.settings.get("variant_extract_is_json_extract", True)
675
676        # Generate JSON_EXTRACT unless the user has configured that a Snowflake / Databricks
677        # VARIANT extract (e.g. col:x.y) should map to dot notation (i.e ROW access) in Presto/Trino
678        if not expression.args.get("variant_extract") or is_json_extract:
679            return self.func(
680                "JSON_EXTRACT", expression.this, expression.expression, *expression.expressions
681            )
682
683        this = self.sql(expression, "this")
684
685        # Convert the JSONPath extraction `JSON_EXTRACT(col, '$.x.y) to a ROW access col.x.y
686        segments = []
687        for path_key in expression.expression.expressions[1:]:
688            if not isinstance(path_key, exp.JSONPathKey):
689                # Cannot transpile subscripts, wildcards etc to dot notation
690                self.unsupported(f"Cannot transpile JSONPath segment '{path_key}' to ROW access")
691                continue
692            key = path_key.this
693            if not exp.SAFE_IDENTIFIER_RE.match(key):
694                key = f'"{key}"'
695            segments.append(f".{key}")
696
697        expr = "".join(segments)
698
699        return f"{this}{expr}"
def groupconcat_sql(self, expression: sqlglot.expressions.aggregate.GroupConcat) -> str:
701    def groupconcat_sql(self, expression: exp.GroupConcat) -> str:
702        return self.func(
703            "ARRAY_JOIN",
704            self.func("ARRAY_AGG", expression.this),
705            expression.args.get("separator"),
706        )
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
AUTO_REFRESH_BARE_INTERVALS
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
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
HISTORICAL_DATA_POST_ALIAS
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
PIVOT_ALIAS_WITH_AS
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
timeserieskey_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
fromiso8601timestampnanos_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
strtodate_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_name
weekstart_sql
chr_sql
block_sql
functionspecification_sql
storedprocedure_sql
ifblock_sql
whileblock_sql
execute_sql
executesql_sql
altermodifysqlsecurity_sql
usingproperty_sql
renameindex_sql