Edit on GitHub

sqlglot.generators.bigquery

  1from __future__ import annotations
  2
  3import logging
  4import re
  5import typing as t
  6
  7from sqlglot import exp, generator, transforms
  8from sqlglot.dialects.dialect import (
  9    arg_max_or_min_no_count,
 10    date_add_interval_sql,
 11    datestrtodate_sql,
 12    filter_array_using_unnest,
 13    generate_series_sql,
 14    if_sql,
 15    inline_array_unless_query,
 16    max_or_greatest,
 17    min_or_least,
 18    no_ilike_sql,
 19    regexp_replace_sql,
 20    rename_func,
 21    sha256_sql,
 22    timestrtotime_sql,
 23    ts_or_ds_add_cast,
 24    unit_to_var,
 25    strposition_sql,
 26    groupconcat_sql,
 27    sha2_digest_sql,
 28)
 29from sqlglot.generator import unsupported_args
 30from sqlglot.helper import seq_get
 31
 32logger = logging.getLogger("sqlglot")
 33
 34JSON_EXTRACT_TYPE = t.Union[exp.JSONExtract, exp.JSONExtractScalar, exp.JSONExtractArray]
 35
 36DQUOTES_ESCAPING_JSON_FUNCTIONS = ("JSON_QUERY", "JSON_VALUE", "JSON_QUERY_ARRAY")
 37
 38
 39def _derived_table_values_to_unnest(self: BigQueryGenerator, expression: exp.Values) -> str:
 40    if not expression.find_ancestor(exp.From, exp.Join):
 41        return self.values_sql(expression)
 42
 43    structs = []
 44    alias = expression.args.get("alias")
 45    for tup in expression.find_all(exp.Tuple):
 46        field_aliases = (
 47            alias.columns
 48            if alias and alias.columns
 49            else (f"_c{i}" for i in range(len(tup.expressions)))
 50        )
 51        expressions = [
 52            exp.PropertyEQ(this=exp.to_identifier(name), expression=fld)
 53            for name, fld in zip(field_aliases, tup.expressions)
 54        ]
 55        structs.append(exp.Struct(expressions=expressions))
 56
 57    # Due to `UNNEST_COLUMN_ONLY`, it is expected that the table alias be contained in the columns expression
 58    alias_name_only = exp.TableAlias(columns=[alias.this]) if alias else None
 59    return self.unnest_sql(
 60        exp.Unnest(expressions=[exp.array(*structs, copy=False)], alias=alias_name_only)
 61    )
 62
 63
 64def _returnsproperty_sql(self: BigQueryGenerator, expression: exp.ReturnsProperty) -> str:
 65    this = expression.this
 66    if isinstance(this, exp.Schema):
 67        this = f"{self.sql(this, 'this')} <{self.expressions(this)}>"
 68    else:
 69        this = self.sql(this)
 70    return f"RETURNS {this}"
 71
 72
 73def _create_sql(self: BigQueryGenerator, expression: exp.Create) -> str:
 74    returns = expression.find(exp.ReturnsProperty)
 75    if expression.kind == "FUNCTION" and returns and returns.args.get("is_table"):
 76        expression.set("kind", "TABLE FUNCTION")
 77
 78        if isinstance(expression.expression, (exp.Subquery, exp.Literal)):
 79            expression.set("expression", expression.expression.this)
 80
 81    return self.create_sql(expression)
 82
 83
 84# https://issuetracker.google.com/issues/162294746
 85# workaround for bigquery bug when grouping by an expression and then ordering
 86# WITH x AS (SELECT 1 y)
 87# SELECT y + 1 z
 88# FROM x
 89# GROUP BY x + 1
 90# ORDER by z
 91def _alias_ordered_group(expression: exp.Expr) -> exp.Expr:
 92    if isinstance(expression, exp.Select):
 93        group = expression.args.get("group")
 94        order = expression.args.get("order")
 95
 96        if group and order:
 97            aliases = {
 98                select.this: select.args["alias"]
 99                for select in expression.selects
100                if isinstance(select, exp.Alias)
101            }
102
103            for grouped in group.expressions:
104                if grouped.is_int:
105                    continue
106                alias = aliases.get(grouped)
107                if alias:
108                    grouped.replace(exp.column(alias))
109
110    return expression
111
112
113def _pushdown_cte_column_names(expression: exp.Expr) -> exp.Expr:
114    """BigQuery doesn't allow column names when defining a CTE, so we try to push them down."""
115    if isinstance(expression, exp.CTE) and expression.alias_column_names:
116        cte_query = expression.this
117
118        if cte_query.is_star:
119            logger.warning(
120                "Can't push down CTE column names for star queries. Run the query through"
121                " the optimizer or use 'qualify' to expand the star projections first."
122            )
123            return expression
124
125        column_names = expression.alias_column_names
126        expression.args["alias"].set("columns", None)
127
128        for name, select in zip(column_names, cte_query.selects):
129            to_replace = select
130
131            if isinstance(select, exp.Alias):
132                select = select.this
133
134            # Inner aliases are shadowed by the CTE column names
135            to_replace.replace(exp.alias_(select, name))
136
137    return expression
138
139
140def _unnest_explode_generate_series(expression: exp.Expr) -> exp.Expr:
141    """
142    Rewrites exploding GENERATE_SERIES projections into table references, e.g.
143
144        SELECT GENERATE_SERIES(1, 2) AS x           -> SELECT x FROM GENERATE_SERIES(1, 2) AS x
145        SELECT y, GENERATE_SERIES(1, 2) AS x FROM t -> SELECT y, x FROM t CROSS JOIN GENERATE_SERIES(1, 2) AS x
146
147    since BigQuery can't explode in the projection and must unnest it in the FROM clause instead.
148    The resulting table reference is unnested downstream by `transforms.unnest_generate_series`.
149    """
150    if isinstance(expression, exp.Select):
151        for projection in expression.selects:
152            if isinstance(series := projection.unalias(), exp.ExplodingGenerateSeries):
153                column_name = projection.output_name or "_gen_series_value"
154
155                projection.replace(exp.column(column_name))
156                table = exp.Table(
157                    this=series, alias=exp.TableAlias(this=exp.to_identifier(column_name))
158                )
159
160                if expression.args.get("from_"):
161                    expression.join(table, copy=False, join_type="CROSS")
162                else:
163                    expression.set("from_", exp.From(this=table))
164
165    return expression
166
167
168def _array_contains_sql(self: BigQueryGenerator, expression: exp.ArrayContains) -> str:
169    return self.sql(
170        exp.Exists(
171            this=exp.select("1")
172            .from_(exp.Unnest(expressions=[expression.left]).as_("_unnest", table=["_col"]))
173            .where(exp.column("_col").eq(expression.right))
174        )
175    )
176
177
178def _ts_or_ds_add_sql(self: BigQueryGenerator, expression: exp.TsOrDsAdd) -> str:
179    return date_add_interval_sql("DATE", "ADD")(self, ts_or_ds_add_cast(expression))
180
181
182def _ts_or_ds_diff_sql(self: BigQueryGenerator, expression: exp.TsOrDsDiff) -> str:
183    expression.this.replace(exp.cast(expression.this, exp.DType.TIMESTAMP))
184    expression.expression.replace(exp.cast(expression.expression, exp.DType.TIMESTAMP))
185    unit = unit_to_var(expression)
186    return self.func("DATE_DIFF", expression.this, expression.expression, unit)
187
188
189def _unix_to_time_sql(self: BigQueryGenerator, expression: exp.UnixToTime) -> str:
190    scale = expression.args.get("scale")
191    timestamp = expression.this
192
193    if scale in (None, exp.UnixToTime.SECONDS):
194        return self.func("TIMESTAMP_SECONDS", timestamp)
195    if scale == exp.UnixToTime.MILLIS:
196        return self.func("TIMESTAMP_MILLIS", timestamp)
197    if scale == exp.UnixToTime.MICROS:
198        return self.func("TIMESTAMP_MICROS", timestamp)
199
200    unix_seconds = exp.cast(
201        exp.Div(this=timestamp, expression=exp.func("POW", 10, scale)), exp.DType.BIGINT
202    )
203    return self.func("TIMESTAMP_SECONDS", unix_seconds)
204
205
206def _str_to_datetime_sql(self: BigQueryGenerator, expression: exp.StrToDate | exp.StrToTime) -> str:
207    this = self.sql(expression, "this")
208    dtype = "DATE" if isinstance(expression, exp.StrToDate) else "TIMESTAMP"
209
210    if expression.args.get("safe"):
211        fmt = self.format_time(
212            expression,
213            self.dialect.INVERSE_FORMAT_MAPPING,
214            self.dialect.INVERSE_FORMAT_TRIE,
215        )
216        return f"SAFE_CAST({this} AS {dtype} FORMAT {fmt})"
217
218    fmt = self.format_time(expression)
219    return self.func(f"PARSE_{dtype}", fmt, this, expression.args.get("zone"))
220
221
222@unsupported_args("ins_cost", "del_cost", "sub_cost")
223def _levenshtein_sql(self: BigQueryGenerator, expression: exp.Levenshtein) -> str:
224    max_dist = expression.args.get("max_dist")
225    if max_dist:
226        max_dist = exp.Kwarg(this=exp.var("max_distance"), expression=max_dist)
227
228    return self.func("EDIT_DISTANCE", expression.this, expression.expression, max_dist)
229
230
231def _json_extract_sql(self: BigQueryGenerator, expression: JSON_EXTRACT_TYPE) -> str:
232    name = expression.meta_get("name") or expression.sql_name()
233    upper = name.upper()
234
235    dquote_escaping = upper in DQUOTES_ESCAPING_JSON_FUNCTIONS
236
237    if dquote_escaping:
238        self._quote_json_path_key_using_brackets = False
239
240    sql = rename_func(upper)(self, expression)
241
242    if dquote_escaping:
243        self._quote_json_path_key_using_brackets = True
244
245    return sql
246
247
248class BigQueryGenerator(generator.Generator):
249    TRY_SUPPORTED = False
250    SUPPORTS_UESCAPE = False
251    SUPPORTS_DECODE_CASE = False
252    INTERVAL_ALLOWS_PLURAL_FORM = False
253    JOIN_HINTS = False
254    QUERY_HINTS = False
255    TABLE_HINTS = False
256    LIMIT_FETCH = "LIMIT"
257    RENAME_TABLE_WITH_DB = False
258    NVL2_SUPPORTED = False
259    UNNEST_WITH_ORDINALITY = False
260    COLLATE_IS_FUNC = True
261    LIMIT_ONLY_LITERALS = True
262    SUPPORTS_TABLE_ALIAS_COLUMNS = False
263    SUPPORTS_NAMED_CTE_COLUMNS = False
264    UNPIVOT_ALIASES_ARE_IDENTIFIERS = False
265    JSON_KEY_VALUE_PAIR_SEP = ","
266    NULL_ORDERING_SUPPORTED: bool | None = False
267    IGNORE_NULLS_IN_FUNC = True
268    JSON_PATH_SINGLE_QUOTE_ESCAPE = True
269    CAN_IMPLEMENT_ARRAY_ANY = True
270    SUPPORTS_TO_NUMBER = False
271    NAMED_PLACEHOLDER_TOKEN = "@"
272    HEX_FUNC = "TO_HEX"
273    WITH_PROPERTIES_PREFIX = "OPTIONS"
274    SUPPORTS_EXPLODING_PROJECTIONS = False
275    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
276    SUPPORTS_UNIX_SECONDS = True
277    DECLARE_DEFAULT_ASSIGNMENT = "DEFAULT"
278
279    SAFE_JSON_PATH_KEY_RE = re.compile(r"^[\-\w]*$")
280
281    WINDOW_FUNCS_WITH_NULL_ORDERING = (
282        exp.CumeDist,
283        exp.DenseRank,
284        exp.FirstValue,
285        exp.Lag,
286        exp.LastValue,
287        exp.Lead,
288        exp.NthValue,
289        exp.Ntile,
290        exp.PercentRank,
291        exp.Rank,
292        exp.RowNumber,
293    )
294
295    TS_OR_DS_TYPES = (
296        exp.TsOrDsToDatetime,
297        exp.TsOrDsToTimestamp,
298        exp.TsOrDsToTime,
299        exp.TsOrDsToDate,
300    )
301
302    TRANSFORMS = {
303        **generator.Generator.TRANSFORMS,
304        exp.AIEmbed: rename_func("EMBED"),
305        exp.AIGenerate: rename_func("GENERATE"),
306        exp.AISimilarity: rename_func("SIMILARITY"),
307        exp.ApproxTopK: rename_func("APPROX_TOP_COUNT"),
308        exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"),
309        exp.ArgMax: arg_max_or_min_no_count("MAX_BY"),
310        exp.ArgMin: arg_max_or_min_no_count("MIN_BY"),
311        exp.Array: inline_array_unless_query,
312        exp.ArrayContains: _array_contains_sql,
313        exp.ArrayFilter: filter_array_using_unnest,
314        exp.ArrayRemove: filter_array_using_unnest,
315        exp.BitwiseAndAgg: rename_func("BIT_AND"),
316        exp.BitwiseOrAgg: rename_func("BIT_OR"),
317        exp.BitwiseXorAgg: rename_func("BIT_XOR"),
318        exp.BitwiseCount: rename_func("BIT_COUNT"),
319        exp.ByteLength: rename_func("BYTE_LENGTH"),
320        exp.Cast: transforms.preprocess([transforms.remove_precision_parameterized_types]),
321        exp.CollateProperty: lambda self, e: (
322            f"DEFAULT COLLATE {self.sql(e, 'this')}"
323            if e.args.get("default")
324            else f"COLLATE {self.sql(e, 'this')}"
325        ),
326        exp.Commit: lambda *_: "COMMIT TRANSACTION",
327        exp.CountIf: rename_func("COUNTIF"),
328        exp.Create: _create_sql,
329        exp.CTE: transforms.preprocess([_pushdown_cte_column_names]),
330        exp.DateAdd: date_add_interval_sql("DATE", "ADD"),
331        exp.DateDiff: lambda self, e: self.func("DATE_DIFF", e.this, e.expression, unit_to_var(e)),
332        exp.DateFromParts: rename_func("DATE"),
333        exp.DateStrToDate: datestrtodate_sql,
334        exp.DateSub: date_add_interval_sql("DATE", "SUB"),
335        exp.DatetimeAdd: date_add_interval_sql("DATETIME", "ADD"),
336        exp.DatetimeSub: date_add_interval_sql("DATETIME", "SUB"),
337        exp.DateFromUnixDate: rename_func("DATE_FROM_UNIX_DATE"),
338        exp.FromTimeZone: lambda self, e: self.func(
339            "DATETIME", self.func("TIMESTAMP", e.this, e.args.get("zone")), "'UTC'"
340        ),
341        exp.GenerateSeries: generate_series_sql("GENERATE_ARRAY"),
342        exp.GroupConcat: lambda self, e: groupconcat_sql(
343            self, e, func_name="STRING_AGG", within_group=False, sep=None
344        ),
345        exp.Hex: lambda self, e: self.func("UPPER", self.func("TO_HEX", self.sql(e, "this"))),
346        exp.HexString: lambda self, e: self.hexstring_sql(e, binary_function_repr="FROM_HEX"),
347        exp.If: if_sql(false_value="NULL"),
348        exp.ILike: no_ilike_sql,
349        exp.IntDiv: rename_func("DIV"),
350        exp.Int64: rename_func("INT64"),
351        exp.JSONBool: rename_func("BOOL"),
352        exp.JSONExtract: _json_extract_sql,
353        exp.JSONExtractArray: _json_extract_sql,
354        exp.JSONExtractScalar: _json_extract_sql,
355        exp.JSONFormat: lambda self, e: self.func(
356            "TO_JSON" if e.args.get("to_json") else "TO_JSON_STRING",
357            e.this,
358            e.args.get("options"),
359        ),
360        exp.JSONKeysAtDepth: rename_func("JSON_KEYS"),
361        exp.JSONValueArray: rename_func("JSON_VALUE_ARRAY"),
362        exp.Levenshtein: _levenshtein_sql,
363        exp.Max: max_or_greatest,
364        exp.MD5: lambda self, e: self.func("TO_HEX", self.func("MD5", e.this)),
365        exp.MD5Digest: rename_func("MD5"),
366        exp.Min: min_or_least,
367        exp.Normalize: lambda self, e: self.func(
368            "NORMALIZE_AND_CASEFOLD" if e.args.get("is_casefold") else "NORMALIZE",
369            e.this,
370            e.args.get("form"),
371        ),
372        exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}",
373        exp.RegexpExtract: lambda self, e: self.func(
374            "REGEXP_EXTRACT",
375            e.this,
376            e.expression,
377            e.args.get("position"),
378            e.args.get("occurrence"),
379        ),
380        exp.RegexpExtractAll: lambda self, e: self.func("REGEXP_EXTRACT_ALL", e.this, e.expression),
381        exp.RegexpReplace: regexp_replace_sql,
382        exp.RegexpLike: rename_func("REGEXP_CONTAINS"),
383        exp.ReturnsProperty: _returnsproperty_sql,
384        exp.Rollback: lambda *_: "ROLLBACK TRANSACTION",
385        exp.ParseTime: lambda self, e: self.func("PARSE_TIME", self.format_time(e), e.this),
386        exp.ParseDatetime: lambda self, e: self.func("PARSE_DATETIME", self.format_time(e), e.this),
387        exp.Select: transforms.preprocess(
388            [
389                _unnest_explode_generate_series,
390                transforms.explode_projection_to_unnest(),
391                transforms.unqualify_unnest,
392                transforms.eliminate_distinct_on,
393                _alias_ordered_group,
394                transforms.eliminate_semi_and_anti_joins,
395            ]
396        ),
397        exp.SHA: rename_func("SHA1"),
398        exp.SHA2: sha256_sql,
399        exp.SHA1Digest: rename_func("SHA1"),
400        exp.SHA2Digest: sha2_digest_sql,
401        exp.StabilityProperty: lambda self, e: (
402            "DETERMINISTIC" if e.name == "IMMUTABLE" else "NOT DETERMINISTIC"
403        ),
404        exp.String: rename_func("STRING"),
405        exp.StrPosition: lambda self, e: strposition_sql(
406            self, e, func_name="INSTR", supports_position=True, supports_occurrence=True
407        ),
408        exp.StrToDate: _str_to_datetime_sql,
409        exp.StrToTime: _str_to_datetime_sql,
410        exp.SessionUser: lambda *_: "SESSION_USER()",
411        exp.Table: transforms.preprocess([transforms.unnest_generate_series]),
412        exp.TimeAdd: date_add_interval_sql("TIME", "ADD"),
413        exp.TimeFromParts: rename_func("TIME"),
414        exp.TimestampFromParts: rename_func("DATETIME"),
415        exp.TimeSub: date_add_interval_sql("TIME", "SUB"),
416        exp.TimestampAdd: date_add_interval_sql("TIMESTAMP", "ADD"),
417        exp.TimestampDiff: rename_func("TIMESTAMP_DIFF"),
418        exp.TimestampSub: date_add_interval_sql("TIMESTAMP", "SUB"),
419        exp.TimeStrToTime: timestrtotime_sql,
420        exp.Transaction: lambda *_: "BEGIN TRANSACTION",
421        exp.TsOrDsAdd: _ts_or_ds_add_sql,
422        exp.TsOrDsDiff: _ts_or_ds_diff_sql,
423        exp.TsOrDsToTime: rename_func("TIME"),
424        exp.TsOrDsToDatetime: rename_func("DATETIME"),
425        exp.TsOrDsToTimestamp: rename_func("TIMESTAMP"),
426        exp.Unhex: rename_func("FROM_HEX"),
427        exp.UnixDate: rename_func("UNIX_DATE"),
428        exp.UnixToTime: _unix_to_time_sql,
429        exp.Uuid: lambda *_: "GENERATE_UUID()",
430        exp.Values: _derived_table_values_to_unnest,
431        exp.VariancePop: rename_func("VAR_POP"),
432        exp.SafeDivide: rename_func("SAFE_DIVIDE"),
433    }
434
435    SUPPORTED_JSON_PATH_PARTS = {
436        exp.JSONPathKey,
437        exp.JSONPathRoot,
438        exp.JSONPathSubscript,
439    }
440
441    TYPE_MAPPING = {
442        **generator.Generator.TYPE_MAPPING,
443        exp.DType.BIGDECIMAL: "BIGNUMERIC",
444        exp.DType.BIGINT: "INT64",
445        exp.DType.BINARY: "BYTES",
446        exp.DType.BLOB: "BYTES",
447        exp.DType.BOOLEAN: "BOOL",
448        exp.DType.CHAR: "STRING",
449        exp.DType.DECIMAL: "NUMERIC",
450        exp.DType.DOUBLE: "FLOAT64",
451        exp.DType.FLOAT: "FLOAT64",
452        exp.DType.INT: "INT64",
453        exp.DType.NCHAR: "STRING",
454        exp.DType.NVARCHAR: "STRING",
455        exp.DType.SMALLINT: "INT64",
456        exp.DType.TEXT: "STRING",
457        exp.DType.TIMESTAMP: "DATETIME",
458        exp.DType.TIMESTAMPNTZ: "DATETIME",
459        exp.DType.TIMESTAMPTZ: "TIMESTAMP",
460        exp.DType.TIMESTAMPLTZ: "TIMESTAMP",
461        exp.DType.TINYINT: "INT64",
462        exp.DType.ROWVERSION: "BYTES",
463        exp.DType.UUID: "STRING",
464        exp.DType.VARBINARY: "BYTES",
465        exp.DType.VARCHAR: "STRING",
466        exp.DType.VARIANT: "ANY TYPE",
467    }
468
469    PROPERTIES_LOCATION = {
470        **generator.Generator.PROPERTIES_LOCATION,
471        exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,
472        exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
473    }
474
475    # WINDOW comes after QUALIFY
476    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#window_clause
477    # BigQuery requires QUALIFY before WINDOW
478    AFTER_HAVING_MODIFIER_TRANSFORMS = {
479        "qualify": generator.AFTER_HAVING_MODIFIER_TRANSFORMS["qualify"],
480        "windows": generator.AFTER_HAVING_MODIFIER_TRANSFORMS["windows"],
481    }
482
483    # from: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
484    RESERVED_KEYWORDS = {
485        "all",
486        "and",
487        "any",
488        "array",
489        "as",
490        "asc",
491        "assert_rows_modified",
492        "at",
493        "between",
494        "by",
495        "case",
496        "cast",
497        "collate",
498        "contains",
499        "create",
500        "cross",
501        "cube",
502        "current",
503        "default",
504        "define",
505        "desc",
506        "distinct",
507        "else",
508        "end",
509        "enum",
510        "escape",
511        "except",
512        "exclude",
513        "exists",
514        "extract",
515        "false",
516        "fetch",
517        "following",
518        "for",
519        "from",
520        "full",
521        "group",
522        "grouping",
523        "groups",
524        "hash",
525        "having",
526        "if",
527        "ignore",
528        "in",
529        "inner",
530        "intersect",
531        "interval",
532        "into",
533        "is",
534        "join",
535        "lateral",
536        "left",
537        "like",
538        "limit",
539        "lookup",
540        "merge",
541        "natural",
542        "new",
543        "no",
544        "not",
545        "null",
546        "nulls",
547        "of",
548        "on",
549        "or",
550        "order",
551        "outer",
552        "over",
553        "partition",
554        "preceding",
555        "proto",
556        "qualify",
557        "range",
558        "recursive",
559        "respect",
560        "right",
561        "rollup",
562        "rows",
563        "select",
564        "set",
565        "some",
566        "struct",
567        "tablesample",
568        "then",
569        "to",
570        "treat",
571        "true",
572        "unbounded",
573        "union",
574        "unnest",
575        "using",
576        "when",
577        "where",
578        "window",
579        "with",
580        "within",
581    }
582
583    def weekstart_sql(self, expression: exp.WeekStart) -> str:
584        if expression.this.name.upper() == "SUNDAY":
585            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
586            return "WEEK"
587
588        return self.func("WEEK", expression.this)
589
590    def datetrunc_sql(self, expression: exp.DateTrunc) -> str:
591        unit = expression.unit
592        unit_sql = unit.name if unit.is_string else self.sql(unit)
593        return self.func("DATE_TRUNC", expression.this, unit_sql, expression.args.get("zone"))
594
595    def mod_sql(self, expression: exp.Mod) -> str:
596        this = expression.this
597        expr = expression.expression
598        return self.func(
599            "MOD",
600            this.unnest() if isinstance(this, exp.Paren) else this,
601            expr.unnest() if isinstance(expr, exp.Paren) else expr,
602        )
603
604    def column_parts(self, expression: exp.Column) -> str:
605        if expression.meta_get("quoted_column"):
606            # If a column reference is of the form `dataset.table`.name, we need
607            # to preserve the quoted table path, otherwise the reference breaks
608            table_parts = ".".join(p.name for p in expression.parts[:-1])
609            table_path = self.sql(exp.Identifier(this=table_parts, quoted=True))
610            return f"{table_path}.{self.sql(expression, 'this')}"
611
612        return super().column_parts(expression)
613
614    def table_parts(self, expression: exp.Table) -> str:
615        # Depending on the context, `x.y` may not resolve to the same data source as `x`.`y`, so
616        # we need to make sure the correct quoting is used in each case.
617        #
618        # For example, if there is a CTE x that clashes with a schema name, then the former will
619        # return the table y in that schema, whereas the latter will return the CTE's y column:
620        #
621        # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x.y`   -> cross join
622        # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x`.`y` -> implicit unnest
623        if expression.meta_get("quoted_table"):
624            table_parts = ".".join(p.name for p in expression.parts)
625            return self.sql(exp.Identifier(this=table_parts, quoted=True))
626
627        return super().table_parts(expression)
628
629    def timetostr_sql(self, expression: exp.TimeToStr) -> str:
630        this = expression.this
631        if isinstance(this, exp.TsOrDsToDatetime):
632            func_name = "FORMAT_DATETIME"
633        elif isinstance(this, exp.TsOrDsToTimestamp):
634            func_name = "FORMAT_TIMESTAMP"
635        elif isinstance(this, exp.TsOrDsToTime):
636            func_name = "FORMAT_TIME"
637        else:
638            func_name = "FORMAT_DATE"
639
640        time_expr = this if isinstance(this, self.TS_OR_DS_TYPES) else expression
641        return self.func(
642            func_name, self.format_time(expression), time_expr.this, expression.args.get("zone")
643        )
644
645    def eq_sql(self, expression: exp.EQ) -> str:
646        # Operands of = cannot be NULL in BigQuery
647        if isinstance(expression.left, exp.Null) or isinstance(expression.right, exp.Null):
648            if not isinstance(expression.parent, exp.Update):
649                return "NULL"
650
651        return self.binary(expression, "=")
652
653    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
654        parent = expression.parent
655
656        # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).
657        # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.
658        if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"):
659            return self.func(
660                "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone"))
661            )
662
663        return super().attimezone_sql(expression)
664
665    def trycast_sql(self, expression: exp.TryCast) -> str:
666        return self.cast_sql(expression, safe_prefix="SAFE_")
667
668    def bracket_sql(self, expression: exp.Bracket) -> str:
669        this = expression.this
670        expressions = expression.expressions
671
672        if len(expressions) == 1 and this and this.is_type(exp.DType.STRUCT):
673            arg = expressions[0]
674            if arg.type is None:
675                from sqlglot.optimizer.annotate_types import annotate_types
676
677                arg = annotate_types(arg, dialect=self.dialect)
678
679            if arg.type and arg.type.this in exp.DataType.TEXT_TYPES:
680                # BQ doesn't support bracket syntax with string values for structs
681                return f"{self.sql(this)}.{arg.name}"
682
683        expressions_sql = self.expressions(expression, flat=True)
684        offset = expression.args.get("offset")
685
686        if offset == 0:
687            expressions_sql = f"OFFSET({expressions_sql})"
688        elif offset == 1:
689            expressions_sql = f"ORDINAL({expressions_sql})"
690        elif offset is not None:
691            self.unsupported(f"Unsupported array offset: {offset}")
692
693        if expression.args.get("safe"):
694            expressions_sql = f"SAFE_{expressions_sql}"
695
696        return f"{self.sql(this)}[{expressions_sql}]"
697
698    def in_unnest_op(self, expression: exp.Unnest) -> str:
699        return self.sql(expression)
700
701    def version_sql(self, expression: exp.Version) -> str:
702        if expression.name == "TIMESTAMP":
703            expression.set("this", "SYSTEM_TIME")
704        return super().version_sql(expression)
705
706    def contains_sql(self, expression: exp.Contains) -> str:
707        this = expression.this
708        expr = expression.expression
709
710        if isinstance(this, exp.Lower) and isinstance(expr, exp.Lower):
711            this = this.this
712            expr = expr.this
713
714        return self.func("CONTAINS_SUBSTR", this, expr, expression.args.get("json_scope"))
715
716    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
717        this = expression.this
718
719        # This ensures that inline type-annotated ARRAY literals like ARRAY<INT64>[1, 2, 3]
720        # are roundtripped unaffected. The inner check excludes ARRAY(SELECT ...) expressions,
721        # because they aren't literals and so the above syntax is invalid BigQuery.
722        if isinstance(this, exp.Array):
723            elem = seq_get(this.expressions, 0)
724            if not (elem and elem.find(exp.Query)):
725                return f"{self.sql(expression, 'to')}{self.sql(this)}"
726
727        return super().cast_sql(expression, safe_prefix=safe_prefix)
728
729    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
730        if expression.this:
731            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
732            return ""
733        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):
249class BigQueryGenerator(generator.Generator):
250    TRY_SUPPORTED = False
251    SUPPORTS_UESCAPE = False
252    SUPPORTS_DECODE_CASE = False
253    INTERVAL_ALLOWS_PLURAL_FORM = False
254    JOIN_HINTS = False
255    QUERY_HINTS = False
256    TABLE_HINTS = False
257    LIMIT_FETCH = "LIMIT"
258    RENAME_TABLE_WITH_DB = False
259    NVL2_SUPPORTED = False
260    UNNEST_WITH_ORDINALITY = False
261    COLLATE_IS_FUNC = True
262    LIMIT_ONLY_LITERALS = True
263    SUPPORTS_TABLE_ALIAS_COLUMNS = False
264    SUPPORTS_NAMED_CTE_COLUMNS = False
265    UNPIVOT_ALIASES_ARE_IDENTIFIERS = False
266    JSON_KEY_VALUE_PAIR_SEP = ","
267    NULL_ORDERING_SUPPORTED: bool | None = False
268    IGNORE_NULLS_IN_FUNC = True
269    JSON_PATH_SINGLE_QUOTE_ESCAPE = True
270    CAN_IMPLEMENT_ARRAY_ANY = True
271    SUPPORTS_TO_NUMBER = False
272    NAMED_PLACEHOLDER_TOKEN = "@"
273    HEX_FUNC = "TO_HEX"
274    WITH_PROPERTIES_PREFIX = "OPTIONS"
275    SUPPORTS_EXPLODING_PROJECTIONS = False
276    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False
277    SUPPORTS_UNIX_SECONDS = True
278    DECLARE_DEFAULT_ASSIGNMENT = "DEFAULT"
279
280    SAFE_JSON_PATH_KEY_RE = re.compile(r"^[\-\w]*$")
281
282    WINDOW_FUNCS_WITH_NULL_ORDERING = (
283        exp.CumeDist,
284        exp.DenseRank,
285        exp.FirstValue,
286        exp.Lag,
287        exp.LastValue,
288        exp.Lead,
289        exp.NthValue,
290        exp.Ntile,
291        exp.PercentRank,
292        exp.Rank,
293        exp.RowNumber,
294    )
295
296    TS_OR_DS_TYPES = (
297        exp.TsOrDsToDatetime,
298        exp.TsOrDsToTimestamp,
299        exp.TsOrDsToTime,
300        exp.TsOrDsToDate,
301    )
302
303    TRANSFORMS = {
304        **generator.Generator.TRANSFORMS,
305        exp.AIEmbed: rename_func("EMBED"),
306        exp.AIGenerate: rename_func("GENERATE"),
307        exp.AISimilarity: rename_func("SIMILARITY"),
308        exp.ApproxTopK: rename_func("APPROX_TOP_COUNT"),
309        exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"),
310        exp.ArgMax: arg_max_or_min_no_count("MAX_BY"),
311        exp.ArgMin: arg_max_or_min_no_count("MIN_BY"),
312        exp.Array: inline_array_unless_query,
313        exp.ArrayContains: _array_contains_sql,
314        exp.ArrayFilter: filter_array_using_unnest,
315        exp.ArrayRemove: filter_array_using_unnest,
316        exp.BitwiseAndAgg: rename_func("BIT_AND"),
317        exp.BitwiseOrAgg: rename_func("BIT_OR"),
318        exp.BitwiseXorAgg: rename_func("BIT_XOR"),
319        exp.BitwiseCount: rename_func("BIT_COUNT"),
320        exp.ByteLength: rename_func("BYTE_LENGTH"),
321        exp.Cast: transforms.preprocess([transforms.remove_precision_parameterized_types]),
322        exp.CollateProperty: lambda self, e: (
323            f"DEFAULT COLLATE {self.sql(e, 'this')}"
324            if e.args.get("default")
325            else f"COLLATE {self.sql(e, 'this')}"
326        ),
327        exp.Commit: lambda *_: "COMMIT TRANSACTION",
328        exp.CountIf: rename_func("COUNTIF"),
329        exp.Create: _create_sql,
330        exp.CTE: transforms.preprocess([_pushdown_cte_column_names]),
331        exp.DateAdd: date_add_interval_sql("DATE", "ADD"),
332        exp.DateDiff: lambda self, e: self.func("DATE_DIFF", e.this, e.expression, unit_to_var(e)),
333        exp.DateFromParts: rename_func("DATE"),
334        exp.DateStrToDate: datestrtodate_sql,
335        exp.DateSub: date_add_interval_sql("DATE", "SUB"),
336        exp.DatetimeAdd: date_add_interval_sql("DATETIME", "ADD"),
337        exp.DatetimeSub: date_add_interval_sql("DATETIME", "SUB"),
338        exp.DateFromUnixDate: rename_func("DATE_FROM_UNIX_DATE"),
339        exp.FromTimeZone: lambda self, e: self.func(
340            "DATETIME", self.func("TIMESTAMP", e.this, e.args.get("zone")), "'UTC'"
341        ),
342        exp.GenerateSeries: generate_series_sql("GENERATE_ARRAY"),
343        exp.GroupConcat: lambda self, e: groupconcat_sql(
344            self, e, func_name="STRING_AGG", within_group=False, sep=None
345        ),
346        exp.Hex: lambda self, e: self.func("UPPER", self.func("TO_HEX", self.sql(e, "this"))),
347        exp.HexString: lambda self, e: self.hexstring_sql(e, binary_function_repr="FROM_HEX"),
348        exp.If: if_sql(false_value="NULL"),
349        exp.ILike: no_ilike_sql,
350        exp.IntDiv: rename_func("DIV"),
351        exp.Int64: rename_func("INT64"),
352        exp.JSONBool: rename_func("BOOL"),
353        exp.JSONExtract: _json_extract_sql,
354        exp.JSONExtractArray: _json_extract_sql,
355        exp.JSONExtractScalar: _json_extract_sql,
356        exp.JSONFormat: lambda self, e: self.func(
357            "TO_JSON" if e.args.get("to_json") else "TO_JSON_STRING",
358            e.this,
359            e.args.get("options"),
360        ),
361        exp.JSONKeysAtDepth: rename_func("JSON_KEYS"),
362        exp.JSONValueArray: rename_func("JSON_VALUE_ARRAY"),
363        exp.Levenshtein: _levenshtein_sql,
364        exp.Max: max_or_greatest,
365        exp.MD5: lambda self, e: self.func("TO_HEX", self.func("MD5", e.this)),
366        exp.MD5Digest: rename_func("MD5"),
367        exp.Min: min_or_least,
368        exp.Normalize: lambda self, e: self.func(
369            "NORMALIZE_AND_CASEFOLD" if e.args.get("is_casefold") else "NORMALIZE",
370            e.this,
371            e.args.get("form"),
372        ),
373        exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}",
374        exp.RegexpExtract: lambda self, e: self.func(
375            "REGEXP_EXTRACT",
376            e.this,
377            e.expression,
378            e.args.get("position"),
379            e.args.get("occurrence"),
380        ),
381        exp.RegexpExtractAll: lambda self, e: self.func("REGEXP_EXTRACT_ALL", e.this, e.expression),
382        exp.RegexpReplace: regexp_replace_sql,
383        exp.RegexpLike: rename_func("REGEXP_CONTAINS"),
384        exp.ReturnsProperty: _returnsproperty_sql,
385        exp.Rollback: lambda *_: "ROLLBACK TRANSACTION",
386        exp.ParseTime: lambda self, e: self.func("PARSE_TIME", self.format_time(e), e.this),
387        exp.ParseDatetime: lambda self, e: self.func("PARSE_DATETIME", self.format_time(e), e.this),
388        exp.Select: transforms.preprocess(
389            [
390                _unnest_explode_generate_series,
391                transforms.explode_projection_to_unnest(),
392                transforms.unqualify_unnest,
393                transforms.eliminate_distinct_on,
394                _alias_ordered_group,
395                transforms.eliminate_semi_and_anti_joins,
396            ]
397        ),
398        exp.SHA: rename_func("SHA1"),
399        exp.SHA2: sha256_sql,
400        exp.SHA1Digest: rename_func("SHA1"),
401        exp.SHA2Digest: sha2_digest_sql,
402        exp.StabilityProperty: lambda self, e: (
403            "DETERMINISTIC" if e.name == "IMMUTABLE" else "NOT DETERMINISTIC"
404        ),
405        exp.String: rename_func("STRING"),
406        exp.StrPosition: lambda self, e: strposition_sql(
407            self, e, func_name="INSTR", supports_position=True, supports_occurrence=True
408        ),
409        exp.StrToDate: _str_to_datetime_sql,
410        exp.StrToTime: _str_to_datetime_sql,
411        exp.SessionUser: lambda *_: "SESSION_USER()",
412        exp.Table: transforms.preprocess([transforms.unnest_generate_series]),
413        exp.TimeAdd: date_add_interval_sql("TIME", "ADD"),
414        exp.TimeFromParts: rename_func("TIME"),
415        exp.TimestampFromParts: rename_func("DATETIME"),
416        exp.TimeSub: date_add_interval_sql("TIME", "SUB"),
417        exp.TimestampAdd: date_add_interval_sql("TIMESTAMP", "ADD"),
418        exp.TimestampDiff: rename_func("TIMESTAMP_DIFF"),
419        exp.TimestampSub: date_add_interval_sql("TIMESTAMP", "SUB"),
420        exp.TimeStrToTime: timestrtotime_sql,
421        exp.Transaction: lambda *_: "BEGIN TRANSACTION",
422        exp.TsOrDsAdd: _ts_or_ds_add_sql,
423        exp.TsOrDsDiff: _ts_or_ds_diff_sql,
424        exp.TsOrDsToTime: rename_func("TIME"),
425        exp.TsOrDsToDatetime: rename_func("DATETIME"),
426        exp.TsOrDsToTimestamp: rename_func("TIMESTAMP"),
427        exp.Unhex: rename_func("FROM_HEX"),
428        exp.UnixDate: rename_func("UNIX_DATE"),
429        exp.UnixToTime: _unix_to_time_sql,
430        exp.Uuid: lambda *_: "GENERATE_UUID()",
431        exp.Values: _derived_table_values_to_unnest,
432        exp.VariancePop: rename_func("VAR_POP"),
433        exp.SafeDivide: rename_func("SAFE_DIVIDE"),
434    }
435
436    SUPPORTED_JSON_PATH_PARTS = {
437        exp.JSONPathKey,
438        exp.JSONPathRoot,
439        exp.JSONPathSubscript,
440    }
441
442    TYPE_MAPPING = {
443        **generator.Generator.TYPE_MAPPING,
444        exp.DType.BIGDECIMAL: "BIGNUMERIC",
445        exp.DType.BIGINT: "INT64",
446        exp.DType.BINARY: "BYTES",
447        exp.DType.BLOB: "BYTES",
448        exp.DType.BOOLEAN: "BOOL",
449        exp.DType.CHAR: "STRING",
450        exp.DType.DECIMAL: "NUMERIC",
451        exp.DType.DOUBLE: "FLOAT64",
452        exp.DType.FLOAT: "FLOAT64",
453        exp.DType.INT: "INT64",
454        exp.DType.NCHAR: "STRING",
455        exp.DType.NVARCHAR: "STRING",
456        exp.DType.SMALLINT: "INT64",
457        exp.DType.TEXT: "STRING",
458        exp.DType.TIMESTAMP: "DATETIME",
459        exp.DType.TIMESTAMPNTZ: "DATETIME",
460        exp.DType.TIMESTAMPTZ: "TIMESTAMP",
461        exp.DType.TIMESTAMPLTZ: "TIMESTAMP",
462        exp.DType.TINYINT: "INT64",
463        exp.DType.ROWVERSION: "BYTES",
464        exp.DType.UUID: "STRING",
465        exp.DType.VARBINARY: "BYTES",
466        exp.DType.VARCHAR: "STRING",
467        exp.DType.VARIANT: "ANY TYPE",
468    }
469
470    PROPERTIES_LOCATION = {
471        **generator.Generator.PROPERTIES_LOCATION,
472        exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,
473        exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
474    }
475
476    # WINDOW comes after QUALIFY
477    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#window_clause
478    # BigQuery requires QUALIFY before WINDOW
479    AFTER_HAVING_MODIFIER_TRANSFORMS = {
480        "qualify": generator.AFTER_HAVING_MODIFIER_TRANSFORMS["qualify"],
481        "windows": generator.AFTER_HAVING_MODIFIER_TRANSFORMS["windows"],
482    }
483
484    # from: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
485    RESERVED_KEYWORDS = {
486        "all",
487        "and",
488        "any",
489        "array",
490        "as",
491        "asc",
492        "assert_rows_modified",
493        "at",
494        "between",
495        "by",
496        "case",
497        "cast",
498        "collate",
499        "contains",
500        "create",
501        "cross",
502        "cube",
503        "current",
504        "default",
505        "define",
506        "desc",
507        "distinct",
508        "else",
509        "end",
510        "enum",
511        "escape",
512        "except",
513        "exclude",
514        "exists",
515        "extract",
516        "false",
517        "fetch",
518        "following",
519        "for",
520        "from",
521        "full",
522        "group",
523        "grouping",
524        "groups",
525        "hash",
526        "having",
527        "if",
528        "ignore",
529        "in",
530        "inner",
531        "intersect",
532        "interval",
533        "into",
534        "is",
535        "join",
536        "lateral",
537        "left",
538        "like",
539        "limit",
540        "lookup",
541        "merge",
542        "natural",
543        "new",
544        "no",
545        "not",
546        "null",
547        "nulls",
548        "of",
549        "on",
550        "or",
551        "order",
552        "outer",
553        "over",
554        "partition",
555        "preceding",
556        "proto",
557        "qualify",
558        "range",
559        "recursive",
560        "respect",
561        "right",
562        "rollup",
563        "rows",
564        "select",
565        "set",
566        "some",
567        "struct",
568        "tablesample",
569        "then",
570        "to",
571        "treat",
572        "true",
573        "unbounded",
574        "union",
575        "unnest",
576        "using",
577        "when",
578        "where",
579        "window",
580        "with",
581        "within",
582    }
583
584    def weekstart_sql(self, expression: exp.WeekStart) -> str:
585        if expression.this.name.upper() == "SUNDAY":
586            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
587            return "WEEK"
588
589        return self.func("WEEK", expression.this)
590
591    def datetrunc_sql(self, expression: exp.DateTrunc) -> str:
592        unit = expression.unit
593        unit_sql = unit.name if unit.is_string else self.sql(unit)
594        return self.func("DATE_TRUNC", expression.this, unit_sql, expression.args.get("zone"))
595
596    def mod_sql(self, expression: exp.Mod) -> str:
597        this = expression.this
598        expr = expression.expression
599        return self.func(
600            "MOD",
601            this.unnest() if isinstance(this, exp.Paren) else this,
602            expr.unnest() if isinstance(expr, exp.Paren) else expr,
603        )
604
605    def column_parts(self, expression: exp.Column) -> str:
606        if expression.meta_get("quoted_column"):
607            # If a column reference is of the form `dataset.table`.name, we need
608            # to preserve the quoted table path, otherwise the reference breaks
609            table_parts = ".".join(p.name for p in expression.parts[:-1])
610            table_path = self.sql(exp.Identifier(this=table_parts, quoted=True))
611            return f"{table_path}.{self.sql(expression, 'this')}"
612
613        return super().column_parts(expression)
614
615    def table_parts(self, expression: exp.Table) -> str:
616        # Depending on the context, `x.y` may not resolve to the same data source as `x`.`y`, so
617        # we need to make sure the correct quoting is used in each case.
618        #
619        # For example, if there is a CTE x that clashes with a schema name, then the former will
620        # return the table y in that schema, whereas the latter will return the CTE's y column:
621        #
622        # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x.y`   -> cross join
623        # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x`.`y` -> implicit unnest
624        if expression.meta_get("quoted_table"):
625            table_parts = ".".join(p.name for p in expression.parts)
626            return self.sql(exp.Identifier(this=table_parts, quoted=True))
627
628        return super().table_parts(expression)
629
630    def timetostr_sql(self, expression: exp.TimeToStr) -> str:
631        this = expression.this
632        if isinstance(this, exp.TsOrDsToDatetime):
633            func_name = "FORMAT_DATETIME"
634        elif isinstance(this, exp.TsOrDsToTimestamp):
635            func_name = "FORMAT_TIMESTAMP"
636        elif isinstance(this, exp.TsOrDsToTime):
637            func_name = "FORMAT_TIME"
638        else:
639            func_name = "FORMAT_DATE"
640
641        time_expr = this if isinstance(this, self.TS_OR_DS_TYPES) else expression
642        return self.func(
643            func_name, self.format_time(expression), time_expr.this, expression.args.get("zone")
644        )
645
646    def eq_sql(self, expression: exp.EQ) -> str:
647        # Operands of = cannot be NULL in BigQuery
648        if isinstance(expression.left, exp.Null) or isinstance(expression.right, exp.Null):
649            if not isinstance(expression.parent, exp.Update):
650                return "NULL"
651
652        return self.binary(expression, "=")
653
654    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
655        parent = expression.parent
656
657        # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).
658        # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.
659        if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"):
660            return self.func(
661                "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone"))
662            )
663
664        return super().attimezone_sql(expression)
665
666    def trycast_sql(self, expression: exp.TryCast) -> str:
667        return self.cast_sql(expression, safe_prefix="SAFE_")
668
669    def bracket_sql(self, expression: exp.Bracket) -> str:
670        this = expression.this
671        expressions = expression.expressions
672
673        if len(expressions) == 1 and this and this.is_type(exp.DType.STRUCT):
674            arg = expressions[0]
675            if arg.type is None:
676                from sqlglot.optimizer.annotate_types import annotate_types
677
678                arg = annotate_types(arg, dialect=self.dialect)
679
680            if arg.type and arg.type.this in exp.DataType.TEXT_TYPES:
681                # BQ doesn't support bracket syntax with string values for structs
682                return f"{self.sql(this)}.{arg.name}"
683
684        expressions_sql = self.expressions(expression, flat=True)
685        offset = expression.args.get("offset")
686
687        if offset == 0:
688            expressions_sql = f"OFFSET({expressions_sql})"
689        elif offset == 1:
690            expressions_sql = f"ORDINAL({expressions_sql})"
691        elif offset is not None:
692            self.unsupported(f"Unsupported array offset: {offset}")
693
694        if expression.args.get("safe"):
695            expressions_sql = f"SAFE_{expressions_sql}"
696
697        return f"{self.sql(this)}[{expressions_sql}]"
698
699    def in_unnest_op(self, expression: exp.Unnest) -> str:
700        return self.sql(expression)
701
702    def version_sql(self, expression: exp.Version) -> str:
703        if expression.name == "TIMESTAMP":
704            expression.set("this", "SYSTEM_TIME")
705        return super().version_sql(expression)
706
707    def contains_sql(self, expression: exp.Contains) -> str:
708        this = expression.this
709        expr = expression.expression
710
711        if isinstance(this, exp.Lower) and isinstance(expr, exp.Lower):
712            this = this.this
713            expr = expr.this
714
715        return self.func("CONTAINS_SUBSTR", this, expr, expression.args.get("json_scope"))
716
717    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
718        this = expression.this
719
720        # This ensures that inline type-annotated ARRAY literals like ARRAY<INT64>[1, 2, 3]
721        # are roundtripped unaffected. The inner check excludes ARRAY(SELECT ...) expressions,
722        # because they aren't literals and so the above syntax is invalid BigQuery.
723        if isinstance(this, exp.Array):
724            elem = seq_get(this.expressions, 0)
725            if not (elem and elem.find(exp.Query)):
726                return f"{self.sql(expression, 'to')}{self.sql(this)}"
727
728        return super().cast_sql(expression, safe_prefix=safe_prefix)
729
730    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
731        if expression.this:
732            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
733            return ""
734        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
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.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.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.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.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.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.string.Normalize'>: <function BigQueryGenerator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <function BigQueryGenerator.<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.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.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.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.StrictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Tags'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
AFTER_HAVING_MODIFIER_TRANSFORMS = {'qualify': <function <lambda>>, 'windows': <function <lambda>>}
RESERVED_KEYWORDS = {'from', 'cast', 'natural', 'and', 'for', 'left', 'window', 'in', 'between', 'within', 'tablesample', 'rows', 'unnest', 'false', 'exclude', 'outer', 'hash', 'enum', 'exists', 'at', 'create', 'limit', 'not', 'preceding', 'cross', 'extract', 'qualify', 'where', 'if', 'set', 'join', 'following', 'default', 'any', 'of', 'unbounded', 'partition', 'union', 'assert_rows_modified', 'treat', 'current', 'all', 'cube', 'true', 'except', 'merge', 'inner', 'by', 'range', 'having', 'grouping', 'proto', 'else', 'order', 'right', 'contains', 'some', 'rollup', 'lookup', 'as', 'desc', 'recursive', 'over', 'escape', 'end', 'null', 'respect', 'on', 'to', 'is', 'when', 'struct', 'fetch', 'array', 'distinct', 'intersect', 'ignore', 'lateral', 'full', 'collate', 'into', 'nulls', 'no', 'case', 'with', 'using', 'like', 'then', 'or', 'groups', 'asc', 'interval', 'select', 'group', 'define', 'new'}
def weekstart_sql(self, expression: sqlglot.expressions.functions.WeekStart) -> str:
584    def weekstart_sql(self, expression: exp.WeekStart) -> str:
585        if expression.this.name.upper() == "SUNDAY":
586            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK
587            return "WEEK"
588
589        return self.func("WEEK", expression.this)
def datetrunc_sql(self, expression: sqlglot.expressions.temporal.DateTrunc) -> str:
591    def datetrunc_sql(self, expression: exp.DateTrunc) -> str:
592        unit = expression.unit
593        unit_sql = unit.name if unit.is_string else self.sql(unit)
594        return self.func("DATE_TRUNC", expression.this, unit_sql, expression.args.get("zone"))
def mod_sql(self, expression: sqlglot.expressions.core.Mod) -> str:
596    def mod_sql(self, expression: exp.Mod) -> str:
597        this = expression.this
598        expr = expression.expression
599        return self.func(
600            "MOD",
601            this.unnest() if isinstance(this, exp.Paren) else this,
602            expr.unnest() if isinstance(expr, exp.Paren) else expr,
603        )
def column_parts(self, expression: sqlglot.expressions.core.Column) -> str:
605    def column_parts(self, expression: exp.Column) -> str:
606        if expression.meta_get("quoted_column"):
607            # If a column reference is of the form `dataset.table`.name, we need
608            # to preserve the quoted table path, otherwise the reference breaks
609            table_parts = ".".join(p.name for p in expression.parts[:-1])
610            table_path = self.sql(exp.Identifier(this=table_parts, quoted=True))
611            return f"{table_path}.{self.sql(expression, 'this')}"
612
613        return super().column_parts(expression)
def table_parts(self, expression: sqlglot.expressions.query.Table) -> str:
615    def table_parts(self, expression: exp.Table) -> str:
616        # Depending on the context, `x.y` may not resolve to the same data source as `x`.`y`, so
617        # we need to make sure the correct quoting is used in each case.
618        #
619        # For example, if there is a CTE x that clashes with a schema name, then the former will
620        # return the table y in that schema, whereas the latter will return the CTE's y column:
621        #
622        # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x.y`   -> cross join
623        # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x`.`y` -> implicit unnest
624        if expression.meta_get("quoted_table"):
625            table_parts = ".".join(p.name for p in expression.parts)
626            return self.sql(exp.Identifier(this=table_parts, quoted=True))
627
628        return super().table_parts(expression)
def timetostr_sql(self, expression: sqlglot.expressions.temporal.TimeToStr) -> str:
630    def timetostr_sql(self, expression: exp.TimeToStr) -> str:
631        this = expression.this
632        if isinstance(this, exp.TsOrDsToDatetime):
633            func_name = "FORMAT_DATETIME"
634        elif isinstance(this, exp.TsOrDsToTimestamp):
635            func_name = "FORMAT_TIMESTAMP"
636        elif isinstance(this, exp.TsOrDsToTime):
637            func_name = "FORMAT_TIME"
638        else:
639            func_name = "FORMAT_DATE"
640
641        time_expr = this if isinstance(this, self.TS_OR_DS_TYPES) else expression
642        return self.func(
643            func_name, self.format_time(expression), time_expr.this, expression.args.get("zone")
644        )
def eq_sql(self, expression: sqlglot.expressions.core.EQ) -> str:
646    def eq_sql(self, expression: exp.EQ) -> str:
647        # Operands of = cannot be NULL in BigQuery
648        if isinstance(expression.left, exp.Null) or isinstance(expression.right, exp.Null):
649            if not isinstance(expression.parent, exp.Update):
650                return "NULL"
651
652        return self.binary(expression, "=")
def attimezone_sql(self, expression: sqlglot.expressions.core.AtTimeZone) -> str:
654    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
655        parent = expression.parent
656
657        # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).
658        # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.
659        if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"):
660            return self.func(
661                "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone"))
662            )
663
664        return super().attimezone_sql(expression)
def trycast_sql(self, expression: sqlglot.expressions.functions.TryCast) -> str:
666    def trycast_sql(self, expression: exp.TryCast) -> str:
667        return self.cast_sql(expression, safe_prefix="SAFE_")
def bracket_sql(self, expression: sqlglot.expressions.core.Bracket) -> str:
669    def bracket_sql(self, expression: exp.Bracket) -> str:
670        this = expression.this
671        expressions = expression.expressions
672
673        if len(expressions) == 1 and this and this.is_type(exp.DType.STRUCT):
674            arg = expressions[0]
675            if arg.type is None:
676                from sqlglot.optimizer.annotate_types import annotate_types
677
678                arg = annotate_types(arg, dialect=self.dialect)
679
680            if arg.type and arg.type.this in exp.DataType.TEXT_TYPES:
681                # BQ doesn't support bracket syntax with string values for structs
682                return f"{self.sql(this)}.{arg.name}"
683
684        expressions_sql = self.expressions(expression, flat=True)
685        offset = expression.args.get("offset")
686
687        if offset == 0:
688            expressions_sql = f"OFFSET({expressions_sql})"
689        elif offset == 1:
690            expressions_sql = f"ORDINAL({expressions_sql})"
691        elif offset is not None:
692            self.unsupported(f"Unsupported array offset: {offset}")
693
694        if expression.args.get("safe"):
695            expressions_sql = f"SAFE_{expressions_sql}"
696
697        return f"{self.sql(this)}[{expressions_sql}]"
def in_unnest_op(self, expression: sqlglot.expressions.array.Unnest) -> str:
699    def in_unnest_op(self, expression: exp.Unnest) -> str:
700        return self.sql(expression)
def version_sql(self, expression: sqlglot.expressions.query.Version) -> str:
702    def version_sql(self, expression: exp.Version) -> str:
703        if expression.name == "TIMESTAMP":
704            expression.set("this", "SYSTEM_TIME")
705        return super().version_sql(expression)
def contains_sql(self, expression: sqlglot.expressions.string.Contains) -> str:
707    def contains_sql(self, expression: exp.Contains) -> str:
708        this = expression.this
709        expr = expression.expression
710
711        if isinstance(this, exp.Lower) and isinstance(expr, exp.Lower):
712            this = this.this
713            expr = expr.this
714
715        return self.func("CONTAINS_SUBSTR", this, expr, expression.args.get("json_scope"))
def cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
717    def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:
718        this = expression.this
719
720        # This ensures that inline type-annotated ARRAY literals like ARRAY<INT64>[1, 2, 3]
721        # are roundtripped unaffected. The inner check excludes ARRAY(SELECT ...) expressions,
722        # because they aren't literals and so the above syntax is invalid BigQuery.
723        if isinstance(this, exp.Array):
724            elem = seq_get(this.expressions, 0)
725            if not (elem and elem.find(exp.Query)):
726                return f"{self.sql(expression, 'to')}{self.sql(this)}"
727
728        return super().cast_sql(expression, safe_prefix=safe_prefix)
def clusterproperty_sql(self, expression: sqlglot.expressions.properties.ClusterProperty) -> str:
730    def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str:
731        if expression.this:
732            self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}")
733            return ""
734        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
INDEX_ON
INOUT_SEPARATOR
DIRECTED_JOINS
QUERY_HINT_SEP
IS_BOOL_ALLOWED
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
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
COPY_PARAMS_ARE_WRAPPED
COPY_PARAMS_EQ_REQUIRED
COPY_HAS_INTO_KEYWORD
UNICODE_SUBSTITUTE
STAR_EXCEPT
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
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
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
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
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
func
format_args
too_wide
format_time
expressions
op_expressions
naked_property
tag_sql
token_sql
userdefinedfunction_sql
macrooverloads_sql
macrooverload_sql
joinhint_sql
kwarg_sql
when_sql
whens_sql
merge_sql
tochar_sql
tonumber_sql
dictproperty_sql
dictrange_sql
dictsubproperty_sql
duplicatekeyproperty_sql
uniquekeyproperty_sql
distributedbyproperty_sql
oncluster_sql
clusteredbyproperty_sql
anyvalue_sql
querytransform_sql
indexconstraintoption_sql
checkcolumnconstraint_sql
indexcolumnconstraint_sql
nvl2_sql
comprehension_sql
columnprefix_sql
opclass_sql
predict_sql
generateembedding_sql
generatetext_sql
generatetable_sql
generatebool_sql
generateint_sql
generatedouble_sql
mltranslate_sql
mlforecast_sql
aiforecast_sql
featuresattime_sql
vectorsearch_sql
forin_sql
refresh_sql
toarray_sql
tsordstotime_sql
tsordstotimestamp_sql
tsordstodatetime_sql
tsordstodate_sql
unixdate_sql
lastday_sql
dateadd_sql
arrayany_sql
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
parsejson_sql
rand_sql
changes_sql
pad_sql
summarize_sql
explodinggenerateseries_sql
converttimezone_sql
json_sql
jsonvalue_sql
skipjsoncolumn_sql
conditionalinsert_sql
multitableinserts_sql
oncondition_sql
jsonextractquote_sql
jsonexists_sql
arrayagg_sql
slice_sql
apply_sql
grant_sql
revoke_sql
grantprivilege_sql
grantprincipal_sql
columns_sql
overlay_sql
todouble_sql
string_sql
median_sql
overflowtruncatebehavior_sql
unixseconds_sql
arraysize_sql
attach_sql
detach_sql
attachoption_sql
watermarkcolumnconstraint_sql
encodeproperty_sql
includeproperty_sql
xmlelement_sql
xmlkeyvalueoption_sql
partitionbyrangeproperty_sql
partitionbyrangepropertydynamic_sql
unpivotcolumns_sql
analyzesample_sql
analyzestatistics_sql
analyzehistogram_sql
analyzedelete_sql
analyzelistchainedrows_sql
analyzevalidate_sql
analyze_sql
xmltable_sql
xmlnamespace_sql
export_sql
declare_sql
declareitem_sql
recursivewithsearch_sql
parameterizedagg_sql
anonymousaggfunc_sql
combinedaggfunc_sql
combinedparameterizedagg_sql
show_sql
install_sql
get_put_sql
translatecharacters_sql
decodecase_sql
semanticview_sql
getextract_sql
datefromunixdate_sql
space_sql
buildproperty_sql
refreshtriggerproperty_sql
modelattribute_sql
directorystage_sql
uuid_sql
initcap_sql
localtime_sql
localtimestamp_sql
weekstart_name
chr_sql
block_sql
functionspecification_sql
storedprocedure_sql
ifblock_sql
whileblock_sql
execute_sql
executesql_sql
altermodifysqlsecurity_sql
usingproperty_sql
renameindex_sql