Edit on GitHub

sqlglot.generators.bigquery

  1from __future__ import annotations
  2
  3import logging
  4import re
  5import typing as t
  6from decimal import Decimal
  7from fractions import Fraction
  8
  9from sqlglot import exp, generator, transforms
 10from sqlglot.dialects.dialect import (
 11    arg_max_or_min_no_count,
 12    array_sort_order,
 13    date_add_interval_sql,
 14    datestrtodate_sql,
 15    filter_array_using_unnest,
 16    generate_series_sql,
 17    if_sql,
 18    inline_array_unless_query,
 19    max_or_greatest,
 20    min_or_least,
 21    no_filter_sql,
 22    no_ilike_sql,
 23    regexp_replace_sql,
 24    rename_func,
 25    sha256_sql,
 26    timestrtotime_sql,
 27    ts_or_ds_add_cast,
 28    unit_to_var,
 29    strposition_sql,
 30    groupconcat_sql,
 31    sha2_digest_sql,
 32)
 33from sqlglot.generator import unsupported_args
 34from sqlglot.helper import seq_get
 35
 36logger = logging.getLogger("sqlglot")
 37
 38JSON_EXTRACT_TYPE = t.Union[exp.JSONExtract, exp.JSONExtractScalar, exp.JSONExtractArray]
 39
 40DQUOTES_ESCAPING_JSON_FUNCTIONS = ("JSON_QUERY", "JSON_VALUE", "JSON_QUERY_ARRAY")
 41
 42
 43def _derived_table_values_to_unnest(self: BigQueryGenerator, expression: exp.Values) -> str:
 44    if not expression.find_ancestor(exp.From, exp.Join):
 45        return self.values_sql(expression)
 46
 47    structs = []
 48    alias = expression.args.get("alias")
 49    for tup in expression.find_all(exp.Tuple):
 50        field_aliases = (
 51            alias.columns
 52            if alias and alias.columns
 53            else (f"_c{i}" for i in range(len(tup.expressions)))
 54        )
 55        expressions = [
 56            exp.PropertyEQ(this=exp.to_identifier(name), expression=fld)
 57            for name, fld in zip(field_aliases, tup.expressions)
 58        ]
 59        structs.append(exp.Struct(expressions=expressions))
 60
 61    # Due to `UNNEST_COLUMN_ONLY`, it is expected that the table alias be contained in the columns expression
 62    alias_name_only = exp.TableAlias(columns=[alias.this]) if alias else None
 63    return self.unnest_sql(
 64        exp.Unnest(expressions=[exp.array(*structs, copy=False)], alias=alias_name_only)
 65    )
 66
 67
 68def _returnsproperty_sql(self: BigQueryGenerator, expression: exp.ReturnsProperty) -> str:
 69    this = expression.this
 70    if isinstance(this, exp.Schema):
 71        this = f"{self.sql(this, 'this')} <{self.expressions(this)}>"
 72    else:
 73        this = self.sql(this)
 74    return f"RETURNS {this}"
 75
 76
 77def _create_sql(self: BigQueryGenerator, expression: exp.Create) -> str:
 78    returns = expression.find(exp.ReturnsProperty)
 79    if expression.kind == "FUNCTION" and returns and returns.args.get("is_table"):
 80        expression.set("kind", "TABLE FUNCTION")
 81
 82        if isinstance(expression.expression, (exp.Subquery, exp.Literal)):
 83            expression.set("expression", expression.expression.this)
 84
 85    return self.create_sql(expression)
 86
 87
 88# https://issuetracker.google.com/issues/162294746
 89# workaround for bigquery bug when grouping by an expression and then ordering
 90# WITH x AS (SELECT 1 y)
 91# SELECT y + 1 z
 92# FROM x
 93# GROUP BY x + 1
 94# ORDER by z
 95def _alias_ordered_group(expression: exp.Expr) -> exp.Expr:
 96    if isinstance(expression, exp.Select):
 97        group = expression.args.get("group")
 98        order = expression.args.get("order")
 99
100        if group and order:
101            aliases = {
102                select.this: select.args["alias"]
103                for select in expression.selects
104                if isinstance(select, exp.Alias)
105            }
106
107            for grouped in group.expressions:
108                if grouped.is_int:
109                    continue
110                alias = aliases.get(grouped)
111                if alias:
112                    grouped.replace(exp.column(alias))
113
114    return expression
115
116
117def _pushdown_cte_column_names(expression: exp.Expr) -> exp.Expr:
118    """BigQuery doesn't allow column names when defining a CTE, so we try to push them down."""
119    if isinstance(expression, exp.CTE) and expression.alias_column_names:
120        cte_query = expression.this
121
122        if cte_query.is_star:
123            logger.warning(
124                "Can't push down CTE column names for star queries. Run the query through"
125                " the optimizer or use 'qualify' to expand the star projections first."
126            )
127            return expression
128
129        column_names = expression.alias_column_names
130        expression.args["alias"].set("columns", None)
131
132        for name, select in zip(column_names, cte_query.selects):
133            to_replace = select
134
135            if isinstance(select, exp.Alias):
136                select = select.this
137
138            # Inner aliases are shadowed by the CTE column names
139            to_replace.replace(exp.alias_(select, name))
140
141    return expression
142
143
144def _unnest_explode_generate_series(expression: exp.Expr) -> exp.Expr:
145    """
146    Rewrites exploding GENERATE_SERIES projections into table references, e.g.
147
148        SELECT GENERATE_SERIES(1, 2) AS x           -> SELECT x FROM GENERATE_SERIES(1, 2) AS x
149        SELECT y, GENERATE_SERIES(1, 2) AS x FROM t -> SELECT y, x FROM t CROSS JOIN GENERATE_SERIES(1, 2) AS x
150
151    since BigQuery can't explode in the projection and must unnest it in the FROM clause instead.
152    The resulting table reference is unnested downstream by `transforms.unnest_generate_series`.
153    """
154    if isinstance(expression, exp.Select):
155        for projection in expression.selects:
156            if isinstance(series := projection.unalias(), exp.ExplodingGenerateSeries):
157                column_name = projection.output_name or "_gen_series_value"
158
159                projection.replace(exp.column(column_name))
160                table = exp.Table(
161                    this=series, alias=exp.TableAlias(this=exp.to_identifier(column_name))
162                )
163
164                if expression.args.get("from_"):
165                    expression.join(table, copy=False, join_type="CROSS")
166                else:
167                    expression.set("from_", exp.From(this=table))
168
169    return expression
170
171
172def _array_contains_sql(self: BigQueryGenerator, expression: exp.ArrayContains) -> str:
173    return self.sql(
174        exp.Exists(
175            this=exp.select("1")
176            .from_(exp.Unnest(expressions=[expression.left]).as_("_unnest", table=["_col"]))
177            .where(exp.column("_col").eq(expression.right))
178        )
179    )
180
181
182def _widen_decimal(expression: exp.Expr) -> exp.Expr:
183    # NUMERIC holds at most 29 integer and 9 fractional digits, e.g. DECIMAL(38, 0) needs BIGNUMERIC
184    for node in expression.find_all(exp.DataType):
185        if node.is_type(exp.DType.DECIMAL) and node.expressions:
186            precision, *rest = (param.this for param in node.expressions)
187            scale = rest[0] if rest else exp.Literal.number(0)
188            if precision.is_int and scale.is_int:
189                scale_digits = int(scale.name)
190                if int(precision.name) - scale_digits > 29 or scale_digits > 9:
191                    node.set("this", exp.DType.BIGDECIMAL)
192    return expression
193
194
195def _extract_part(part: str) -> t.Callable[[BigQueryGenerator, exp.Func], str]:
196    return lambda self, e: self.sql(exp.func("EXTRACT", exp.var(part), e.this, copy=False))
197
198
199def _day_of_week_iso_sql(self: BigQueryGenerator, expression: exp.DayOfWeekIso) -> str:
200    # DAYOFWEEK is Sunday=1..Saturday=7, ISO is Monday=1..Sunday=7
201    template: exp.Expr = exp.maybe_parse("(MOD(EXTRACT(DAYOFWEEK FROM :this) + 5, 7) + 1)")
202    return self.sql(exp.replace_placeholders(template, this=expression.this))
203
204
205def _array_agg_fold_sql(
206    self: BigQueryGenerator, expression: exp.ArrayDistinct | exp.ArraySort
207) -> str:
208    distinct = False
209    ignore_nulls = False
210    sort: exp.ArraySort | None = None
211    agg: exp.Expr = expression
212    while isinstance(agg, (exp.ArrayDistinct, exp.ArraySort)) and not agg.expression:
213        if isinstance(agg, exp.ArrayDistinct):
214            distinct = True
215            ignore_nulls |= agg.args.get("check_null") is False
216        elif sort is None:
217            sort = agg
218        agg = agg.this
219
220    order = array_sort_order(sort) if sort else None
221
222    # An ARRAY_AGG with its own ORDER BY or LIMIT can't take another
223    if (
224        not isinstance(agg, exp.ArrayAgg)
225        or isinstance(agg.this, (exp.Order, exp.Limit))
226        or (sort is not None and order is None)
227    ):
228        self.unsupported(
229            f"{expression.sql_name()} is only supported on an ARRAY_AGG without ORDER BY or LIMIT and with a literal order"
230        )
231        return self.func(expression.sql_name(), expression.this, expression.expression)
232
233    value = agg.this
234    if isinstance(value, exp.Distinct):
235        distinct, value = True, value.expressions[0]
236
237    folded: exp.Expr = exp.Distinct(expressions=[value]) if distinct else value
238    if order is not None:
239        asc, nulls_first = order
240        keys = [exp.Ordered(this=value.copy(), desc=not asc or None, nulls_first=asc)]
241
242        # Aggregate ORDER BY only puts NULLs first when ascending, so other placements need a key
243        if not ignore_nulls and nulls_first is not None and nulls_first != asc:
244            if distinct:
245                self.unsupported("ARRAY_AGG(DISTINCT) only puts NULLs first when ascending")
246            else:
247                is_null = value.copy().is_(exp.null())
248                keys.insert(
249                    0,
250                    exp.Ordered(
251                        this=is_null, desc=nulls_first or None, nulls_first=not nulls_first
252                    ),
253                )
254        folded = exp.Order(this=folded, expressions=keys)
255    agg.set("this", folded)
256    if ignore_nulls:
257        # All-NULL input yields [], but an empty input still yields NULL.
258        agg = exp.If(
259            this=exp.Count(this=exp.Star()).eq(0),
260            true=exp.null(),
261            false=exp.func("COALESCE", exp.IgnoreNulls(this=agg), exp.array()),
262        )
263    return self.sql(agg)
264
265
266def _ts_or_ds_add_sql(self: BigQueryGenerator, expression: exp.TsOrDsAdd) -> str:
267    return date_add_interval_sql("DATE", "ADD")(self, ts_or_ds_add_cast(expression))
268
269
270def _ts_or_ds_diff_sql(self: BigQueryGenerator, expression: exp.TsOrDsDiff) -> str:
271    expression.this.replace(exp.cast(expression.this, exp.DType.TIMESTAMP))
272    expression.expression.replace(exp.cast(expression.expression, exp.DType.TIMESTAMP))
273    unit = unit_to_var(expression)
274    return self.func("DATE_DIFF", expression.this, expression.expression, unit)
275
276
277def _unix_to_time_sql(self: BigQueryGenerator, expression: exp.UnixToTime) -> str:
278    scale = expression.args.get("scale")
279    timestamp = expression.this
280
281    if scale in (None, exp.UnixToTime.SECONDS):
282        return self.func("TIMESTAMP_SECONDS", timestamp)
283    if scale == exp.UnixToTime.MILLIS:
284        return self.func("TIMESTAMP_MILLIS", timestamp)
285    if scale == exp.UnixToTime.MICROS:
286        return self.func("TIMESTAMP_MICROS", timestamp)
287
288    unix_seconds = exp.cast(
289        exp.Div(this=timestamp, expression=exp.func("POW", 10, scale)), exp.DType.BIGINT
290    )
291    return self.func("TIMESTAMP_SECONDS", unix_seconds)
292
293
294def _str_to_datetime_sql(self: BigQueryGenerator, expression: exp.StrToDate | exp.StrToTime) -> str:
295    this = self.sql(expression, "this")
296    dtype = "DATE" if isinstance(expression, exp.StrToDate) else "TIMESTAMP"
297
298    if expression.args.get("safe"):
299        fmt = self.format_time(
300            expression,
301            self.dialect.INVERSE_FORMAT_MAPPING,
302            self.dialect.INVERSE_FORMAT_TRIE,
303        )
304        return f"SAFE_CAST({this} AS {dtype} FORMAT {fmt})"
305
306    fmt = self.format_time(expression)
307    return self.func(f"PARSE_{dtype}", fmt, this, expression.args.get("zone"))
308
309
310@unsupported_args("ins_cost", "del_cost", "sub_cost")
311def _levenshtein_sql(self: BigQueryGenerator, expression: exp.Levenshtein) -> str:
312    max_dist = expression.args.get("max_dist")
313    if max_dist:
314        max_dist = exp.Kwarg(this=exp.var("max_distance"), expression=max_dist)
315
316    return self.func("EDIT_DISTANCE", expression.this, expression.expression, max_dist)
317
318
319def _json_extract_sql(self: BigQueryGenerator, expression: JSON_EXTRACT_TYPE) -> str:
320    upper = self.function_name(expression).upper()
321
322    dquote_escaping = upper in DQUOTES_ESCAPING_JSON_FUNCTIONS
323
324    if dquote_escaping:
325        self._quote_json_path_key_using_brackets = False
326
327    sql = rename_func(upper)(self, expression)
328
329    if dquote_escaping:
330        self._quote_json_path_key_using_brackets = True
331
332    return sql
333
334
335class BigQueryGenerator(generator.Generator):
336    TRY_SUPPORTED = False
337    SUPPORTS_UESCAPE = False
338    SUPPORTS_DECODE_CASE = False
339    INTERVAL_ALLOWS_PLURAL_FORM = False
340    JOIN_HINTS = False
341    QUERY_HINTS = False
342    TABLE_HINTS = False
343    LIMIT_FETCH = "LIMIT"
344    RENAME_TABLE_WITH_DB = False
345    NVL2_SUPPORTED = False
346    UNNEST_WITH_ORDINALITY = False
347    CAST_TO_INT_ROUNDS = True
348    COLLATE_IS_FUNC = True
349    LIMIT_ONLY_LITERALS = True
350    SUPPORTS_TABLE_ALIAS_COLUMNS = False
351    SUPPORTS_NAMED_CTE_COLUMNS = False
352    UNPIVOT_ALIASES_ARE_IDENTIFIERS = False
353    JSON_KEY_VALUE_PAIR_SEP = ","
354    NULL_ORDERING_SUPPORTED: bool | None = False
355    IGNORE_NULLS_IN_FUNC = True
356    JSON_PATH_SINGLE_QUOTE_ESCAPE = True
357    CAN_IMPLEMENT_ARRAY_ANY = True
358    SUPPORTS_TO_NUMBER = False
359    NAMED_PLACEHOLDER_TOKEN = "@"
360    HEX_FUNC = "TO_HEX"
361    WITH_PROPERTIES_PREFIX = "OPTIONS"
362    SUPPORTS_EXPLODING_PROJECTIONS = False
363    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
364    SUPPORTS_UNIX_SECONDS = True
365    DECLARE_DEFAULT_ASSIGNMENT = "DEFAULT"
366
367    SAFE_JSON_PATH_KEY_RE = re.compile(r"^[\-\w]*$")
368
369    WINDOW_FUNCS_WITH_NULL_ORDERING = (
370        exp.CumeDist,
371        exp.DenseRank,
372        exp.FirstValue,
373        exp.Lag,
374        exp.LastValue,
375        exp.Lead,
376        exp.NthValue,
377        exp.Ntile,
378        exp.PercentRank,
379        exp.Rank,
380        exp.RowNumber,
381    )
382
383    TS_OR_DS_TYPES: t.ClassVar = (
384        exp.TsOrDsToDatetime,
385        exp.TsOrDsToTimestamp,
386        exp.TsOrDsToTime,
387        exp.TsOrDsToDate,
388    )
389
390    TRANSFORMS = {
391        **generator.Generator.TRANSFORMS,
392        exp.AIEmbed: rename_func("EMBED"),
393        exp.AIGenerate: rename_func("GENERATE"),
394        exp.AISimilarity: rename_func("SIMILARITY"),
395        exp.ApproxTopK: rename_func("APPROX_TOP_COUNT"),
396        exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"),
397        exp.ArgMax: arg_max_or_min_no_count("MAX_BY"),
398        exp.ArgMin: arg_max_or_min_no_count("MIN_BY"),
399        exp.Array: inline_array_unless_query,
400        exp.ArrayContains: _array_contains_sql,
401        exp.ArrayDistinct: _array_agg_fold_sql,
402        exp.ArraySort: _array_agg_fold_sql,
403        exp.ArrayFilter: filter_array_using_unnest,
404        exp.ArrayRemove: filter_array_using_unnest,
405        exp.BitwiseAndAgg: rename_func("BIT_AND"),
406        exp.BitwiseOrAgg: rename_func("BIT_OR"),
407        exp.BitwiseXorAgg: rename_func("BIT_XOR"),
408        exp.BitwiseCount: rename_func("BIT_COUNT"),
409        exp.ByteLength: rename_func("BYTE_LENGTH"),
410        exp.Cast: transforms.preprocess(
411            [_widen_decimal, transforms.remove_precision_parameterized_types]
412        ),
413        exp.CollateProperty: lambda self, e: (
414            f"DEFAULT COLLATE {self.sql(e, 'this')}"
415            if e.args.get("default")
416            else f"COLLATE {self.sql(e, 'this')}"
417        ),
418        exp.Commit: lambda *_: "COMMIT TRANSACTION",
419        exp.CountIf: rename_func("COUNTIF"),
420        exp.Create: _create_sql,
421        exp.CTE: transforms.preprocess([_pushdown_cte_column_names]),
422        exp.DateAdd: date_add_interval_sql("DATE", "ADD"),
423        exp.DateDiff: lambda self, e: self.func("DATE_DIFF", e.this, e.expression, unit_to_var(e)),
424        exp.DateFromParts: rename_func("DATE"),
425        exp.DateStrToDate: datestrtodate_sql,
426        exp.DateSub: date_add_interval_sql("DATE", "SUB"),
427        exp.DatetimeAdd: date_add_interval_sql("DATETIME", "ADD"),
428        exp.DatetimeSub: date_add_interval_sql("DATETIME", "SUB"),
429        exp.DateFromUnixDate: rename_func("DATE_FROM_UNIX_DATE"),
430        exp.Day: _extract_part("DAY"),
431        exp.DayOfMonth: _extract_part("DAY"),
432        exp.DayOfWeekIso: _day_of_week_iso_sql,
433        exp.DayOfYear: _extract_part("DAYOFYEAR"),
434        exp.Filter: no_filter_sql,
435        exp.FromTimeZone: lambda self, e: self.func(
436            "DATETIME", self.func("TIMESTAMP", e.this, e.args.get("zone")), "'UTC'"
437        ),
438        exp.GenerateSeries: generate_series_sql("GENERATE_ARRAY"),
439        exp.GroupConcat: lambda self, e: groupconcat_sql(
440            self, e, func_name="STRING_AGG", within_group=False, sep=None
441        ),
442        exp.Hex: lambda self, e: self.func("UPPER", self.func("TO_HEX", self.sql(e, "this"))),
443        exp.HexString: lambda self, e: self.hexstring_sql(e, binary_function_repr="FROM_HEX"),
444        exp.Hour: _extract_part("HOUR"),
445        exp.If: if_sql(false_value="NULL"),
446        exp.ILike: no_ilike_sql,
447        exp.IntDiv: rename_func("DIV"),
448        exp.Int64: rename_func("INT64"),
449        exp.JSONBool: rename_func("BOOL"),
450        exp.JSONExtract: _json_extract_sql,
451        exp.JSONExtractArray: _json_extract_sql,
452        exp.JSONExtractScalar: _json_extract_sql,
453        exp.JSONFormat: lambda self, e: self.func(
454            "TO_JSON" if e.args.get("to_json") else "TO_JSON_STRING",
455            e.this,
456            e.args.get("options"),
457        ),
458        exp.JSONKeysAtDepth: rename_func("JSON_KEYS"),
459        exp.JSONValueArray: rename_func("JSON_VALUE_ARRAY"),
460        exp.Levenshtein: _levenshtein_sql,
461        exp.Max: max_or_greatest,
462        exp.MD5: lambda self, e: self.func("TO_HEX", self.func("MD5", e.this)),
463        exp.MD5Digest: rename_func("MD5"),
464        exp.Min: min_or_least,
465        exp.Month: _extract_part("MONTH"),
466        exp.Normalize: lambda self, e: self.func(
467            "NORMALIZE_AND_CASEFOLD" if e.args.get("is_casefold") else "NORMALIZE",
468            e.this,
469            e.args.get("form"),
470        ),
471        exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}",
472        exp.Quarter: _extract_part("QUARTER"),
473        exp.RegexpExtract: lambda self, e: self.func(
474            "REGEXP_EXTRACT",
475            e.this,
476            e.expression,
477            e.args.get("position"),
478            e.args.get("occurrence"),
479        ),
480        exp.RegexpExtractAll: lambda self, e: self.func("REGEXP_EXTRACT_ALL", e.this, e.expression),
481        exp.RegexpReplace: regexp_replace_sql,
482        exp.RegexpLike: rename_func("REGEXP_CONTAINS"),
483        exp.ReturnsProperty: _returnsproperty_sql,
484        exp.Rollback: lambda *_: "ROLLBACK TRANSACTION",
485        exp.ParseTime: lambda self, e: self.func("PARSE_TIME", self.format_time(e), e.this),
486        exp.ParseDatetime: lambda self, e: self.func("PARSE_DATETIME", self.format_time(e), e.this),
487        exp.Select: transforms.preprocess(
488            [
489                _unnest_explode_generate_series,
490                transforms.explode_projection_to_unnest(),
491                transforms.unqualify_unnest,
492                transforms.eliminate_distinct_on,
493                _alias_ordered_group,
494                transforms.eliminate_semi_and_anti_joins,
495            ]
496        ),
497        exp.SHA: rename_func("SHA1"),
498        exp.SHA2: sha256_sql,
499        exp.SHA1Digest: rename_func("SHA1"),
500        exp.SHA2Digest: sha2_digest_sql,
501        exp.StabilityProperty: lambda self, e: (
502            "DETERMINISTIC" if e.name == "IMMUTABLE" else "NOT DETERMINISTIC"
503        ),
504        exp.String: rename_func("STRING"),
505        exp.StrPosition: lambda self, e: strposition_sql(
506            self, e, func_name="INSTR", supports_position=True, supports_occurrence=True
507        ),
508        exp.StrToDate: _str_to_datetime_sql,
509        exp.StrToTime: _str_to_datetime_sql,
510        exp.SessionUser: lambda *_: "SESSION_USER()",
511        exp.Table: transforms.preprocess([transforms.unnest_generate_series]),
512        exp.TimeAdd: date_add_interval_sql("TIME", "ADD"),
513        exp.TimeFromParts: rename_func("TIME"),
514        exp.TimestampFromParts: rename_func("DATETIME"),
515        exp.TimeSub: date_add_interval_sql("TIME", "SUB"),
516        exp.TimestampAdd: date_add_interval_sql("TIMESTAMP", "ADD"),
517        exp.TimestampDiff: rename_func("TIMESTAMP_DIFF"),
518        exp.TimestampSub: date_add_interval_sql("TIMESTAMP", "SUB"),
519        exp.TimeStrToTime: timestrtotime_sql,
520        exp.Transaction: lambda *_: "BEGIN TRANSACTION",
521        exp.TsOrDsAdd: _ts_or_ds_add_sql,
522        exp.TsOrDsDiff: _ts_or_ds_diff_sql,
523        exp.TsOrDsToTime: rename_func("TIME"),
524        exp.TsOrDsToDatetime: rename_func("DATETIME"),
525        exp.TsOrDsToTimestamp: rename_func("TIMESTAMP"),
526        exp.Unhex: rename_func("FROM_HEX"),
527        exp.UnixDate: rename_func("UNIX_DATE"),
528        exp.UnixToTime: _unix_to_time_sql,
529        exp.Uuid: lambda *_: "GENERATE_UUID()",
530        exp.Values: _derived_table_values_to_unnest,
531        exp.VariancePop: rename_func("VAR_POP"),
532        exp.WeekOfYear: _extract_part("ISOWEEK"),
533        exp.Year: _extract_part("YEAR"),
534        exp.SafeDivide: rename_func("SAFE_DIVIDE"),
535    }
536
537    SUPPORTED_JSON_PATH_PARTS = {
538        exp.JSONPathKey,
539        exp.JSONPathRoot,
540        exp.JSONPathSubscript,
541    }
542
543    TYPE_MAPPING = {
544        **generator.Generator.TYPE_MAPPING,
545        exp.DType.BIGDECIMAL: "BIGNUMERIC",
546        exp.DType.BIGINT: "INT64",
547        exp.DType.BINARY: "BYTES",
548        exp.DType.BLOB: "BYTES",
549        exp.DType.BOOLEAN: "BOOL",
550        exp.DType.CHAR: "STRING",
551        exp.DType.DECIMAL: "NUMERIC",
552        exp.DType.DOUBLE: "FLOAT64",
553        exp.DType.FLOAT: "FLOAT64",
554        exp.DType.INT: "INT64",
555        exp.DType.NCHAR: "STRING",
556        exp.DType.NVARCHAR: "STRING",
557        exp.DType.SMALLINT: "INT64",
558        exp.DType.TEXT: "STRING",
559        exp.DType.TIMESTAMP: "DATETIME",
560        exp.DType.TIMESTAMPNTZ: "DATETIME",
561        exp.DType.TIMESTAMPTZ: "TIMESTAMP",
562        exp.DType.TIMESTAMPLTZ: "TIMESTAMP",
563        exp.DType.TINYINT: "INT64",
564        exp.DType.ROWVERSION: "BYTES",
565        exp.DType.UUID: "STRING",
566        exp.DType.VARBINARY: "BYTES",
567        exp.DType.VARCHAR: "STRING",
568        exp.DType.VARIANT: "ANY TYPE",
569    }
570
571    PROPERTIES_LOCATION = {
572        **generator.Generator.PROPERTIES_LOCATION,
573        exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,
574        exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
575    }
576
577    # WINDOW comes after QUALIFY
578    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#window_clause
579    # BigQuery requires QUALIFY before WINDOW
580    AFTER_HAVING_MODIFIER_TRANSFORMS = {
581        "qualify": generator.AFTER_HAVING_MODIFIER_TRANSFORMS["qualify"],
582        "windows": generator.AFTER_HAVING_MODIFIER_TRANSFORMS["windows"],
583    }
584
585    # from: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
586    RESERVED_KEYWORDS = {
587        "all",
588        "and",
589        "any",
590        "array",
591        "as",
592        "asc",
593        "assert_rows_modified",
594        "at",
595        "between",
596        "by",
597        "case",
598        "cast",
599        "collate",
600        "contains",
601        "create",
602        "cross",
603        "cube",
604        "current",
605        "default",
606        "define",
607        "desc",
608        "distinct",
609        "else",
610        "end",
611        "enum",
612        "escape",
613        "except",
614        "exclude",
615        "exists",
616        "extract",
617        "false",
618        "fetch",
619        "following",
620        "for",
621        "from",
622        "full",
623        "group",
624        "grouping",
625        "groups",
626        "hash",
627        "having",
628        "if",
629        "ignore",
630        "in",
631        "inner",
632        "intersect",
633        "interval",
634        "into",
635        "is",
636        "join",
637        "lateral",
638        "left",
639        "like",
640        "limit",
641        "lookup",
642        "merge",
643        "natural",
644        "new",
645        "no",
646        "not",
647        "null",
648        "nulls",
649        "of",
650        "on",
651        "or",
652        "order",
653        "outer",
654        "over",
655        "partition",
656        "preceding",
657        "proto",
658        "qualify",
659        "range",
660        "recursive",
661        "respect",
662        "right",
663        "rollup",
664        "rows",
665        "select",
666        "set",
667        "some",
668        "struct",
669        "tablesample",
670        "then",
671        "to",
672        "treat",
673        "true",
674        "unbounded",
675        "union",
676        "unnest",
677        "using",
678        "when",
679        "where",
680        "window",
681        "with",
682        "within",
683    }
684
685    def frombase_sql(self, expression: exp.FromBase) -> str:
686        # Hex is the only base BigQuery can cast from, and the sign goes before the prefix, e.g. -0xff
687        if expression.expression.name != "16":
688            self.unsupported("FROM_BASE is only supported for base 16")
689            return self.function_fallback_sql(expression)
690
691        prefixed = exp.func(
692            "REGEXP_REPLACE",
693            expression.this,
694            exp.Literal.string("^([+-]?)"),
695            exp.Literal.string("\\10x"),
696            copy=False,
697        )
698        return self.sql(exp.cast(prefixed, exp.DType.BIGINT, copy=False))
699
700    def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str:
701        node: exp.Expr = expression
702        while node.parent and not isinstance(node.parent, (exp.Window, exp.AggFunc, exp.Select)):
703            node = node.parent
704        if isinstance(node.parent, exp.Window) and node.arg_key == "this":
705            self.unsupported("APPROX_QUANTILES does not support window functions")
706            return self.function_fallback_sql(expression)
707
708        # The k/n quantile is APPROX_QUANTILES(x, n)[k], e.g. 0.95 -> n = 20, k = 19. n is at most
709        # 100,000, so the quantile has at most 5 decimal places, and Decimal comparisons are exact
710        quantile = expression.args["quantile"]
711        weight = expression.args.get("weight")
712        value = Decimal(quantile.to_py()) if quantile.is_number else None
713        if (
714            value is None
715            or not 0 <= value <= 1
716            or round(value, 5) != value
717            or (weight and (not weight.is_number or weight.to_py() != 1))
718        ):
719            self.unsupported("APPROX_QUANTILE needs a literal quantile and no weight")
720            return self.function_fallback_sql(expression)
721
722        fraction = Fraction(value)
723        quantiles = exp.func("APPROX_QUANTILES", expression.this, fraction.denominator, copy=False)
724        return self.sql(quantiles[fraction.numerator])
725
726    def weekstart_sql(self, expression: exp.WeekStart) -> str:
727        if expression.this.name.upper() == "SUNDAY":
728            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
729            return "WEEK"
730
731        return self.func("WEEK", expression.this)
732
733    def datetrunc_sql(self, expression: exp.DateTrunc) -> str:
734        unit = expression.unit
735        unit_sql = unit.name if unit.is_string else self.sql(unit)
736        return self.func("DATE_TRUNC", expression.this, unit_sql, expression.args.get("zone"))
737
738    def mod_sql(self, expression: exp.Mod) -> str:
739        this = expression.this
740        expr = expression.expression
741        return self.func(
742            "MOD",
743            this.unnest() if isinstance(this, exp.Paren) else this,
744            expr.unnest() if isinstance(expr, exp.Paren) else expr,
745        )
746
747    def column_parts(self, expression: exp.Column) -> str:
748        if expression.meta_get("quoted_column"):
749            # If a column reference is of the form `dataset.table`.name, we need
750            # to preserve the quoted table path, otherwise the reference breaks
751            table_parts = ".".join(p.name for p in expression.parts[:-1])
752            table_path = self.sql(exp.Identifier(this=table_parts, quoted=True))
753            return f"{table_path}.{self.sql(expression, 'this')}"
754
755        return super().column_parts(expression)
756
757    def table_parts(self, expression: exp.Table) -> str:
758        # Depending on the context, `x.y` may not resolve to the same data source as `x`.`y`, so
759        # we need to make sure the correct quoting is used in each case.
760        #
761        # For example, if there is a CTE x that clashes with a schema name, then the former will
762        # return the table y in that schema, whereas the latter will return the CTE's y column:
763        #
764        # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x.y`   -> cross join
765        # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x`.`y` -> implicit unnest
766        if expression.meta_get("quoted_table"):
767            table_parts = ".".join(p.name for p in expression.parts)
768            return self.sql(exp.Identifier(this=table_parts, quoted=True))
769
770        return super().table_parts(expression)
771
772    def timetostr_sql(self, expression: exp.TimeToStr) -> str:
773        this = expression.this
774        if isinstance(this, exp.TsOrDsToDatetime):
775            func_name = "FORMAT_DATETIME"
776        elif isinstance(this, exp.TsOrDsToTimestamp):
777            func_name = "FORMAT_TIMESTAMP"
778        elif isinstance(this, exp.TsOrDsToTime):
779            func_name = "FORMAT_TIME"
780        else:
781            func_name = "FORMAT_DATE"
782
783        time_expr = this if isinstance(this, self.TS_OR_DS_TYPES) else expression
784        return self.func(
785            func_name, self.format_time(expression), time_expr.this, expression.args.get("zone")
786        )
787
788    def eq_sql(self, expression: exp.EQ) -> str:
789        # Operands of = cannot be NULL in BigQuery
790        if isinstance(expression.left, exp.Null) or isinstance(expression.right, exp.Null):
791            if not isinstance(expression.parent, exp.Update):
792                return "NULL"
793
794        return self.binary(expression, "=")
795
796    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
797        parent = expression.parent
798
799        # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).
800        # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.
801        if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"):
802            return self.func(
803                "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone"))
804            )
805
806        return super().attimezone_sql(expression)
807
808    def trycast_sql(self, expression: exp.TryCast) -> str:
809        return self.cast_sql(expression, safe_prefix="SAFE_")
810
811    def bracket_sql(self, expression: exp.Bracket) -> str:
812        this = expression.this
813        expressions = expression.expressions
814
815        if len(expressions) == 1 and this and this.is_type(exp.DType.STRUCT):
816            arg = expressions[0]
817            if arg.type is None:
818                from sqlglot.optimizer.annotate_types import annotate_types
819
820                arg = annotate_types(arg, dialect=self.dialect)
821
822            if arg.type and arg.type.this in exp.DataType.TEXT_TYPES:
823                # BQ doesn't support bracket syntax with string values for structs
824                return f"{self.sql(this)}.{arg.name}"
825
826        expressions_sql = self.expressions(expression, flat=True)
827        offset = expression.args.get("offset")
828
829        if offset == 0:
830            expressions_sql = f"OFFSET({expressions_sql})"
831        elif offset == 1:
832            expressions_sql = f"ORDINAL({expressions_sql})"
833        elif offset is not None:
834            self.unsupported(f"Unsupported array offset: {offset}")
835
836        if expression.args.get("safe"):
837            expressions_sql = f"SAFE_{expressions_sql}"
838
839        return f"{self.sql(this)}[{expressions_sql}]"
840
841    def in_unnest_op(self, expression: exp.Unnest) -> str:
842        return self.sql(expression)
843
844    def version_sql(self, expression: exp.Version) -> str:
845        if expression.name == "TIMESTAMP":
846            expression.set("this", "SYSTEM_TIME")
847        return super().version_sql(expression)
848
849    def contains_sql(self, expression: exp.Contains) -> str:
850        this = expression.this
851        expr = expression.expression
852
853        if isinstance(this, exp.Lower) and isinstance(expr, exp.Lower):
854            this = this.this
855            expr = expr.this
856
857        return self.func("CONTAINS_SUBSTR", this, expr, expression.args.get("json_scope"))
858
859    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
860        if expression.args.get("is_literal"):
861            return f"JSON {self.sql(expression, 'this')}"
862        return super().parsejson_sql(expression)
863
864    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
865        this = expression.this
866
867        # This ensures that inline type-annotated ARRAY literals like ARRAY<INT64>[1, 2, 3]
868        # are roundtripped unaffected. The inner check excludes ARRAY(SELECT ...) expressions,
869        # because they aren't literals and so the above syntax is invalid BigQuery.
870        if isinstance(this, exp.Array):
871            elem = seq_get(this.expressions, 0)
872            if not (elem and elem.find(exp.Query)):
873                return f"{self.sql(expression, 'to')}{self.sql(this)}"
874
875        return super().cast_sql(expression, safe_prefix=safe_prefix)
876
877    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
878        if expression.this:
879            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
880            return ""
881        return self.op_expressions("CLUSTER BY", expression)
logger = <Logger sqlglot (WARNING)>
DQUOTES_ESCAPING_JSON_FUNCTIONS = ('JSON_QUERY', 'JSON_VALUE', 'JSON_QUERY_ARRAY')
class BigQueryGenerator(sqlglot.generator.Generator):
336class BigQueryGenerator(generator.Generator):
337    TRY_SUPPORTED = False
338    SUPPORTS_UESCAPE = False
339    SUPPORTS_DECODE_CASE = False
340    INTERVAL_ALLOWS_PLURAL_FORM = False
341    JOIN_HINTS = False
342    QUERY_HINTS = False
343    TABLE_HINTS = False
344    LIMIT_FETCH = "LIMIT"
345    RENAME_TABLE_WITH_DB = False
346    NVL2_SUPPORTED = False
347    UNNEST_WITH_ORDINALITY = False
348    CAST_TO_INT_ROUNDS = True
349    COLLATE_IS_FUNC = True
350    LIMIT_ONLY_LITERALS = True
351    SUPPORTS_TABLE_ALIAS_COLUMNS = False
352    SUPPORTS_NAMED_CTE_COLUMNS = False
353    UNPIVOT_ALIASES_ARE_IDENTIFIERS = False
354    JSON_KEY_VALUE_PAIR_SEP = ","
355    NULL_ORDERING_SUPPORTED: bool | None = False
356    IGNORE_NULLS_IN_FUNC = True
357    JSON_PATH_SINGLE_QUOTE_ESCAPE = True
358    CAN_IMPLEMENT_ARRAY_ANY = True
359    SUPPORTS_TO_NUMBER = False
360    NAMED_PLACEHOLDER_TOKEN = "@"
361    HEX_FUNC = "TO_HEX"
362    WITH_PROPERTIES_PREFIX = "OPTIONS"
363    SUPPORTS_EXPLODING_PROJECTIONS = False
364    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
365    SUPPORTS_UNIX_SECONDS = True
366    DECLARE_DEFAULT_ASSIGNMENT = "DEFAULT"
367
368    SAFE_JSON_PATH_KEY_RE = re.compile(r"^[\-\w]*$")
369
370    WINDOW_FUNCS_WITH_NULL_ORDERING = (
371        exp.CumeDist,
372        exp.DenseRank,
373        exp.FirstValue,
374        exp.Lag,
375        exp.LastValue,
376        exp.Lead,
377        exp.NthValue,
378        exp.Ntile,
379        exp.PercentRank,
380        exp.Rank,
381        exp.RowNumber,
382    )
383
384    TS_OR_DS_TYPES: t.ClassVar = (
385        exp.TsOrDsToDatetime,
386        exp.TsOrDsToTimestamp,
387        exp.TsOrDsToTime,
388        exp.TsOrDsToDate,
389    )
390
391    TRANSFORMS = {
392        **generator.Generator.TRANSFORMS,
393        exp.AIEmbed: rename_func("EMBED"),
394        exp.AIGenerate: rename_func("GENERATE"),
395        exp.AISimilarity: rename_func("SIMILARITY"),
396        exp.ApproxTopK: rename_func("APPROX_TOP_COUNT"),
397        exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"),
398        exp.ArgMax: arg_max_or_min_no_count("MAX_BY"),
399        exp.ArgMin: arg_max_or_min_no_count("MIN_BY"),
400        exp.Array: inline_array_unless_query,
401        exp.ArrayContains: _array_contains_sql,
402        exp.ArrayDistinct: _array_agg_fold_sql,
403        exp.ArraySort: _array_agg_fold_sql,
404        exp.ArrayFilter: filter_array_using_unnest,
405        exp.ArrayRemove: filter_array_using_unnest,
406        exp.BitwiseAndAgg: rename_func("BIT_AND"),
407        exp.BitwiseOrAgg: rename_func("BIT_OR"),
408        exp.BitwiseXorAgg: rename_func("BIT_XOR"),
409        exp.BitwiseCount: rename_func("BIT_COUNT"),
410        exp.ByteLength: rename_func("BYTE_LENGTH"),
411        exp.Cast: transforms.preprocess(
412            [_widen_decimal, transforms.remove_precision_parameterized_types]
413        ),
414        exp.CollateProperty: lambda self, e: (
415            f"DEFAULT COLLATE {self.sql(e, 'this')}"
416            if e.args.get("default")
417            else f"COLLATE {self.sql(e, 'this')}"
418        ),
419        exp.Commit: lambda *_: "COMMIT TRANSACTION",
420        exp.CountIf: rename_func("COUNTIF"),
421        exp.Create: _create_sql,
422        exp.CTE: transforms.preprocess([_pushdown_cte_column_names]),
423        exp.DateAdd: date_add_interval_sql("DATE", "ADD"),
424        exp.DateDiff: lambda self, e: self.func("DATE_DIFF", e.this, e.expression, unit_to_var(e)),
425        exp.DateFromParts: rename_func("DATE"),
426        exp.DateStrToDate: datestrtodate_sql,
427        exp.DateSub: date_add_interval_sql("DATE", "SUB"),
428        exp.DatetimeAdd: date_add_interval_sql("DATETIME", "ADD"),
429        exp.DatetimeSub: date_add_interval_sql("DATETIME", "SUB"),
430        exp.DateFromUnixDate: rename_func("DATE_FROM_UNIX_DATE"),
431        exp.Day: _extract_part("DAY"),
432        exp.DayOfMonth: _extract_part("DAY"),
433        exp.DayOfWeekIso: _day_of_week_iso_sql,
434        exp.DayOfYear: _extract_part("DAYOFYEAR"),
435        exp.Filter: no_filter_sql,
436        exp.FromTimeZone: lambda self, e: self.func(
437            "DATETIME", self.func("TIMESTAMP", e.this, e.args.get("zone")), "'UTC'"
438        ),
439        exp.GenerateSeries: generate_series_sql("GENERATE_ARRAY"),
440        exp.GroupConcat: lambda self, e: groupconcat_sql(
441            self, e, func_name="STRING_AGG", within_group=False, sep=None
442        ),
443        exp.Hex: lambda self, e: self.func("UPPER", self.func("TO_HEX", self.sql(e, "this"))),
444        exp.HexString: lambda self, e: self.hexstring_sql(e, binary_function_repr="FROM_HEX"),
445        exp.Hour: _extract_part("HOUR"),
446        exp.If: if_sql(false_value="NULL"),
447        exp.ILike: no_ilike_sql,
448        exp.IntDiv: rename_func("DIV"),
449        exp.Int64: rename_func("INT64"),
450        exp.JSONBool: rename_func("BOOL"),
451        exp.JSONExtract: _json_extract_sql,
452        exp.JSONExtractArray: _json_extract_sql,
453        exp.JSONExtractScalar: _json_extract_sql,
454        exp.JSONFormat: lambda self, e: self.func(
455            "TO_JSON" if e.args.get("to_json") else "TO_JSON_STRING",
456            e.this,
457            e.args.get("options"),
458        ),
459        exp.JSONKeysAtDepth: rename_func("JSON_KEYS"),
460        exp.JSONValueArray: rename_func("JSON_VALUE_ARRAY"),
461        exp.Levenshtein: _levenshtein_sql,
462        exp.Max: max_or_greatest,
463        exp.MD5: lambda self, e: self.func("TO_HEX", self.func("MD5", e.this)),
464        exp.MD5Digest: rename_func("MD5"),
465        exp.Min: min_or_least,
466        exp.Month: _extract_part("MONTH"),
467        exp.Normalize: lambda self, e: self.func(
468            "NORMALIZE_AND_CASEFOLD" if e.args.get("is_casefold") else "NORMALIZE",
469            e.this,
470            e.args.get("form"),
471        ),
472        exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}",
473        exp.Quarter: _extract_part("QUARTER"),
474        exp.RegexpExtract: lambda self, e: self.func(
475            "REGEXP_EXTRACT",
476            e.this,
477            e.expression,
478            e.args.get("position"),
479            e.args.get("occurrence"),
480        ),
481        exp.RegexpExtractAll: lambda self, e: self.func("REGEXP_EXTRACT_ALL", e.this, e.expression),
482        exp.RegexpReplace: regexp_replace_sql,
483        exp.RegexpLike: rename_func("REGEXP_CONTAINS"),
484        exp.ReturnsProperty: _returnsproperty_sql,
485        exp.Rollback: lambda *_: "ROLLBACK TRANSACTION",
486        exp.ParseTime: lambda self, e: self.func("PARSE_TIME", self.format_time(e), e.this),
487        exp.ParseDatetime: lambda self, e: self.func("PARSE_DATETIME", self.format_time(e), e.this),
488        exp.Select: transforms.preprocess(
489            [
490                _unnest_explode_generate_series,
491                transforms.explode_projection_to_unnest(),
492                transforms.unqualify_unnest,
493                transforms.eliminate_distinct_on,
494                _alias_ordered_group,
495                transforms.eliminate_semi_and_anti_joins,
496            ]
497        ),
498        exp.SHA: rename_func("SHA1"),
499        exp.SHA2: sha256_sql,
500        exp.SHA1Digest: rename_func("SHA1"),
501        exp.SHA2Digest: sha2_digest_sql,
502        exp.StabilityProperty: lambda self, e: (
503            "DETERMINISTIC" if e.name == "IMMUTABLE" else "NOT DETERMINISTIC"
504        ),
505        exp.String: rename_func("STRING"),
506        exp.StrPosition: lambda self, e: strposition_sql(
507            self, e, func_name="INSTR", supports_position=True, supports_occurrence=True
508        ),
509        exp.StrToDate: _str_to_datetime_sql,
510        exp.StrToTime: _str_to_datetime_sql,
511        exp.SessionUser: lambda *_: "SESSION_USER()",
512        exp.Table: transforms.preprocess([transforms.unnest_generate_series]),
513        exp.TimeAdd: date_add_interval_sql("TIME", "ADD"),
514        exp.TimeFromParts: rename_func("TIME"),
515        exp.TimestampFromParts: rename_func("DATETIME"),
516        exp.TimeSub: date_add_interval_sql("TIME", "SUB"),
517        exp.TimestampAdd: date_add_interval_sql("TIMESTAMP", "ADD"),
518        exp.TimestampDiff: rename_func("TIMESTAMP_DIFF"),
519        exp.TimestampSub: date_add_interval_sql("TIMESTAMP", "SUB"),
520        exp.TimeStrToTime: timestrtotime_sql,
521        exp.Transaction: lambda *_: "BEGIN TRANSACTION",
522        exp.TsOrDsAdd: _ts_or_ds_add_sql,
523        exp.TsOrDsDiff: _ts_or_ds_diff_sql,
524        exp.TsOrDsToTime: rename_func("TIME"),
525        exp.TsOrDsToDatetime: rename_func("DATETIME"),
526        exp.TsOrDsToTimestamp: rename_func("TIMESTAMP"),
527        exp.Unhex: rename_func("FROM_HEX"),
528        exp.UnixDate: rename_func("UNIX_DATE"),
529        exp.UnixToTime: _unix_to_time_sql,
530        exp.Uuid: lambda *_: "GENERATE_UUID()",
531        exp.Values: _derived_table_values_to_unnest,
532        exp.VariancePop: rename_func("VAR_POP"),
533        exp.WeekOfYear: _extract_part("ISOWEEK"),
534        exp.Year: _extract_part("YEAR"),
535        exp.SafeDivide: rename_func("SAFE_DIVIDE"),
536    }
537
538    SUPPORTED_JSON_PATH_PARTS = {
539        exp.JSONPathKey,
540        exp.JSONPathRoot,
541        exp.JSONPathSubscript,
542    }
543
544    TYPE_MAPPING = {
545        **generator.Generator.TYPE_MAPPING,
546        exp.DType.BIGDECIMAL: "BIGNUMERIC",
547        exp.DType.BIGINT: "INT64",
548        exp.DType.BINARY: "BYTES",
549        exp.DType.BLOB: "BYTES",
550        exp.DType.BOOLEAN: "BOOL",
551        exp.DType.CHAR: "STRING",
552        exp.DType.DECIMAL: "NUMERIC",
553        exp.DType.DOUBLE: "FLOAT64",
554        exp.DType.FLOAT: "FLOAT64",
555        exp.DType.INT: "INT64",
556        exp.DType.NCHAR: "STRING",
557        exp.DType.NVARCHAR: "STRING",
558        exp.DType.SMALLINT: "INT64",
559        exp.DType.TEXT: "STRING",
560        exp.DType.TIMESTAMP: "DATETIME",
561        exp.DType.TIMESTAMPNTZ: "DATETIME",
562        exp.DType.TIMESTAMPTZ: "TIMESTAMP",
563        exp.DType.TIMESTAMPLTZ: "TIMESTAMP",
564        exp.DType.TINYINT: "INT64",
565        exp.DType.ROWVERSION: "BYTES",
566        exp.DType.UUID: "STRING",
567        exp.DType.VARBINARY: "BYTES",
568        exp.DType.VARCHAR: "STRING",
569        exp.DType.VARIANT: "ANY TYPE",
570    }
571
572    PROPERTIES_LOCATION = {
573        **generator.Generator.PROPERTIES_LOCATION,
574        exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,
575        exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
576    }
577
578    # WINDOW comes after QUALIFY
579    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#window_clause
580    # BigQuery requires QUALIFY before WINDOW
581    AFTER_HAVING_MODIFIER_TRANSFORMS = {
582        "qualify": generator.AFTER_HAVING_MODIFIER_TRANSFORMS["qualify"],
583        "windows": generator.AFTER_HAVING_MODIFIER_TRANSFORMS["windows"],
584    }
585
586    # from: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
587    RESERVED_KEYWORDS = {
588        "all",
589        "and",
590        "any",
591        "array",
592        "as",
593        "asc",
594        "assert_rows_modified",
595        "at",
596        "between",
597        "by",
598        "case",
599        "cast",
600        "collate",
601        "contains",
602        "create",
603        "cross",
604        "cube",
605        "current",
606        "default",
607        "define",
608        "desc",
609        "distinct",
610        "else",
611        "end",
612        "enum",
613        "escape",
614        "except",
615        "exclude",
616        "exists",
617        "extract",
618        "false",
619        "fetch",
620        "following",
621        "for",
622        "from",
623        "full",
624        "group",
625        "grouping",
626        "groups",
627        "hash",
628        "having",
629        "if",
630        "ignore",
631        "in",
632        "inner",
633        "intersect",
634        "interval",
635        "into",
636        "is",
637        "join",
638        "lateral",
639        "left",
640        "like",
641        "limit",
642        "lookup",
643        "merge",
644        "natural",
645        "new",
646        "no",
647        "not",
648        "null",
649        "nulls",
650        "of",
651        "on",
652        "or",
653        "order",
654        "outer",
655        "over",
656        "partition",
657        "preceding",
658        "proto",
659        "qualify",
660        "range",
661        "recursive",
662        "respect",
663        "right",
664        "rollup",
665        "rows",
666        "select",
667        "set",
668        "some",
669        "struct",
670        "tablesample",
671        "then",
672        "to",
673        "treat",
674        "true",
675        "unbounded",
676        "union",
677        "unnest",
678        "using",
679        "when",
680        "where",
681        "window",
682        "with",
683        "within",
684    }
685
686    def frombase_sql(self, expression: exp.FromBase) -> str:
687        # Hex is the only base BigQuery can cast from, and the sign goes before the prefix, e.g. -0xff
688        if expression.expression.name != "16":
689            self.unsupported("FROM_BASE is only supported for base 16")
690            return self.function_fallback_sql(expression)
691
692        prefixed = exp.func(
693            "REGEXP_REPLACE",
694            expression.this,
695            exp.Literal.string("^([+-]?)"),
696            exp.Literal.string("\\10x"),
697            copy=False,
698        )
699        return self.sql(exp.cast(prefixed, exp.DType.BIGINT, copy=False))
700
701    def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str:
702        node: exp.Expr = expression
703        while node.parent and not isinstance(node.parent, (exp.Window, exp.AggFunc, exp.Select)):
704            node = node.parent
705        if isinstance(node.parent, exp.Window) and node.arg_key == "this":
706            self.unsupported("APPROX_QUANTILES does not support window functions")
707            return self.function_fallback_sql(expression)
708
709        # The k/n quantile is APPROX_QUANTILES(x, n)[k], e.g. 0.95 -> n = 20, k = 19. n is at most
710        # 100,000, so the quantile has at most 5 decimal places, and Decimal comparisons are exact
711        quantile = expression.args["quantile"]
712        weight = expression.args.get("weight")
713        value = Decimal(quantile.to_py()) if quantile.is_number else None
714        if (
715            value is None
716            or not 0 <= value <= 1
717            or round(value, 5) != value
718            or (weight and (not weight.is_number or weight.to_py() != 1))
719        ):
720            self.unsupported("APPROX_QUANTILE needs a literal quantile and no weight")
721            return self.function_fallback_sql(expression)
722
723        fraction = Fraction(value)
724        quantiles = exp.func("APPROX_QUANTILES", expression.this, fraction.denominator, copy=False)
725        return self.sql(quantiles[fraction.numerator])
726
727    def weekstart_sql(self, expression: exp.WeekStart) -> str:
728        if expression.this.name.upper() == "SUNDAY":
729            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
730            return "WEEK"
731
732        return self.func("WEEK", expression.this)
733
734    def datetrunc_sql(self, expression: exp.DateTrunc) -> str:
735        unit = expression.unit
736        unit_sql = unit.name if unit.is_string else self.sql(unit)
737        return self.func("DATE_TRUNC", expression.this, unit_sql, expression.args.get("zone"))
738
739    def mod_sql(self, expression: exp.Mod) -> str:
740        this = expression.this
741        expr = expression.expression
742        return self.func(
743            "MOD",
744            this.unnest() if isinstance(this, exp.Paren) else this,
745            expr.unnest() if isinstance(expr, exp.Paren) else expr,
746        )
747
748    def column_parts(self, expression: exp.Column) -> str:
749        if expression.meta_get("quoted_column"):
750            # If a column reference is of the form `dataset.table`.name, we need
751            # to preserve the quoted table path, otherwise the reference breaks
752            table_parts = ".".join(p.name for p in expression.parts[:-1])
753            table_path = self.sql(exp.Identifier(this=table_parts, quoted=True))
754            return f"{table_path}.{self.sql(expression, 'this')}"
755
756        return super().column_parts(expression)
757
758    def table_parts(self, expression: exp.Table) -> str:
759        # Depending on the context, `x.y` may not resolve to the same data source as `x`.`y`, so
760        # we need to make sure the correct quoting is used in each case.
761        #
762        # For example, if there is a CTE x that clashes with a schema name, then the former will
763        # return the table y in that schema, whereas the latter will return the CTE's y column:
764        #
765        # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x.y`   -> cross join
766        # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x`.`y` -> implicit unnest
767        if expression.meta_get("quoted_table"):
768            table_parts = ".".join(p.name for p in expression.parts)
769            return self.sql(exp.Identifier(this=table_parts, quoted=True))
770
771        return super().table_parts(expression)
772
773    def timetostr_sql(self, expression: exp.TimeToStr) -> str:
774        this = expression.this
775        if isinstance(this, exp.TsOrDsToDatetime):
776            func_name = "FORMAT_DATETIME"
777        elif isinstance(this, exp.TsOrDsToTimestamp):
778            func_name = "FORMAT_TIMESTAMP"
779        elif isinstance(this, exp.TsOrDsToTime):
780            func_name = "FORMAT_TIME"
781        else:
782            func_name = "FORMAT_DATE"
783
784        time_expr = this if isinstance(this, self.TS_OR_DS_TYPES) else expression
785        return self.func(
786            func_name, self.format_time(expression), time_expr.this, expression.args.get("zone")
787        )
788
789    def eq_sql(self, expression: exp.EQ) -> str:
790        # Operands of = cannot be NULL in BigQuery
791        if isinstance(expression.left, exp.Null) or isinstance(expression.right, exp.Null):
792            if not isinstance(expression.parent, exp.Update):
793                return "NULL"
794
795        return self.binary(expression, "=")
796
797    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
798        parent = expression.parent
799
800        # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).
801        # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.
802        if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"):
803            return self.func(
804                "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone"))
805            )
806
807        return super().attimezone_sql(expression)
808
809    def trycast_sql(self, expression: exp.TryCast) -> str:
810        return self.cast_sql(expression, safe_prefix="SAFE_")
811
812    def bracket_sql(self, expression: exp.Bracket) -> str:
813        this = expression.this
814        expressions = expression.expressions
815
816        if len(expressions) == 1 and this and this.is_type(exp.DType.STRUCT):
817            arg = expressions[0]
818            if arg.type is None:
819                from sqlglot.optimizer.annotate_types import annotate_types
820
821                arg = annotate_types(arg, dialect=self.dialect)
822
823            if arg.type and arg.type.this in exp.DataType.TEXT_TYPES:
824                # BQ doesn't support bracket syntax with string values for structs
825                return f"{self.sql(this)}.{arg.name}"
826
827        expressions_sql = self.expressions(expression, flat=True)
828        offset = expression.args.get("offset")
829
830        if offset == 0:
831            expressions_sql = f"OFFSET({expressions_sql})"
832        elif offset == 1:
833            expressions_sql = f"ORDINAL({expressions_sql})"
834        elif offset is not None:
835            self.unsupported(f"Unsupported array offset: {offset}")
836
837        if expression.args.get("safe"):
838            expressions_sql = f"SAFE_{expressions_sql}"
839
840        return f"{self.sql(this)}[{expressions_sql}]"
841
842    def in_unnest_op(self, expression: exp.Unnest) -> str:
843        return self.sql(expression)
844
845    def version_sql(self, expression: exp.Version) -> str:
846        if expression.name == "TIMESTAMP":
847            expression.set("this", "SYSTEM_TIME")
848        return super().version_sql(expression)
849
850    def contains_sql(self, expression: exp.Contains) -> str:
851        this = expression.this
852        expr = expression.expression
853
854        if isinstance(this, exp.Lower) and isinstance(expr, exp.Lower):
855            this = this.this
856            expr = expr.this
857
858        return self.func("CONTAINS_SUBSTR", this, expr, expression.args.get("json_scope"))
859
860    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
861        if expression.args.get("is_literal"):
862            return f"JSON {self.sql(expression, 'this')}"
863        return super().parsejson_sql(expression)
864
865    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
866        this = expression.this
867
868        # This ensures that inline type-annotated ARRAY literals like ARRAY<INT64>[1, 2, 3]
869        # are roundtripped unaffected. The inner check excludes ARRAY(SELECT ...) expressions,
870        # because they aren't literals and so the above syntax is invalid BigQuery.
871        if isinstance(this, exp.Array):
872            elem = seq_get(this.expressions, 0)
873            if not (elem and elem.find(exp.Query)):
874                return f"{self.sql(expression, 'to')}{self.sql(this)}"
875
876        return super().cast_sql(expression, safe_prefix=safe_prefix)
877
878    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
879        if expression.this:
880            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
881            return ""
882        return self.op_expressions("CLUSTER BY", 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
TRY_SUPPORTED = False
SUPPORTS_UESCAPE = False
SUPPORTS_DECODE_CASE = False
INTERVAL_ALLOWS_PLURAL_FORM = False
JOIN_HINTS = False
QUERY_HINTS = False
TABLE_HINTS = False
LIMIT_FETCH = 'LIMIT'
RENAME_TABLE_WITH_DB = False
NVL2_SUPPORTED = False
UNNEST_WITH_ORDINALITY = False
CAST_TO_INT_ROUNDS = True
COLLATE_IS_FUNC = True
LIMIT_ONLY_LITERALS = True
SUPPORTS_TABLE_ALIAS_COLUMNS = False
SUPPORTS_NAMED_CTE_COLUMNS = False
UNPIVOT_ALIASES_ARE_IDENTIFIERS = False
JSON_KEY_VALUE_PAIR_SEP = ','
NULL_ORDERING_SUPPORTED: bool | None = False
IGNORE_NULLS_IN_FUNC = True
JSON_PATH_SINGLE_QUOTE_ESCAPE = True
CAN_IMPLEMENT_ARRAY_ANY = True
SUPPORTS_TO_NUMBER = False
NAMED_PLACEHOLDER_TOKEN = '@'
HEX_FUNC = 'TO_HEX'
WITH_PROPERTIES_PREFIX = 'OPTIONS'
SUPPORTS_EXPLODING_PROJECTIONS = False
EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
SUPPORTS_UNIX_SECONDS = True
DECLARE_DEFAULT_ASSIGNMENT = 'DEFAULT'
SAFE_JSON_PATH_KEY_RE = re.compile('^[\\-\\w]*$')
TRANSFORMS = {<class 'sqlglot.expressions.query.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRoot'>: <function <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 Generator.<lambda>>, <class 'sqlglot.expressions.constraints.AssumeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.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 BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.constraints.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApiProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.EndStatement'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HybridProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.datatypes.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.Int64'>: <function rename_func.<locals>.<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 Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObjectAgg'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.NetFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InvisibleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <function _returnsproperty_sql>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.SafeFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.query.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.TriggerExecute'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ForceProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.AIEmbed'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.functions.AIGenerate'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.functions.AISimilarity'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.ApproxTopK'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.core.ApproxDistinct'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.ArgMax'>: <function arg_max_or_min_no_count.<locals>._arg_max_or_min_sql>, <class 'sqlglot.expressions.aggregate.ArgMin'>: <function arg_max_or_min_no_count.<locals>._arg_max_or_min_sql>, <class 'sqlglot.expressions.array.Array'>: <function inline_array_unless_query>, <class 'sqlglot.expressions.array.ArrayContains'>: <function _array_contains_sql>, <class 'sqlglot.expressions.array.ArrayDistinct'>: <function _array_agg_fold_sql>, <class 'sqlglot.expressions.array.ArraySort'>: <function _array_agg_fold_sql>, <class 'sqlglot.expressions.array.ArrayFilter'>: <function filter_array_using_unnest>, <class 'sqlglot.expressions.array.ArrayRemove'>: <function filter_array_using_unnest>, <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.math.BitwiseCount'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.ByteLength'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.functions.Cast'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.properties.CollateProperty'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.ddl.Commit'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.aggregate.CountIf'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.ddl.Create'>: <function _create_sql>, <class 'sqlglot.expressions.query.CTE'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.temporal.DateAdd'>: <function date_add_interval_sql.<locals>.func>, <class 'sqlglot.expressions.temporal.DateDiff'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.DateFromParts'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.DateStrToDate'>: <function datestrtodate_sql>, <class 'sqlglot.expressions.temporal.DateSub'>: <function date_add_interval_sql.<locals>.func>, <class 'sqlglot.expressions.temporal.DatetimeAdd'>: <function date_add_interval_sql.<locals>.func>, <class 'sqlglot.expressions.temporal.DatetimeSub'>: <function date_add_interval_sql.<locals>.func>, <class 'sqlglot.expressions.temporal.DateFromUnixDate'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.Day'>: <function _extract_part.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.DayOfMonth'>: <function _extract_part.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.DayOfWeekIso'>: <function _day_of_week_iso_sql>, <class 'sqlglot.expressions.temporal.DayOfYear'>: <function _extract_part.<locals>.<lambda>>, <class 'sqlglot.expressions.core.Filter'>: <function no_filter_sql>, <class 'sqlglot.expressions.core.FromTimeZone'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.array.GenerateSeries'>: <function generate_series_sql.<locals>._generate_series_sql>, <class 'sqlglot.expressions.aggregate.GroupConcat'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.string.Hex'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.query.HexString'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.Hour'>: <function _extract_part.<locals>.<lambda>>, <class 'sqlglot.expressions.functions.If'>: <function if_sql.<locals>._if_sql>, <class 'sqlglot.expressions.core.ILike'>: <function no_ilike_sql>, <class 'sqlglot.expressions.core.IntDiv'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.json.JSONBool'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.json.JSONExtract'>: <function _json_extract_sql>, <class 'sqlglot.expressions.json.JSONExtractArray'>: <function _json_extract_sql>, <class 'sqlglot.expressions.json.JSONExtractScalar'>: <function _json_extract_sql>, <class 'sqlglot.expressions.json.JSONFormat'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.json.JSONKeysAtDepth'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.query.JSONValueArray'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.Levenshtein'>: <function _levenshtein_sql>, <class 'sqlglot.expressions.aggregate.Max'>: <function max_or_greatest>, <class 'sqlglot.expressions.string.MD5'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.string.MD5Digest'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.aggregate.Min'>: <function min_or_least>, <class 'sqlglot.expressions.temporal.Month'>: <function _extract_part.<locals>.<lambda>>, <class 'sqlglot.expressions.string.Normalize'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.Quarter'>: <function _extract_part.<locals>.<lambda>>, <class 'sqlglot.expressions.string.RegexpExtract'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.string.RegexpExtractAll'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.string.RegexpReplace'>: <function regexp_replace_sql>, <class 'sqlglot.expressions.core.RegexpLike'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.ddl.Rollback'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.ParseTime'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.ParseDatetime'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.query.Select'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.string.SHA'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.SHA2'>: <function sha256_sql>, <class 'sqlglot.expressions.string.SHA1Digest'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.SHA2Digest'>: <function sha2_digest_sql>, <class 'sqlglot.expressions.string.String'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.StrPosition'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.StrToDate'>: <function _str_to_datetime_sql>, <class 'sqlglot.expressions.temporal.StrToTime'>: <function _str_to_datetime_sql>, <class 'sqlglot.expressions.query.Table'>: <function preprocess.<locals>._to_sql>, <class 'sqlglot.expressions.temporal.TimeAdd'>: <function date_add_interval_sql.<locals>.func>, <class 'sqlglot.expressions.temporal.TimeFromParts'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.TimestampFromParts'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.TimeSub'>: <function date_add_interval_sql.<locals>.func>, <class 'sqlglot.expressions.temporal.TimestampAdd'>: <function date_add_interval_sql.<locals>.func>, <class 'sqlglot.expressions.temporal.TimestampDiff'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.TimestampSub'>: <function date_add_interval_sql.<locals>.func>, <class 'sqlglot.expressions.temporal.TimeStrToTime'>: <function timestrtotime_sql>, <class 'sqlglot.expressions.ddl.Transaction'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.temporal.TsOrDsAdd'>: <function _ts_or_ds_add_sql>, <class 'sqlglot.expressions.temporal.TsOrDsDiff'>: <function _ts_or_ds_diff_sql>, <class 'sqlglot.expressions.temporal.TsOrDsToTime'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.TsOrDsToDatetime'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.TsOrDsToTimestamp'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.string.Unhex'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.UnixDate'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.UnixToTime'>: <function _unix_to_time_sql>, <class 'sqlglot.expressions.functions.Uuid'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.query.Values'>: <function _derived_table_values_to_unnest>, <class 'sqlglot.expressions.aggregate.VariancePop'>: <function rename_func.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.WeekOfYear'>: <function _extract_part.<locals>.<lambda>>, <class 'sqlglot.expressions.temporal.Year'>: <function _extract_part.<locals>.<lambda>>, <class 'sqlglot.expressions.math.SafeDivide'>: <function rename_func.<locals>.<lambda>>}
TYPE_MAPPING = {<DType.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <DType.NCHAR: 'NCHAR'>: 'STRING', <DType.NVARCHAR: 'NVARCHAR'>: 'STRING', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'BYTES', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'BYTES', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP', <DType.BIGDECIMAL: 'BIGDECIMAL'>: 'BIGNUMERIC', <DType.BIGINT: 'BIGINT'>: 'INT64', <DType.BINARY: 'BINARY'>: 'BYTES', <DType.BOOLEAN: 'BOOLEAN'>: 'BOOL', <DType.CHAR: 'CHAR'>: 'STRING', <DType.DECIMAL: 'DECIMAL'>: 'NUMERIC', <DType.DOUBLE: 'DOUBLE'>: 'FLOAT64', <DType.FLOAT: 'FLOAT'>: 'FLOAT64', <DType.INT: 'INT'>: 'INT64', <DType.SMALLINT: 'SMALLINT'>: 'INT64', <DType.TEXT: 'TEXT'>: 'STRING', <DType.TIMESTAMP: 'TIMESTAMP'>: 'DATETIME', <DType.TIMESTAMPNTZ: 'TIMESTAMPNTZ'>: 'DATETIME', <DType.TIMESTAMPTZ: 'TIMESTAMPTZ'>: 'TIMESTAMP', <DType.TIMESTAMPLTZ: 'TIMESTAMPLTZ'>: 'TIMESTAMP', <DType.TINYINT: 'TINYINT'>: 'INT64', <DType.UUID: 'UUID'>: 'STRING', <DType.VARBINARY: 'VARBINARY'>: 'BYTES', <DType.VARCHAR: 'VARCHAR'>: 'STRING', <DType.VARIANT: 'VARIANT'>: 'ANY TYPE'}
PROPERTIES_LOCATION = {<class 'sqlglot.expressions.properties.AggregateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <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_SCHEMA: 'POST_SCHEMA'>, <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.properties.PhysicalProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <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.TableSpaceProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <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'>}
AFTER_HAVING_MODIFIER_TRANSFORMS = {'qualify': <function <lambda>>, 'windows': <function <lambda>>}
RESERVED_KEYWORDS = {'into', 'some', 'merge', 'at', 'outer', 'true', 'ignore', 'cast', 'following', 'where', 'of', 'proto', 'rows', 'distinct', 'group', 'define', 'null', 'grouping', 'fetch', 'hash', 'lookup', 'using', 'false', 'enum', 'end', 'and', 'inner', 'on', 'when', 'struct', 'full', 'left', 'new', 'else', 'treat', 'escape', 'union', 'collate', 'case', 'rollup', 'as', 'except', 'desc', 'by', 'extract', 'preceding', 'between', 'to', 'unnest', 'cross', 'assert_rows_modified', 'natural', 'groups', 'set', 'current', 'for', 'all', 'intersect', 'array', 'within', 'limit', 'from', 'exclude', 'recursive', 'select', 'over', 'default', 'or', 'cube', 'having', 'window', 'create', 'not', 'interval', 'is', 'order', 'right', 'tablesample', 'like', 'lateral', 'unbounded', 'qualify', 'any', 'partition', 'respect', 'nulls', 'asc', 'no', 'exists', 'range', 'contains', 'then', 'if', 'join', 'in', 'with'}
def frombase_sql(self, expression: sqlglot.expressions.string.FromBase) -> str:
686    def frombase_sql(self, expression: exp.FromBase) -> str:
687        # Hex is the only base BigQuery can cast from, and the sign goes before the prefix, e.g. -0xff
688        if expression.expression.name != "16":
689            self.unsupported("FROM_BASE is only supported for base 16")
690            return self.function_fallback_sql(expression)
691
692        prefixed = exp.func(
693            "REGEXP_REPLACE",
694            expression.this,
695            exp.Literal.string("^([+-]?)"),
696            exp.Literal.string("\\10x"),
697            copy=False,
698        )
699        return self.sql(exp.cast(prefixed, exp.DType.BIGINT, copy=False))
def approxquantile_sql(self, expression: sqlglot.expressions.aggregate.ApproxQuantile) -> str:
701    def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str:
702        node: exp.Expr = expression
703        while node.parent and not isinstance(node.parent, (exp.Window, exp.AggFunc, exp.Select)):
704            node = node.parent
705        if isinstance(node.parent, exp.Window) and node.arg_key == "this":
706            self.unsupported("APPROX_QUANTILES does not support window functions")
707            return self.function_fallback_sql(expression)
708
709        # The k/n quantile is APPROX_QUANTILES(x, n)[k], e.g. 0.95 -> n = 20, k = 19. n is at most
710        # 100,000, so the quantile has at most 5 decimal places, and Decimal comparisons are exact
711        quantile = expression.args["quantile"]
712        weight = expression.args.get("weight")
713        value = Decimal(quantile.to_py()) if quantile.is_number else None
714        if (
715            value is None
716            or not 0 <= value <= 1
717            or round(value, 5) != value
718            or (weight and (not weight.is_number or weight.to_py() != 1))
719        ):
720            self.unsupported("APPROX_QUANTILE needs a literal quantile and no weight")
721            return self.function_fallback_sql(expression)
722
723        fraction = Fraction(value)
724        quantiles = exp.func("APPROX_QUANTILES", expression.this, fraction.denominator, copy=False)
725        return self.sql(quantiles[fraction.numerator])
def weekstart_sql(self, expression: sqlglot.expressions.functions.WeekStart) -> str:
727    def weekstart_sql(self, expression: exp.WeekStart) -> str:
728        if expression.this.name.upper() == "SUNDAY":
729            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
730            return "WEEK"
731
732        return self.func("WEEK", expression.this)
def datetrunc_sql(self, expression: sqlglot.expressions.temporal.DateTrunc) -> str:
734    def datetrunc_sql(self, expression: exp.DateTrunc) -> str:
735        unit = expression.unit
736        unit_sql = unit.name if unit.is_string else self.sql(unit)
737        return self.func("DATE_TRUNC", expression.this, unit_sql, expression.args.get("zone"))
def mod_sql(self, expression: sqlglot.expressions.core.Mod) -> str:
739    def mod_sql(self, expression: exp.Mod) -> str:
740        this = expression.this
741        expr = expression.expression
742        return self.func(
743            "MOD",
744            this.unnest() if isinstance(this, exp.Paren) else this,
745            expr.unnest() if isinstance(expr, exp.Paren) else expr,
746        )
def column_parts(self, expression: sqlglot.expressions.core.Column) -> str:
748    def column_parts(self, expression: exp.Column) -> str:
749        if expression.meta_get("quoted_column"):
750            # If a column reference is of the form `dataset.table`.name, we need
751            # to preserve the quoted table path, otherwise the reference breaks
752            table_parts = ".".join(p.name for p in expression.parts[:-1])
753            table_path = self.sql(exp.Identifier(this=table_parts, quoted=True))
754            return f"{table_path}.{self.sql(expression, 'this')}"
755
756        return super().column_parts(expression)
def table_parts(self, expression: sqlglot.expressions.query.Table) -> str:
758    def table_parts(self, expression: exp.Table) -> str:
759        # Depending on the context, `x.y` may not resolve to the same data source as `x`.`y`, so
760        # we need to make sure the correct quoting is used in each case.
761        #
762        # For example, if there is a CTE x that clashes with a schema name, then the former will
763        # return the table y in that schema, whereas the latter will return the CTE's y column:
764        #
765        # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x.y`   -> cross join
766        # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x`.`y` -> implicit unnest
767        if expression.meta_get("quoted_table"):
768            table_parts = ".".join(p.name for p in expression.parts)
769            return self.sql(exp.Identifier(this=table_parts, quoted=True))
770
771        return super().table_parts(expression)
def timetostr_sql(self, expression: sqlglot.expressions.temporal.TimeToStr) -> str:
773    def timetostr_sql(self, expression: exp.TimeToStr) -> str:
774        this = expression.this
775        if isinstance(this, exp.TsOrDsToDatetime):
776            func_name = "FORMAT_DATETIME"
777        elif isinstance(this, exp.TsOrDsToTimestamp):
778            func_name = "FORMAT_TIMESTAMP"
779        elif isinstance(this, exp.TsOrDsToTime):
780            func_name = "FORMAT_TIME"
781        else:
782            func_name = "FORMAT_DATE"
783
784        time_expr = this if isinstance(this, self.TS_OR_DS_TYPES) else expression
785        return self.func(
786            func_name, self.format_time(expression), time_expr.this, expression.args.get("zone")
787        )
def eq_sql(self, expression: sqlglot.expressions.core.EQ) -> str:
789    def eq_sql(self, expression: exp.EQ) -> str:
790        # Operands of = cannot be NULL in BigQuery
791        if isinstance(expression.left, exp.Null) or isinstance(expression.right, exp.Null):
792            if not isinstance(expression.parent, exp.Update):
793                return "NULL"
794
795        return self.binary(expression, "=")
def attimezone_sql(self, expression: sqlglot.expressions.core.AtTimeZone) -> str:
797    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
798        parent = expression.parent
799
800        # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).
801        # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.
802        if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"):
803            return self.func(
804                "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone"))
805            )
806
807        return super().attimezone_sql(expression)
def trycast_sql(self, expression: sqlglot.expressions.functions.TryCast) -> str:
809    def trycast_sql(self, expression: exp.TryCast) -> str:
810        return self.cast_sql(expression, safe_prefix="SAFE_")
def bracket_sql(self, expression: sqlglot.expressions.core.Bracket) -> str:
812    def bracket_sql(self, expression: exp.Bracket) -> str:
813        this = expression.this
814        expressions = expression.expressions
815
816        if len(expressions) == 1 and this and this.is_type(exp.DType.STRUCT):
817            arg = expressions[0]
818            if arg.type is None:
819                from sqlglot.optimizer.annotate_types import annotate_types
820
821                arg = annotate_types(arg, dialect=self.dialect)
822
823            if arg.type and arg.type.this in exp.DataType.TEXT_TYPES:
824                # BQ doesn't support bracket syntax with string values for structs
825                return f"{self.sql(this)}.{arg.name}"
826
827        expressions_sql = self.expressions(expression, flat=True)
828        offset = expression.args.get("offset")
829
830        if offset == 0:
831            expressions_sql = f"OFFSET({expressions_sql})"
832        elif offset == 1:
833            expressions_sql = f"ORDINAL({expressions_sql})"
834        elif offset is not None:
835            self.unsupported(f"Unsupported array offset: {offset}")
836
837        if expression.args.get("safe"):
838            expressions_sql = f"SAFE_{expressions_sql}"
839
840        return f"{self.sql(this)}[{expressions_sql}]"
def in_unnest_op(self, expression: sqlglot.expressions.array.Unnest) -> str:
842    def in_unnest_op(self, expression: exp.Unnest) -> str:
843        return self.sql(expression)
def version_sql(self, expression: sqlglot.expressions.query.Version) -> str:
845    def version_sql(self, expression: exp.Version) -> str:
846        if expression.name == "TIMESTAMP":
847            expression.set("this", "SYSTEM_TIME")
848        return super().version_sql(expression)
def contains_sql(self, expression: sqlglot.expressions.string.Contains) -> str:
850    def contains_sql(self, expression: exp.Contains) -> str:
851        this = expression.this
852        expr = expression.expression
853
854        if isinstance(this, exp.Lower) and isinstance(expr, exp.Lower):
855            this = this.this
856            expr = expr.this
857
858        return self.func("CONTAINS_SUBSTR", this, expr, expression.args.get("json_scope"))
def parsejson_sql(self, expression: sqlglot.expressions.json.ParseJSON) -> str:
860    def parsejson_sql(self, expression: exp.ParseJSON) -> str:
861        if expression.args.get("is_literal"):
862            return f"JSON {self.sql(expression, 'this')}"
863        return super().parsejson_sql(expression)
def cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
865    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
866        this = expression.this
867
868        # This ensures that inline type-annotated ARRAY literals like ARRAY<INT64>[1, 2, 3]
869        # are roundtripped unaffected. The inner check excludes ARRAY(SELECT ...) expressions,
870        # because they aren't literals and so the above syntax is invalid BigQuery.
871        if isinstance(this, exp.Array):
872            elem = seq_get(this.expressions, 0)
873            if not (elem and elem.find(exp.Query)):
874                return f"{self.sql(expression, 'to')}{self.sql(this)}"
875
876        return super().cast_sql(expression, safe_prefix=safe_prefix)
def clusterproperty_sql(self, expression: sqlglot.expressions.properties.ClusterProperty) -> str:
878    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
879        if expression.this:
880            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
881            return ""
882        return self.op_expressions("CLUSTER BY", expression)
Inherited Members
sqlglot.generator.Generator
Generator
IGNORE_NULLS_BEFORE_ORDER
LOCKING_READS_SUPPORTED
WRAP_DERIVED_VALUES
CREATE_FUNCTION_RETURN_AS
MATCHED_BY_SOURCE
SUPPORTS_MERGE_WHERE
SINGLE_STRING_INTERVAL
AUTO_REFRESH_BARE_INTERVALS
GROUPINGS_SEP
SUPPORTS_GROUPING_SETS_AS_SUFFIX
INDEX_ON
INOUT_SEPARATOR
DIRECTED_JOINS
QUERY_HINT_SEP
IS_BOOL_ALLOWED
SAFE_INT_DIVISION
DUPLICATE_KEY_UPDATE_WITH_SET
LIMIT_IS_TOP
RETURNING_END
EXTRACT_ALLOWS_QUOTES
TZ_TO_WITH_TIME_ZONE
SELECT_KINDS
VALUES_AS_TABLE
ALTER_TABLE_INCLUDE_COLUMN_KEYWORD
SEMI_ANTI_JOIN_WITH_SIDE
COMPUTED_COLUMN_WITH_TYPE
SUPPORTS_TABLE_COPY
TABLESAMPLE_REQUIRES_PARENS
TABLESAMPLE_SIZE_IS_ROWS
TABLESAMPLE_KEYWORDS
TABLESAMPLE_WITH_METHOD
TABLESAMPLE_SEED_KEYWORD
HISTORICAL_DATA_POST_ALIAS
DATA_TYPE_SPECIFIERS_ALLOWED
ENSURE_BOOLS
CTE_RECURSIVE_KEYWORD_REQUIRED
SUPPORTS_SINGLE_ARG_CONCAT
LAST_DAY_SUPPORTS_DATE_PART
PIVOT_ALIAS_WITH_AS
INSERT_OVERWRITE
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_KEY_QUOTED_FORCES_BRACKETS
SUPPORTS_WINDOW_EXCLUDE
SET_OP_MODIFIERS
SET_OP_LIMITS
SET_OP_PARENTHESIZED_OPERANDS
COPY_PARAMS_ARE_WRAPPED
COPY_PARAMS_EQ_REQUIRED
COPY_HAS_INTO_KEYWORD
UNICODE_SUBSTITUTE
STAR_EXCEPT
BYTE_STRING_FUNCTION
QUOTE_JSON_PATH
PAD_FILL_PATTERN_IS_REQUIRED
ARRAY_CONCAT_IS_VAR_LEN
SUPPORTS_CONVERT_TIMEZONE
SUPPORTS_MEDIAN
ALTER_SET_WRAPPED
NORMALIZE_EXTRACT_DATE_PARTS
PARSE_JSON_NAME
ARRAY_SIZE_NAME
ALTER_SET_TYPE
ARRAY_SIZE_DIM_REQUIRED
SUPPORTS_BETWEEN_FLAGS
SUPPORTS_LIKE_QUANTIFIERS
MATCH_AGAINST_TABLE_PREFIX
SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD
UPDATE_STATEMENT_SUPPORTS_FROM
STAR_EXCLUDE_REQUIRES_DERIVED_TABLE
SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY
UNSUPPORTED_TYPES
TYPE_PARAM_SETTINGS
TIME_PART_SINGULARS
TOKEN_MAPPING
STRUCT_DELIMITER
PARAMETER_TOKEN
EXPRESSION_PRECEDES_PROPERTIES_CREATABLES
WITH_SEPARATED_COMMENTS
EXCLUDE_COMMENTS
UNWRAPPED_INTERVAL_VALUES
PARAMETERIZABLE_TEXT_TYPES
EXPRESSIONS_WITHOUT_NESTED_CTES
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS
MOD_OPERATOR
MOD_PAREN_PARENT_TYPES
ALTER_PARTITION_OPTIONS
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_sql
pseudocolumn_sql
columnposition_sql
columndef_sql
columnconstraint_sql
computedcolumnconstraint_sql
autoincrementcolumnconstraint_sql
compresscolumnconstraint_sql
generatedasidentitycolumnconstraint_sql
generatedasrowcolumnconstraint_sql
periodforsystemtimeconstraint_sql
notnullcolumnconstraint_sql
primarykeycolumnconstraint_sql
uniquecolumnconstraint_sql
inoutcolumnconstraint_sql
createable_sql
create_sql
sequenceproperties_sql
triggerproperties_sql
triggerreferencing_sql
triggerevent_sql
clone_sql
describe_sql
heredoc_sql
prepend_ctes
with_sql
cte_sql
tablealias_sql
bitstring_sql
hexstring_sql
bytestring_sql
unicodestring_sql
rawstring_sql
datatypeparam_sql
datatype_param_bound_limiter
datatype_sql
directory_sql
delete_sql
drop_sql
set_operation
set_operations
fetch_sql
limitoptions_sql
filter_sql
hint_sql
indexparameters_sql
index_sql
dynamicidentifier_sql
identifier_sql
hex_sql
lowerhex_sql
inputoutputformat_sql
national_sql
partition_sql
properties_sql
root_properties
properties
with_properties
locate_properties
property_name
property_sql
uuidproperty_sql
likeproperty_sql
fallbackproperty_sql
journalproperty_sql
freespaceproperty_sql
checksumproperty_sql
mergeblockratioproperty_sql
moduleproperty_sql
datablocksizeproperty_sql
blockcompressionproperty_sql
isolatedloadingproperty_sql
partitionboundspec_sql
partitionedofproperty_sql
lockingproperty_sql
lockingstatement_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_sql
tablefromrows_sql
tablesample_sql
pivot_sql
tuple_sql
update_sql
values_sql
var_sql
into_sql
from_sql
groupingsets_sql
rollup_sql
rollupindex_sql
rollupproperty_sql
cube_sql
group_sql
having_sql
connect_sql
prior_sql
join_sql
lambda_sql
lateral_op
lateral_sql
limit_sql
offset_sql
setitem_sql
set_sql
queryband_sql
pragma_sql
lock_sql
literal_sql
escape_str
loaddata_sql
null_sql
boolean_sql
booland_sql
boolor_sql
order_sql
withfill_sql
cluster_sql
distribute_sql
sort_sql
ordered_sql
matchrecognizemeasure_sql
matchrecognize_sql
query_modifiers
options_modifier
forclause_sql
queryoption_sql
offset_limit_modifiers
after_limit_modifiers
select_sql
schema_sql
schema_columns_sql
star_sql
parameter_sql
sessionparameter_sql
placeholder_sql
subquery_sql
qualify_sql
unnest_sql
prewhere_sql
where_sql
window_sql
partition_by_sql
partitionbyexcluding_sql
windowspec_sql
withingroup_sql
between_sql
bracket_offset_expressions
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
interval_sql
return_sql
reference_sql
anonymous_sql
paren_sql
neg_sql
not_sql
alias_sql
pivotalias_sql
aliases_sql
atindex_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
strtotime_sql
strtodate_sql
parsedatetime_sql
currentdate_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
alterset_sql
alter_sql
altersession_sql
add_column_sql
droppartition_sql
dropprimarykey_sql
addconstraint_sql
replacecolumns_sql
addpartition_sql
distinct_sql
ignorenulls_sql
respectnulls_sql
havingmax_sql
intdiv_sql
dpipe_sql
div_sql
safedivide_sql
overlaps_sql
distance_sql
distancend_sql
dot_sql
propertyeq_sql
escape_sql
glob_sql
gt_sql
gte_sql
is_sql
like_sql
ilike_sql
match_sql
similarto_sql
lt_sql
lte_sql
mul_sql
neq_sql
nullsafeeq_sql
nullsafeneq_sql
sub_sql
jsoncast_sql
try_sql
log_sql
use_sql
binary
ceil_floor
function_fallback_sql
function_name
func
format_args
too_wide
format_time
expressions
op_expressions
naked_property
tag_sql
token_sql
userdefinedfunction_sql
macrooverloads_sql
macrooverload_sql
joinhint_sql
kwarg_sql
when_sql
whens_sql
merge_sql
tochar_sql
tonumber_sql
dictproperty_sql
dictrange_sql
dictsubproperty_sql
aggregatekeyproperty_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
arrayinsert_sql
arrayany_sql
struct_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
rand_sql
changes_sql
pad_sql
summarize_sql
explodinggenerateseries_sql
converttimezone_sql
json_sql
jsonvalue_sql
skipjsoncolumn_sql
conditionalinsert_sql
multitableinserts_sql
oncondition_sql
jsonextractquote_sql
jsonexists_sql
arrayagg_sql
slice_sql
apply_sql
grant_sql
revoke_sql
grantprivilege_sql
grantprincipal_sql
columns_sql
overlay_sql
todouble_sql
string_sql
median_sql
overflowtruncatebehavior_sql
unixseconds_sql
arraysort_sql
arraysize_sql
attach_sql
detach_sql
attachoption_sql
watermarkcolumnconstraint_sql
encodeproperty_sql
includeproperty_sql
xmlelement_sql
xmlkeyvalueoption_sql
partitionbyrangeproperty_sql
partitionbyrangepropertydynamic_sql
unpivotcolumns_sql
analyzesample_sql
analyzestatistics_sql
analyzehistogram_sql
analyzedelete_sql
analyzelistchainedrows_sql
analyzevalidate_sql
analyze_sql
xmltable_sql
xmlnamespace_sql
export_sql
declare_sql
declareitem_sql
recursivewithsearch_sql
parameterizedagg_sql
anonymousaggfunc_sql
combinedaggfunc_sql
combinedparameterizedagg_sql
show_sql
install_sql
liststage_sql
get_put_sql
translatecharacters_sql
decodecase_sql
semanticview_sql
getextract_sql
datefromunixdate_sql
space_sql
buildproperty_sql
refreshtriggerproperty_sql
modelattribute_sql
directorystage_sql
uuid_sql
initcap_sql
localtime_sql
localtimestamp_sql
weekstart_name
chr_sql
weightstring_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