Edit on GitHub

sqlglot.generators.snowflake

   1from __future__ import annotations
   2
   3import typing as t
   4from collections import defaultdict
   5
   6from sqlglot import exp, generator, transforms
   7from sqlglot.dialects.dialect import (
   8    array_append_sql,
   9    array_concat_sql,
  10    date_delta_sql,
  11    datestrtodate_sql,
  12    groupconcat_sql,
  13    if_sql,
  14    inline_array_sql,
  15    map_date_part,
  16    max_or_greatest,
  17    min_or_least,
  18    no_make_interval_sql,
  19    no_timestamp_sql,
  20    nth_value_from_sql,
  21    rename_func,
  22    strposition_sql,
  23    timestampdiff_sql,
  24    timestamptrunc_sql,
  25    timestrtotime_sql,
  26    unit_to_str,
  27    var_map_sql,
  28)
  29from sqlglot.generator import unsupported_args
  30from sqlglot.helper import find_new_name, flatten, seq_get
  31from sqlglot.optimizer.scope import build_scope, find_all_in_scope
  32from sqlglot.parsers.snowflake import (
  33    RANKING_WINDOW_FUNCTIONS_WITH_FRAME,
  34    TIMESTAMP_TYPES,
  35    SnowflakeParser,
  36    build_object_construct,
  37)
  38from sqlglot.tokens import TokenType
  39
  40if t.TYPE_CHECKING:
  41    from sqlglot._typing import E
  42
  43
  44def _build_datediff(args: list) -> exp.DateDiff:
  45    return exp.DateDiff(
  46        this=seq_get(args, 2),
  47        expression=seq_get(args, 1),
  48        unit=map_date_part(seq_get(args, 0)),
  49        date_part_boundary=True,
  50    )
  51
  52
  53def _build_date_time_add(expr_type: type[E]) -> t.Callable[[list], E]:
  54    def _builder(args: list) -> E:
  55        return expr_type(
  56            this=seq_get(args, 2),
  57            expression=seq_get(args, 1),
  58            unit=map_date_part(seq_get(args, 0)),
  59        )
  60
  61    return _builder
  62
  63
  64def _regexpilike_sql(self: SnowflakeGenerator, expression: exp.RegexpILike) -> str:
  65    flag = expression.text("flag")
  66
  67    if "i" not in flag:
  68        flag += "i"
  69
  70    return self.func(
  71        "REGEXP_LIKE", expression.this, expression.expression, exp.Literal.string(flag)
  72    )
  73
  74
  75def _unqualify_pivot_columns(expression: exp.Expr) -> exp.Expr:
  76    """
  77    Snowflake doesn't allow columns referenced in UNPIVOT to be qualified,
  78    so we need to unqualify them. Same goes for ANY ORDER BY <column>.
  79
  80    Example:
  81        >>> from sqlglot import parse_one
  82        >>> expr = parse_one("SELECT * FROM m_sales UNPIVOT(sales FOR month IN (m_sales.jan, feb, mar, april))")
  83        >>> print(_unqualify_pivot_columns(expr).sql(dialect="snowflake"))
  84        SELECT * FROM m_sales UNPIVOT(sales FOR month IN (jan, feb, mar, april))
  85    """
  86    if isinstance(expression, exp.Pivot):
  87        if expression.unpivot:
  88            expression = transforms.unqualify_columns(expression)
  89        else:
  90            for field in expression.fields:
  91                field_expr = seq_get(field.expressions if field else [], 0)
  92
  93                if isinstance(field_expr, exp.PivotAny):
  94                    unqualified_field_expr = transforms.unqualify_columns(field_expr)
  95                    t.cast(exp.Expr, field).set("expressions", unqualified_field_expr, 0)
  96
  97    return expression
  98
  99
 100def _unnest_generate_date_array(unnest: exp.Unnest) -> None:
 101    generate_date_array = unnest.expressions[0]
 102    start = generate_date_array.args.get("start")
 103    end = generate_date_array.args.get("end")
 104    step = generate_date_array.args.get("step")
 105
 106    if not start or not end or not isinstance(step, exp.Interval) or step.name != "1":
 107        return
 108
 109    unit = step.args.get("unit")
 110
 111    unnest_alias = unnest.args.get("alias")
 112    if unnest_alias:
 113        unnest_alias = unnest_alias.copy()
 114        sequence_value_name = seq_get(unnest_alias.columns, 0) or "value"
 115    else:
 116        sequence_value_name = "value"
 117
 118    # We'll add the next sequence value to the starting date and project the result
 119    date_add = _build_date_time_add(exp.DateAdd)(
 120        [unit, exp.cast(sequence_value_name, "int"), exp.cast(start, "date")]
 121    )
 122
 123    # We use DATEDIFF to compute the number of sequence values needed
 124    number_sequence = SnowflakeParser.FUNCTIONS["ARRAY_GENERATE_RANGE"](
 125        [exp.Literal.number(0), _build_datediff([unit, start, end]) + 1]
 126    )
 127
 128    unnest.set("expressions", [number_sequence])
 129
 130    unnest_parent = unnest.parent
 131    if isinstance(unnest_parent, exp.Join):
 132        select = unnest_parent.parent
 133        if isinstance(select, exp.Select):
 134            replace_column_name = (
 135                sequence_value_name
 136                if isinstance(sequence_value_name, str)
 137                else sequence_value_name.name
 138            )
 139
 140            scope = build_scope(select)
 141            if scope:
 142                for column in scope.columns:
 143                    if column.name.lower() == replace_column_name.lower():
 144                        column.replace(
 145                            date_add.as_(replace_column_name)
 146                            if isinstance(column.parent, exp.Select)
 147                            else date_add
 148                        )
 149
 150            lateral = exp.Lateral(this=unnest_parent.this.pop())
 151            unnest_parent.replace(exp.Join(this=lateral))
 152    else:
 153        unnest.replace(
 154            exp.select(date_add.as_(sequence_value_name))
 155            .from_(unnest.copy())
 156            .subquery(unnest_alias)
 157        )
 158
 159
 160def _transform_generate_date_array(expression: exp.Expr) -> exp.Expr:
 161    if isinstance(expression, exp.Select):
 162        for generate_date_array in expression.find_all(exp.GenerateDateArray):
 163            parent = generate_date_array.parent
 164
 165            # If GENERATE_DATE_ARRAY is used directly as an array (e.g passed into ARRAY_LENGTH), the transformed Snowflake
 166            # query is the following (it'll be unnested properly on the next iteration due to copy):
 167            # SELECT ref(GENERATE_DATE_ARRAY(...)) -> SELECT ref((SELECT ARRAY_AGG(*) FROM UNNEST(GENERATE_DATE_ARRAY(...))))
 168            if not isinstance(parent, exp.Unnest):
 169                unnest = exp.Unnest(expressions=[generate_date_array.copy()])
 170                generate_date_array.replace(
 171                    exp.select(exp.ArrayAgg(this=exp.Star())).from_(unnest).subquery()
 172                )
 173
 174            if (
 175                isinstance(parent, exp.Unnest)
 176                and isinstance(parent.parent, (exp.From, exp.Join))
 177                and len(parent.expressions) == 1
 178            ):
 179                _unnest_generate_date_array(parent)
 180
 181    return expression
 182
 183
 184def _regexpextract_sql(
 185    self: SnowflakeGenerator, expression: exp.RegexpExtract | exp.RegexpExtractAll
 186) -> str:
 187    # Other dialects don't support all of the following parameters, so we need to
 188    # generate default values as necessary to ensure the transpilation is correct
 189    group = expression.args.get("group")
 190
 191    # To avoid generating all these default values, we set group to None if
 192    # it's 0 (also default value) which doesn't trigger the following chain
 193    if group and group.name == "0":
 194        group = None
 195
 196    parameters = expression.args.get("parameters") or (group and exp.Literal.string("c"))
 197    occurrence = expression.args.get("occurrence") or (parameters and exp.Literal.number(1))
 198    position = expression.args.get("position") or (occurrence and exp.Literal.number(1))
 199
 200    return self.func(
 201        "REGEXP_SUBSTR" if isinstance(expression, exp.RegexpExtract) else "REGEXP_SUBSTR_ALL",
 202        expression.this,
 203        expression.expression,
 204        position,
 205        occurrence,
 206        parameters,
 207        group,
 208    )
 209
 210
 211def _json_extract_value_array_sql(
 212    self: SnowflakeGenerator, expression: exp.JSONValueArray | exp.JSONExtractArray
 213) -> str:
 214    json_extract = exp.JSONExtract(this=expression.this, expression=expression.expression)
 215    ident = exp.to_identifier("x")
 216
 217    if isinstance(expression, exp.JSONValueArray):
 218        this: exp.Expr = exp.cast(ident, to=exp.DType.VARCHAR)
 219    else:
 220        this = exp.ParseJSON(this=f"TO_JSON({ident})")
 221
 222    transform_lambda = exp.Lambda(expressions=[ident], this=this)
 223
 224    return self.func("TRANSFORM", json_extract, transform_lambda)
 225
 226
 227def _qualify_unnested_columns(expression: exp.Expr) -> exp.Expr:
 228    if isinstance(expression, exp.Select):
 229        scope = build_scope(expression)
 230        if not scope:
 231            return expression
 232
 233        unnests = list(scope.find_all(exp.Unnest))
 234
 235        if not unnests:
 236            return expression
 237
 238        taken_source_names = set(scope.sources)
 239        column_source: dict[str, exp.Identifier] = {}
 240        unnest_to_identifier: dict[exp.Unnest, exp.Identifier] = {}
 241
 242        unnest_identifier: exp.Identifier | None = None
 243        orig_expression = expression.copy()
 244
 245        for unnest in unnests:
 246            if not isinstance(unnest.parent, (exp.From, exp.Join)):
 247                continue
 248
 249            # Try to infer column names produced by an unnest operator. This is only possible
 250            # when we can peek into the (statically known) contents of the unnested value.
 251            unnest_columns: set[str] = set()
 252            for unnest_expr in unnest.expressions:
 253                if not isinstance(unnest_expr, exp.Array):
 254                    continue
 255
 256                for array_expr in unnest_expr.expressions:
 257                    if not (
 258                        isinstance(array_expr, exp.Struct)
 259                        and array_expr.expressions
 260                        and all(
 261                            isinstance(struct_expr, exp.PropertyEQ)
 262                            for struct_expr in array_expr.expressions
 263                        )
 264                    ):
 265                        continue
 266
 267                    unnest_columns.update(
 268                        struct_expr.this.name.lower() for struct_expr in array_expr.expressions
 269                    )
 270                    break
 271
 272                if unnest_columns:
 273                    break
 274
 275            unnest_alias = unnest.args.get("alias")
 276            if not unnest_alias:
 277                alias_name = find_new_name(taken_source_names, "value")
 278                taken_source_names.add(alias_name)
 279
 280                # Produce a `TableAlias` AST similar to what is produced for BigQuery. This
 281                # will be corrected later, when we generate SQL for the `Unnest` AST node.
 282                aliased_unnest = exp.alias_(unnest, None, table=[alias_name])
 283                scope.replace(unnest, aliased_unnest)
 284
 285                unnest_identifier = aliased_unnest.args["alias"].columns[0]
 286            else:
 287                alias_columns = getattr(unnest_alias, "columns", [])
 288                unnest_identifier = unnest_alias.this or seq_get(alias_columns, 0)
 289
 290            if not isinstance(unnest_identifier, exp.Identifier):
 291                return orig_expression
 292
 293            unnest_to_identifier[unnest] = unnest_identifier
 294            column_source.update({c.lower(): unnest_identifier for c in unnest_columns})
 295
 296        for column in scope.columns:
 297            if column.table:
 298                continue
 299
 300            table = column_source.get(column.name.lower())
 301            if (
 302                unnest_identifier
 303                and not table
 304                and len(scope.sources) == 1
 305                and column.name.lower() != unnest_identifier.name.lower()
 306            ):
 307                unnest_ancestor = column.find_ancestor(exp.Unnest, exp.Select)
 308                if isinstance(unnest_ancestor, exp.Unnest):
 309                    ancestor_identifier = unnest_to_identifier.get(unnest_ancestor)
 310                    if (
 311                        ancestor_identifier
 312                        and ancestor_identifier.name.lower() == unnest_identifier.name.lower()
 313                    ):
 314                        continue
 315
 316                table = unnest_identifier
 317
 318            column.set("table", table and table.copy())
 319
 320    return expression
 321
 322
 323def _eliminate_dot_variant_lookup(expression: exp.Expr) -> exp.Expr:
 324    if isinstance(expression, exp.Select):
 325        # This transformation is used to facilitate transpilation of BigQuery `UNNEST` operations
 326        # to Snowflake. It should not affect roundtrip because `Unnest` nodes cannot be produced
 327        # by Snowflake's parser.
 328        #
 329        # Additionally, at the time of writing this, BigQuery is the only dialect that produces a
 330        # `TableAlias` node that only fills `columns` and not `this`, due to `UNNEST_COLUMN_ONLY`.
 331        unnest_aliases = set()
 332        for unnest in find_all_in_scope(expression, exp.Unnest):
 333            unnest_alias = unnest.args.get("alias")
 334            if (
 335                isinstance(unnest_alias, exp.TableAlias)
 336                and not unnest_alias.this
 337                and len(unnest_alias.columns) == 1
 338            ):
 339                unnest_aliases.add(unnest_alias.columns[0].name)
 340
 341        if unnest_aliases:
 342            for c in find_all_in_scope(expression, exp.Column):
 343                if c.table in unnest_aliases:
 344                    bracket_lhs = c.args["table"]
 345                    bracket_rhs = exp.Literal.string(c.name)
 346                    bracket = exp.Bracket(this=bracket_lhs, expressions=[bracket_rhs])
 347
 348                    if c.parent is expression:
 349                        # Retain column projection names by using aliases
 350                        c.replace(exp.alias_(bracket, c.this.copy()))
 351                    else:
 352                        c.replace(bracket)
 353
 354    return expression
 355
 356
 357class SnowflakeGenerator(generator.Generator):
 358    SELECT_KINDS: tuple[str, ...] = ()
 359    PARAMETER_TOKEN = "$"
 360    MATCHED_BY_SOURCE = False
 361    SINGLE_STRING_INTERVAL = True
 362    JOIN_HINTS = False
 363    TABLE_HINTS = False
 364    QUERY_HINTS = False
 365    SUPPORTS_TABLE_COPY = False
 366    COLLATE_IS_FUNC = True
 367    LIMIT_ONLY_LITERALS = True
 368    JSON_KEY_VALUE_PAIR_SEP = ","
 369    INSERT_OVERWRITE = " OVERWRITE INTO"
 370    STRUCT_DELIMITER = ("(", ")")
 371    COPY_PARAMS_ARE_WRAPPED = False
 372    COPY_PARAMS_EQ_REQUIRED = True
 373    STAR_EXCEPT = "EXCLUDE"
 374    SUPPORTS_EXPLODING_PROJECTIONS = False
 375    ARRAY_CONCAT_IS_VAR_LEN = False
 376    SUPPORTS_CONVERT_TIMEZONE = True
 377    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
 378    SUPPORTS_MEDIAN = True
 379    ARRAY_SIZE_NAME = "ARRAY_SIZE"
 380    SUPPORTS_DECODE_CASE = True
 381
 382    AFTER_HAVING_MODIFIER_TRANSFORMS = generator.AFTER_HAVING_MODIFIER_TRANSFORMS
 383
 384    IS_BOOL_ALLOWED = False
 385    DIRECTED_JOINS = True
 386    SUPPORTS_UESCAPE = False
 387    TRY_SUPPORTED = False
 388
 389    TRANSFORMS = {
 390        **generator.Generator.TRANSFORMS,
 391        exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"),
 392        exp.ArgMax: rename_func("MAX_BY"),
 393        exp.ArgMin: rename_func("MIN_BY"),
 394        exp.Array: transforms.preprocess([transforms.inherit_struct_field_names]),
 395        exp.ArrayConcat: array_concat_sql("ARRAY_CAT"),
 396        exp.ArrayAppend: array_append_sql("ARRAY_APPEND"),
 397        exp.ArrayPrepend: array_append_sql("ARRAY_PREPEND"),
 398        exp.ArrayContains: lambda self, e: self.func(
 399            "ARRAY_CONTAINS",
 400            e.expression
 401            if e.args.get("ensure_variant") is False
 402            else exp.cast(e.expression, exp.DType.VARIANT, copy=False),
 403            e.this,
 404        ),
 405        exp.ArrayPosition: lambda self, e: self.func(
 406            "ARRAY_POSITION",
 407            e.expression,
 408            e.this,
 409        ),
 410        exp.ArrayIntersect: rename_func("ARRAY_INTERSECTION"),
 411        exp.ArrayOverlaps: rename_func("ARRAYS_OVERLAP"),
 412        exp.AtTimeZone: lambda self, e: self.func("CONVERT_TIMEZONE", e.args.get("zone"), e.this),
 413        exp.BitwiseOr: rename_func("BITOR"),
 414        exp.BitwiseXor: rename_func("BITXOR"),
 415        exp.BitwiseAnd: rename_func("BITAND"),
 416        exp.BitwiseAndAgg: rename_func("BITANDAGG"),
 417        exp.BitwiseOrAgg: rename_func("BITORAGG"),
 418        exp.BitwiseXorAgg: rename_func("BITXORAGG"),
 419        exp.BitwiseNot: rename_func("BITNOT"),
 420        exp.BitwiseLeftShift: rename_func("BITSHIFTLEFT"),
 421        exp.BitwiseRightShift: rename_func("BITSHIFTRIGHT"),
 422        exp.CurrentTimestamp: lambda self, e: (
 423            self.func("SYSDATE") if e.args.get("sysdate") else self.function_fallback_sql(e)
 424        ),
 425        exp.CurrentSchemas: lambda self, e: self.func("CURRENT_SCHEMAS"),
 426        exp.Localtime: lambda self, e: (
 427            self.func("CURRENT_TIME", e.this) if e.this else "CURRENT_TIME"
 428        ),
 429        exp.Localtimestamp: lambda self, e: (
 430            self.func("CURRENT_TIMESTAMP", e.this) if e.this else "CURRENT_TIMESTAMP"
 431        ),
 432        exp.DateAdd: date_delta_sql("DATEADD"),
 433        exp.DateDiff: date_delta_sql("DATEDIFF"),
 434        exp.DatetimeAdd: date_delta_sql("TIMESTAMPADD"),
 435        exp.DatetimeDiff: timestampdiff_sql,
 436        exp.DateStrToDate: datestrtodate_sql,
 437        exp.Decrypt: lambda self, e: self.func(
 438            f"{'TRY_' if e.args.get('safe') else ''}DECRYPT",
 439            e.this,
 440            e.args.get("passphrase"),
 441            e.args.get("aad"),
 442            e.args.get("encryption_method"),
 443        ),
 444        exp.DecryptRaw: lambda self, e: self.func(
 445            f"{'TRY_' if e.args.get('safe') else ''}DECRYPT_RAW",
 446            e.this,
 447            e.args.get("key"),
 448            e.args.get("iv"),
 449            e.args.get("aad"),
 450            e.args.get("encryption_method"),
 451            e.args.get("aead"),
 452        ),
 453        exp.DayOfMonth: rename_func("DAYOFMONTH"),
 454        exp.DayOfWeek: rename_func("DAYOFWEEK"),
 455        exp.DayOfWeekIso: rename_func("DAYOFWEEKISO"),
 456        exp.DayOfYear: rename_func("DAYOFYEAR"),
 457        exp.DotProduct: rename_func("VECTOR_INNER_PRODUCT"),
 458        exp.Explode: rename_func("FLATTEN"),
 459        exp.Extract: lambda self, e: self.func(
 460            "DATE_PART", map_date_part(e.this, self.dialect), e.expression
 461        ),
 462        exp.CosineDistance: rename_func("VECTOR_COSINE_SIMILARITY"),
 463        exp.EuclideanDistance: rename_func("VECTOR_L2_DISTANCE"),
 464        exp.HandlerProperty: lambda self, e: f"HANDLER = {self.sql(e, 'this')}",
 465        exp.FileFormatProperty: lambda self, e: (
 466            f"FILE_FORMAT=({self.expressions(e, 'expressions', sep=' ')})"
 467        ),
 468        exp.FromTimeZone: lambda self, e: self.func(
 469            "CONVERT_TIMEZONE", e.args.get("zone"), "'UTC'", e.this
 470        ),
 471        exp.GenerateSeries: lambda self, e: self.func(
 472            "ARRAY_GENERATE_RANGE",
 473            e.args["start"],
 474            e.args["end"] if e.args.get("is_end_exclusive") else e.args["end"] + 1,
 475            e.args.get("step"),
 476        ),
 477        exp.GetExtract: rename_func("GET"),
 478        exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, sep=""),
 479        exp.If: if_sql(name="IFF", false_value="NULL"),
 480        exp.JSONArray: lambda self, e: self.func(
 481            "TO_VARIANT", self.func("ARRAY_CONSTRUCT", *e.expressions)
 482        ),
 483        exp.JSONExtractArray: _json_extract_value_array_sql,
 484        exp.JSONExtractScalar: lambda self, e: self.func(
 485            "JSON_EXTRACT_PATH_TEXT", e.this, e.expression
 486        ),
 487        exp.JSONKeys: rename_func("OBJECT_KEYS"),
 488        exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions),
 489        exp.JSONPathRoot: lambda *_: "",
 490        exp.JSONValueArray: _json_extract_value_array_sql,
 491        exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost")(
 492            rename_func("EDITDISTANCE")
 493        ),
 494        exp.LocationProperty: lambda self, e: f"LOCATION={self.sql(e, 'this')}",
 495        exp.LogicalAnd: rename_func("BOOLAND_AGG"),
 496        exp.LogicalOr: rename_func("BOOLOR_AGG"),
 497        exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"),
 498        exp.ManhattanDistance: rename_func("VECTOR_L1_DISTANCE"),
 499        exp.MakeInterval: no_make_interval_sql,
 500        exp.Max: max_or_greatest,
 501        exp.Min: min_or_least,
 502        exp.NthValue: nth_value_from_sql,
 503        exp.ParseJSON: lambda self, e: self.func(
 504            f"{'TRY_' if e.args.get('safe') else ''}PARSE_JSON", e.this
 505        ),
 506        exp.ToBinary: lambda self, e: self.func(
 507            f"{'TRY_' if e.args.get('safe') else ''}TO_BINARY", e.this, e.args.get("format")
 508        ),
 509        exp.ToBoolean: lambda self, e: self.func(
 510            f"{'TRY_' if e.args.get('safe') else ''}TO_BOOLEAN", e.this
 511        ),
 512        exp.ToDouble: lambda self, e: self.func(
 513            f"{'TRY_' if e.args.get('safe') else ''}TO_DOUBLE", e.this, e.args.get("format")
 514        ),
 515        exp.ToFile: lambda self, e: self.func(
 516            f"{'TRY_' if e.args.get('safe') else ''}TO_FILE", e.this, e.args.get("path")
 517        ),
 518        exp.JSONFormat: rename_func("TO_JSON"),
 519        exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}",
 520        exp.PercentileCont: transforms.preprocess([transforms.add_within_group_for_percentiles]),
 521        exp.PercentileDisc: transforms.preprocess([transforms.add_within_group_for_percentiles]),
 522        exp.Pivot: transforms.preprocess([_unqualify_pivot_columns]),
 523        exp.RegexpExtract: _regexpextract_sql,
 524        exp.RegexpExtractAll: _regexpextract_sql,
 525        exp.RegexpILike: _regexpilike_sql,
 526        exp.RowAccessProperty: lambda self, e: self.rowaccessproperty_sql(e),
 527        exp.Select: transforms.preprocess(
 528            [
 529                transforms.eliminate_window_clause,
 530                transforms.eliminate_distinct_on,
 531                transforms.explode_projection_to_unnest(),
 532                transforms.eliminate_semi_and_anti_joins,
 533                _transform_generate_date_array,
 534                _qualify_unnested_columns,
 535                _eliminate_dot_variant_lookup,
 536            ]
 537        ),
 538        exp.SHA: rename_func("SHA1"),
 539        exp.SHA1Digest: rename_func("SHA1_BINARY"),
 540        exp.MD5Digest: rename_func("MD5_BINARY"),
 541        exp.MD5NumberLower64: rename_func("MD5_NUMBER_LOWER64"),
 542        exp.MD5NumberUpper64: rename_func("MD5_NUMBER_UPPER64"),
 543        exp.Hex: rename_func("HEX_ENCODE"),
 544        exp.LowerHex: rename_func("TO_CHAR"),
 545        exp.Skewness: rename_func("SKEW"),
 546        exp.StarMap: rename_func("OBJECT_CONSTRUCT"),
 547        exp.StartsWith: rename_func("STARTSWITH"),
 548        exp.EndsWith: rename_func("ENDSWITH"),
 549        exp.Rand: lambda self, e: self.func("RANDOM", e.this),
 550        exp.StrPosition: lambda self, e: strposition_sql(
 551            self, e, func_name="CHARINDEX", supports_position=True
 552        ),
 553        exp.StrToDate: lambda self, e: self.func("DATE", e.this, self.format_time(e)),
 554        exp.StringToArray: rename_func("STRTOK_TO_ARRAY"),
 555        exp.StrtokToArray: rename_func("STRTOK_TO_ARRAY"),
 556        exp.Stuff: rename_func("INSERT"),
 557        exp.StPoint: rename_func("ST_MAKEPOINT"),
 558        exp.TimeAdd: date_delta_sql("TIMEADD"),
 559        exp.TimeSlice: lambda self, e: self.func(
 560            "TIME_SLICE",
 561            e.this,
 562            e.expression,
 563            unit_to_str(e),
 564            e.args.get("kind"),
 565        ),
 566        exp.Timestamp: no_timestamp_sql,
 567        exp.TimestampAdd: date_delta_sql("TIMESTAMPADD"),
 568        exp.TimestampDiff: lambda self, e: self.func("TIMESTAMPDIFF", e.unit, e.expression, e.this),
 569        exp.TimestampTrunc: timestamptrunc_sql(),
 570        exp.TimeStrToTime: timestrtotime_sql,
 571        exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})",
 572        exp.ToArray: rename_func("TO_ARRAY"),
 573        exp.ToChar: lambda self, e: self.function_fallback_sql(e),
 574        exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True),
 575        exp.TsOrDsDiff: date_delta_sql("DATEDIFF"),
 576        exp.TsOrDsToDate: lambda self, e: self.func(
 577            f"{'TRY_' if e.args.get('safe') else ''}TO_DATE", e.this, self.format_time(e)
 578        ),
 579        exp.TsOrDsToTime: lambda self, e: self.func(
 580            f"{'TRY_' if e.args.get('safe') else ''}TO_TIME", e.this, self.format_time(e)
 581        ),
 582        exp.Unhex: rename_func("HEX_DECODE_BINARY"),
 583        exp.UnixToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, e.args.get("scale")),
 584        exp.Uuid: rename_func("UUID_STRING"),
 585        exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"),
 586        exp.Booland: rename_func("BOOLAND"),
 587        exp.Boolor: rename_func("BOOLOR"),
 588        exp.WeekOfYear: rename_func("WEEKISO"),
 589        exp.YearOfWeek: rename_func("YEAROFWEEK"),
 590        exp.YearOfWeekIso: rename_func("YEAROFWEEKISO"),
 591        exp.Xor: rename_func("BOOLXOR"),
 592        exp.ByteLength: rename_func("OCTET_LENGTH"),
 593        exp.Flatten: rename_func("ARRAY_FLATTEN"),
 594        exp.ArrayConcatAgg: lambda self, e: self.func("ARRAY_FLATTEN", exp.ArrayAgg(this=e.this)),
 595        exp.SHA2Digest: lambda self, e: self.func(
 596            "SHA2_BINARY", e.this, e.args.get("length") or exp.Literal.number(256)
 597        ),
 598    }
 599
 600    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
 601        this = self.func("IDENTIFIER", expression.this)
 602        if "expressions" in expression.args:
 603            # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
 604            return self.func(this, *expression.expressions, normalize=False)
 605        return this
 606
 607    def sortarray_sql(self, expression: exp.SortArray) -> str:
 608        asc = expression.args.get("asc")
 609        nulls_first = expression.args.get("nulls_first")
 610        if asc == exp.false() and nulls_first == exp.true():
 611            nulls_first = None
 612        return self.func("ARRAY_SORT", expression.this, asc, nulls_first)
 613
 614    SUPPORTED_JSON_PATH_PARTS = {
 615        exp.JSONPathKey,
 616        exp.JSONPathRoot,
 617        exp.JSONPathSubscript,
 618    }
 619
 620    TYPE_MAPPING = {
 621        **generator.Generator.TYPE_MAPPING,
 622        exp.DType.BIGDECIMAL: "DOUBLE",
 623        exp.DType.JSON: "VARIANT",
 624        exp.DType.NESTED: "OBJECT",
 625        exp.DType.STRUCT: "OBJECT",
 626        exp.DType.TEXT: "VARCHAR",
 627    }
 628
 629    TOKEN_MAPPING = {
 630        TokenType.AUTO_INCREMENT: "AUTOINCREMENT",
 631    }
 632
 633    PROPERTIES_LOCATION = {
 634        **generator.Generator.PROPERTIES_LOCATION,
 635        exp.CredentialsProperty: exp.Properties.Location.POST_WITH,
 636        exp.LocationProperty: exp.Properties.Location.POST_WITH,
 637        exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,
 638        exp.RowAccessProperty: exp.Properties.Location.POST_SCHEMA,
 639        exp.SetProperty: exp.Properties.Location.UNSUPPORTED,
 640        exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
 641    }
 642
 643    UNSUPPORTED_VALUES_EXPRESSIONS: t.ClassVar = {
 644        exp.Map,
 645        exp.StarMap,
 646        exp.Struct,
 647        exp.VarMap,
 648    }
 649
 650    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS = (exp.ArrayAgg,)
 651
 652    def with_properties(self, properties: exp.Properties) -> str:
 653        return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ")
 654
 655    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
 656        if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS):
 657            values_as_table = False
 658
 659        return super().values_sql(expression, values_as_table=values_as_table)
 660
 661    def datatype_sql(self, expression: exp.DataType) -> str:
 662        # Check if this is a FLOAT type nested inside a VECTOR type
 663        # VECTOR only accepts FLOAT (not DOUBLE), INT, and STRING as element types
 664        # https://docs.snowflake.com/en/sql-reference/data-types-vector
 665        if expression.is_type(exp.DType.DOUBLE):
 666            parent = expression.parent
 667            if isinstance(parent, exp.DataType) and parent.is_type(exp.DType.VECTOR):
 668                # Preserve FLOAT for VECTOR types instead of mapping to synonym DOUBLE
 669                return "FLOAT"
 670
 671        expressions = expression.expressions
 672        if expressions and expression.is_type(*exp.DataType.STRUCT_TYPES):
 673            for field_type in expressions:
 674                # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ]
 675                if isinstance(field_type, exp.DataType):
 676                    return "OBJECT"
 677                if (
 678                    isinstance(field_type, exp.ColumnDef)
 679                    and field_type.this
 680                    and field_type.this.is_string
 681                ):
 682                    # Doing OBJECT('foo' VARCHAR) is invalid snowflake Syntax. Moreover, besides
 683                    # converting 'foo' into an identifier, we also need to quote it because these
 684                    # keys are case-sensitive. For example:
 685                    #
 686                    # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:x FROM t -- correct
 687                    # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:X FROM t -- incorrect, returns NULL
 688                    field_type.this.replace(exp.to_identifier(field_type.name, quoted=True))
 689
 690        return super().datatype_sql(expression)
 691
 692    def tonumber_sql(self, expression: exp.ToNumber) -> str:
 693        precision = expression.args.get("precision")
 694        scale = expression.args.get("scale")
 695
 696        default_precision = isinstance(precision, exp.Literal) and precision.name == "38"
 697        default_scale = isinstance(scale, exp.Literal) and scale.name == "0"
 698
 699        if default_precision and default_scale:
 700            precision = None
 701            scale = None
 702        elif default_scale:
 703            scale = None
 704
 705        func_name = "TRY_TO_NUMBER" if expression.args.get("safe") else "TO_NUMBER"
 706
 707        return self.func(
 708            func_name,
 709            expression.this,
 710            expression.args.get("format"),
 711            precision,
 712            scale,
 713        )
 714
 715    def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str:
 716        milli = expression.args.get("milli")
 717        if milli is not None:
 718            milli_to_nano = milli.pop() * exp.Literal.number(1000000)
 719            expression.set("nano", milli_to_nano)
 720
 721        return rename_func("TIMESTAMP_FROM_PARTS")(self, expression)
 722
 723    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
 724        if expression.is_type(exp.DType.GEOGRAPHY):
 725            return self.func("TO_GEOGRAPHY", expression.this)
 726        if expression.is_type(exp.DType.GEOMETRY):
 727            return self.func("TO_GEOMETRY", expression.this)
 728
 729        return super().cast_sql(expression, safe_prefix=safe_prefix)
 730
 731    def trycast_sql(self, expression: exp.TryCast) -> str:
 732        value = expression.this
 733
 734        if value.type is None:
 735            from sqlglot.optimizer.annotate_types import annotate_types
 736
 737            value = annotate_types(value, dialect=self.dialect)
 738
 739        # Snowflake requires that TRY_CAST's value be a string
 740        # If TRY_CAST is being roundtripped (since Snowflake is the only dialect that sets "requires_string") or
 741        # if we can deduce that the value is a string, then we can generate TRY_CAST
 742        if expression.args.get("requires_string") or value.is_type(*exp.DataType.TEXT_TYPES):
 743            return super().trycast_sql(expression)
 744
 745        return self.cast_sql(expression)
 746
 747    def log_sql(self, expression: exp.Log) -> str:
 748        if not expression.expression:
 749            return self.func("LN", expression.this)
 750
 751        return super().log_sql(expression)
 752
 753    def greatest_sql(self, expression: exp.Greatest) -> str:
 754        name = "GREATEST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "GREATEST"
 755        return self.func(name, expression.this, *expression.expressions)
 756
 757    def least_sql(self, expression: exp.Least) -> str:
 758        name = "LEAST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "LEAST"
 759        return self.func(name, expression.this, *expression.expressions)
 760
 761    def generator_sql(self, expression: exp.Generator) -> str:
 762        args = []
 763        rowcount = expression.args.get("rowcount")
 764        timelimit = expression.args.get("timelimit")
 765
 766        if rowcount:
 767            args.append(exp.Kwarg(this=exp.var("ROWCOUNT"), expression=rowcount))
 768        if timelimit:
 769            args.append(exp.Kwarg(this=exp.var("TIMELIMIT"), expression=timelimit))
 770
 771        return self.func("GENERATOR", *args)
 772
 773    def unnest_sql(self, expression: exp.Unnest) -> str:
 774        unnest_alias = expression.args.get("alias")
 775        offset = expression.args.get("offset")
 776
 777        unnest_alias_columns = unnest_alias.columns if unnest_alias else []
 778        value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value")
 779
 780        columns = [
 781            exp.to_identifier("seq"),
 782            exp.to_identifier("key"),
 783            exp.to_identifier("path"),
 784            offset.pop() if isinstance(offset, exp.Expr) else exp.to_identifier("index"),
 785            value,
 786            exp.to_identifier("this"),
 787        ]
 788
 789        if unnest_alias:
 790            unnest_alias.set("columns", columns)
 791        else:
 792            unnest_alias = exp.TableAlias(this="_u", columns=columns)
 793
 794        table_input = self.sql(expression.expressions[0])
 795        if not table_input.startswith("INPUT =>"):
 796            table_input = f"INPUT => {table_input}"
 797
 798        expression_parent = expression.parent
 799
 800        explode = (
 801            f"FLATTEN({table_input})"
 802            if isinstance(expression_parent, exp.Lateral)
 803            else f"TABLE(FLATTEN({table_input}))"
 804        )
 805        alias = self.sql(unnest_alias)
 806        alias = f" AS {alias}" if alias else ""
 807        value = (
 808            ""
 809            if isinstance(expression_parent, (exp.From, exp.Join, exp.Lateral))
 810            else f"{value} FROM "
 811        )
 812
 813        return f"{value}{explode}{alias}"
 814
 815    def undrop_sql(self, expression: exp.Undrop) -> str:
 816        this = self.sql(expression, "this")
 817        kind = expression.kind
 818        rename = self.sql(expression, "rename")
 819        rename = f" RENAME TO {rename}" if rename else ""
 820        return f"UNDROP {kind} {this}{rename}"
 821
 822    def show_sql(self, expression: exp.Show) -> str:
 823        terse = "TERSE " if expression.args.get("terse") else ""
 824        iceberg = "ICEBERG " if expression.args.get("iceberg") else ""
 825        history = " HISTORY" if expression.args.get("history") else ""
 826        like = self.sql(expression, "like")
 827        like = f" LIKE {like}" if like else ""
 828
 829        scope = self.sql(expression, "scope")
 830        scope = f" {scope}" if scope else ""
 831
 832        scope_kind = self.sql(expression, "scope_kind")
 833        if scope_kind:
 834            scope_kind = f" IN {scope_kind}"
 835
 836        starts_with = self.sql(expression, "starts_with")
 837        if starts_with:
 838            starts_with = f" STARTS WITH {starts_with}"
 839
 840        limit = self.sql(expression, "limit")
 841
 842        from_ = self.sql(expression, "from_")
 843        if from_:
 844            from_ = f" FROM {from_}"
 845
 846        privileges = self.expressions(expression, key="privileges", flat=True)
 847        privileges = f" WITH PRIVILEGES {privileges}" if privileges else ""
 848
 849        return f"SHOW {terse}{iceberg}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}"
 850
 851    def rowaccessproperty_sql(self, expression: exp.RowAccessProperty) -> str:
 852        if not expression.this:
 853            return "ROW ACCESS"
 854        on = f" ON ({self.expressions(expression, flat=True)})" if expression.expressions else ""
 855        return f"WITH ROW ACCESS POLICY {self.sql(expression, 'this')}{on}"
 856
 857    def describe_sql(self, expression: exp.Describe) -> str:
 858        kind_value = expression.args.get("kind") or "TABLE"
 859
 860        properties = expression.args.get("properties")
 861        if properties:
 862            qualifier = self.expressions(properties, sep=" ")
 863            kind = f" {qualifier} {kind_value}"
 864        else:
 865            kind = f" {kind_value}"
 866
 867        this = f" {self.sql(expression, 'this')}"
 868        expressions = self.expressions(expression, flat=True)
 869        expressions = f" {expressions}" if expressions else ""
 870        return f"DESCRIBE{kind}{this}{expressions}"
 871
 872    def generatedasidentitycolumnconstraint_sql(
 873        self, expression: exp.GeneratedAsIdentityColumnConstraint
 874    ) -> str:
 875        start = expression.args.get("start")
 876        start = f" START {start}" if start else ""
 877        increment = expression.args.get("increment")
 878        increment = f" INCREMENT {increment}" if increment else ""
 879
 880        order = expression.args.get("order")
 881        if order is not None:
 882            order_clause = " ORDER" if order else " NOORDER"
 883        else:
 884            order_clause = ""
 885
 886        return f"AUTOINCREMENT{start}{increment}{order_clause}"
 887
 888    def struct_sql(self, expression: exp.Struct) -> str:
 889        if len(expression.expressions) == 1:
 890            arg = expression.expressions[0]
 891            if arg.is_star or (isinstance(arg, exp.ILike) and arg.left.is_star):
 892                # Wildcard syntax: https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object
 893                return f"{{{self.sql(expression.expressions[0])}}}"
 894
 895        keys = []
 896        values = []
 897
 898        for i, e in enumerate(expression.expressions):
 899            if isinstance(e, exp.PropertyEQ):
 900                keys.append(
 901                    exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this
 902                )
 903                values.append(e.expression)
 904            else:
 905                keys.append(exp.Literal.string(f"_{i}"))
 906                values.append(e)
 907
 908        return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values)))
 909
 910    @unsupported_args("weight", "accuracy")
 911    def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str:
 912        return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile"))
 913
 914    def alterset_sql(self, expression: exp.AlterSet) -> str:
 915        exprs = self.expressions(expression, flat=True)
 916        exprs = f" {exprs}" if exprs else ""
 917        file_format = self.expressions(expression, key="file_format", flat=True, sep=" ")
 918        file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else ""
 919        copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ")
 920        copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else ""
 921        tag = self.expressions(expression, key="tag", flat=True)
 922        tag = f" TAG {tag}" if tag else ""
 923
 924        return f"SET{exprs}{file_format}{copy_options}{tag}"
 925
 926    def strtotime_sql(self, expression: exp.StrToTime):
 927        # target_type is stored as a DataType instance
 928        target_type = expression.args.get("target_type")
 929
 930        # Get the type enum from DataType instance or from type annotation
 931        if isinstance(target_type, exp.DataType):
 932            type_enum = target_type.this
 933        elif expression.type:
 934            type_enum = expression.type.this
 935        else:
 936            type_enum = exp.DType.TIMESTAMP
 937
 938        func_name = TIMESTAMP_TYPES.get(type_enum, "TO_TIMESTAMP")
 939
 940        return self.func(
 941            f"{'TRY_' if expression.args.get('safe') else ''}{func_name}",
 942            expression.this,
 943            self.format_time(expression),
 944        )
 945
 946    def timestampsub_sql(self, expression: exp.TimestampSub):
 947        return self.sql(
 948            exp.TimestampAdd(
 949                this=expression.this,
 950                expression=expression.expression * -1,
 951                unit=expression.unit,
 952            )
 953        )
 954
 955    def jsonextract_sql(self, expression: exp.JSONExtract):
 956        this = expression.this
 957
 958        # JSON strings are valid coming from other dialects such as BQ so
 959        # for these cases we PARSE_JSON preemptively
 960        if not isinstance(this, (exp.ParseJSON, exp.JSONExtract)) and not expression.args.get(
 961            "requires_json"
 962        ):
 963            this = exp.ParseJSON(this=this)
 964
 965        return self.func(
 966            "GET_PATH",
 967            this,
 968            expression.expression,
 969        )
 970
 971    def timetostr_sql(self, expression: exp.TimeToStr) -> str:
 972        this = expression.this
 973        if this.is_string:
 974            this = exp.cast(this, exp.DType.TIMESTAMP)
 975
 976        return self.func("TO_CHAR", this, self.format_time(expression))
 977
 978    def datesub_sql(self, expression: exp.DateSub) -> str:
 979        value = expression.expression
 980        if value:
 981            value.replace(value * (-1))
 982        else:
 983            self.unsupported("DateSub cannot be transpiled if the subtracted count is unknown")
 984
 985        return date_delta_sql("DATEADD")(self, expression)
 986
 987    def select_sql(self, expression: exp.Select) -> str:
 988        limit = expression.args.get("limit")
 989        offset = expression.args.get("offset")
 990        if offset and not limit:
 991            expression.limit(exp.Null(), copy=False)
 992        return super().select_sql(expression)
 993
 994    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
 995        is_materialized = expression.find(exp.MaterializedProperty)
 996        copy_grants_property = expression.find(exp.CopyGrantsProperty)
 997
 998        if expression.kind == "VIEW" and is_materialized and copy_grants_property:
 999            # For materialized views, COPY GRANTS is located *before* the columns list
1000            # This is in contrast to normal views where COPY GRANTS is located *after* the columns list
1001            # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected
1002            # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax
1003            # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax
1004            post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA]
1005            post_schema_properties.pop(post_schema_properties.index(copy_grants_property))
1006
1007            this_name = self.sql(expression.this, "this")
1008            copy_grants = self.sql(copy_grants_property)
1009            this_schema = self.schema_columns_sql(expression.this)
1010            this_schema = f"{self.sep()}{this_schema}" if this_schema else ""
1011
1012            return f"{this_name}{self.sep()}{copy_grants}{this_schema}"
1013
1014        return super().createable_sql(expression, locations)
1015
1016    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
1017        this = expression.this
1018
1019        # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG
1020        # and add it later as part of the WITHIN GROUP clause
1021        order = this if isinstance(this, exp.Order) else None
1022        if order:
1023            expression.set("this", order.this.pop())
1024
1025        expr_sql = super().arrayagg_sql(expression)
1026
1027        if order:
1028            expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order))
1029
1030        return expr_sql
1031
1032    def arraydistinct_sql(self, expression: exp.ArrayDistinct) -> str:
1033        if expression.args.get("check_null"):
1034            return self.func("ARRAY_DISTINCT", expression.this)
1035        return self.func("ARRAY_DISTINCT", exp.ArrayCompact(this=expression.this))
1036
1037    def arraytostring_sql(self, expression: exp.ArrayToString) -> str:
1038        return self.func("ARRAY_TO_STRING", expression.this, expression.expression)
1039
1040    def array_sql(self, expression: exp.Array) -> str:
1041        expressions = expression.expressions
1042
1043        first_expr = seq_get(expressions, 0)
1044        if isinstance(first_expr, exp.Select):
1045            # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo))
1046            if first_expr.text("kind").upper() == "STRUCT":
1047                object_construct_args = []
1048                for expr in first_expr.expressions:
1049                    # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo)
1050                    # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo)
1051                    name = expr.this if isinstance(expr, exp.Alias) else expr
1052
1053                    object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name])
1054
1055                array_agg = exp.ArrayAgg(this=build_object_construct(args=object_construct_args))
1056
1057                first_expr.set("kind", None)
1058                first_expr.set("expressions", [array_agg])
1059
1060                return self.sql(first_expr.subquery())
1061
1062        return inline_array_sql(self, expression)
1063
1064    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
1065        zone = self.sql(expression, "this")
1066        if not zone:
1067            return super().currentdate_sql(expression)
1068
1069        expr = exp.Cast(
1070            this=exp.ConvertTimezone(target_tz=zone, timestamp=exp.CurrentTimestamp()),
1071            to=exp.DataType(this=exp.DType.DATE),
1072        )
1073        return self.sql(expr)
1074
1075    def dot_sql(self, expression: exp.Dot) -> str:
1076        this = expression.this
1077
1078        if not this.type:
1079            from sqlglot.optimizer.annotate_types import annotate_types
1080
1081            this = annotate_types(this, dialect=self.dialect)
1082
1083        if not isinstance(this, exp.Dot) and this.is_type(exp.DType.STRUCT):
1084            # Generate colon notation for the top level STRUCT
1085            return f"{self.sql(this)}:{self.sql(expression, 'expression')}"
1086
1087        return super().dot_sql(expression)
1088
1089    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
1090        return f"{self.sql(expression, 'this')}!{self.sql(expression, 'expression')}"
1091
1092    def format_sql(self, expression: exp.Format) -> str:
1093        if expression.name.lower() == "%s" and len(expression.expressions) == 1:
1094            return self.func("TO_CHAR", expression.expressions[0])
1095
1096        return self.function_fallback_sql(expression)
1097
1098    def splitpart_sql(self, expression: exp.SplitPart) -> str:
1099        # Set part_index to 1 if missing
1100        if not expression.args.get("delimiter"):
1101            expression.set("delimiter", exp.Literal.string(" "))
1102
1103        if not expression.args.get("part_index"):
1104            expression.set("part_index", exp.Literal.number(1))
1105
1106        return rename_func("SPLIT_PART")(self, expression)
1107
1108    def uniform_sql(self, expression: exp.Uniform) -> str:
1109        gen = expression.args.get("gen")
1110        seed = expression.args.get("seed")
1111
1112        # From Databricks UNIFORM(min, max, seed) -> Wrap gen in RANDOM(seed)
1113        if seed:
1114            gen = exp.Rand(this=seed)
1115
1116        # No gen argument (from Databricks 2-arg UNIFORM(min, max)) -> Add RANDOM()
1117        if not gen:
1118            gen = exp.Rand()
1119
1120        return self.func("UNIFORM", expression.this, expression.expression, gen)
1121
1122    def window_sql(self, expression: exp.Window) -> str:
1123        spec = expression.args.get("spec")
1124        this = expression.this
1125
1126        if (
1127            (
1128                isinstance(this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME)
1129                or (
1130                    isinstance(this, (exp.RespectNulls, exp.IgnoreNulls))
1131                    and isinstance(this.this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME)
1132                )
1133            )
1134            and spec
1135            and (
1136                spec.text("kind").upper() == "ROWS"
1137                and spec.text("start").upper() == "UNBOUNDED"
1138                and spec.text("start_side").upper() == "PRECEDING"
1139                and spec.text("end").upper() == "UNBOUNDED"
1140                and spec.text("end_side").upper() == "FOLLOWING"
1141            )
1142        ):
1143            # omit the default window from window ranking functions
1144            expression.set("spec", None)
1145        return super().window_sql(expression)
1146
1147    def filter_sql(self, expression: exp.Filter) -> str:
1148        # Snowflake doesn't support FILTER (WHERE cond), so we rewrite it into an
1149        # equivalent conditional aggregation, i.e. wrap the input values in an IFF
1150        agg = expression.this
1151        agg_arg = seq_get(agg.expressions, 0) if isinstance(agg, exp.Anonymous) else agg.this
1152        cond = expression.expression.this
1153
1154        if isinstance(agg, exp.WithinGroup):
1155            # Ordered-set aggregates take their input from the ORDER BY key, so the
1156            # condition has to wrap that instead of the aggregate's own argument
1157            if isinstance(agg_arg, (exp.Mode, *exp.PERCENTILES)):
1158                for ordered in agg.expression.expressions:
1159                    key = ordered.this
1160                    key.replace(exp.If(this=cond.copy(), true=key.copy()))
1161
1162                return self.sql(agg)
1163
1164            # Besides the percentile functions, these are the only functions Snowflake
1165            # accepts WITHIN GROUP for, so anything else can't be rewritten correctly
1166            if isinstance(agg_arg, (exp.ArrayAgg, exp.GroupConcat)):
1167                agg_arg = agg_arg.this
1168            else:
1169                self.unsupported("Unable to rewrite FILTER into the aggregate's arguments")
1170                return self.sql(agg)
1171
1172        # `COUNT(*/t.*) FILTER (WHERE cond)` counts qualifying rows, but a star can't be an IFF
1173        # argument: `IFF(cond, *, NULL)` expands to multiple columns once the table has 2+ of
1174        # them, which Snowflake rejects. Use its native COUNT_IF instead.
1175        if isinstance(agg, exp.Count) and isinstance(agg_arg, exp.Expression) and agg_arg.is_star:
1176            return self.func("COUNT_IF", cond)
1177
1178        # `DISTINCT` and `ORDER BY` are part of the aggregate's own argument list, so the
1179        # condition has to wrap the values underneath them rather than the whole clause --
1180        # `IFF(cond, DISTINCT x, NULL)` is not a call any dialect accepts.
1181        if isinstance(agg_arg, exp.Order):
1182            agg_arg = agg_arg.this
1183
1184        if isinstance(agg_arg, exp.Distinct):
1185            targets = agg_arg.expressions
1186        else:
1187            targets = [agg_arg]
1188
1189        for target in targets:
1190            target.replace(exp.If(this=cond.copy(), true=target.copy()))
1191
1192        return self.sql(agg)
1193
1194    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
1195        # Snowflake's MODE doesn't support the ordered-set syntax, i.e. it only
1196        # accepts the value to aggregate as an argument: MODE(<expr>)
1197        if isinstance(expression.this, exp.Mode) and not expression.this.this:
1198            order = expression.expression
1199            if isinstance(order, exp.Order) and len(order.expressions) == 1:
1200                return self.sql(exp.Mode(this=order.expressions[0].this))
1201
1202        return super().withingroup_sql(expression)
class SnowflakeGenerator(sqlglot.generator.Generator):
 358class SnowflakeGenerator(generator.Generator):
 359    SELECT_KINDS: tuple[str, ...] = ()
 360    PARAMETER_TOKEN = "$"
 361    MATCHED_BY_SOURCE = False
 362    SINGLE_STRING_INTERVAL = True
 363    JOIN_HINTS = False
 364    TABLE_HINTS = False
 365    QUERY_HINTS = False
 366    SUPPORTS_TABLE_COPY = False
 367    COLLATE_IS_FUNC = True
 368    LIMIT_ONLY_LITERALS = True
 369    JSON_KEY_VALUE_PAIR_SEP = ","
 370    INSERT_OVERWRITE = " OVERWRITE INTO"
 371    STRUCT_DELIMITER = ("(", ")")
 372    COPY_PARAMS_ARE_WRAPPED = False
 373    COPY_PARAMS_EQ_REQUIRED = True
 374    STAR_EXCEPT = "EXCLUDE"
 375    SUPPORTS_EXPLODING_PROJECTIONS = False
 376    ARRAY_CONCAT_IS_VAR_LEN = False
 377    SUPPORTS_CONVERT_TIMEZONE = True
 378    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
 379    SUPPORTS_MEDIAN = True
 380    ARRAY_SIZE_NAME = "ARRAY_SIZE"
 381    SUPPORTS_DECODE_CASE = True
 382
 383    AFTER_HAVING_MODIFIER_TRANSFORMS = generator.AFTER_HAVING_MODIFIER_TRANSFORMS
 384
 385    IS_BOOL_ALLOWED = False
 386    DIRECTED_JOINS = True
 387    SUPPORTS_UESCAPE = False
 388    TRY_SUPPORTED = False
 389
 390    TRANSFORMS = {
 391        **generator.Generator.TRANSFORMS,
 392        exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"),
 393        exp.ArgMax: rename_func("MAX_BY"),
 394        exp.ArgMin: rename_func("MIN_BY"),
 395        exp.Array: transforms.preprocess([transforms.inherit_struct_field_names]),
 396        exp.ArrayConcat: array_concat_sql("ARRAY_CAT"),
 397        exp.ArrayAppend: array_append_sql("ARRAY_APPEND"),
 398        exp.ArrayPrepend: array_append_sql("ARRAY_PREPEND"),
 399        exp.ArrayContains: lambda self, e: self.func(
 400            "ARRAY_CONTAINS",
 401            e.expression
 402            if e.args.get("ensure_variant") is False
 403            else exp.cast(e.expression, exp.DType.VARIANT, copy=False),
 404            e.this,
 405        ),
 406        exp.ArrayPosition: lambda self, e: self.func(
 407            "ARRAY_POSITION",
 408            e.expression,
 409            e.this,
 410        ),
 411        exp.ArrayIntersect: rename_func("ARRAY_INTERSECTION"),
 412        exp.ArrayOverlaps: rename_func("ARRAYS_OVERLAP"),
 413        exp.AtTimeZone: lambda self, e: self.func("CONVERT_TIMEZONE", e.args.get("zone"), e.this),
 414        exp.BitwiseOr: rename_func("BITOR"),
 415        exp.BitwiseXor: rename_func("BITXOR"),
 416        exp.BitwiseAnd: rename_func("BITAND"),
 417        exp.BitwiseAndAgg: rename_func("BITANDAGG"),
 418        exp.BitwiseOrAgg: rename_func("BITORAGG"),
 419        exp.BitwiseXorAgg: rename_func("BITXORAGG"),
 420        exp.BitwiseNot: rename_func("BITNOT"),
 421        exp.BitwiseLeftShift: rename_func("BITSHIFTLEFT"),
 422        exp.BitwiseRightShift: rename_func("BITSHIFTRIGHT"),
 423        exp.CurrentTimestamp: lambda self, e: (
 424            self.func("SYSDATE") if e.args.get("sysdate") else self.function_fallback_sql(e)
 425        ),
 426        exp.CurrentSchemas: lambda self, e: self.func("CURRENT_SCHEMAS"),
 427        exp.Localtime: lambda self, e: (
 428            self.func("CURRENT_TIME", e.this) if e.this else "CURRENT_TIME"
 429        ),
 430        exp.Localtimestamp: lambda self, e: (
 431            self.func("CURRENT_TIMESTAMP", e.this) if e.this else "CURRENT_TIMESTAMP"
 432        ),
 433        exp.DateAdd: date_delta_sql("DATEADD"),
 434        exp.DateDiff: date_delta_sql("DATEDIFF"),
 435        exp.DatetimeAdd: date_delta_sql("TIMESTAMPADD"),
 436        exp.DatetimeDiff: timestampdiff_sql,
 437        exp.DateStrToDate: datestrtodate_sql,
 438        exp.Decrypt: lambda self, e: self.func(
 439            f"{'TRY_' if e.args.get('safe') else ''}DECRYPT",
 440            e.this,
 441            e.args.get("passphrase"),
 442            e.args.get("aad"),
 443            e.args.get("encryption_method"),
 444        ),
 445        exp.DecryptRaw: lambda self, e: self.func(
 446            f"{'TRY_' if e.args.get('safe') else ''}DECRYPT_RAW",
 447            e.this,
 448            e.args.get("key"),
 449            e.args.get("iv"),
 450            e.args.get("aad"),
 451            e.args.get("encryption_method"),
 452            e.args.get("aead"),
 453        ),
 454        exp.DayOfMonth: rename_func("DAYOFMONTH"),
 455        exp.DayOfWeek: rename_func("DAYOFWEEK"),
 456        exp.DayOfWeekIso: rename_func("DAYOFWEEKISO"),
 457        exp.DayOfYear: rename_func("DAYOFYEAR"),
 458        exp.DotProduct: rename_func("VECTOR_INNER_PRODUCT"),
 459        exp.Explode: rename_func("FLATTEN"),
 460        exp.Extract: lambda self, e: self.func(
 461            "DATE_PART", map_date_part(e.this, self.dialect), e.expression
 462        ),
 463        exp.CosineDistance: rename_func("VECTOR_COSINE_SIMILARITY"),
 464        exp.EuclideanDistance: rename_func("VECTOR_L2_DISTANCE"),
 465        exp.HandlerProperty: lambda self, e: f"HANDLER = {self.sql(e, 'this')}",
 466        exp.FileFormatProperty: lambda self, e: (
 467            f"FILE_FORMAT=({self.expressions(e, 'expressions', sep=' ')})"
 468        ),
 469        exp.FromTimeZone: lambda self, e: self.func(
 470            "CONVERT_TIMEZONE", e.args.get("zone"), "'UTC'", e.this
 471        ),
 472        exp.GenerateSeries: lambda self, e: self.func(
 473            "ARRAY_GENERATE_RANGE",
 474            e.args["start"],
 475            e.args["end"] if e.args.get("is_end_exclusive") else e.args["end"] + 1,
 476            e.args.get("step"),
 477        ),
 478        exp.GetExtract: rename_func("GET"),
 479        exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, sep=""),
 480        exp.If: if_sql(name="IFF", false_value="NULL"),
 481        exp.JSONArray: lambda self, e: self.func(
 482            "TO_VARIANT", self.func("ARRAY_CONSTRUCT", *e.expressions)
 483        ),
 484        exp.JSONExtractArray: _json_extract_value_array_sql,
 485        exp.JSONExtractScalar: lambda self, e: self.func(
 486            "JSON_EXTRACT_PATH_TEXT", e.this, e.expression
 487        ),
 488        exp.JSONKeys: rename_func("OBJECT_KEYS"),
 489        exp.JSONObject: lambda self, e: self.func("OBJECT_CONSTRUCT_KEEP_NULL", *e.expressions),
 490        exp.JSONPathRoot: lambda *_: "",
 491        exp.JSONValueArray: _json_extract_value_array_sql,
 492        exp.Levenshtein: unsupported_args("ins_cost", "del_cost", "sub_cost")(
 493            rename_func("EDITDISTANCE")
 494        ),
 495        exp.LocationProperty: lambda self, e: f"LOCATION={self.sql(e, 'this')}",
 496        exp.LogicalAnd: rename_func("BOOLAND_AGG"),
 497        exp.LogicalOr: rename_func("BOOLOR_AGG"),
 498        exp.Map: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"),
 499        exp.ManhattanDistance: rename_func("VECTOR_L1_DISTANCE"),
 500        exp.MakeInterval: no_make_interval_sql,
 501        exp.Max: max_or_greatest,
 502        exp.Min: min_or_least,
 503        exp.NthValue: nth_value_from_sql,
 504        exp.ParseJSON: lambda self, e: self.func(
 505            f"{'TRY_' if e.args.get('safe') else ''}PARSE_JSON", e.this
 506        ),
 507        exp.ToBinary: lambda self, e: self.func(
 508            f"{'TRY_' if e.args.get('safe') else ''}TO_BINARY", e.this, e.args.get("format")
 509        ),
 510        exp.ToBoolean: lambda self, e: self.func(
 511            f"{'TRY_' if e.args.get('safe') else ''}TO_BOOLEAN", e.this
 512        ),
 513        exp.ToDouble: lambda self, e: self.func(
 514            f"{'TRY_' if e.args.get('safe') else ''}TO_DOUBLE", e.this, e.args.get("format")
 515        ),
 516        exp.ToFile: lambda self, e: self.func(
 517            f"{'TRY_' if e.args.get('safe') else ''}TO_FILE", e.this, e.args.get("path")
 518        ),
 519        exp.JSONFormat: rename_func("TO_JSON"),
 520        exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}",
 521        exp.PercentileCont: transforms.preprocess([transforms.add_within_group_for_percentiles]),
 522        exp.PercentileDisc: transforms.preprocess([transforms.add_within_group_for_percentiles]),
 523        exp.Pivot: transforms.preprocess([_unqualify_pivot_columns]),
 524        exp.RegexpExtract: _regexpextract_sql,
 525        exp.RegexpExtractAll: _regexpextract_sql,
 526        exp.RegexpILike: _regexpilike_sql,
 527        exp.RowAccessProperty: lambda self, e: self.rowaccessproperty_sql(e),
 528        exp.Select: transforms.preprocess(
 529            [
 530                transforms.eliminate_window_clause,
 531                transforms.eliminate_distinct_on,
 532                transforms.explode_projection_to_unnest(),
 533                transforms.eliminate_semi_and_anti_joins,
 534                _transform_generate_date_array,
 535                _qualify_unnested_columns,
 536                _eliminate_dot_variant_lookup,
 537            ]
 538        ),
 539        exp.SHA: rename_func("SHA1"),
 540        exp.SHA1Digest: rename_func("SHA1_BINARY"),
 541        exp.MD5Digest: rename_func("MD5_BINARY"),
 542        exp.MD5NumberLower64: rename_func("MD5_NUMBER_LOWER64"),
 543        exp.MD5NumberUpper64: rename_func("MD5_NUMBER_UPPER64"),
 544        exp.Hex: rename_func("HEX_ENCODE"),
 545        exp.LowerHex: rename_func("TO_CHAR"),
 546        exp.Skewness: rename_func("SKEW"),
 547        exp.StarMap: rename_func("OBJECT_CONSTRUCT"),
 548        exp.StartsWith: rename_func("STARTSWITH"),
 549        exp.EndsWith: rename_func("ENDSWITH"),
 550        exp.Rand: lambda self, e: self.func("RANDOM", e.this),
 551        exp.StrPosition: lambda self, e: strposition_sql(
 552            self, e, func_name="CHARINDEX", supports_position=True
 553        ),
 554        exp.StrToDate: lambda self, e: self.func("DATE", e.this, self.format_time(e)),
 555        exp.StringToArray: rename_func("STRTOK_TO_ARRAY"),
 556        exp.StrtokToArray: rename_func("STRTOK_TO_ARRAY"),
 557        exp.Stuff: rename_func("INSERT"),
 558        exp.StPoint: rename_func("ST_MAKEPOINT"),
 559        exp.TimeAdd: date_delta_sql("TIMEADD"),
 560        exp.TimeSlice: lambda self, e: self.func(
 561            "TIME_SLICE",
 562            e.this,
 563            e.expression,
 564            unit_to_str(e),
 565            e.args.get("kind"),
 566        ),
 567        exp.Timestamp: no_timestamp_sql,
 568        exp.TimestampAdd: date_delta_sql("TIMESTAMPADD"),
 569        exp.TimestampDiff: lambda self, e: self.func("TIMESTAMPDIFF", e.unit, e.expression, e.this),
 570        exp.TimestampTrunc: timestamptrunc_sql(),
 571        exp.TimeStrToTime: timestrtotime_sql,
 572        exp.TimeToUnix: lambda self, e: f"EXTRACT(epoch_second FROM {self.sql(e, 'this')})",
 573        exp.ToArray: rename_func("TO_ARRAY"),
 574        exp.ToChar: lambda self, e: self.function_fallback_sql(e),
 575        exp.TsOrDsAdd: date_delta_sql("DATEADD", cast=True),
 576        exp.TsOrDsDiff: date_delta_sql("DATEDIFF"),
 577        exp.TsOrDsToDate: lambda self, e: self.func(
 578            f"{'TRY_' if e.args.get('safe') else ''}TO_DATE", e.this, self.format_time(e)
 579        ),
 580        exp.TsOrDsToTime: lambda self, e: self.func(
 581            f"{'TRY_' if e.args.get('safe') else ''}TO_TIME", e.this, self.format_time(e)
 582        ),
 583        exp.Unhex: rename_func("HEX_DECODE_BINARY"),
 584        exp.UnixToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, e.args.get("scale")),
 585        exp.Uuid: rename_func("UUID_STRING"),
 586        exp.VarMap: lambda self, e: var_map_sql(self, e, "OBJECT_CONSTRUCT"),
 587        exp.Booland: rename_func("BOOLAND"),
 588        exp.Boolor: rename_func("BOOLOR"),
 589        exp.WeekOfYear: rename_func("WEEKISO"),
 590        exp.YearOfWeek: rename_func("YEAROFWEEK"),
 591        exp.YearOfWeekIso: rename_func("YEAROFWEEKISO"),
 592        exp.Xor: rename_func("BOOLXOR"),
 593        exp.ByteLength: rename_func("OCTET_LENGTH"),
 594        exp.Flatten: rename_func("ARRAY_FLATTEN"),
 595        exp.ArrayConcatAgg: lambda self, e: self.func("ARRAY_FLATTEN", exp.ArrayAgg(this=e.this)),
 596        exp.SHA2Digest: lambda self, e: self.func(
 597            "SHA2_BINARY", e.this, e.args.get("length") or exp.Literal.number(256)
 598        ),
 599    }
 600
 601    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
 602        this = self.func("IDENTIFIER", expression.this)
 603        if "expressions" in expression.args:
 604            # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
 605            return self.func(this, *expression.expressions, normalize=False)
 606        return this
 607
 608    def sortarray_sql(self, expression: exp.SortArray) -> str:
 609        asc = expression.args.get("asc")
 610        nulls_first = expression.args.get("nulls_first")
 611        if asc == exp.false() and nulls_first == exp.true():
 612            nulls_first = None
 613        return self.func("ARRAY_SORT", expression.this, asc, nulls_first)
 614
 615    SUPPORTED_JSON_PATH_PARTS = {
 616        exp.JSONPathKey,
 617        exp.JSONPathRoot,
 618        exp.JSONPathSubscript,
 619    }
 620
 621    TYPE_MAPPING = {
 622        **generator.Generator.TYPE_MAPPING,
 623        exp.DType.BIGDECIMAL: "DOUBLE",
 624        exp.DType.JSON: "VARIANT",
 625        exp.DType.NESTED: "OBJECT",
 626        exp.DType.STRUCT: "OBJECT",
 627        exp.DType.TEXT: "VARCHAR",
 628    }
 629
 630    TOKEN_MAPPING = {
 631        TokenType.AUTO_INCREMENT: "AUTOINCREMENT",
 632    }
 633
 634    PROPERTIES_LOCATION = {
 635        **generator.Generator.PROPERTIES_LOCATION,
 636        exp.CredentialsProperty: exp.Properties.Location.POST_WITH,
 637        exp.LocationProperty: exp.Properties.Location.POST_WITH,
 638        exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,
 639        exp.RowAccessProperty: exp.Properties.Location.POST_SCHEMA,
 640        exp.SetProperty: exp.Properties.Location.UNSUPPORTED,
 641        exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
 642    }
 643
 644    UNSUPPORTED_VALUES_EXPRESSIONS: t.ClassVar = {
 645        exp.Map,
 646        exp.StarMap,
 647        exp.Struct,
 648        exp.VarMap,
 649    }
 650
 651    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS = (exp.ArrayAgg,)
 652
 653    def with_properties(self, properties: exp.Properties) -> str:
 654        return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ")
 655
 656    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
 657        if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS):
 658            values_as_table = False
 659
 660        return super().values_sql(expression, values_as_table=values_as_table)
 661
 662    def datatype_sql(self, expression: exp.DataType) -> str:
 663        # Check if this is a FLOAT type nested inside a VECTOR type
 664        # VECTOR only accepts FLOAT (not DOUBLE), INT, and STRING as element types
 665        # https://docs.snowflake.com/en/sql-reference/data-types-vector
 666        if expression.is_type(exp.DType.DOUBLE):
 667            parent = expression.parent
 668            if isinstance(parent, exp.DataType) and parent.is_type(exp.DType.VECTOR):
 669                # Preserve FLOAT for VECTOR types instead of mapping to synonym DOUBLE
 670                return "FLOAT"
 671
 672        expressions = expression.expressions
 673        if expressions and expression.is_type(*exp.DataType.STRUCT_TYPES):
 674            for field_type in expressions:
 675                # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ]
 676                if isinstance(field_type, exp.DataType):
 677                    return "OBJECT"
 678                if (
 679                    isinstance(field_type, exp.ColumnDef)
 680                    and field_type.this
 681                    and field_type.this.is_string
 682                ):
 683                    # Doing OBJECT('foo' VARCHAR) is invalid snowflake Syntax. Moreover, besides
 684                    # converting 'foo' into an identifier, we also need to quote it because these
 685                    # keys are case-sensitive. For example:
 686                    #
 687                    # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:x FROM t -- correct
 688                    # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:X FROM t -- incorrect, returns NULL
 689                    field_type.this.replace(exp.to_identifier(field_type.name, quoted=True))
 690
 691        return super().datatype_sql(expression)
 692
 693    def tonumber_sql(self, expression: exp.ToNumber) -> str:
 694        precision = expression.args.get("precision")
 695        scale = expression.args.get("scale")
 696
 697        default_precision = isinstance(precision, exp.Literal) and precision.name == "38"
 698        default_scale = isinstance(scale, exp.Literal) and scale.name == "0"
 699
 700        if default_precision and default_scale:
 701            precision = None
 702            scale = None
 703        elif default_scale:
 704            scale = None
 705
 706        func_name = "TRY_TO_NUMBER" if expression.args.get("safe") else "TO_NUMBER"
 707
 708        return self.func(
 709            func_name,
 710            expression.this,
 711            expression.args.get("format"),
 712            precision,
 713            scale,
 714        )
 715
 716    def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str:
 717        milli = expression.args.get("milli")
 718        if milli is not None:
 719            milli_to_nano = milli.pop() * exp.Literal.number(1000000)
 720            expression.set("nano", milli_to_nano)
 721
 722        return rename_func("TIMESTAMP_FROM_PARTS")(self, expression)
 723
 724    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
 725        if expression.is_type(exp.DType.GEOGRAPHY):
 726            return self.func("TO_GEOGRAPHY", expression.this)
 727        if expression.is_type(exp.DType.GEOMETRY):
 728            return self.func("TO_GEOMETRY", expression.this)
 729
 730        return super().cast_sql(expression, safe_prefix=safe_prefix)
 731
 732    def trycast_sql(self, expression: exp.TryCast) -> str:
 733        value = expression.this
 734
 735        if value.type is None:
 736            from sqlglot.optimizer.annotate_types import annotate_types
 737
 738            value = annotate_types(value, dialect=self.dialect)
 739
 740        # Snowflake requires that TRY_CAST's value be a string
 741        # If TRY_CAST is being roundtripped (since Snowflake is the only dialect that sets "requires_string") or
 742        # if we can deduce that the value is a string, then we can generate TRY_CAST
 743        if expression.args.get("requires_string") or value.is_type(*exp.DataType.TEXT_TYPES):
 744            return super().trycast_sql(expression)
 745
 746        return self.cast_sql(expression)
 747
 748    def log_sql(self, expression: exp.Log) -> str:
 749        if not expression.expression:
 750            return self.func("LN", expression.this)
 751
 752        return super().log_sql(expression)
 753
 754    def greatest_sql(self, expression: exp.Greatest) -> str:
 755        name = "GREATEST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "GREATEST"
 756        return self.func(name, expression.this, *expression.expressions)
 757
 758    def least_sql(self, expression: exp.Least) -> str:
 759        name = "LEAST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "LEAST"
 760        return self.func(name, expression.this, *expression.expressions)
 761
 762    def generator_sql(self, expression: exp.Generator) -> str:
 763        args = []
 764        rowcount = expression.args.get("rowcount")
 765        timelimit = expression.args.get("timelimit")
 766
 767        if rowcount:
 768            args.append(exp.Kwarg(this=exp.var("ROWCOUNT"), expression=rowcount))
 769        if timelimit:
 770            args.append(exp.Kwarg(this=exp.var("TIMELIMIT"), expression=timelimit))
 771
 772        return self.func("GENERATOR", *args)
 773
 774    def unnest_sql(self, expression: exp.Unnest) -> str:
 775        unnest_alias = expression.args.get("alias")
 776        offset = expression.args.get("offset")
 777
 778        unnest_alias_columns = unnest_alias.columns if unnest_alias else []
 779        value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value")
 780
 781        columns = [
 782            exp.to_identifier("seq"),
 783            exp.to_identifier("key"),
 784            exp.to_identifier("path"),
 785            offset.pop() if isinstance(offset, exp.Expr) else exp.to_identifier("index"),
 786            value,
 787            exp.to_identifier("this"),
 788        ]
 789
 790        if unnest_alias:
 791            unnest_alias.set("columns", columns)
 792        else:
 793            unnest_alias = exp.TableAlias(this="_u", columns=columns)
 794
 795        table_input = self.sql(expression.expressions[0])
 796        if not table_input.startswith("INPUT =>"):
 797            table_input = f"INPUT => {table_input}"
 798
 799        expression_parent = expression.parent
 800
 801        explode = (
 802            f"FLATTEN({table_input})"
 803            if isinstance(expression_parent, exp.Lateral)
 804            else f"TABLE(FLATTEN({table_input}))"
 805        )
 806        alias = self.sql(unnest_alias)
 807        alias = f" AS {alias}" if alias else ""
 808        value = (
 809            ""
 810            if isinstance(expression_parent, (exp.From, exp.Join, exp.Lateral))
 811            else f"{value} FROM "
 812        )
 813
 814        return f"{value}{explode}{alias}"
 815
 816    def undrop_sql(self, expression: exp.Undrop) -> str:
 817        this = self.sql(expression, "this")
 818        kind = expression.kind
 819        rename = self.sql(expression, "rename")
 820        rename = f" RENAME TO {rename}" if rename else ""
 821        return f"UNDROP {kind} {this}{rename}"
 822
 823    def show_sql(self, expression: exp.Show) -> str:
 824        terse = "TERSE " if expression.args.get("terse") else ""
 825        iceberg = "ICEBERG " if expression.args.get("iceberg") else ""
 826        history = " HISTORY" if expression.args.get("history") else ""
 827        like = self.sql(expression, "like")
 828        like = f" LIKE {like}" if like else ""
 829
 830        scope = self.sql(expression, "scope")
 831        scope = f" {scope}" if scope else ""
 832
 833        scope_kind = self.sql(expression, "scope_kind")
 834        if scope_kind:
 835            scope_kind = f" IN {scope_kind}"
 836
 837        starts_with = self.sql(expression, "starts_with")
 838        if starts_with:
 839            starts_with = f" STARTS WITH {starts_with}"
 840
 841        limit = self.sql(expression, "limit")
 842
 843        from_ = self.sql(expression, "from_")
 844        if from_:
 845            from_ = f" FROM {from_}"
 846
 847        privileges = self.expressions(expression, key="privileges", flat=True)
 848        privileges = f" WITH PRIVILEGES {privileges}" if privileges else ""
 849
 850        return f"SHOW {terse}{iceberg}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}"
 851
 852    def rowaccessproperty_sql(self, expression: exp.RowAccessProperty) -> str:
 853        if not expression.this:
 854            return "ROW ACCESS"
 855        on = f" ON ({self.expressions(expression, flat=True)})" if expression.expressions else ""
 856        return f"WITH ROW ACCESS POLICY {self.sql(expression, 'this')}{on}"
 857
 858    def describe_sql(self, expression: exp.Describe) -> str:
 859        kind_value = expression.args.get("kind") or "TABLE"
 860
 861        properties = expression.args.get("properties")
 862        if properties:
 863            qualifier = self.expressions(properties, sep=" ")
 864            kind = f" {qualifier} {kind_value}"
 865        else:
 866            kind = f" {kind_value}"
 867
 868        this = f" {self.sql(expression, 'this')}"
 869        expressions = self.expressions(expression, flat=True)
 870        expressions = f" {expressions}" if expressions else ""
 871        return f"DESCRIBE{kind}{this}{expressions}"
 872
 873    def generatedasidentitycolumnconstraint_sql(
 874        self, expression: exp.GeneratedAsIdentityColumnConstraint
 875    ) -> str:
 876        start = expression.args.get("start")
 877        start = f" START {start}" if start else ""
 878        increment = expression.args.get("increment")
 879        increment = f" INCREMENT {increment}" if increment else ""
 880
 881        order = expression.args.get("order")
 882        if order is not None:
 883            order_clause = " ORDER" if order else " NOORDER"
 884        else:
 885            order_clause = ""
 886
 887        return f"AUTOINCREMENT{start}{increment}{order_clause}"
 888
 889    def struct_sql(self, expression: exp.Struct) -> str:
 890        if len(expression.expressions) == 1:
 891            arg = expression.expressions[0]
 892            if arg.is_star or (isinstance(arg, exp.ILike) and arg.left.is_star):
 893                # Wildcard syntax: https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object
 894                return f"{{{self.sql(expression.expressions[0])}}}"
 895
 896        keys = []
 897        values = []
 898
 899        for i, e in enumerate(expression.expressions):
 900            if isinstance(e, exp.PropertyEQ):
 901                keys.append(
 902                    exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this
 903                )
 904                values.append(e.expression)
 905            else:
 906                keys.append(exp.Literal.string(f"_{i}"))
 907                values.append(e)
 908
 909        return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values)))
 910
 911    @unsupported_args("weight", "accuracy")
 912    def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str:
 913        return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile"))
 914
 915    def alterset_sql(self, expression: exp.AlterSet) -> str:
 916        exprs = self.expressions(expression, flat=True)
 917        exprs = f" {exprs}" if exprs else ""
 918        file_format = self.expressions(expression, key="file_format", flat=True, sep=" ")
 919        file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else ""
 920        copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ")
 921        copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else ""
 922        tag = self.expressions(expression, key="tag", flat=True)
 923        tag = f" TAG {tag}" if tag else ""
 924
 925        return f"SET{exprs}{file_format}{copy_options}{tag}"
 926
 927    def strtotime_sql(self, expression: exp.StrToTime):
 928        # target_type is stored as a DataType instance
 929        target_type = expression.args.get("target_type")
 930
 931        # Get the type enum from DataType instance or from type annotation
 932        if isinstance(target_type, exp.DataType):
 933            type_enum = target_type.this
 934        elif expression.type:
 935            type_enum = expression.type.this
 936        else:
 937            type_enum = exp.DType.TIMESTAMP
 938
 939        func_name = TIMESTAMP_TYPES.get(type_enum, "TO_TIMESTAMP")
 940
 941        return self.func(
 942            f"{'TRY_' if expression.args.get('safe') else ''}{func_name}",
 943            expression.this,
 944            self.format_time(expression),
 945        )
 946
 947    def timestampsub_sql(self, expression: exp.TimestampSub):
 948        return self.sql(
 949            exp.TimestampAdd(
 950                this=expression.this,
 951                expression=expression.expression * -1,
 952                unit=expression.unit,
 953            )
 954        )
 955
 956    def jsonextract_sql(self, expression: exp.JSONExtract):
 957        this = expression.this
 958
 959        # JSON strings are valid coming from other dialects such as BQ so
 960        # for these cases we PARSE_JSON preemptively
 961        if not isinstance(this, (exp.ParseJSON, exp.JSONExtract)) and not expression.args.get(
 962            "requires_json"
 963        ):
 964            this = exp.ParseJSON(this=this)
 965
 966        return self.func(
 967            "GET_PATH",
 968            this,
 969            expression.expression,
 970        )
 971
 972    def timetostr_sql(self, expression: exp.TimeToStr) -> str:
 973        this = expression.this
 974        if this.is_string:
 975            this = exp.cast(this, exp.DType.TIMESTAMP)
 976
 977        return self.func("TO_CHAR", this, self.format_time(expression))
 978
 979    def datesub_sql(self, expression: exp.DateSub) -> str:
 980        value = expression.expression
 981        if value:
 982            value.replace(value * (-1))
 983        else:
 984            self.unsupported("DateSub cannot be transpiled if the subtracted count is unknown")
 985
 986        return date_delta_sql("DATEADD")(self, expression)
 987
 988    def select_sql(self, expression: exp.Select) -> str:
 989        limit = expression.args.get("limit")
 990        offset = expression.args.get("offset")
 991        if offset and not limit:
 992            expression.limit(exp.Null(), copy=False)
 993        return super().select_sql(expression)
 994
 995    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
 996        is_materialized = expression.find(exp.MaterializedProperty)
 997        copy_grants_property = expression.find(exp.CopyGrantsProperty)
 998
 999        if expression.kind == "VIEW" and is_materialized and copy_grants_property:
1000            # For materialized views, COPY GRANTS is located *before* the columns list
1001            # This is in contrast to normal views where COPY GRANTS is located *after* the columns list
1002            # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected
1003            # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax
1004            # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax
1005            post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA]
1006            post_schema_properties.pop(post_schema_properties.index(copy_grants_property))
1007
1008            this_name = self.sql(expression.this, "this")
1009            copy_grants = self.sql(copy_grants_property)
1010            this_schema = self.schema_columns_sql(expression.this)
1011            this_schema = f"{self.sep()}{this_schema}" if this_schema else ""
1012
1013            return f"{this_name}{self.sep()}{copy_grants}{this_schema}"
1014
1015        return super().createable_sql(expression, locations)
1016
1017    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
1018        this = expression.this
1019
1020        # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG
1021        # and add it later as part of the WITHIN GROUP clause
1022        order = this if isinstance(this, exp.Order) else None
1023        if order:
1024            expression.set("this", order.this.pop())
1025
1026        expr_sql = super().arrayagg_sql(expression)
1027
1028        if order:
1029            expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order))
1030
1031        return expr_sql
1032
1033    def arraydistinct_sql(self, expression: exp.ArrayDistinct) -> str:
1034        if expression.args.get("check_null"):
1035            return self.func("ARRAY_DISTINCT", expression.this)
1036        return self.func("ARRAY_DISTINCT", exp.ArrayCompact(this=expression.this))
1037
1038    def arraytostring_sql(self, expression: exp.ArrayToString) -> str:
1039        return self.func("ARRAY_TO_STRING", expression.this, expression.expression)
1040
1041    def array_sql(self, expression: exp.Array) -> str:
1042        expressions = expression.expressions
1043
1044        first_expr = seq_get(expressions, 0)
1045        if isinstance(first_expr, exp.Select):
1046            # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo))
1047            if first_expr.text("kind").upper() == "STRUCT":
1048                object_construct_args = []
1049                for expr in first_expr.expressions:
1050                    # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo)
1051                    # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo)
1052                    name = expr.this if isinstance(expr, exp.Alias) else expr
1053
1054                    object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name])
1055
1056                array_agg = exp.ArrayAgg(this=build_object_construct(args=object_construct_args))
1057
1058                first_expr.set("kind", None)
1059                first_expr.set("expressions", [array_agg])
1060
1061                return self.sql(first_expr.subquery())
1062
1063        return inline_array_sql(self, expression)
1064
1065    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
1066        zone = self.sql(expression, "this")
1067        if not zone:
1068            return super().currentdate_sql(expression)
1069
1070        expr = exp.Cast(
1071            this=exp.ConvertTimezone(target_tz=zone, timestamp=exp.CurrentTimestamp()),
1072            to=exp.DataType(this=exp.DType.DATE),
1073        )
1074        return self.sql(expr)
1075
1076    def dot_sql(self, expression: exp.Dot) -> str:
1077        this = expression.this
1078
1079        if not this.type:
1080            from sqlglot.optimizer.annotate_types import annotate_types
1081
1082            this = annotate_types(this, dialect=self.dialect)
1083
1084        if not isinstance(this, exp.Dot) and this.is_type(exp.DType.STRUCT):
1085            # Generate colon notation for the top level STRUCT
1086            return f"{self.sql(this)}:{self.sql(expression, 'expression')}"
1087
1088        return super().dot_sql(expression)
1089
1090    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
1091        return f"{self.sql(expression, 'this')}!{self.sql(expression, 'expression')}"
1092
1093    def format_sql(self, expression: exp.Format) -> str:
1094        if expression.name.lower() == "%s" and len(expression.expressions) == 1:
1095            return self.func("TO_CHAR", expression.expressions[0])
1096
1097        return self.function_fallback_sql(expression)
1098
1099    def splitpart_sql(self, expression: exp.SplitPart) -> str:
1100        # Set part_index to 1 if missing
1101        if not expression.args.get("delimiter"):
1102            expression.set("delimiter", exp.Literal.string(" "))
1103
1104        if not expression.args.get("part_index"):
1105            expression.set("part_index", exp.Literal.number(1))
1106
1107        return rename_func("SPLIT_PART")(self, expression)
1108
1109    def uniform_sql(self, expression: exp.Uniform) -> str:
1110        gen = expression.args.get("gen")
1111        seed = expression.args.get("seed")
1112
1113        # From Databricks UNIFORM(min, max, seed) -> Wrap gen in RANDOM(seed)
1114        if seed:
1115            gen = exp.Rand(this=seed)
1116
1117        # No gen argument (from Databricks 2-arg UNIFORM(min, max)) -> Add RANDOM()
1118        if not gen:
1119            gen = exp.Rand()
1120
1121        return self.func("UNIFORM", expression.this, expression.expression, gen)
1122
1123    def window_sql(self, expression: exp.Window) -> str:
1124        spec = expression.args.get("spec")
1125        this = expression.this
1126
1127        if (
1128            (
1129                isinstance(this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME)
1130                or (
1131                    isinstance(this, (exp.RespectNulls, exp.IgnoreNulls))
1132                    and isinstance(this.this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME)
1133                )
1134            )
1135            and spec
1136            and (
1137                spec.text("kind").upper() == "ROWS"
1138                and spec.text("start").upper() == "UNBOUNDED"
1139                and spec.text("start_side").upper() == "PRECEDING"
1140                and spec.text("end").upper() == "UNBOUNDED"
1141                and spec.text("end_side").upper() == "FOLLOWING"
1142            )
1143        ):
1144            # omit the default window from window ranking functions
1145            expression.set("spec", None)
1146        return super().window_sql(expression)
1147
1148    def filter_sql(self, expression: exp.Filter) -> str:
1149        # Snowflake doesn't support FILTER (WHERE cond), so we rewrite it into an
1150        # equivalent conditional aggregation, i.e. wrap the input values in an IFF
1151        agg = expression.this
1152        agg_arg = seq_get(agg.expressions, 0) if isinstance(agg, exp.Anonymous) else agg.this
1153        cond = expression.expression.this
1154
1155        if isinstance(agg, exp.WithinGroup):
1156            # Ordered-set aggregates take their input from the ORDER BY key, so the
1157            # condition has to wrap that instead of the aggregate's own argument
1158            if isinstance(agg_arg, (exp.Mode, *exp.PERCENTILES)):
1159                for ordered in agg.expression.expressions:
1160                    key = ordered.this
1161                    key.replace(exp.If(this=cond.copy(), true=key.copy()))
1162
1163                return self.sql(agg)
1164
1165            # Besides the percentile functions, these are the only functions Snowflake
1166            # accepts WITHIN GROUP for, so anything else can't be rewritten correctly
1167            if isinstance(agg_arg, (exp.ArrayAgg, exp.GroupConcat)):
1168                agg_arg = agg_arg.this
1169            else:
1170                self.unsupported("Unable to rewrite FILTER into the aggregate's arguments")
1171                return self.sql(agg)
1172
1173        # `COUNT(*/t.*) FILTER (WHERE cond)` counts qualifying rows, but a star can't be an IFF
1174        # argument: `IFF(cond, *, NULL)` expands to multiple columns once the table has 2+ of
1175        # them, which Snowflake rejects. Use its native COUNT_IF instead.
1176        if isinstance(agg, exp.Count) and isinstance(agg_arg, exp.Expression) and agg_arg.is_star:
1177            return self.func("COUNT_IF", cond)
1178
1179        # `DISTINCT` and `ORDER BY` are part of the aggregate's own argument list, so the
1180        # condition has to wrap the values underneath them rather than the whole clause --
1181        # `IFF(cond, DISTINCT x, NULL)` is not a call any dialect accepts.
1182        if isinstance(agg_arg, exp.Order):
1183            agg_arg = agg_arg.this
1184
1185        if isinstance(agg_arg, exp.Distinct):
1186            targets = agg_arg.expressions
1187        else:
1188            targets = [agg_arg]
1189
1190        for target in targets:
1191            target.replace(exp.If(this=cond.copy(), true=target.copy()))
1192
1193        return self.sql(agg)
1194
1195    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
1196        # Snowflake's MODE doesn't support the ordered-set syntax, i.e. it only
1197        # accepts the value to aggregate as an argument: MODE(<expr>)
1198        if isinstance(expression.this, exp.Mode) and not expression.this.this:
1199            order = expression.expression
1200            if isinstance(order, exp.Order) and len(order.expressions) == 1:
1201                return self.sql(exp.Mode(this=order.expressions[0].this))
1202
1203        return super().withingroup_sql(expression)

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, ...] = ()
PARAMETER_TOKEN = '$'
MATCHED_BY_SOURCE = False
SINGLE_STRING_INTERVAL = True
JOIN_HINTS = False
TABLE_HINTS = False
QUERY_HINTS = False
SUPPORTS_TABLE_COPY = False
COLLATE_IS_FUNC = True
LIMIT_ONLY_LITERALS = True
JSON_KEY_VALUE_PAIR_SEP = ','
INSERT_OVERWRITE = ' OVERWRITE INTO'
STRUCT_DELIMITER = ('(', ')')
COPY_PARAMS_ARE_WRAPPED = False
COPY_PARAMS_EQ_REQUIRED = True
STAR_EXCEPT = 'EXCLUDE'
SUPPORTS_EXPLODING_PROJECTIONS = False
ARRAY_CONCAT_IS_VAR_LEN = False
SUPPORTS_CONVERT_TIMEZONE = True
EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
SUPPORTS_MEDIAN = True
ARRAY_SIZE_NAME = 'ARRAY_SIZE'
SUPPORTS_DECODE_CASE = True
AFTER_HAVING_MODIFIER_TRANSFORMS = {'windows': <function <lambda>>, 'qualify': <function <lambda>>}
IS_BOOL_ALLOWED = False
DIRECTED_JOINS = True
SUPPORTS_UESCAPE = False
TRY_SUPPORTED = False
TRANSFORMS = {<class 'sqlglot.expressions.query.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRoot'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.query.JSONPathSubscript'>: <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 rename_func.<locals>.<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.BinaryColumnConstraint'>: <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 SnowflakeGenerator.<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.JSONBContainsTopKey'>: <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 SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.json.JSONObjectAgg'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LocationProperty'>: <function SnowflakeGenerator.<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 SnowflakeGenerator.<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 Generator.<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 SnowflakeGenerator.<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.core.ApproxDistinct'>: <function rename_func.<locals>.<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.ArrayConcat'>: <function array_concat_sql.<locals>._array_concat_sql>, <class 'sqlglot.expressions.array.ArrayAppend'>: <function array_append_sql.<locals>._array_append_sql>, <class 'sqlglot.expressions.array.ArrayPrepend'>: <function array_append_sql.<locals>._array_append_sql>, <class 'sqlglot.expressions.array.ArrayContains'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.array.ArrayPosition'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.array.ArrayIntersect'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.AtTimeZone'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.core.BitwiseOr'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.BitwiseXor'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.BitwiseAnd'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.BitwiseAndAgg'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.BitwiseOrAgg'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.BitwiseXorAgg'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.BitwiseNot'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.BitwiseLeftShift'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.BitwiseRightShift'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.CurrentTimestamp'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentSchemas'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.Localtime'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.Localtimestamp'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.DateAdd'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.DateDiff'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.DatetimeAdd'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.DatetimeDiff'>: <function timestampdiff_sql>, <class 'sqlglot.expressions.temporal.DateStrToDate'>: <function datestrtodate_sql>, <class 'sqlglot.expressions.string.Decrypt'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.DecryptRaw'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.DayOfMonth'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.DayOfWeek'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.DayOfWeekIso'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.DayOfYear'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.DotProduct'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.Explode'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.Extract'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.math.CosineDistance'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.EuclideanDistance'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.core.FromTimeZone'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.array.GenerateSeries'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.GetExtract'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.GroupConcat'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.functions.If'>: <function if_sql.<locals>._if_sql>, <class 'sqlglot.expressions.json.JSONArray'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.json.JSONExtractArray'>: <function _json_extract_value_array_sql>, <class 'sqlglot.expressions.json.JSONExtractScalar'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.json.JSONKeys'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.query.JSONValueArray'>: <function _json_extract_value_array_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.array.Map'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.math.ManhattanDistance'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.MakeInterval'>: <function no_make_interval_sql>, <class 'sqlglot.expressions.aggregate.Max'>: <function max_or_greatest>, <class 'sqlglot.expressions.aggregate.Min'>: <function min_or_least>, <class 'sqlglot.expressions.aggregate.NthValue'>: <function nth_value_from_sql>, <class 'sqlglot.expressions.json.ParseJSON'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.ToBinary'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.functions.ToBoolean'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.ToDouble'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.ToFile'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.json.JSONFormat'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.aggregate.PercentileCont'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.aggregate.PercentileDisc'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.query.Pivot'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.string.RegexpExtract'>: <function _regexpextract_sql>, <class 'sqlglot.expressions.string.RegexpExtractAll'>: <function _regexpextract_sql>, <class 'sqlglot.expressions.string.RegexpILike'>: <function _regexpilike_sql>, <class 'sqlglot.expressions.query.Select'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.string.SHA'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.SHA1Digest'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.MD5Digest'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.MD5NumberLower64'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.MD5NumberUpper64'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.Hex'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.LowerHex'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.Skewness'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.StarMap'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.StartsWith'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.EndsWith'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.functions.Rand'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.StrPosition'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.StrToDate'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.array.StringToArray'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.StrtokToArray'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.Stuff'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.StPoint'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.TimeAdd'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.TimeSlice'>: <function SnowflakeGenerator.<lambda>>, <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.TimestampDiff'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.TimestampTrunc'>: <function timestamptrunc_sql.<locals>._timestamptrunc_sql>, <class 'sqlglot.expressions.temporal.TimeStrToTime'>: <function timestrtotime_sql>, <class 'sqlglot.expressions.temporal.TimeToUnix'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.array.ToArray'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.ToChar'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.TsOrDsAdd'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.TsOrDsDiff'>: <function date_delta_sql.<locals>._delta_sql>, <class 'sqlglot.expressions.temporal.TsOrDsToDate'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.TsOrDsToTime'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.Unhex'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.UnixToTime'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.functions.Uuid'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.Booland'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.math.Boolor'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.WeekOfYear'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.YearOfWeek'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.YearOfWeekIso'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.Xor'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.ByteLength'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.array.Flatten'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.ArrayConcatAgg'>: <function SnowflakeGenerator.<lambda>>, <class 'sqlglot.expressions.string.SHA2Digest'>: <function SnowflakeGenerator.<lambda>>}
def dynamicidentifier_sql(self, expression: sqlglot.expressions.core.DynamicIdentifier) -> str:
601    def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str:
602        this = self.func("IDENTIFIER", expression.this)
603        if "expressions" in expression.args:
604            # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)`
605            return self.func(this, *expression.expressions, normalize=False)
606        return this
def sortarray_sql(self, expression: sqlglot.expressions.array.SortArray) -> str:
608    def sortarray_sql(self, expression: exp.SortArray) -> str:
609        asc = expression.args.get("asc")
610        nulls_first = expression.args.get("nulls_first")
611        if asc == exp.false() and nulls_first == exp.true():
612            nulls_first = None
613        return self.func("ARRAY_SORT", expression.this, asc, nulls_first)
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.BIGDECIMAL: 'BIGDECIMAL'>: 'DOUBLE', <DType.JSON: 'JSON'>: 'VARIANT', <DType.NESTED: 'NESTED'>: 'OBJECT', <DType.STRUCT: 'STRUCT'>: 'OBJECT', <DType.TEXT: 'TEXT'>: 'VARCHAR'}
TOKEN_MAPPING = {<TokenType.AUTO_INCREMENT: 228>: 'AUTOINCREMENT'}
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.POST_WITH: 'POST_WITH'>, <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_SCHEMA: 'POST_SCHEMA'>, <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.POST_SCHEMA: 'POST_SCHEMA'>, <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.UNSUPPORTED: 'UNSUPPORTED'>, <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'>, <class 'sqlglot.expressions.properties.CredentialsProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>}
UNSUPPORTED_VALUES_EXPRESSIONS: ClassVar = {<class 'sqlglot.expressions.array.Map'>, <class 'sqlglot.expressions.array.Struct'>, <class 'sqlglot.expressions.array.StarMap'>, <class 'sqlglot.expressions.array.VarMap'>}
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS = (<class 'sqlglot.expressions.aggregate.ArrayAgg'>,)
def with_properties(self, properties: sqlglot.expressions.properties.Properties) -> str:
653    def with_properties(self, properties: exp.Properties) -> str:
654        return self.properties(properties, wrapped=False, prefix=self.sep(""), sep=" ")
def values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
656    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:
657        if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS):
658            values_as_table = False
659
660        return super().values_sql(expression, values_as_table=values_as_table)
def datatype_sql(self, expression: sqlglot.expressions.datatypes.DataType) -> str:
662    def datatype_sql(self, expression: exp.DataType) -> str:
663        # Check if this is a FLOAT type nested inside a VECTOR type
664        # VECTOR only accepts FLOAT (not DOUBLE), INT, and STRING as element types
665        # https://docs.snowflake.com/en/sql-reference/data-types-vector
666        if expression.is_type(exp.DType.DOUBLE):
667            parent = expression.parent
668            if isinstance(parent, exp.DataType) and parent.is_type(exp.DType.VECTOR):
669                # Preserve FLOAT for VECTOR types instead of mapping to synonym DOUBLE
670                return "FLOAT"
671
672        expressions = expression.expressions
673        if expressions and expression.is_type(*exp.DataType.STRUCT_TYPES):
674            for field_type in expressions:
675                # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ]
676                if isinstance(field_type, exp.DataType):
677                    return "OBJECT"
678                if (
679                    isinstance(field_type, exp.ColumnDef)
680                    and field_type.this
681                    and field_type.this.is_string
682                ):
683                    # Doing OBJECT('foo' VARCHAR) is invalid snowflake Syntax. Moreover, besides
684                    # converting 'foo' into an identifier, we also need to quote it because these
685                    # keys are case-sensitive. For example:
686                    #
687                    # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:x FROM t -- correct
688                    # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:X FROM t -- incorrect, returns NULL
689                    field_type.this.replace(exp.to_identifier(field_type.name, quoted=True))
690
691        return super().datatype_sql(expression)
def tonumber_sql(self, expression: sqlglot.expressions.string.ToNumber) -> str:
693    def tonumber_sql(self, expression: exp.ToNumber) -> str:
694        precision = expression.args.get("precision")
695        scale = expression.args.get("scale")
696
697        default_precision = isinstance(precision, exp.Literal) and precision.name == "38"
698        default_scale = isinstance(scale, exp.Literal) and scale.name == "0"
699
700        if default_precision and default_scale:
701            precision = None
702            scale = None
703        elif default_scale:
704            scale = None
705
706        func_name = "TRY_TO_NUMBER" if expression.args.get("safe") else "TO_NUMBER"
707
708        return self.func(
709            func_name,
710            expression.this,
711            expression.args.get("format"),
712            precision,
713            scale,
714        )
def timestampfromparts_sql(self, expression: sqlglot.expressions.temporal.TimestampFromParts) -> str:
716    def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str:
717        milli = expression.args.get("milli")
718        if milli is not None:
719            milli_to_nano = milli.pop() * exp.Literal.number(1000000)
720            expression.set("nano", milli_to_nano)
721
722        return rename_func("TIMESTAMP_FROM_PARTS")(self, expression)
def cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
724    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
725        if expression.is_type(exp.DType.GEOGRAPHY):
726            return self.func("TO_GEOGRAPHY", expression.this)
727        if expression.is_type(exp.DType.GEOMETRY):
728            return self.func("TO_GEOMETRY", expression.this)
729
730        return super().cast_sql(expression, safe_prefix=safe_prefix)
def trycast_sql(self, expression: sqlglot.expressions.functions.TryCast) -> str:
732    def trycast_sql(self, expression: exp.TryCast) -> str:
733        value = expression.this
734
735        if value.type is None:
736            from sqlglot.optimizer.annotate_types import annotate_types
737
738            value = annotate_types(value, dialect=self.dialect)
739
740        # Snowflake requires that TRY_CAST's value be a string
741        # If TRY_CAST is being roundtripped (since Snowflake is the only dialect that sets "requires_string") or
742        # if we can deduce that the value is a string, then we can generate TRY_CAST
743        if expression.args.get("requires_string") or value.is_type(*exp.DataType.TEXT_TYPES):
744            return super().trycast_sql(expression)
745
746        return self.cast_sql(expression)
def log_sql(self, expression: sqlglot.expressions.math.Log) -> str:
748    def log_sql(self, expression: exp.Log) -> str:
749        if not expression.expression:
750            return self.func("LN", expression.this)
751
752        return super().log_sql(expression)
def greatest_sql(self, expression: sqlglot.expressions.functions.Greatest) -> str:
754    def greatest_sql(self, expression: exp.Greatest) -> str:
755        name = "GREATEST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "GREATEST"
756        return self.func(name, expression.this, *expression.expressions)
def least_sql(self, expression: sqlglot.expressions.functions.Least) -> str:
758    def least_sql(self, expression: exp.Least) -> str:
759        name = "LEAST_IGNORE_NULLS" if expression.args.get("ignore_nulls") else "LEAST"
760        return self.func(name, expression.this, *expression.expressions)
def generator_sql(self, expression: sqlglot.expressions.array.Generator) -> str:
762    def generator_sql(self, expression: exp.Generator) -> str:
763        args = []
764        rowcount = expression.args.get("rowcount")
765        timelimit = expression.args.get("timelimit")
766
767        if rowcount:
768            args.append(exp.Kwarg(this=exp.var("ROWCOUNT"), expression=rowcount))
769        if timelimit:
770            args.append(exp.Kwarg(this=exp.var("TIMELIMIT"), expression=timelimit))
771
772        return self.func("GENERATOR", *args)
def unnest_sql(self, expression: sqlglot.expressions.array.Unnest) -> str:
774    def unnest_sql(self, expression: exp.Unnest) -> str:
775        unnest_alias = expression.args.get("alias")
776        offset = expression.args.get("offset")
777
778        unnest_alias_columns = unnest_alias.columns if unnest_alias else []
779        value = seq_get(unnest_alias_columns, 0) or exp.to_identifier("value")
780
781        columns = [
782            exp.to_identifier("seq"),
783            exp.to_identifier("key"),
784            exp.to_identifier("path"),
785            offset.pop() if isinstance(offset, exp.Expr) else exp.to_identifier("index"),
786            value,
787            exp.to_identifier("this"),
788        ]
789
790        if unnest_alias:
791            unnest_alias.set("columns", columns)
792        else:
793            unnest_alias = exp.TableAlias(this="_u", columns=columns)
794
795        table_input = self.sql(expression.expressions[0])
796        if not table_input.startswith("INPUT =>"):
797            table_input = f"INPUT => {table_input}"
798
799        expression_parent = expression.parent
800
801        explode = (
802            f"FLATTEN({table_input})"
803            if isinstance(expression_parent, exp.Lateral)
804            else f"TABLE(FLATTEN({table_input}))"
805        )
806        alias = self.sql(unnest_alias)
807        alias = f" AS {alias}" if alias else ""
808        value = (
809            ""
810            if isinstance(expression_parent, (exp.From, exp.Join, exp.Lateral))
811            else f"{value} FROM "
812        )
813
814        return f"{value}{explode}{alias}"
def undrop_sql(self, expression: sqlglot.expressions.ddl.Undrop) -> str:
816    def undrop_sql(self, expression: exp.Undrop) -> str:
817        this = self.sql(expression, "this")
818        kind = expression.kind
819        rename = self.sql(expression, "rename")
820        rename = f" RENAME TO {rename}" if rename else ""
821        return f"UNDROP {kind} {this}{rename}"
def show_sql(self, expression: sqlglot.expressions.ddl.Show) -> str:
823    def show_sql(self, expression: exp.Show) -> str:
824        terse = "TERSE " if expression.args.get("terse") else ""
825        iceberg = "ICEBERG " if expression.args.get("iceberg") else ""
826        history = " HISTORY" if expression.args.get("history") else ""
827        like = self.sql(expression, "like")
828        like = f" LIKE {like}" if like else ""
829
830        scope = self.sql(expression, "scope")
831        scope = f" {scope}" if scope else ""
832
833        scope_kind = self.sql(expression, "scope_kind")
834        if scope_kind:
835            scope_kind = f" IN {scope_kind}"
836
837        starts_with = self.sql(expression, "starts_with")
838        if starts_with:
839            starts_with = f" STARTS WITH {starts_with}"
840
841        limit = self.sql(expression, "limit")
842
843        from_ = self.sql(expression, "from_")
844        if from_:
845            from_ = f" FROM {from_}"
846
847        privileges = self.expressions(expression, key="privileges", flat=True)
848        privileges = f" WITH PRIVILEGES {privileges}" if privileges else ""
849
850        return f"SHOW {terse}{iceberg}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}"
def rowaccessproperty_sql( self, expression: sqlglot.expressions.properties.RowAccessProperty) -> str:
852    def rowaccessproperty_sql(self, expression: exp.RowAccessProperty) -> str:
853        if not expression.this:
854            return "ROW ACCESS"
855        on = f" ON ({self.expressions(expression, flat=True)})" if expression.expressions else ""
856        return f"WITH ROW ACCESS POLICY {self.sql(expression, 'this')}{on}"
def describe_sql(self, expression: sqlglot.expressions.ddl.Describe) -> str:
858    def describe_sql(self, expression: exp.Describe) -> str:
859        kind_value = expression.args.get("kind") or "TABLE"
860
861        properties = expression.args.get("properties")
862        if properties:
863            qualifier = self.expressions(properties, sep=" ")
864            kind = f" {qualifier} {kind_value}"
865        else:
866            kind = f" {kind_value}"
867
868        this = f" {self.sql(expression, 'this')}"
869        expressions = self.expressions(expression, flat=True)
870        expressions = f" {expressions}" if expressions else ""
871        return f"DESCRIBE{kind}{this}{expressions}"
def generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsIdentityColumnConstraint) -> str:
873    def generatedasidentitycolumnconstraint_sql(
874        self, expression: exp.GeneratedAsIdentityColumnConstraint
875    ) -> str:
876        start = expression.args.get("start")
877        start = f" START {start}" if start else ""
878        increment = expression.args.get("increment")
879        increment = f" INCREMENT {increment}" if increment else ""
880
881        order = expression.args.get("order")
882        if order is not None:
883            order_clause = " ORDER" if order else " NOORDER"
884        else:
885            order_clause = ""
886
887        return f"AUTOINCREMENT{start}{increment}{order_clause}"
def struct_sql(self, expression: sqlglot.expressions.array.Struct) -> str:
889    def struct_sql(self, expression: exp.Struct) -> str:
890        if len(expression.expressions) == 1:
891            arg = expression.expressions[0]
892            if arg.is_star or (isinstance(arg, exp.ILike) and arg.left.is_star):
893                # Wildcard syntax: https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object
894                return f"{{{self.sql(expression.expressions[0])}}}"
895
896        keys = []
897        values = []
898
899        for i, e in enumerate(expression.expressions):
900            if isinstance(e, exp.PropertyEQ):
901                keys.append(
902                    exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this
903                )
904                values.append(e.expression)
905            else:
906                keys.append(exp.Literal.string(f"_{i}"))
907                values.append(e)
908
909        return self.func("OBJECT_CONSTRUCT", *flatten(zip(keys, values)))
@unsupported_args('weight', 'accuracy')
def approxquantile_sql(self, expression: sqlglot.expressions.aggregate.ApproxQuantile) -> str:
911    @unsupported_args("weight", "accuracy")
912    def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str:
913        return self.func("APPROX_PERCENTILE", expression.this, expression.args.get("quantile"))
def alterset_sql(self, expression: sqlglot.expressions.ddl.AlterSet) -> str:
915    def alterset_sql(self, expression: exp.AlterSet) -> str:
916        exprs = self.expressions(expression, flat=True)
917        exprs = f" {exprs}" if exprs else ""
918        file_format = self.expressions(expression, key="file_format", flat=True, sep=" ")
919        file_format = f" STAGE_FILE_FORMAT = ({file_format})" if file_format else ""
920        copy_options = self.expressions(expression, key="copy_options", flat=True, sep=" ")
921        copy_options = f" STAGE_COPY_OPTIONS = ({copy_options})" if copy_options else ""
922        tag = self.expressions(expression, key="tag", flat=True)
923        tag = f" TAG {tag}" if tag else ""
924
925        return f"SET{exprs}{file_format}{copy_options}{tag}"
def strtotime_sql(self, expression: sqlglot.expressions.temporal.StrToTime):
927    def strtotime_sql(self, expression: exp.StrToTime):
928        # target_type is stored as a DataType instance
929        target_type = expression.args.get("target_type")
930
931        # Get the type enum from DataType instance or from type annotation
932        if isinstance(target_type, exp.DataType):
933            type_enum = target_type.this
934        elif expression.type:
935            type_enum = expression.type.this
936        else:
937            type_enum = exp.DType.TIMESTAMP
938
939        func_name = TIMESTAMP_TYPES.get(type_enum, "TO_TIMESTAMP")
940
941        return self.func(
942            f"{'TRY_' if expression.args.get('safe') else ''}{func_name}",
943            expression.this,
944            self.format_time(expression),
945        )
def timestampsub_sql(self, expression: sqlglot.expressions.temporal.TimestampSub):
947    def timestampsub_sql(self, expression: exp.TimestampSub):
948        return self.sql(
949            exp.TimestampAdd(
950                this=expression.this,
951                expression=expression.expression * -1,
952                unit=expression.unit,
953            )
954        )
def jsonextract_sql(self, expression: sqlglot.expressions.json.JSONExtract):
956    def jsonextract_sql(self, expression: exp.JSONExtract):
957        this = expression.this
958
959        # JSON strings are valid coming from other dialects such as BQ so
960        # for these cases we PARSE_JSON preemptively
961        if not isinstance(this, (exp.ParseJSON, exp.JSONExtract)) and not expression.args.get(
962            "requires_json"
963        ):
964            this = exp.ParseJSON(this=this)
965
966        return self.func(
967            "GET_PATH",
968            this,
969            expression.expression,
970        )
def timetostr_sql(self, expression: sqlglot.expressions.temporal.TimeToStr) -> str:
972    def timetostr_sql(self, expression: exp.TimeToStr) -> str:
973        this = expression.this
974        if this.is_string:
975            this = exp.cast(this, exp.DType.TIMESTAMP)
976
977        return self.func("TO_CHAR", this, self.format_time(expression))
def datesub_sql(self, expression: sqlglot.expressions.temporal.DateSub) -> str:
979    def datesub_sql(self, expression: exp.DateSub) -> str:
980        value = expression.expression
981        if value:
982            value.replace(value * (-1))
983        else:
984            self.unsupported("DateSub cannot be transpiled if the subtracted count is unknown")
985
986        return date_delta_sql("DATEADD")(self, expression)
def select_sql(self, expression: sqlglot.expressions.query.Select) -> str:
988    def select_sql(self, expression: exp.Select) -> str:
989        limit = expression.args.get("limit")
990        offset = expression.args.get("offset")
991        if offset and not limit:
992            expression.limit(exp.Null(), copy=False)
993        return super().select_sql(expression)
def createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
 995    def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str:
 996        is_materialized = expression.find(exp.MaterializedProperty)
 997        copy_grants_property = expression.find(exp.CopyGrantsProperty)
 998
 999        if expression.kind == "VIEW" and is_materialized and copy_grants_property:
1000            # For materialized views, COPY GRANTS is located *before* the columns list
1001            # This is in contrast to normal views where COPY GRANTS is located *after* the columns list
1002            # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected
1003            # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax
1004            # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax
1005            post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA]
1006            post_schema_properties.pop(post_schema_properties.index(copy_grants_property))
1007
1008            this_name = self.sql(expression.this, "this")
1009            copy_grants = self.sql(copy_grants_property)
1010            this_schema = self.schema_columns_sql(expression.this)
1011            this_schema = f"{self.sep()}{this_schema}" if this_schema else ""
1012
1013            return f"{this_name}{self.sep()}{copy_grants}{this_schema}"
1014
1015        return super().createable_sql(expression, locations)
def arrayagg_sql(self, expression: sqlglot.expressions.aggregate.ArrayAgg) -> str:
1017    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:
1018        this = expression.this
1019
1020        # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG
1021        # and add it later as part of the WITHIN GROUP clause
1022        order = this if isinstance(this, exp.Order) else None
1023        if order:
1024            expression.set("this", order.this.pop())
1025
1026        expr_sql = super().arrayagg_sql(expression)
1027
1028        if order:
1029            expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order))
1030
1031        return expr_sql
def arraydistinct_sql(self, expression: sqlglot.expressions.array.ArrayDistinct) -> str:
1033    def arraydistinct_sql(self, expression: exp.ArrayDistinct) -> str:
1034        if expression.args.get("check_null"):
1035            return self.func("ARRAY_DISTINCT", expression.this)
1036        return self.func("ARRAY_DISTINCT", exp.ArrayCompact(this=expression.this))
def arraytostring_sql(self, expression: sqlglot.expressions.array.ArrayToString) -> str:
1038    def arraytostring_sql(self, expression: exp.ArrayToString) -> str:
1039        return self.func("ARRAY_TO_STRING", expression.this, expression.expression)
def array_sql(self, expression: sqlglot.expressions.array.Array) -> str:
1041    def array_sql(self, expression: exp.Array) -> str:
1042        expressions = expression.expressions
1043
1044        first_expr = seq_get(expressions, 0)
1045        if isinstance(first_expr, exp.Select):
1046            # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo))
1047            if first_expr.text("kind").upper() == "STRUCT":
1048                object_construct_args = []
1049                for expr in first_expr.expressions:
1050                    # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo)
1051                    # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo)
1052                    name = expr.this if isinstance(expr, exp.Alias) else expr
1053
1054                    object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name])
1055
1056                array_agg = exp.ArrayAgg(this=build_object_construct(args=object_construct_args))
1057
1058                first_expr.set("kind", None)
1059                first_expr.set("expressions", [array_agg])
1060
1061                return self.sql(first_expr.subquery())
1062
1063        return inline_array_sql(self, expression)
def currentdate_sql(self, expression: sqlglot.expressions.temporal.CurrentDate) -> str:
1065    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
1066        zone = self.sql(expression, "this")
1067        if not zone:
1068            return super().currentdate_sql(expression)
1069
1070        expr = exp.Cast(
1071            this=exp.ConvertTimezone(target_tz=zone, timestamp=exp.CurrentTimestamp()),
1072            to=exp.DataType(this=exp.DType.DATE),
1073        )
1074        return self.sql(expr)
def dot_sql(self, expression: sqlglot.expressions.core.Dot) -> str:
1076    def dot_sql(self, expression: exp.Dot) -> str:
1077        this = expression.this
1078
1079        if not this.type:
1080            from sqlglot.optimizer.annotate_types import annotate_types
1081
1082            this = annotate_types(this, dialect=self.dialect)
1083
1084        if not isinstance(this, exp.Dot) and this.is_type(exp.DType.STRUCT):
1085            # Generate colon notation for the top level STRUCT
1086            return f"{self.sql(this)}:{self.sql(expression, 'expression')}"
1087
1088        return super().dot_sql(expression)
def modelattribute_sql(self, expression: sqlglot.expressions.query.ModelAttribute) -> str:
1090    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:
1091        return f"{self.sql(expression, 'this')}!{self.sql(expression, 'expression')}"
def format_sql(self, expression: sqlglot.expressions.string.Format) -> str:
1093    def format_sql(self, expression: exp.Format) -> str:
1094        if expression.name.lower() == "%s" and len(expression.expressions) == 1:
1095            return self.func("TO_CHAR", expression.expressions[0])
1096
1097        return self.function_fallback_sql(expression)
def splitpart_sql(self, expression: sqlglot.expressions.string.SplitPart) -> str:
1099    def splitpart_sql(self, expression: exp.SplitPart) -> str:
1100        # Set part_index to 1 if missing
1101        if not expression.args.get("delimiter"):
1102            expression.set("delimiter", exp.Literal.string(" "))
1103
1104        if not expression.args.get("part_index"):
1105            expression.set("part_index", exp.Literal.number(1))
1106
1107        return rename_func("SPLIT_PART")(self, expression)
def uniform_sql(self, expression: sqlglot.expressions.functions.Uniform) -> str:
1109    def uniform_sql(self, expression: exp.Uniform) -> str:
1110        gen = expression.args.get("gen")
1111        seed = expression.args.get("seed")
1112
1113        # From Databricks UNIFORM(min, max, seed) -> Wrap gen in RANDOM(seed)
1114        if seed:
1115            gen = exp.Rand(this=seed)
1116
1117        # No gen argument (from Databricks 2-arg UNIFORM(min, max)) -> Add RANDOM()
1118        if not gen:
1119            gen = exp.Rand()
1120
1121        return self.func("UNIFORM", expression.this, expression.expression, gen)
def window_sql(self, expression: sqlglot.expressions.query.Window) -> str:
1123    def window_sql(self, expression: exp.Window) -> str:
1124        spec = expression.args.get("spec")
1125        this = expression.this
1126
1127        if (
1128            (
1129                isinstance(this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME)
1130                or (
1131                    isinstance(this, (exp.RespectNulls, exp.IgnoreNulls))
1132                    and isinstance(this.this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME)
1133                )
1134            )
1135            and spec
1136            and (
1137                spec.text("kind").upper() == "ROWS"
1138                and spec.text("start").upper() == "UNBOUNDED"
1139                and spec.text("start_side").upper() == "PRECEDING"
1140                and spec.text("end").upper() == "UNBOUNDED"
1141                and spec.text("end_side").upper() == "FOLLOWING"
1142            )
1143        ):
1144            # omit the default window from window ranking functions
1145            expression.set("spec", None)
1146        return super().window_sql(expression)
def filter_sql(self, expression: sqlglot.expressions.core.Filter) -> str:
1148    def filter_sql(self, expression: exp.Filter) -> str:
1149        # Snowflake doesn't support FILTER (WHERE cond), so we rewrite it into an
1150        # equivalent conditional aggregation, i.e. wrap the input values in an IFF
1151        agg = expression.this
1152        agg_arg = seq_get(agg.expressions, 0) if isinstance(agg, exp.Anonymous) else agg.this
1153        cond = expression.expression.this
1154
1155        if isinstance(agg, exp.WithinGroup):
1156            # Ordered-set aggregates take their input from the ORDER BY key, so the
1157            # condition has to wrap that instead of the aggregate's own argument
1158            if isinstance(agg_arg, (exp.Mode, *exp.PERCENTILES)):
1159                for ordered in agg.expression.expressions:
1160                    key = ordered.this
1161                    key.replace(exp.If(this=cond.copy(), true=key.copy()))
1162
1163                return self.sql(agg)
1164
1165            # Besides the percentile functions, these are the only functions Snowflake
1166            # accepts WITHIN GROUP for, so anything else can't be rewritten correctly
1167            if isinstance(agg_arg, (exp.ArrayAgg, exp.GroupConcat)):
1168                agg_arg = agg_arg.this
1169            else:
1170                self.unsupported("Unable to rewrite FILTER into the aggregate's arguments")
1171                return self.sql(agg)
1172
1173        # `COUNT(*/t.*) FILTER (WHERE cond)` counts qualifying rows, but a star can't be an IFF
1174        # argument: `IFF(cond, *, NULL)` expands to multiple columns once the table has 2+ of
1175        # them, which Snowflake rejects. Use its native COUNT_IF instead.
1176        if isinstance(agg, exp.Count) and isinstance(agg_arg, exp.Expression) and agg_arg.is_star:
1177            return self.func("COUNT_IF", cond)
1178
1179        # `DISTINCT` and `ORDER BY` are part of the aggregate's own argument list, so the
1180        # condition has to wrap the values underneath them rather than the whole clause --
1181        # `IFF(cond, DISTINCT x, NULL)` is not a call any dialect accepts.
1182        if isinstance(agg_arg, exp.Order):
1183            agg_arg = agg_arg.this
1184
1185        if isinstance(agg_arg, exp.Distinct):
1186            targets = agg_arg.expressions
1187        else:
1188            targets = [agg_arg]
1189
1190        for target in targets:
1191            target.replace(exp.If(this=cond.copy(), true=target.copy()))
1192
1193        return self.sql(agg)
def withingroup_sql(self, expression: sqlglot.expressions.core.WithinGroup) -> str:
1195    def withingroup_sql(self, expression: exp.WithinGroup) -> str:
1196        # Snowflake's MODE doesn't support the ordered-set syntax, i.e. it only
1197        # accepts the value to aggregate as an argument: MODE(<expr>)
1198        if isinstance(expression.this, exp.Mode) and not expression.this.this:
1199            order = expression.expression
1200            if isinstance(order, exp.Order) and len(order.expressions) == 1:
1201                return self.sql(exp.Mode(this=order.expressions[0].this))
1202
1203        return super().withingroup_sql(expression)
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
SUPPORTS_MERGE_WHERE
INTERVAL_ALLOWS_PLURAL_FORM
AUTO_REFRESH_BARE_INTERVALS
LIMIT_FETCH
RENAME_TABLE_WITH_DB
GROUPINGS_SEP
SUPPORTS_GROUPING_SETS_AS_SUFFIX
INDEX_ON
INOUT_SEPARATOR
QUERY_HINT_SEP
DUPLICATE_KEY_UPDATE_WITH_SET
LIMIT_IS_TOP
RETURNING_END
EXTRACT_ALLOWS_QUOTES
TZ_TO_WITH_TIME_ZONE
NVL2_SUPPORTED
VALUES_AS_TABLE
ALTER_TABLE_INCLUDE_COLUMN_KEYWORD
UNNEST_WITH_ORDINALITY
SEMI_ANTI_JOIN_WITH_SIDE
COMPUTED_COLUMN_WITH_TYPE
TABLESAMPLE_REQUIRES_PARENS
TABLESAMPLE_SIZE_IS_ROWS
TABLESAMPLE_KEYWORDS
TABLESAMPLE_WITH_METHOD
TABLESAMPLE_SEED_KEYWORD
HISTORICAL_DATA_POST_ALIAS
DATA_TYPE_SPECIFIERS_ALLOWED
ENSURE_BOOLS
CTE_RECURSIVE_KEYWORD_REQUIRED
SUPPORTS_SINGLE_ARG_CONCAT
LAST_DAY_SUPPORTS_DATE_PART
SUPPORTS_TABLE_ALIAS_COLUMNS
SUPPORTS_NAMED_CTE_COLUMNS
UNPIVOT_ALIASES_ARE_IDENTIFIERS
PIVOT_ALIAS_WITH_AS
SUPPORTS_SELECT_INTO
SUPPORTS_UNLOGGED_TABLES
SUPPORTS_CREATE_TABLE_LIKE
SUPPORTS_MODIFY_COLUMN
SUPPORTS_CHANGE_COLUMN
SUPPORTS_ALTER_COLUMN_NULLABILITY
SUPPORTS_ALTER_COLUMN_IF_EXISTS
LIKE_PROPERTY_INSIDE_SCHEMA
MULTI_ARG_DISTINCT
JSON_TYPE_REQUIRED_FOR_EXTRACTION
JSON_PATH_BRACKETED_KEY_SUPPORTED
JSON_PATH_SINGLE_QUOTE_ESCAPE
JSON_PATH_KEY_QUOTED_FORCES_BRACKETS
CAN_IMPLEMENT_ARRAY_ANY
SUPPORTS_TO_NUMBER
SUPPORTS_WINDOW_EXCLUDE
SET_OP_MODIFIERS
COPY_HAS_INTO_KEYWORD
UNICODE_SUBSTITUTE
HEX_FUNC
WITH_PROPERTIES_PREFIX
QUOTE_JSON_PATH
PAD_FILL_PATTERN_IS_REQUIRED
SUPPORTS_UNIX_SECONDS
ALTER_SET_WRAPPED
NORMALIZE_EXTRACT_DATE_PARTS
PARSE_JSON_NAME
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
NAMED_PLACEHOLDER_TOKEN
EXPRESSION_PRECEDES_PROPERTIES_CREATABLES
RESERVED_KEYWORDS
WITH_SEPARATED_COMMENTS
EXCLUDE_COMMENTS
UNWRAPPED_INTERVAL_VALUES
PARAMETERIZABLE_TEXT_TYPES
EXPRESSIONS_WITHOUT_NESTED_CTES
MOD_OPERATOR
MOD_PAREN_PARENT_TYPES
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
generatedasrowcolumnconstraint_sql
periodforsystemtimeconstraint_sql
notnullcolumnconstraint_sql
primarykeycolumnconstraint_sql
uniquecolumnconstraint_sql
inoutcolumnconstraint_sql
create_sql
sequenceproperties_sql
triggerproperties_sql
triggerreferencing_sql
triggerevent_sql
clone_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
directory_sql
delete_sql
drop_sql
set_operation
set_operations
fetch_sql
limitoptions_sql
hint_sql
indexparameters_sql
index_sql
identifier_sql
hex_sql
lowerhex_sql
inputoutputformat_sql
national_sql
partition_sql
properties_sql
root_properties
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
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
offset_limit_modifiers
after_limit_modifiers
schema_sql
schema_columns_sql
star_sql
parameter_sql
sessionparameter_sql
placeholder_sql
subquery_sql
qualify_sql
prewhere_sql
where_sql
partition_by_sql
windowspec_sql
between_sql
bracket_offset_expressions
bracket_sql
all_sql
any_sql
exists_sql
case_sql
constraint_sql
nextvaluefor_sql
extract_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
interval_sql
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
strtodate_sql
parsedatetime_sql
collate_sql
command_sql
comment_sql
mergetreettlaction_sql
mergetreettl_sql
transaction_sql
commit_sql
rollback_sql
altercolumn_sql
modifycolumn_sql
alterindex_sql
alterdiststyle_sql
altersortkey_sql
alterrename_sql
renamecolumn_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
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
jsoncast_sql
try_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
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
nthvalue_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
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
install_sql
get_put_sql
translatecharacters_sql
decodecase_sql
semanticview_sql
getextract_sql
datefromunixdate_sql
space_sql
buildproperty_sql
refreshtriggerproperty_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
casestatement_sql
whileblock_sql
loopblock_sql
repeatblock_sql
leave_sql
iterate_sql
execute_sql
executesql_sql
altermodifysqlsecurity_sql
usingproperty_sql
renameindex_sql