From a45bbbb6f2fbd117d5d314e34e85afc2b48ad677 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 20 Sep 2023 11:22:25 +0200 Subject: Merging upstream version 18.5.1. Signed-off-by: Daniel Baumann --- docs/sqlglot/dialects/bigquery.html | 3154 ++++++++++++++++++----------------- 1 file changed, 1583 insertions(+), 1571 deletions(-) (limited to 'docs/sqlglot/dialects/bigquery.html') diff --git a/docs/sqlglot/dialects/bigquery.html b/docs/sqlglot/dialects/bigquery.html index a4bc180..dc2ecd3 100644 --- a/docs/sqlglot/dialects/bigquery.html +++ b/docs/sqlglot/dialects/bigquery.html @@ -39,6 +39,9 @@
  • UNNEST_COLUMN_ONLY
  • +
  • + SUPPORTS_USER_DEFINED_TYPES +
  • RESOLVES_IDENTIFIERS_AS_UPPERCASE
  • @@ -99,9 +102,6 @@
  • LOG_DEFAULTS_TO_LN
  • -
  • - SUPPORTS_USER_DEFINED_TYPES -
  • FUNCTIONS
  • @@ -129,6 +129,9 @@
  • UNNEST_COLUMN_ONLY
  • +
  • + SUPPORTS_USER_DEFINED_TYPES +
  • NORMALIZE_FUNCTIONS
  • @@ -551,511 +554,510 @@ 187 188class BigQuery(Dialect): 189 UNNEST_COLUMN_ONLY = True -190 -191 # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#case_sensitivity -192 RESOLVES_IDENTIFIERS_AS_UPPERCASE = None -193 -194 # bigquery udfs are case sensitive -195 NORMALIZE_FUNCTIONS = False -196 -197 TIME_MAPPING = { -198 "%D": "%m/%d/%y", -199 } -200 -201 FORMAT_MAPPING = { -202 "DD": "%d", -203 "MM": "%m", -204 "MON": "%b", -205 "MONTH": "%B", -206 "YYYY": "%Y", -207 "YY": "%y", -208 "HH": "%I", -209 "HH12": "%I", -210 "HH24": "%H", -211 "MI": "%M", -212 "SS": "%S", -213 "SSSSS": "%f", -214 "TZH": "%z", -215 } -216 -217 # The _PARTITIONTIME and _PARTITIONDATE pseudo-columns are not returned by a SELECT * statement -218 # https://cloud.google.com/bigquery/docs/querying-partitioned-tables#query_an_ingestion-time_partitioned_table -219 PSEUDOCOLUMNS = {"_PARTITIONTIME", "_PARTITIONDATE"} -220 -221 @classmethod -222 def normalize_identifier(cls, expression: E) -> E: -223 # In BigQuery, CTEs aren't case-sensitive, but table names are (by default, at least). -224 # The following check is essentially a heuristic to detect tables based on whether or -225 # not they're qualified. -226 if isinstance(expression, exp.Identifier): -227 parent = expression.parent -228 -229 while isinstance(parent, exp.Dot): -230 parent = parent.parent -231 -232 if ( -233 not isinstance(parent, exp.UserDefinedFunction) -234 and not (isinstance(parent, exp.Table) and parent.db) -235 and not expression.meta.get("is_table") -236 ): -237 expression.set("this", expression.this.lower()) -238 -239 return expression -240 -241 class Tokenizer(tokens.Tokenizer): -242 QUOTES = ["'", '"', '"""', "'''"] -243 COMMENTS = ["--", "#", ("/*", "*/")] -244 IDENTIFIERS = ["`"] -245 STRING_ESCAPES = ["\\"] -246 -247 HEX_STRINGS = [("0x", ""), ("0X", "")] -248 -249 BYTE_STRINGS = [ -250 (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in ("b", "B") -251 ] -252 -253 RAW_STRINGS = [ -254 (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in ("r", "R") -255 ] -256 -257 KEYWORDS = { -258 **tokens.Tokenizer.KEYWORDS, -259 "ANY TYPE": TokenType.VARIANT, -260 "BEGIN": TokenType.COMMAND, -261 "BEGIN TRANSACTION": TokenType.BEGIN, -262 "CURRENT_DATETIME": TokenType.CURRENT_DATETIME, -263 "BYTES": TokenType.BINARY, -264 "DECLARE": TokenType.COMMAND, -265 "FLOAT64": TokenType.DOUBLE, -266 "INT64": TokenType.BIGINT, -267 "RECORD": TokenType.STRUCT, -268 "TIMESTAMP": TokenType.TIMESTAMPTZ, -269 "NOT DETERMINISTIC": TokenType.VOLATILE, -270 "UNKNOWN": TokenType.NULL, -271 "FOR SYSTEM_TIME": TokenType.TIMESTAMP_SNAPSHOT, -272 } -273 KEYWORDS.pop("DIV") -274 -275 class Parser(parser.Parser): -276 PREFIXED_PIVOT_COLUMNS = True -277 -278 LOG_BASE_FIRST = False -279 LOG_DEFAULTS_TO_LN = True -280 -281 SUPPORTS_USER_DEFINED_TYPES = False -282 -283 FUNCTIONS = { -284 **parser.Parser.FUNCTIONS, -285 "DATE": _parse_date, -286 "DATE_ADD": parse_date_delta_with_interval(exp.DateAdd), -287 "DATE_SUB": parse_date_delta_with_interval(exp.DateSub), -288 "DATE_TRUNC": lambda args: exp.DateTrunc( -289 unit=exp.Literal.string(str(seq_get(args, 1))), -290 this=seq_get(args, 0), -291 ), -292 "DATETIME_ADD": parse_date_delta_with_interval(exp.DatetimeAdd), -293 "DATETIME_SUB": parse_date_delta_with_interval(exp.DatetimeSub), -294 "DIV": binary_from_function(exp.IntDiv), -295 "GENERATE_ARRAY": exp.GenerateSeries.from_arg_list, -296 "MD5": exp.MD5Digest.from_arg_list, -297 "TO_HEX": _parse_to_hex, -298 "PARSE_DATE": lambda args: format_time_lambda(exp.StrToDate, "bigquery")( -299 [seq_get(args, 1), seq_get(args, 0)] -300 ), -301 "PARSE_TIMESTAMP": _parse_timestamp, -302 "REGEXP_CONTAINS": exp.RegexpLike.from_arg_list, -303 "REGEXP_EXTRACT": lambda args: exp.RegexpExtract( -304 this=seq_get(args, 0), -305 expression=seq_get(args, 1), -306 position=seq_get(args, 2), -307 occurrence=seq_get(args, 3), -308 group=exp.Literal.number(1) -309 if re.compile(str(seq_get(args, 1))).groups == 1 -310 else None, -311 ), -312 "SHA256": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)), -313 "SHA512": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)), -314 "SPLIT": lambda args: exp.Split( -315 # https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#split -316 this=seq_get(args, 0), -317 expression=seq_get(args, 1) or exp.Literal.string(","), -318 ), -319 "TIME_ADD": parse_date_delta_with_interval(exp.TimeAdd), -320 "TIME_SUB": parse_date_delta_with_interval(exp.TimeSub), -321 "TIMESTAMP_ADD": parse_date_delta_with_interval(exp.TimestampAdd), -322 "TIMESTAMP_SUB": parse_date_delta_with_interval(exp.TimestampSub), -323 "TO_JSON_STRING": exp.JSONFormat.from_arg_list, -324 } -325 -326 FUNCTION_PARSERS = { -327 **parser.Parser.FUNCTION_PARSERS, -328 "ARRAY": lambda self: self.expression(exp.Array, expressions=[self._parse_statement()]), -329 } -330 FUNCTION_PARSERS.pop("TRIM") -331 -332 NO_PAREN_FUNCTIONS = { -333 **parser.Parser.NO_PAREN_FUNCTIONS, -334 TokenType.CURRENT_DATETIME: exp.CurrentDatetime, -335 } -336 -337 NESTED_TYPE_TOKENS = { -338 *parser.Parser.NESTED_TYPE_TOKENS, -339 TokenType.TABLE, -340 } -341 -342 ID_VAR_TOKENS = { -343 *parser.Parser.ID_VAR_TOKENS, -344 TokenType.VALUES, -345 } -346 -347 PROPERTY_PARSERS = { -348 **parser.Parser.PROPERTY_PARSERS, -349 "NOT DETERMINISTIC": lambda self: self.expression( -350 exp.StabilityProperty, this=exp.Literal.string("VOLATILE") -351 ), -352 "OPTIONS": lambda self: self._parse_with_property(), -353 } -354 -355 CONSTRAINT_PARSERS = { -356 **parser.Parser.CONSTRAINT_PARSERS, -357 "OPTIONS": lambda self: exp.Properties(expressions=self._parse_with_property()), -358 } -359 -360 def _parse_table_part(self, schema: bool = False) -> t.Optional[exp.Expression]: -361 this = super()._parse_table_part(schema=schema) or self._parse_number() -362 -363 # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#table_names -364 if isinstance(this, exp.Identifier): -365 table_name = this.name -366 while self._match(TokenType.DASH, advance=False) and self._next: -367 self._advance(2) -368 table_name += f"-{self._prev.text}" -369 -370 this = exp.Identifier(this=table_name, quoted=this.args.get("quoted")) -371 elif isinstance(this, exp.Literal): -372 table_name = this.name -373 -374 if ( -375 self._curr -376 and self._prev.end == self._curr.start - 1 -377 and self._parse_var(any_token=True) -378 ): -379 table_name += self._prev.text -380 -381 this = exp.Identifier(this=table_name, quoted=True) -382 -383 return this -384 -385 def _parse_table_parts(self, schema: bool = False) -> exp.Table: -386 table = super()._parse_table_parts(schema=schema) -387 if isinstance(table.this, exp.Identifier) and "." in table.name: -388 catalog, db, this, *rest = ( -389 t.cast(t.Optional[exp.Expression], exp.to_identifier(x)) -390 for x in split_num_words(table.name, ".", 3) -391 ) -392 -393 if rest and this: -394 this = exp.Dot.build(t.cast(t.List[exp.Expression], [this, *rest])) -395 -396 table = exp.Table(this=this, db=db, catalog=catalog) -397 -398 return table -399 -400 def _parse_json_object(self) -> exp.JSONObject: -401 json_object = super()._parse_json_object() -402 array_kv_pair = seq_get(json_object.expressions, 0) -403 -404 # Converts BQ's "signature 2" of JSON_OBJECT into SQLGlot's canonical representation -405 # https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_object_signature2 -406 if ( -407 array_kv_pair -408 and isinstance(array_kv_pair.this, exp.Array) -409 and isinstance(array_kv_pair.expression, exp.Array) -410 ): -411 keys = array_kv_pair.this.expressions -412 values = array_kv_pair.expression.expressions -413 -414 json_object.set( -415 "expressions", -416 [exp.JSONKeyValue(this=k, expression=v) for k, v in zip(keys, values)], -417 ) -418 -419 return json_object -420 -421 class Generator(generator.Generator): -422 EXPLICIT_UNION = True -423 INTERVAL_ALLOWS_PLURAL_FORM = False -424 JOIN_HINTS = False -425 QUERY_HINTS = False -426 TABLE_HINTS = False -427 LIMIT_FETCH = "LIMIT" -428 RENAME_TABLE_WITH_DB = False -429 ESCAPE_LINE_BREAK = True -430 NVL2_SUPPORTED = False -431 -432 TRANSFORMS = { -433 **generator.Generator.TRANSFORMS, -434 exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), -435 exp.ArraySize: rename_func("ARRAY_LENGTH"), -436 exp.Cast: transforms.preprocess([transforms.remove_precision_parameterized_types]), -437 exp.Create: _create_sql, -438 exp.CTE: transforms.preprocess([_pushdown_cte_column_names]), -439 exp.DateAdd: _date_add_sql("DATE", "ADD"), -440 exp.DateDiff: lambda self, e: f"DATE_DIFF({self.sql(e, 'this')}, {self.sql(e, 'expression')}, {self.sql(e.args.get('unit', 'DAY'))})", -441 exp.DateFromParts: rename_func("DATE"), -442 exp.DateStrToDate: datestrtodate_sql, -443 exp.DateSub: _date_add_sql("DATE", "SUB"), -444 exp.DatetimeAdd: _date_add_sql("DATETIME", "ADD"), -445 exp.DatetimeSub: _date_add_sql("DATETIME", "SUB"), -446 exp.DateTrunc: lambda self, e: self.func("DATE_TRUNC", e.this, e.text("unit")), -447 exp.GenerateSeries: rename_func("GENERATE_ARRAY"), -448 exp.GroupConcat: rename_func("STRING_AGG"), -449 exp.Hex: rename_func("TO_HEX"), -450 exp.ILike: no_ilike_sql, -451 exp.IntDiv: rename_func("DIV"), -452 exp.JSONFormat: rename_func("TO_JSON_STRING"), -453 exp.JSONKeyValue: json_keyvalue_comma_sql, -454 exp.Max: max_or_greatest, -455 exp.MD5: lambda self, e: self.func("TO_HEX", self.func("MD5", e.this)), -456 exp.MD5Digest: rename_func("MD5"), -457 exp.Min: min_or_least, -458 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", -459 exp.RegexpExtract: lambda self, e: self.func( -460 "REGEXP_EXTRACT", -461 e.this, -462 e.expression, -463 e.args.get("position"), -464 e.args.get("occurrence"), -465 ), -466 exp.RegexpReplace: regexp_replace_sql, -467 exp.RegexpLike: rename_func("REGEXP_CONTAINS"), -468 exp.ReturnsProperty: _returnsproperty_sql, -469 exp.Select: transforms.preprocess( -470 [ -471 transforms.explode_to_unnest, -472 _unqualify_unnest, -473 transforms.eliminate_distinct_on, -474 _alias_ordered_group, -475 ] -476 ), -477 exp.SHA2: lambda self, e: self.func( -478 f"SHA256" if e.text("length") == "256" else "SHA512", e.this -479 ), -480 exp.StabilityProperty: lambda self, e: f"DETERMINISTIC" -481 if e.name == "IMMUTABLE" -482 else "NOT DETERMINISTIC", -483 exp.StrToDate: lambda self, e: f"PARSE_DATE({self.format_time(e)}, {self.sql(e, 'this')})", -484 exp.StrToTime: lambda self, e: self.func( -485 "PARSE_TIMESTAMP", self.format_time(e), e.this, e.args.get("zone") -486 ), -487 exp.TimeAdd: _date_add_sql("TIME", "ADD"), -488 exp.TimeSub: _date_add_sql("TIME", "SUB"), -489 exp.TimestampAdd: _date_add_sql("TIMESTAMP", "ADD"), -490 exp.TimestampSub: _date_add_sql("TIMESTAMP", "SUB"), -491 exp.TimeStrToTime: timestrtotime_sql, -492 exp.Trim: lambda self, e: self.func(f"TRIM", e.this, e.expression), -493 exp.TsOrDsAdd: _date_add_sql("DATE", "ADD"), -494 exp.TsOrDsToDate: ts_or_ds_to_date_sql("bigquery"), -495 exp.Unhex: rename_func("FROM_HEX"), -496 exp.Values: _derived_table_values_to_unnest, -497 exp.VariancePop: rename_func("VAR_POP"), -498 } -499 -500 TYPE_MAPPING = { -501 **generator.Generator.TYPE_MAPPING, -502 exp.DataType.Type.BIGDECIMAL: "BIGNUMERIC", -503 exp.DataType.Type.BIGINT: "INT64", -504 exp.DataType.Type.BINARY: "BYTES", -505 exp.DataType.Type.BOOLEAN: "BOOL", -506 exp.DataType.Type.CHAR: "STRING", -507 exp.DataType.Type.DECIMAL: "NUMERIC", -508 exp.DataType.Type.DOUBLE: "FLOAT64", -509 exp.DataType.Type.FLOAT: "FLOAT64", -510 exp.DataType.Type.INT: "INT64", -511 exp.DataType.Type.NCHAR: "STRING", -512 exp.DataType.Type.NVARCHAR: "STRING", -513 exp.DataType.Type.SMALLINT: "INT64", -514 exp.DataType.Type.TEXT: "STRING", -515 exp.DataType.Type.TIMESTAMP: "DATETIME", -516 exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP", -517 exp.DataType.Type.TIMESTAMPLTZ: "TIMESTAMP", -518 exp.DataType.Type.TINYINT: "INT64", -519 exp.DataType.Type.VARBINARY: "BYTES", -520 exp.DataType.Type.VARCHAR: "STRING", -521 exp.DataType.Type.VARIANT: "ANY TYPE", -522 } -523 -524 PROPERTIES_LOCATION = { -525 **generator.Generator.PROPERTIES_LOCATION, -526 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, -527 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, -528 } -529 -530 # from: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords -531 RESERVED_KEYWORDS = { -532 *generator.Generator.RESERVED_KEYWORDS, -533 "all", -534 "and", -535 "any", -536 "array", -537 "as", -538 "asc", -539 "assert_rows_modified", -540 "at", -541 "between", -542 "by", -543 "case", -544 "cast", -545 "collate", -546 "contains", -547 "create", -548 "cross", -549 "cube", -550 "current", -551 "default", -552 "define", -553 "desc", -554 "distinct", -555 "else", -556 "end", -557 "enum", -558 "escape", -559 "except", -560 "exclude", -561 "exists", -562 "extract", -563 "false", -564 "fetch", -565 "following", -566 "for", -567 "from", -568 "full", -569 "group", -570 "grouping", -571 "groups", -572 "hash", -573 "having", -574 "if", -575 "ignore", -576 "in", -577 "inner", -578 "intersect", -579 "interval", -580 "into", -581 "is", -582 "join", -583 "lateral", -584 "left", -585 "like", -586 "limit", -587 "lookup", -588 "merge", -589 "natural", -590 "new", -591 "no", -592 "not", -593 "null", -594 "nulls", -595 "of", -596 "on", -597 "or", -598 "order", -599 "outer", -600 "over", -601 "partition", -602 "preceding", -603 "proto", -604 "qualify", -605 "range", -606 "recursive", -607 "respect", -608 "right", -609 "rollup", -610 "rows", -611 "select", -612 "set", -613 "some", -614 "struct", -615 "tablesample", -616 "then", -617 "to", -618 "treat", -619 "true", -620 "unbounded", -621 "union", -622 "unnest", -623 "using", -624 "when", -625 "where", -626 "window", -627 "with", -628 "within", -629 } -630 -631 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: -632 parent = expression.parent -633 -634 # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]). -635 # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included. -636 if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"): -637 return self.func( -638 "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone")) -639 ) -640 -641 return super().attimezone_sql(expression) -642 -643 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: -644 # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#json_literals -645 if expression.is_type("json"): -646 return f"JSON {self.sql(expression, 'this')}" -647 -648 return super().cast_sql(expression, safe_prefix=safe_prefix) -649 -650 def trycast_sql(self, expression: exp.TryCast) -> str: -651 return self.cast_sql(expression, safe_prefix="SAFE_") -652 -653 def cte_sql(self, expression: exp.CTE) -> str: -654 if expression.alias_column_names: -655 self.unsupported("Column names in CTE definition are not supported.") -656 return super().cte_sql(expression) -657 -658 def array_sql(self, expression: exp.Array) -> str: -659 first_arg = seq_get(expression.expressions, 0) -660 if isinstance(first_arg, exp.Subqueryable): -661 return f"ARRAY{self.wrap(self.sql(first_arg))}" -662 -663 return inline_array_sql(self, expression) -664 -665 def transaction_sql(self, *_) -> str: -666 return "BEGIN TRANSACTION" -667 -668 def commit_sql(self, *_) -> str: -669 return "COMMIT TRANSACTION" -670 -671 def rollback_sql(self, *_) -> str: -672 return "ROLLBACK TRANSACTION" -673 -674 def in_unnest_op(self, expression: exp.Unnest) -> str: -675 return self.sql(expression) -676 -677 def except_op(self, expression: exp.Except) -> str: -678 if not expression.args.get("distinct", False): -679 self.unsupported("EXCEPT without DISTINCT is not supported in BigQuery") -680 return f"EXCEPT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}" -681 -682 def intersect_op(self, expression: exp.Intersect) -> str: -683 if not expression.args.get("distinct", False): -684 self.unsupported("INTERSECT without DISTINCT is not supported in BigQuery") -685 return f"INTERSECT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}" -686 -687 def with_properties(self, properties: exp.Properties) -> str: -688 return self.properties(properties, prefix=self.seg("OPTIONS")) -689 -690 def version_sql(self, expression: exp.Version) -> str: -691 if expression.name == "TIMESTAMP": -692 expression = expression.copy() -693 expression.set("this", "SYSTEM_TIME") -694 return super().version_sql(expression) +190 SUPPORTS_USER_DEFINED_TYPES = False +191 +192 # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#case_sensitivity +193 RESOLVES_IDENTIFIERS_AS_UPPERCASE = None +194 +195 # bigquery udfs are case sensitive +196 NORMALIZE_FUNCTIONS = False +197 +198 TIME_MAPPING = { +199 "%D": "%m/%d/%y", +200 } +201 +202 FORMAT_MAPPING = { +203 "DD": "%d", +204 "MM": "%m", +205 "MON": "%b", +206 "MONTH": "%B", +207 "YYYY": "%Y", +208 "YY": "%y", +209 "HH": "%I", +210 "HH12": "%I", +211 "HH24": "%H", +212 "MI": "%M", +213 "SS": "%S", +214 "SSSSS": "%f", +215 "TZH": "%z", +216 } +217 +218 # The _PARTITIONTIME and _PARTITIONDATE pseudo-columns are not returned by a SELECT * statement +219 # https://cloud.google.com/bigquery/docs/querying-partitioned-tables#query_an_ingestion-time_partitioned_table +220 PSEUDOCOLUMNS = {"_PARTITIONTIME", "_PARTITIONDATE"} +221 +222 @classmethod +223 def normalize_identifier(cls, expression: E) -> E: +224 # In BigQuery, CTEs aren't case-sensitive, but table names are (by default, at least). +225 # The following check is essentially a heuristic to detect tables based on whether or +226 # not they're qualified. +227 if isinstance(expression, exp.Identifier): +228 parent = expression.parent +229 +230 while isinstance(parent, exp.Dot): +231 parent = parent.parent +232 +233 if ( +234 not isinstance(parent, exp.UserDefinedFunction) +235 and not (isinstance(parent, exp.Table) and parent.db) +236 and not expression.meta.get("is_table") +237 ): +238 expression.set("this", expression.this.lower()) +239 +240 return expression +241 +242 class Tokenizer(tokens.Tokenizer): +243 QUOTES = ["'", '"', '"""', "'''"] +244 COMMENTS = ["--", "#", ("/*", "*/")] +245 IDENTIFIERS = ["`"] +246 STRING_ESCAPES = ["\\"] +247 +248 HEX_STRINGS = [("0x", ""), ("0X", "")] +249 +250 BYTE_STRINGS = [ +251 (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in ("b", "B") +252 ] +253 +254 RAW_STRINGS = [ +255 (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in ("r", "R") +256 ] +257 +258 KEYWORDS = { +259 **tokens.Tokenizer.KEYWORDS, +260 "ANY TYPE": TokenType.VARIANT, +261 "BEGIN": TokenType.COMMAND, +262 "BEGIN TRANSACTION": TokenType.BEGIN, +263 "CURRENT_DATETIME": TokenType.CURRENT_DATETIME, +264 "BYTES": TokenType.BINARY, +265 "DECLARE": TokenType.COMMAND, +266 "FLOAT64": TokenType.DOUBLE, +267 "INT64": TokenType.BIGINT, +268 "RECORD": TokenType.STRUCT, +269 "TIMESTAMP": TokenType.TIMESTAMPTZ, +270 "NOT DETERMINISTIC": TokenType.VOLATILE, +271 "UNKNOWN": TokenType.NULL, +272 "FOR SYSTEM_TIME": TokenType.TIMESTAMP_SNAPSHOT, +273 } +274 KEYWORDS.pop("DIV") +275 +276 class Parser(parser.Parser): +277 PREFIXED_PIVOT_COLUMNS = True +278 +279 LOG_BASE_FIRST = False +280 LOG_DEFAULTS_TO_LN = True +281 +282 FUNCTIONS = { +283 **parser.Parser.FUNCTIONS, +284 "DATE": _parse_date, +285 "DATE_ADD": parse_date_delta_with_interval(exp.DateAdd), +286 "DATE_SUB": parse_date_delta_with_interval(exp.DateSub), +287 "DATE_TRUNC": lambda args: exp.DateTrunc( +288 unit=exp.Literal.string(str(seq_get(args, 1))), +289 this=seq_get(args, 0), +290 ), +291 "DATETIME_ADD": parse_date_delta_with_interval(exp.DatetimeAdd), +292 "DATETIME_SUB": parse_date_delta_with_interval(exp.DatetimeSub), +293 "DIV": binary_from_function(exp.IntDiv), +294 "GENERATE_ARRAY": exp.GenerateSeries.from_arg_list, +295 "MD5": exp.MD5Digest.from_arg_list, +296 "TO_HEX": _parse_to_hex, +297 "PARSE_DATE": lambda args: format_time_lambda(exp.StrToDate, "bigquery")( +298 [seq_get(args, 1), seq_get(args, 0)] +299 ), +300 "PARSE_TIMESTAMP": _parse_timestamp, +301 "REGEXP_CONTAINS": exp.RegexpLike.from_arg_list, +302 "REGEXP_EXTRACT": lambda args: exp.RegexpExtract( +303 this=seq_get(args, 0), +304 expression=seq_get(args, 1), +305 position=seq_get(args, 2), +306 occurrence=seq_get(args, 3), +307 group=exp.Literal.number(1) +308 if re.compile(str(seq_get(args, 1))).groups == 1 +309 else None, +310 ), +311 "SHA256": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)), +312 "SHA512": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)), +313 "SPLIT": lambda args: exp.Split( +314 # https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#split +315 this=seq_get(args, 0), +316 expression=seq_get(args, 1) or exp.Literal.string(","), +317 ), +318 "TIME_ADD": parse_date_delta_with_interval(exp.TimeAdd), +319 "TIME_SUB": parse_date_delta_with_interval(exp.TimeSub), +320 "TIMESTAMP_ADD": parse_date_delta_with_interval(exp.TimestampAdd), +321 "TIMESTAMP_SUB": parse_date_delta_with_interval(exp.TimestampSub), +322 "TO_JSON_STRING": exp.JSONFormat.from_arg_list, +323 } +324 +325 FUNCTION_PARSERS = { +326 **parser.Parser.FUNCTION_PARSERS, +327 "ARRAY": lambda self: self.expression(exp.Array, expressions=[self._parse_statement()]), +328 } +329 FUNCTION_PARSERS.pop("TRIM") +330 +331 NO_PAREN_FUNCTIONS = { +332 **parser.Parser.NO_PAREN_FUNCTIONS, +333 TokenType.CURRENT_DATETIME: exp.CurrentDatetime, +334 } +335 +336 NESTED_TYPE_TOKENS = { +337 *parser.Parser.NESTED_TYPE_TOKENS, +338 TokenType.TABLE, +339 } +340 +341 ID_VAR_TOKENS = { +342 *parser.Parser.ID_VAR_TOKENS, +343 TokenType.VALUES, +344 } +345 +346 PROPERTY_PARSERS = { +347 **parser.Parser.PROPERTY_PARSERS, +348 "NOT DETERMINISTIC": lambda self: self.expression( +349 exp.StabilityProperty, this=exp.Literal.string("VOLATILE") +350 ), +351 "OPTIONS": lambda self: self._parse_with_property(), +352 } +353 +354 CONSTRAINT_PARSERS = { +355 **parser.Parser.CONSTRAINT_PARSERS, +356 "OPTIONS": lambda self: exp.Properties(expressions=self._parse_with_property()), +357 } +358 +359 def _parse_table_part(self, schema: bool = False) -> t.Optional[exp.Expression]: +360 this = super()._parse_table_part(schema=schema) or self._parse_number() +361 +362 # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#table_names +363 if isinstance(this, exp.Identifier): +364 table_name = this.name +365 while self._match(TokenType.DASH, advance=False) and self._next: +366 self._advance(2) +367 table_name += f"-{self._prev.text}" +368 +369 this = exp.Identifier(this=table_name, quoted=this.args.get("quoted")) +370 elif isinstance(this, exp.Literal): +371 table_name = this.name +372 +373 if ( +374 self._curr +375 and self._prev.end == self._curr.start - 1 +376 and self._parse_var(any_token=True) +377 ): +378 table_name += self._prev.text +379 +380 this = exp.Identifier(this=table_name, quoted=True) +381 +382 return this +383 +384 def _parse_table_parts(self, schema: bool = False) -> exp.Table: +385 table = super()._parse_table_parts(schema=schema) +386 if isinstance(table.this, exp.Identifier) and "." in table.name: +387 catalog, db, this, *rest = ( +388 t.cast(t.Optional[exp.Expression], exp.to_identifier(x)) +389 for x in split_num_words(table.name, ".", 3) +390 ) +391 +392 if rest and this: +393 this = exp.Dot.build(t.cast(t.List[exp.Expression], [this, *rest])) +394 +395 table = exp.Table(this=this, db=db, catalog=catalog) +396 +397 return table +398 +399 def _parse_json_object(self) -> exp.JSONObject: +400 json_object = super()._parse_json_object() +401 array_kv_pair = seq_get(json_object.expressions, 0) +402 +403 # Converts BQ's "signature 2" of JSON_OBJECT into SQLGlot's canonical representation +404 # https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_object_signature2 +405 if ( +406 array_kv_pair +407 and isinstance(array_kv_pair.this, exp.Array) +408 and isinstance(array_kv_pair.expression, exp.Array) +409 ): +410 keys = array_kv_pair.this.expressions +411 values = array_kv_pair.expression.expressions +412 +413 json_object.set( +414 "expressions", +415 [exp.JSONKeyValue(this=k, expression=v) for k, v in zip(keys, values)], +416 ) +417 +418 return json_object +419 +420 class Generator(generator.Generator): +421 EXPLICIT_UNION = True +422 INTERVAL_ALLOWS_PLURAL_FORM = False +423 JOIN_HINTS = False +424 QUERY_HINTS = False +425 TABLE_HINTS = False +426 LIMIT_FETCH = "LIMIT" +427 RENAME_TABLE_WITH_DB = False +428 ESCAPE_LINE_BREAK = True +429 NVL2_SUPPORTED = False +430 +431 TRANSFORMS = { +432 **generator.Generator.TRANSFORMS, +433 exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"), +434 exp.ArraySize: rename_func("ARRAY_LENGTH"), +435 exp.Cast: transforms.preprocess([transforms.remove_precision_parameterized_types]), +436 exp.Create: _create_sql, +437 exp.CTE: transforms.preprocess([_pushdown_cte_column_names]), +438 exp.DateAdd: _date_add_sql("DATE", "ADD"), +439 exp.DateDiff: lambda self, e: f"DATE_DIFF({self.sql(e, 'this')}, {self.sql(e, 'expression')}, {self.sql(e.args.get('unit', 'DAY'))})", +440 exp.DateFromParts: rename_func("DATE"), +441 exp.DateStrToDate: datestrtodate_sql, +442 exp.DateSub: _date_add_sql("DATE", "SUB"), +443 exp.DatetimeAdd: _date_add_sql("DATETIME", "ADD"), +444 exp.DatetimeSub: _date_add_sql("DATETIME", "SUB"), +445 exp.DateTrunc: lambda self, e: self.func("DATE_TRUNC", e.this, e.text("unit")), +446 exp.GenerateSeries: rename_func("GENERATE_ARRAY"), +447 exp.GroupConcat: rename_func("STRING_AGG"), +448 exp.Hex: rename_func("TO_HEX"), +449 exp.ILike: no_ilike_sql, +450 exp.IntDiv: rename_func("DIV"), +451 exp.JSONFormat: rename_func("TO_JSON_STRING"), +452 exp.JSONKeyValue: json_keyvalue_comma_sql, +453 exp.Max: max_or_greatest, +454 exp.MD5: lambda self, e: self.func("TO_HEX", self.func("MD5", e.this)), +455 exp.MD5Digest: rename_func("MD5"), +456 exp.Min: min_or_least, +457 exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}", +458 exp.RegexpExtract: lambda self, e: self.func( +459 "REGEXP_EXTRACT", +460 e.this, +461 e.expression, +462 e.args.get("position"), +463 e.args.get("occurrence"), +464 ), +465 exp.RegexpReplace: regexp_replace_sql, +466 exp.RegexpLike: rename_func("REGEXP_CONTAINS"), +467 exp.ReturnsProperty: _returnsproperty_sql, +468 exp.Select: transforms.preprocess( +469 [ +470 transforms.explode_to_unnest, +471 _unqualify_unnest, +472 transforms.eliminate_distinct_on, +473 _alias_ordered_group, +474 ] +475 ), +476 exp.SHA2: lambda self, e: self.func( +477 f"SHA256" if e.text("length") == "256" else "SHA512", e.this +478 ), +479 exp.StabilityProperty: lambda self, e: f"DETERMINISTIC" +480 if e.name == "IMMUTABLE" +481 else "NOT DETERMINISTIC", +482 exp.StrToDate: lambda self, e: f"PARSE_DATE({self.format_time(e)}, {self.sql(e, 'this')})", +483 exp.StrToTime: lambda self, e: self.func( +484 "PARSE_TIMESTAMP", self.format_time(e), e.this, e.args.get("zone") +485 ), +486 exp.TimeAdd: _date_add_sql("TIME", "ADD"), +487 exp.TimeSub: _date_add_sql("TIME", "SUB"), +488 exp.TimestampAdd: _date_add_sql("TIMESTAMP", "ADD"), +489 exp.TimestampSub: _date_add_sql("TIMESTAMP", "SUB"), +490 exp.TimeStrToTime: timestrtotime_sql, +491 exp.Trim: lambda self, e: self.func(f"TRIM", e.this, e.expression), +492 exp.TsOrDsAdd: _date_add_sql("DATE", "ADD"), +493 exp.TsOrDsToDate: ts_or_ds_to_date_sql("bigquery"), +494 exp.Unhex: rename_func("FROM_HEX"), +495 exp.Values: _derived_table_values_to_unnest, +496 exp.VariancePop: rename_func("VAR_POP"), +497 } +498 +499 TYPE_MAPPING = { +500 **generator.Generator.TYPE_MAPPING, +501 exp.DataType.Type.BIGDECIMAL: "BIGNUMERIC", +502 exp.DataType.Type.BIGINT: "INT64", +503 exp.DataType.Type.BINARY: "BYTES", +504 exp.DataType.Type.BOOLEAN: "BOOL", +505 exp.DataType.Type.CHAR: "STRING", +506 exp.DataType.Type.DECIMAL: "NUMERIC", +507 exp.DataType.Type.DOUBLE: "FLOAT64", +508 exp.DataType.Type.FLOAT: "FLOAT64", +509 exp.DataType.Type.INT: "INT64", +510 exp.DataType.Type.NCHAR: "STRING", +511 exp.DataType.Type.NVARCHAR: "STRING", +512 exp.DataType.Type.SMALLINT: "INT64", +513 exp.DataType.Type.TEXT: "STRING", +514 exp.DataType.Type.TIMESTAMP: "DATETIME", +515 exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP", +516 exp.DataType.Type.TIMESTAMPLTZ: "TIMESTAMP", +517 exp.DataType.Type.TINYINT: "INT64", +518 exp.DataType.Type.VARBINARY: "BYTES", +519 exp.DataType.Type.VARCHAR: "STRING", +520 exp.DataType.Type.VARIANT: "ANY TYPE", +521 } +522 +523 PROPERTIES_LOCATION = { +524 **generator.Generator.PROPERTIES_LOCATION, +525 exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA, +526 exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED, +527 } +528 +529 # from: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords +530 RESERVED_KEYWORDS = { +531 *generator.Generator.RESERVED_KEYWORDS, +532 "all", +533 "and", +534 "any", +535 "array", +536 "as", +537 "asc", +538 "assert_rows_modified", +539 "at", +540 "between", +541 "by", +542 "case", +543 "cast", +544 "collate", +545 "contains", +546 "create", +547 "cross", +548 "cube", +549 "current", +550 "default", +551 "define", +552 "desc", +553 "distinct", +554 "else", +555 "end", +556 "enum", +557 "escape", +558 "except", +559 "exclude", +560 "exists", +561 "extract", +562 "false", +563 "fetch", +564 "following", +565 "for", +566 "from", +567 "full", +568 "group", +569 "grouping", +570 "groups", +571 "hash", +572 "having", +573 "if", +574 "ignore", +575 "in", +576 "inner", +577 "intersect", +578 "interval", +579 "into", +580 "is", +581 "join", +582 "lateral", +583 "left", +584 "like", +585 "limit", +586 "lookup", +587 "merge", +588 "natural", +589 "new", +590 "no", +591 "not", +592 "null", +593 "nulls", +594 "of", +595 "on", +596 "or", +597 "order", +598 "outer", +599 "over", +600 "partition", +601 "preceding", +602 "proto", +603 "qualify", +604 "range", +605 "recursive", +606 "respect", +607 "right", +608 "rollup", +609 "rows", +610 "select", +611 "set", +612 "some", +613 "struct", +614 "tablesample", +615 "then", +616 "to", +617 "treat", +618 "true", +619 "unbounded", +620 "union", +621 "unnest", +622 "using", +623 "when", +624 "where", +625 "window", +626 "with", +627 "within", +628 } +629 +630 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: +631 parent = expression.parent +632 +633 # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]). +634 # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included. +635 if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"): +636 return self.func( +637 "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone")) +638 ) +639 +640 return super().attimezone_sql(expression) +641 +642 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: +643 # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#json_literals +644 if expression.is_type("json"): +645 return f"JSON {self.sql(expression, 'this')}" +646 +647 return super().cast_sql(expression, safe_prefix=safe_prefix) +648 +649 def trycast_sql(self, expression: exp.TryCast) -> str: +650 return self.cast_sql(expression, safe_prefix="SAFE_") +651 +652 def cte_sql(self, expression: exp.CTE) -> str: +653 if expression.alias_column_names: +654 self.unsupported("Column names in CTE definition are not supported.") +655 return super().cte_sql(expression) +656 +657 def array_sql(self, expression: exp.Array) -> str: +658 first_arg = seq_get(expression.expressions, 0) +659 if isinstance(first_arg, exp.Subqueryable): +660 return f"ARRAY{self.wrap(self.sql(first_arg))}" +661 +662 return inline_array_sql(self, expression) +663 +664 def transaction_sql(self, *_) -> str: +665 return "BEGIN TRANSACTION" +666 +667 def commit_sql(self, *_) -> str: +668 return "COMMIT TRANSACTION" +669 +670 def rollback_sql(self, *_) -> str: +671 return "ROLLBACK TRANSACTION" +672 +673 def in_unnest_op(self, expression: exp.Unnest) -> str: +674 return self.sql(expression) +675 +676 def except_op(self, expression: exp.Except) -> str: +677 if not expression.args.get("distinct", False): +678 self.unsupported("EXCEPT without DISTINCT is not supported in BigQuery") +679 return f"EXCEPT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}" +680 +681 def intersect_op(self, expression: exp.Intersect) -> str: +682 if not expression.args.get("distinct", False): +683 self.unsupported("INTERSECT without DISTINCT is not supported in BigQuery") +684 return f"INTERSECT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}" +685 +686 def with_properties(self, properties: exp.Properties) -> str: +687 return self.properties(properties, prefix=self.seg("OPTIONS")) +688 +689 def version_sql(self, expression: exp.Version) -> str: +690 if expression.name == "TIMESTAMP": +691 expression = expression.copy() +692 expression.set("this", "SYSTEM_TIME") +693 return super().version_sql(expression) @@ -1085,511 +1087,510 @@
    189class BigQuery(Dialect):
     190    UNNEST_COLUMN_ONLY = True
    -191
    -192    # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#case_sensitivity
    -193    RESOLVES_IDENTIFIERS_AS_UPPERCASE = None
    -194
    -195    # bigquery udfs are case sensitive
    -196    NORMALIZE_FUNCTIONS = False
    -197
    -198    TIME_MAPPING = {
    -199        "%D": "%m/%d/%y",
    -200    }
    -201
    -202    FORMAT_MAPPING = {
    -203        "DD": "%d",
    -204        "MM": "%m",
    -205        "MON": "%b",
    -206        "MONTH": "%B",
    -207        "YYYY": "%Y",
    -208        "YY": "%y",
    -209        "HH": "%I",
    -210        "HH12": "%I",
    -211        "HH24": "%H",
    -212        "MI": "%M",
    -213        "SS": "%S",
    -214        "SSSSS": "%f",
    -215        "TZH": "%z",
    -216    }
    -217
    -218    # The _PARTITIONTIME and _PARTITIONDATE pseudo-columns are not returned by a SELECT * statement
    -219    # https://cloud.google.com/bigquery/docs/querying-partitioned-tables#query_an_ingestion-time_partitioned_table
    -220    PSEUDOCOLUMNS = {"_PARTITIONTIME", "_PARTITIONDATE"}
    -221
    -222    @classmethod
    -223    def normalize_identifier(cls, expression: E) -> E:
    -224        # In BigQuery, CTEs aren't case-sensitive, but table names are (by default, at least).
    -225        # The following check is essentially a heuristic to detect tables based on whether or
    -226        # not they're qualified.
    -227        if isinstance(expression, exp.Identifier):
    -228            parent = expression.parent
    -229
    -230            while isinstance(parent, exp.Dot):
    -231                parent = parent.parent
    -232
    -233            if (
    -234                not isinstance(parent, exp.UserDefinedFunction)
    -235                and not (isinstance(parent, exp.Table) and parent.db)
    -236                and not expression.meta.get("is_table")
    -237            ):
    -238                expression.set("this", expression.this.lower())
    -239
    -240        return expression
    -241
    -242    class Tokenizer(tokens.Tokenizer):
    -243        QUOTES = ["'", '"', '"""', "'''"]
    -244        COMMENTS = ["--", "#", ("/*", "*/")]
    -245        IDENTIFIERS = ["`"]
    -246        STRING_ESCAPES = ["\\"]
    -247
    -248        HEX_STRINGS = [("0x", ""), ("0X", "")]
    -249
    -250        BYTE_STRINGS = [
    -251            (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in ("b", "B")
    -252        ]
    -253
    -254        RAW_STRINGS = [
    -255            (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in ("r", "R")
    -256        ]
    -257
    -258        KEYWORDS = {
    -259            **tokens.Tokenizer.KEYWORDS,
    -260            "ANY TYPE": TokenType.VARIANT,
    -261            "BEGIN": TokenType.COMMAND,
    -262            "BEGIN TRANSACTION": TokenType.BEGIN,
    -263            "CURRENT_DATETIME": TokenType.CURRENT_DATETIME,
    -264            "BYTES": TokenType.BINARY,
    -265            "DECLARE": TokenType.COMMAND,
    -266            "FLOAT64": TokenType.DOUBLE,
    -267            "INT64": TokenType.BIGINT,
    -268            "RECORD": TokenType.STRUCT,
    -269            "TIMESTAMP": TokenType.TIMESTAMPTZ,
    -270            "NOT DETERMINISTIC": TokenType.VOLATILE,
    -271            "UNKNOWN": TokenType.NULL,
    -272            "FOR SYSTEM_TIME": TokenType.TIMESTAMP_SNAPSHOT,
    -273        }
    -274        KEYWORDS.pop("DIV")
    -275
    -276    class Parser(parser.Parser):
    -277        PREFIXED_PIVOT_COLUMNS = True
    -278
    -279        LOG_BASE_FIRST = False
    -280        LOG_DEFAULTS_TO_LN = True
    -281
    -282        SUPPORTS_USER_DEFINED_TYPES = False
    -283
    -284        FUNCTIONS = {
    -285            **parser.Parser.FUNCTIONS,
    -286            "DATE": _parse_date,
    -287            "DATE_ADD": parse_date_delta_with_interval(exp.DateAdd),
    -288            "DATE_SUB": parse_date_delta_with_interval(exp.DateSub),
    -289            "DATE_TRUNC": lambda args: exp.DateTrunc(
    -290                unit=exp.Literal.string(str(seq_get(args, 1))),
    -291                this=seq_get(args, 0),
    -292            ),
    -293            "DATETIME_ADD": parse_date_delta_with_interval(exp.DatetimeAdd),
    -294            "DATETIME_SUB": parse_date_delta_with_interval(exp.DatetimeSub),
    -295            "DIV": binary_from_function(exp.IntDiv),
    -296            "GENERATE_ARRAY": exp.GenerateSeries.from_arg_list,
    -297            "MD5": exp.MD5Digest.from_arg_list,
    -298            "TO_HEX": _parse_to_hex,
    -299            "PARSE_DATE": lambda args: format_time_lambda(exp.StrToDate, "bigquery")(
    -300                [seq_get(args, 1), seq_get(args, 0)]
    -301            ),
    -302            "PARSE_TIMESTAMP": _parse_timestamp,
    -303            "REGEXP_CONTAINS": exp.RegexpLike.from_arg_list,
    -304            "REGEXP_EXTRACT": lambda args: exp.RegexpExtract(
    -305                this=seq_get(args, 0),
    -306                expression=seq_get(args, 1),
    -307                position=seq_get(args, 2),
    -308                occurrence=seq_get(args, 3),
    -309                group=exp.Literal.number(1)
    -310                if re.compile(str(seq_get(args, 1))).groups == 1
    -311                else None,
    -312            ),
    -313            "SHA256": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)),
    -314            "SHA512": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)),
    -315            "SPLIT": lambda args: exp.Split(
    -316                # https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#split
    -317                this=seq_get(args, 0),
    -318                expression=seq_get(args, 1) or exp.Literal.string(","),
    -319            ),
    -320            "TIME_ADD": parse_date_delta_with_interval(exp.TimeAdd),
    -321            "TIME_SUB": parse_date_delta_with_interval(exp.TimeSub),
    -322            "TIMESTAMP_ADD": parse_date_delta_with_interval(exp.TimestampAdd),
    -323            "TIMESTAMP_SUB": parse_date_delta_with_interval(exp.TimestampSub),
    -324            "TO_JSON_STRING": exp.JSONFormat.from_arg_list,
    -325        }
    -326
    -327        FUNCTION_PARSERS = {
    -328            **parser.Parser.FUNCTION_PARSERS,
    -329            "ARRAY": lambda self: self.expression(exp.Array, expressions=[self._parse_statement()]),
    -330        }
    -331        FUNCTION_PARSERS.pop("TRIM")
    -332
    -333        NO_PAREN_FUNCTIONS = {
    -334            **parser.Parser.NO_PAREN_FUNCTIONS,
    -335            TokenType.CURRENT_DATETIME: exp.CurrentDatetime,
    -336        }
    -337
    -338        NESTED_TYPE_TOKENS = {
    -339            *parser.Parser.NESTED_TYPE_TOKENS,
    -340            TokenType.TABLE,
    -341        }
    -342
    -343        ID_VAR_TOKENS = {
    -344            *parser.Parser.ID_VAR_TOKENS,
    -345            TokenType.VALUES,
    -346        }
    -347
    -348        PROPERTY_PARSERS = {
    -349            **parser.Parser.PROPERTY_PARSERS,
    -350            "NOT DETERMINISTIC": lambda self: self.expression(
    -351                exp.StabilityProperty, this=exp.Literal.string("VOLATILE")
    -352            ),
    -353            "OPTIONS": lambda self: self._parse_with_property(),
    -354        }
    -355
    -356        CONSTRAINT_PARSERS = {
    -357            **parser.Parser.CONSTRAINT_PARSERS,
    -358            "OPTIONS": lambda self: exp.Properties(expressions=self._parse_with_property()),
    -359        }
    -360
    -361        def _parse_table_part(self, schema: bool = False) -> t.Optional[exp.Expression]:
    -362            this = super()._parse_table_part(schema=schema) or self._parse_number()
    -363
    -364            # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#table_names
    -365            if isinstance(this, exp.Identifier):
    -366                table_name = this.name
    -367                while self._match(TokenType.DASH, advance=False) and self._next:
    -368                    self._advance(2)
    -369                    table_name += f"-{self._prev.text}"
    -370
    -371                this = exp.Identifier(this=table_name, quoted=this.args.get("quoted"))
    -372            elif isinstance(this, exp.Literal):
    -373                table_name = this.name
    -374
    -375                if (
    -376                    self._curr
    -377                    and self._prev.end == self._curr.start - 1
    -378                    and self._parse_var(any_token=True)
    -379                ):
    -380                    table_name += self._prev.text
    -381
    -382                this = exp.Identifier(this=table_name, quoted=True)
    -383
    -384            return this
    -385
    -386        def _parse_table_parts(self, schema: bool = False) -> exp.Table:
    -387            table = super()._parse_table_parts(schema=schema)
    -388            if isinstance(table.this, exp.Identifier) and "." in table.name:
    -389                catalog, db, this, *rest = (
    -390                    t.cast(t.Optional[exp.Expression], exp.to_identifier(x))
    -391                    for x in split_num_words(table.name, ".", 3)
    -392                )
    -393
    -394                if rest and this:
    -395                    this = exp.Dot.build(t.cast(t.List[exp.Expression], [this, *rest]))
    -396
    -397                table = exp.Table(this=this, db=db, catalog=catalog)
    -398
    -399            return table
    -400
    -401        def _parse_json_object(self) -> exp.JSONObject:
    -402            json_object = super()._parse_json_object()
    -403            array_kv_pair = seq_get(json_object.expressions, 0)
    -404
    -405            # Converts BQ's "signature 2" of JSON_OBJECT into SQLGlot's canonical representation
    -406            # https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_object_signature2
    -407            if (
    -408                array_kv_pair
    -409                and isinstance(array_kv_pair.this, exp.Array)
    -410                and isinstance(array_kv_pair.expression, exp.Array)
    -411            ):
    -412                keys = array_kv_pair.this.expressions
    -413                values = array_kv_pair.expression.expressions
    -414
    -415                json_object.set(
    -416                    "expressions",
    -417                    [exp.JSONKeyValue(this=k, expression=v) for k, v in zip(keys, values)],
    -418                )
    -419
    -420            return json_object
    -421
    -422    class Generator(generator.Generator):
    -423        EXPLICIT_UNION = True
    -424        INTERVAL_ALLOWS_PLURAL_FORM = False
    -425        JOIN_HINTS = False
    -426        QUERY_HINTS = False
    -427        TABLE_HINTS = False
    -428        LIMIT_FETCH = "LIMIT"
    -429        RENAME_TABLE_WITH_DB = False
    -430        ESCAPE_LINE_BREAK = True
    -431        NVL2_SUPPORTED = False
    -432
    -433        TRANSFORMS = {
    -434            **generator.Generator.TRANSFORMS,
    -435            exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"),
    -436            exp.ArraySize: rename_func("ARRAY_LENGTH"),
    -437            exp.Cast: transforms.preprocess([transforms.remove_precision_parameterized_types]),
    -438            exp.Create: _create_sql,
    -439            exp.CTE: transforms.preprocess([_pushdown_cte_column_names]),
    -440            exp.DateAdd: _date_add_sql("DATE", "ADD"),
    -441            exp.DateDiff: lambda self, e: f"DATE_DIFF({self.sql(e, 'this')}, {self.sql(e, 'expression')}, {self.sql(e.args.get('unit', 'DAY'))})",
    -442            exp.DateFromParts: rename_func("DATE"),
    -443            exp.DateStrToDate: datestrtodate_sql,
    -444            exp.DateSub: _date_add_sql("DATE", "SUB"),
    -445            exp.DatetimeAdd: _date_add_sql("DATETIME", "ADD"),
    -446            exp.DatetimeSub: _date_add_sql("DATETIME", "SUB"),
    -447            exp.DateTrunc: lambda self, e: self.func("DATE_TRUNC", e.this, e.text("unit")),
    -448            exp.GenerateSeries: rename_func("GENERATE_ARRAY"),
    -449            exp.GroupConcat: rename_func("STRING_AGG"),
    -450            exp.Hex: rename_func("TO_HEX"),
    -451            exp.ILike: no_ilike_sql,
    -452            exp.IntDiv: rename_func("DIV"),
    -453            exp.JSONFormat: rename_func("TO_JSON_STRING"),
    -454            exp.JSONKeyValue: json_keyvalue_comma_sql,
    -455            exp.Max: max_or_greatest,
    -456            exp.MD5: lambda self, e: self.func("TO_HEX", self.func("MD5", e.this)),
    -457            exp.MD5Digest: rename_func("MD5"),
    -458            exp.Min: min_or_least,
    -459            exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}",
    -460            exp.RegexpExtract: lambda self, e: self.func(
    -461                "REGEXP_EXTRACT",
    -462                e.this,
    -463                e.expression,
    -464                e.args.get("position"),
    -465                e.args.get("occurrence"),
    -466            ),
    -467            exp.RegexpReplace: regexp_replace_sql,
    -468            exp.RegexpLike: rename_func("REGEXP_CONTAINS"),
    -469            exp.ReturnsProperty: _returnsproperty_sql,
    -470            exp.Select: transforms.preprocess(
    -471                [
    -472                    transforms.explode_to_unnest,
    -473                    _unqualify_unnest,
    -474                    transforms.eliminate_distinct_on,
    -475                    _alias_ordered_group,
    -476                ]
    -477            ),
    -478            exp.SHA2: lambda self, e: self.func(
    -479                f"SHA256" if e.text("length") == "256" else "SHA512", e.this
    -480            ),
    -481            exp.StabilityProperty: lambda self, e: f"DETERMINISTIC"
    -482            if e.name == "IMMUTABLE"
    -483            else "NOT DETERMINISTIC",
    -484            exp.StrToDate: lambda self, e: f"PARSE_DATE({self.format_time(e)}, {self.sql(e, 'this')})",
    -485            exp.StrToTime: lambda self, e: self.func(
    -486                "PARSE_TIMESTAMP", self.format_time(e), e.this, e.args.get("zone")
    -487            ),
    -488            exp.TimeAdd: _date_add_sql("TIME", "ADD"),
    -489            exp.TimeSub: _date_add_sql("TIME", "SUB"),
    -490            exp.TimestampAdd: _date_add_sql("TIMESTAMP", "ADD"),
    -491            exp.TimestampSub: _date_add_sql("TIMESTAMP", "SUB"),
    -492            exp.TimeStrToTime: timestrtotime_sql,
    -493            exp.Trim: lambda self, e: self.func(f"TRIM", e.this, e.expression),
    -494            exp.TsOrDsAdd: _date_add_sql("DATE", "ADD"),
    -495            exp.TsOrDsToDate: ts_or_ds_to_date_sql("bigquery"),
    -496            exp.Unhex: rename_func("FROM_HEX"),
    -497            exp.Values: _derived_table_values_to_unnest,
    -498            exp.VariancePop: rename_func("VAR_POP"),
    -499        }
    -500
    -501        TYPE_MAPPING = {
    -502            **generator.Generator.TYPE_MAPPING,
    -503            exp.DataType.Type.BIGDECIMAL: "BIGNUMERIC",
    -504            exp.DataType.Type.BIGINT: "INT64",
    -505            exp.DataType.Type.BINARY: "BYTES",
    -506            exp.DataType.Type.BOOLEAN: "BOOL",
    -507            exp.DataType.Type.CHAR: "STRING",
    -508            exp.DataType.Type.DECIMAL: "NUMERIC",
    -509            exp.DataType.Type.DOUBLE: "FLOAT64",
    -510            exp.DataType.Type.FLOAT: "FLOAT64",
    -511            exp.DataType.Type.INT: "INT64",
    -512            exp.DataType.Type.NCHAR: "STRING",
    -513            exp.DataType.Type.NVARCHAR: "STRING",
    -514            exp.DataType.Type.SMALLINT: "INT64",
    -515            exp.DataType.Type.TEXT: "STRING",
    -516            exp.DataType.Type.TIMESTAMP: "DATETIME",
    -517            exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP",
    -518            exp.DataType.Type.TIMESTAMPLTZ: "TIMESTAMP",
    -519            exp.DataType.Type.TINYINT: "INT64",
    -520            exp.DataType.Type.VARBINARY: "BYTES",
    -521            exp.DataType.Type.VARCHAR: "STRING",
    -522            exp.DataType.Type.VARIANT: "ANY TYPE",
    -523        }
    -524
    -525        PROPERTIES_LOCATION = {
    -526            **generator.Generator.PROPERTIES_LOCATION,
    -527            exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,
    -528            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
    -529        }
    -530
    -531        # from: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
    -532        RESERVED_KEYWORDS = {
    -533            *generator.Generator.RESERVED_KEYWORDS,
    -534            "all",
    -535            "and",
    -536            "any",
    -537            "array",
    -538            "as",
    -539            "asc",
    -540            "assert_rows_modified",
    -541            "at",
    -542            "between",
    -543            "by",
    -544            "case",
    -545            "cast",
    -546            "collate",
    -547            "contains",
    -548            "create",
    -549            "cross",
    -550            "cube",
    -551            "current",
    -552            "default",
    -553            "define",
    -554            "desc",
    -555            "distinct",
    -556            "else",
    -557            "end",
    -558            "enum",
    -559            "escape",
    -560            "except",
    -561            "exclude",
    -562            "exists",
    -563            "extract",
    -564            "false",
    -565            "fetch",
    -566            "following",
    -567            "for",
    -568            "from",
    -569            "full",
    -570            "group",
    -571            "grouping",
    -572            "groups",
    -573            "hash",
    -574            "having",
    -575            "if",
    -576            "ignore",
    -577            "in",
    -578            "inner",
    -579            "intersect",
    -580            "interval",
    -581            "into",
    -582            "is",
    -583            "join",
    -584            "lateral",
    -585            "left",
    -586            "like",
    -587            "limit",
    -588            "lookup",
    -589            "merge",
    -590            "natural",
    -591            "new",
    -592            "no",
    -593            "not",
    -594            "null",
    -595            "nulls",
    -596            "of",
    -597            "on",
    -598            "or",
    -599            "order",
    -600            "outer",
    -601            "over",
    -602            "partition",
    -603            "preceding",
    -604            "proto",
    -605            "qualify",
    -606            "range",
    -607            "recursive",
    -608            "respect",
    -609            "right",
    -610            "rollup",
    -611            "rows",
    -612            "select",
    -613            "set",
    -614            "some",
    -615            "struct",
    -616            "tablesample",
    -617            "then",
    -618            "to",
    -619            "treat",
    -620            "true",
    -621            "unbounded",
    -622            "union",
    -623            "unnest",
    -624            "using",
    -625            "when",
    -626            "where",
    -627            "window",
    -628            "with",
    -629            "within",
    -630        }
    -631
    -632        def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
    -633            parent = expression.parent
    -634
    -635            # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).
    -636            # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.
    -637            if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"):
    -638                return self.func(
    -639                    "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone"))
    -640                )
    -641
    -642            return super().attimezone_sql(expression)
    -643
    -644        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
    -645            # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#json_literals
    -646            if expression.is_type("json"):
    -647                return f"JSON {self.sql(expression, 'this')}"
    -648
    -649            return super().cast_sql(expression, safe_prefix=safe_prefix)
    -650
    -651        def trycast_sql(self, expression: exp.TryCast) -> str:
    -652            return self.cast_sql(expression, safe_prefix="SAFE_")
    -653
    -654        def cte_sql(self, expression: exp.CTE) -> str:
    -655            if expression.alias_column_names:
    -656                self.unsupported("Column names in CTE definition are not supported.")
    -657            return super().cte_sql(expression)
    -658
    -659        def array_sql(self, expression: exp.Array) -> str:
    -660            first_arg = seq_get(expression.expressions, 0)
    -661            if isinstance(first_arg, exp.Subqueryable):
    -662                return f"ARRAY{self.wrap(self.sql(first_arg))}"
    -663
    -664            return inline_array_sql(self, expression)
    -665
    -666        def transaction_sql(self, *_) -> str:
    -667            return "BEGIN TRANSACTION"
    -668
    -669        def commit_sql(self, *_) -> str:
    -670            return "COMMIT TRANSACTION"
    -671
    -672        def rollback_sql(self, *_) -> str:
    -673            return "ROLLBACK TRANSACTION"
    -674
    -675        def in_unnest_op(self, expression: exp.Unnest) -> str:
    -676            return self.sql(expression)
    -677
    -678        def except_op(self, expression: exp.Except) -> str:
    -679            if not expression.args.get("distinct", False):
    -680                self.unsupported("EXCEPT without DISTINCT is not supported in BigQuery")
    -681            return f"EXCEPT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}"
    -682
    -683        def intersect_op(self, expression: exp.Intersect) -> str:
    -684            if not expression.args.get("distinct", False):
    -685                self.unsupported("INTERSECT without DISTINCT is not supported in BigQuery")
    -686            return f"INTERSECT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}"
    -687
    -688        def with_properties(self, properties: exp.Properties) -> str:
    -689            return self.properties(properties, prefix=self.seg("OPTIONS"))
    -690
    -691        def version_sql(self, expression: exp.Version) -> str:
    -692            if expression.name == "TIMESTAMP":
    -693                expression = expression.copy()
    -694                expression.set("this", "SYSTEM_TIME")
    -695            return super().version_sql(expression)
    +191    SUPPORTS_USER_DEFINED_TYPES = False
    +192
    +193    # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#case_sensitivity
    +194    RESOLVES_IDENTIFIERS_AS_UPPERCASE = None
    +195
    +196    # bigquery udfs are case sensitive
    +197    NORMALIZE_FUNCTIONS = False
    +198
    +199    TIME_MAPPING = {
    +200        "%D": "%m/%d/%y",
    +201    }
    +202
    +203    FORMAT_MAPPING = {
    +204        "DD": "%d",
    +205        "MM": "%m",
    +206        "MON": "%b",
    +207        "MONTH": "%B",
    +208        "YYYY": "%Y",
    +209        "YY": "%y",
    +210        "HH": "%I",
    +211        "HH12": "%I",
    +212        "HH24": "%H",
    +213        "MI": "%M",
    +214        "SS": "%S",
    +215        "SSSSS": "%f",
    +216        "TZH": "%z",
    +217    }
    +218
    +219    # The _PARTITIONTIME and _PARTITIONDATE pseudo-columns are not returned by a SELECT * statement
    +220    # https://cloud.google.com/bigquery/docs/querying-partitioned-tables#query_an_ingestion-time_partitioned_table
    +221    PSEUDOCOLUMNS = {"_PARTITIONTIME", "_PARTITIONDATE"}
    +222
    +223    @classmethod
    +224    def normalize_identifier(cls, expression: E) -> E:
    +225        # In BigQuery, CTEs aren't case-sensitive, but table names are (by default, at least).
    +226        # The following check is essentially a heuristic to detect tables based on whether or
    +227        # not they're qualified.
    +228        if isinstance(expression, exp.Identifier):
    +229            parent = expression.parent
    +230
    +231            while isinstance(parent, exp.Dot):
    +232                parent = parent.parent
    +233
    +234            if (
    +235                not isinstance(parent, exp.UserDefinedFunction)
    +236                and not (isinstance(parent, exp.Table) and parent.db)
    +237                and not expression.meta.get("is_table")
    +238            ):
    +239                expression.set("this", expression.this.lower())
    +240
    +241        return expression
    +242
    +243    class Tokenizer(tokens.Tokenizer):
    +244        QUOTES = ["'", '"', '"""', "'''"]
    +245        COMMENTS = ["--", "#", ("/*", "*/")]
    +246        IDENTIFIERS = ["`"]
    +247        STRING_ESCAPES = ["\\"]
    +248
    +249        HEX_STRINGS = [("0x", ""), ("0X", "")]
    +250
    +251        BYTE_STRINGS = [
    +252            (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in ("b", "B")
    +253        ]
    +254
    +255        RAW_STRINGS = [
    +256            (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in ("r", "R")
    +257        ]
    +258
    +259        KEYWORDS = {
    +260            **tokens.Tokenizer.KEYWORDS,
    +261            "ANY TYPE": TokenType.VARIANT,
    +262            "BEGIN": TokenType.COMMAND,
    +263            "BEGIN TRANSACTION": TokenType.BEGIN,
    +264            "CURRENT_DATETIME": TokenType.CURRENT_DATETIME,
    +265            "BYTES": TokenType.BINARY,
    +266            "DECLARE": TokenType.COMMAND,
    +267            "FLOAT64": TokenType.DOUBLE,
    +268            "INT64": TokenType.BIGINT,
    +269            "RECORD": TokenType.STRUCT,
    +270            "TIMESTAMP": TokenType.TIMESTAMPTZ,
    +271            "NOT DETERMINISTIC": TokenType.VOLATILE,
    +272            "UNKNOWN": TokenType.NULL,
    +273            "FOR SYSTEM_TIME": TokenType.TIMESTAMP_SNAPSHOT,
    +274        }
    +275        KEYWORDS.pop("DIV")
    +276
    +277    class Parser(parser.Parser):
    +278        PREFIXED_PIVOT_COLUMNS = True
    +279
    +280        LOG_BASE_FIRST = False
    +281        LOG_DEFAULTS_TO_LN = True
    +282
    +283        FUNCTIONS = {
    +284            **parser.Parser.FUNCTIONS,
    +285            "DATE": _parse_date,
    +286            "DATE_ADD": parse_date_delta_with_interval(exp.DateAdd),
    +287            "DATE_SUB": parse_date_delta_with_interval(exp.DateSub),
    +288            "DATE_TRUNC": lambda args: exp.DateTrunc(
    +289                unit=exp.Literal.string(str(seq_get(args, 1))),
    +290                this=seq_get(args, 0),
    +291            ),
    +292            "DATETIME_ADD": parse_date_delta_with_interval(exp.DatetimeAdd),
    +293            "DATETIME_SUB": parse_date_delta_with_interval(exp.DatetimeSub),
    +294            "DIV": binary_from_function(exp.IntDiv),
    +295            "GENERATE_ARRAY": exp.GenerateSeries.from_arg_list,
    +296            "MD5": exp.MD5Digest.from_arg_list,
    +297            "TO_HEX": _parse_to_hex,
    +298            "PARSE_DATE": lambda args: format_time_lambda(exp.StrToDate, "bigquery")(
    +299                [seq_get(args, 1), seq_get(args, 0)]
    +300            ),
    +301            "PARSE_TIMESTAMP": _parse_timestamp,
    +302            "REGEXP_CONTAINS": exp.RegexpLike.from_arg_list,
    +303            "REGEXP_EXTRACT": lambda args: exp.RegexpExtract(
    +304                this=seq_get(args, 0),
    +305                expression=seq_get(args, 1),
    +306                position=seq_get(args, 2),
    +307                occurrence=seq_get(args, 3),
    +308                group=exp.Literal.number(1)
    +309                if re.compile(str(seq_get(args, 1))).groups == 1
    +310                else None,
    +311            ),
    +312            "SHA256": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)),
    +313            "SHA512": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)),
    +314            "SPLIT": lambda args: exp.Split(
    +315                # https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#split
    +316                this=seq_get(args, 0),
    +317                expression=seq_get(args, 1) or exp.Literal.string(","),
    +318            ),
    +319            "TIME_ADD": parse_date_delta_with_interval(exp.TimeAdd),
    +320            "TIME_SUB": parse_date_delta_with_interval(exp.TimeSub),
    +321            "TIMESTAMP_ADD": parse_date_delta_with_interval(exp.TimestampAdd),
    +322            "TIMESTAMP_SUB": parse_date_delta_with_interval(exp.TimestampSub),
    +323            "TO_JSON_STRING": exp.JSONFormat.from_arg_list,
    +324        }
    +325
    +326        FUNCTION_PARSERS = {
    +327            **parser.Parser.FUNCTION_PARSERS,
    +328            "ARRAY": lambda self: self.expression(exp.Array, expressions=[self._parse_statement()]),
    +329        }
    +330        FUNCTION_PARSERS.pop("TRIM")
    +331
    +332        NO_PAREN_FUNCTIONS = {
    +333            **parser.Parser.NO_PAREN_FUNCTIONS,
    +334            TokenType.CURRENT_DATETIME: exp.CurrentDatetime,
    +335        }
    +336
    +337        NESTED_TYPE_TOKENS = {
    +338            *parser.Parser.NESTED_TYPE_TOKENS,
    +339            TokenType.TABLE,
    +340        }
    +341
    +342        ID_VAR_TOKENS = {
    +343            *parser.Parser.ID_VAR_TOKENS,
    +344            TokenType.VALUES,
    +345        }
    +346
    +347        PROPERTY_PARSERS = {
    +348            **parser.Parser.PROPERTY_PARSERS,
    +349            "NOT DETERMINISTIC": lambda self: self.expression(
    +350                exp.StabilityProperty, this=exp.Literal.string("VOLATILE")
    +351            ),
    +352            "OPTIONS": lambda self: self._parse_with_property(),
    +353        }
    +354
    +355        CONSTRAINT_PARSERS = {
    +356            **parser.Parser.CONSTRAINT_PARSERS,
    +357            "OPTIONS": lambda self: exp.Properties(expressions=self._parse_with_property()),
    +358        }
    +359
    +360        def _parse_table_part(self, schema: bool = False) -> t.Optional[exp.Expression]:
    +361            this = super()._parse_table_part(schema=schema) or self._parse_number()
    +362
    +363            # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#table_names
    +364            if isinstance(this, exp.Identifier):
    +365                table_name = this.name
    +366                while self._match(TokenType.DASH, advance=False) and self._next:
    +367                    self._advance(2)
    +368                    table_name += f"-{self._prev.text}"
    +369
    +370                this = exp.Identifier(this=table_name, quoted=this.args.get("quoted"))
    +371            elif isinstance(this, exp.Literal):
    +372                table_name = this.name
    +373
    +374                if (
    +375                    self._curr
    +376                    and self._prev.end == self._curr.start - 1
    +377                    and self._parse_var(any_token=True)
    +378                ):
    +379                    table_name += self._prev.text
    +380
    +381                this = exp.Identifier(this=table_name, quoted=True)
    +382
    +383            return this
    +384
    +385        def _parse_table_parts(self, schema: bool = False) -> exp.Table:
    +386            table = super()._parse_table_parts(schema=schema)
    +387            if isinstance(table.this, exp.Identifier) and "." in table.name:
    +388                catalog, db, this, *rest = (
    +389                    t.cast(t.Optional[exp.Expression], exp.to_identifier(x))
    +390                    for x in split_num_words(table.name, ".", 3)
    +391                )
    +392
    +393                if rest and this:
    +394                    this = exp.Dot.build(t.cast(t.List[exp.Expression], [this, *rest]))
    +395
    +396                table = exp.Table(this=this, db=db, catalog=catalog)
    +397
    +398            return table
    +399
    +400        def _parse_json_object(self) -> exp.JSONObject:
    +401            json_object = super()._parse_json_object()
    +402            array_kv_pair = seq_get(json_object.expressions, 0)
    +403
    +404            # Converts BQ's "signature 2" of JSON_OBJECT into SQLGlot's canonical representation
    +405            # https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_object_signature2
    +406            if (
    +407                array_kv_pair
    +408                and isinstance(array_kv_pair.this, exp.Array)
    +409                and isinstance(array_kv_pair.expression, exp.Array)
    +410            ):
    +411                keys = array_kv_pair.this.expressions
    +412                values = array_kv_pair.expression.expressions
    +413
    +414                json_object.set(
    +415                    "expressions",
    +416                    [exp.JSONKeyValue(this=k, expression=v) for k, v in zip(keys, values)],
    +417                )
    +418
    +419            return json_object
    +420
    +421    class Generator(generator.Generator):
    +422        EXPLICIT_UNION = True
    +423        INTERVAL_ALLOWS_PLURAL_FORM = False
    +424        JOIN_HINTS = False
    +425        QUERY_HINTS = False
    +426        TABLE_HINTS = False
    +427        LIMIT_FETCH = "LIMIT"
    +428        RENAME_TABLE_WITH_DB = False
    +429        ESCAPE_LINE_BREAK = True
    +430        NVL2_SUPPORTED = False
    +431
    +432        TRANSFORMS = {
    +433            **generator.Generator.TRANSFORMS,
    +434            exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"),
    +435            exp.ArraySize: rename_func("ARRAY_LENGTH"),
    +436            exp.Cast: transforms.preprocess([transforms.remove_precision_parameterized_types]),
    +437            exp.Create: _create_sql,
    +438            exp.CTE: transforms.preprocess([_pushdown_cte_column_names]),
    +439            exp.DateAdd: _date_add_sql("DATE", "ADD"),
    +440            exp.DateDiff: lambda self, e: f"DATE_DIFF({self.sql(e, 'this')}, {self.sql(e, 'expression')}, {self.sql(e.args.get('unit', 'DAY'))})",
    +441            exp.DateFromParts: rename_func("DATE"),
    +442            exp.DateStrToDate: datestrtodate_sql,
    +443            exp.DateSub: _date_add_sql("DATE", "SUB"),
    +444            exp.DatetimeAdd: _date_add_sql("DATETIME", "ADD"),
    +445            exp.DatetimeSub: _date_add_sql("DATETIME", "SUB"),
    +446            exp.DateTrunc: lambda self, e: self.func("DATE_TRUNC", e.this, e.text("unit")),
    +447            exp.GenerateSeries: rename_func("GENERATE_ARRAY"),
    +448            exp.GroupConcat: rename_func("STRING_AGG"),
    +449            exp.Hex: rename_func("TO_HEX"),
    +450            exp.ILike: no_ilike_sql,
    +451            exp.IntDiv: rename_func("DIV"),
    +452            exp.JSONFormat: rename_func("TO_JSON_STRING"),
    +453            exp.JSONKeyValue: json_keyvalue_comma_sql,
    +454            exp.Max: max_or_greatest,
    +455            exp.MD5: lambda self, e: self.func("TO_HEX", self.func("MD5", e.this)),
    +456            exp.MD5Digest: rename_func("MD5"),
    +457            exp.Min: min_or_least,
    +458            exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}",
    +459            exp.RegexpExtract: lambda self, e: self.func(
    +460                "REGEXP_EXTRACT",
    +461                e.this,
    +462                e.expression,
    +463                e.args.get("position"),
    +464                e.args.get("occurrence"),
    +465            ),
    +466            exp.RegexpReplace: regexp_replace_sql,
    +467            exp.RegexpLike: rename_func("REGEXP_CONTAINS"),
    +468            exp.ReturnsProperty: _returnsproperty_sql,
    +469            exp.Select: transforms.preprocess(
    +470                [
    +471                    transforms.explode_to_unnest,
    +472                    _unqualify_unnest,
    +473                    transforms.eliminate_distinct_on,
    +474                    _alias_ordered_group,
    +475                ]
    +476            ),
    +477            exp.SHA2: lambda self, e: self.func(
    +478                f"SHA256" if e.text("length") == "256" else "SHA512", e.this
    +479            ),
    +480            exp.StabilityProperty: lambda self, e: f"DETERMINISTIC"
    +481            if e.name == "IMMUTABLE"
    +482            else "NOT DETERMINISTIC",
    +483            exp.StrToDate: lambda self, e: f"PARSE_DATE({self.format_time(e)}, {self.sql(e, 'this')})",
    +484            exp.StrToTime: lambda self, e: self.func(
    +485                "PARSE_TIMESTAMP", self.format_time(e), e.this, e.args.get("zone")
    +486            ),
    +487            exp.TimeAdd: _date_add_sql("TIME", "ADD"),
    +488            exp.TimeSub: _date_add_sql("TIME", "SUB"),
    +489            exp.TimestampAdd: _date_add_sql("TIMESTAMP", "ADD"),
    +490            exp.TimestampSub: _date_add_sql("TIMESTAMP", "SUB"),
    +491            exp.TimeStrToTime: timestrtotime_sql,
    +492            exp.Trim: lambda self, e: self.func(f"TRIM", e.this, e.expression),
    +493            exp.TsOrDsAdd: _date_add_sql("DATE", "ADD"),
    +494            exp.TsOrDsToDate: ts_or_ds_to_date_sql("bigquery"),
    +495            exp.Unhex: rename_func("FROM_HEX"),
    +496            exp.Values: _derived_table_values_to_unnest,
    +497            exp.VariancePop: rename_func("VAR_POP"),
    +498        }
    +499
    +500        TYPE_MAPPING = {
    +501            **generator.Generator.TYPE_MAPPING,
    +502            exp.DataType.Type.BIGDECIMAL: "BIGNUMERIC",
    +503            exp.DataType.Type.BIGINT: "INT64",
    +504            exp.DataType.Type.BINARY: "BYTES",
    +505            exp.DataType.Type.BOOLEAN: "BOOL",
    +506            exp.DataType.Type.CHAR: "STRING",
    +507            exp.DataType.Type.DECIMAL: "NUMERIC",
    +508            exp.DataType.Type.DOUBLE: "FLOAT64",
    +509            exp.DataType.Type.FLOAT: "FLOAT64",
    +510            exp.DataType.Type.INT: "INT64",
    +511            exp.DataType.Type.NCHAR: "STRING",
    +512            exp.DataType.Type.NVARCHAR: "STRING",
    +513            exp.DataType.Type.SMALLINT: "INT64",
    +514            exp.DataType.Type.TEXT: "STRING",
    +515            exp.DataType.Type.TIMESTAMP: "DATETIME",
    +516            exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP",
    +517            exp.DataType.Type.TIMESTAMPLTZ: "TIMESTAMP",
    +518            exp.DataType.Type.TINYINT: "INT64",
    +519            exp.DataType.Type.VARBINARY: "BYTES",
    +520            exp.DataType.Type.VARCHAR: "STRING",
    +521            exp.DataType.Type.VARIANT: "ANY TYPE",
    +522        }
    +523
    +524        PROPERTIES_LOCATION = {
    +525            **generator.Generator.PROPERTIES_LOCATION,
    +526            exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,
    +527            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
    +528        }
    +529
    +530        # from: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
    +531        RESERVED_KEYWORDS = {
    +532            *generator.Generator.RESERVED_KEYWORDS,
    +533            "all",
    +534            "and",
    +535            "any",
    +536            "array",
    +537            "as",
    +538            "asc",
    +539            "assert_rows_modified",
    +540            "at",
    +541            "between",
    +542            "by",
    +543            "case",
    +544            "cast",
    +545            "collate",
    +546            "contains",
    +547            "create",
    +548            "cross",
    +549            "cube",
    +550            "current",
    +551            "default",
    +552            "define",
    +553            "desc",
    +554            "distinct",
    +555            "else",
    +556            "end",
    +557            "enum",
    +558            "escape",
    +559            "except",
    +560            "exclude",
    +561            "exists",
    +562            "extract",
    +563            "false",
    +564            "fetch",
    +565            "following",
    +566            "for",
    +567            "from",
    +568            "full",
    +569            "group",
    +570            "grouping",
    +571            "groups",
    +572            "hash",
    +573            "having",
    +574            "if",
    +575            "ignore",
    +576            "in",
    +577            "inner",
    +578            "intersect",
    +579            "interval",
    +580            "into",
    +581            "is",
    +582            "join",
    +583            "lateral",
    +584            "left",
    +585            "like",
    +586            "limit",
    +587            "lookup",
    +588            "merge",
    +589            "natural",
    +590            "new",
    +591            "no",
    +592            "not",
    +593            "null",
    +594            "nulls",
    +595            "of",
    +596            "on",
    +597            "or",
    +598            "order",
    +599            "outer",
    +600            "over",
    +601            "partition",
    +602            "preceding",
    +603            "proto",
    +604            "qualify",
    +605            "range",
    +606            "recursive",
    +607            "respect",
    +608            "right",
    +609            "rollup",
    +610            "rows",
    +611            "select",
    +612            "set",
    +613            "some",
    +614            "struct",
    +615            "tablesample",
    +616            "then",
    +617            "to",
    +618            "treat",
    +619            "true",
    +620            "unbounded",
    +621            "union",
    +622            "unnest",
    +623            "using",
    +624            "when",
    +625            "where",
    +626            "window",
    +627            "with",
    +628            "within",
    +629        }
    +630
    +631        def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
    +632            parent = expression.parent
    +633
    +634            # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).
    +635            # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.
    +636            if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"):
    +637                return self.func(
    +638                    "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone"))
    +639                )
    +640
    +641            return super().attimezone_sql(expression)
    +642
    +643        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
    +644            # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#json_literals
    +645            if expression.is_type("json"):
    +646                return f"JSON {self.sql(expression, 'this')}"
    +647
    +648            return super().cast_sql(expression, safe_prefix=safe_prefix)
    +649
    +650        def trycast_sql(self, expression: exp.TryCast) -> str:
    +651            return self.cast_sql(expression, safe_prefix="SAFE_")
    +652
    +653        def cte_sql(self, expression: exp.CTE) -> str:
    +654            if expression.alias_column_names:
    +655                self.unsupported("Column names in CTE definition are not supported.")
    +656            return super().cte_sql(expression)
    +657
    +658        def array_sql(self, expression: exp.Array) -> str:
    +659            first_arg = seq_get(expression.expressions, 0)
    +660            if isinstance(first_arg, exp.Subqueryable):
    +661                return f"ARRAY{self.wrap(self.sql(first_arg))}"
    +662
    +663            return inline_array_sql(self, expression)
    +664
    +665        def transaction_sql(self, *_) -> str:
    +666            return "BEGIN TRANSACTION"
    +667
    +668        def commit_sql(self, *_) -> str:
    +669            return "COMMIT TRANSACTION"
    +670
    +671        def rollback_sql(self, *_) -> str:
    +672            return "ROLLBACK TRANSACTION"
    +673
    +674        def in_unnest_op(self, expression: exp.Unnest) -> str:
    +675            return self.sql(expression)
    +676
    +677        def except_op(self, expression: exp.Except) -> str:
    +678            if not expression.args.get("distinct", False):
    +679                self.unsupported("EXCEPT without DISTINCT is not supported in BigQuery")
    +680            return f"EXCEPT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}"
    +681
    +682        def intersect_op(self, expression: exp.Intersect) -> str:
    +683            if not expression.args.get("distinct", False):
    +684                self.unsupported("INTERSECT without DISTINCT is not supported in BigQuery")
    +685            return f"INTERSECT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}"
    +686
    +687        def with_properties(self, properties: exp.Properties) -> str:
    +688            return self.properties(properties, prefix=self.seg("OPTIONS"))
    +689
    +690        def version_sql(self, expression: exp.Version) -> str:
    +691            if expression.name == "TIMESTAMP":
    +692                expression = expression.copy()
    +693                expression.set("this", "SYSTEM_TIME")
    +694            return super().version_sql(expression)
     
    @@ -1606,6 +1607,18 @@ + +
    +
    + SUPPORTS_USER_DEFINED_TYPES = +False + + +
    + + + +
    @@ -1659,7 +1672,7 @@
    PSEUDOCOLUMNS: Set[str] = -{'_PARTITIONTIME', '_PARTITIONDATE'} +{'_PARTITIONDATE', '_PARTITIONTIME'}
    @@ -1680,25 +1693,25 @@
    -
    222    @classmethod
    -223    def normalize_identifier(cls, expression: E) -> E:
    -224        # In BigQuery, CTEs aren't case-sensitive, but table names are (by default, at least).
    -225        # The following check is essentially a heuristic to detect tables based on whether or
    -226        # not they're qualified.
    -227        if isinstance(expression, exp.Identifier):
    -228            parent = expression.parent
    -229
    -230            while isinstance(parent, exp.Dot):
    -231                parent = parent.parent
    -232
    -233            if (
    -234                not isinstance(parent, exp.UserDefinedFunction)
    -235                and not (isinstance(parent, exp.Table) and parent.db)
    -236                and not expression.meta.get("is_table")
    -237            ):
    -238                expression.set("this", expression.this.lower())
    -239
    -240        return expression
    +            
    223    @classmethod
    +224    def normalize_identifier(cls, expression: E) -> E:
    +225        # In BigQuery, CTEs aren't case-sensitive, but table names are (by default, at least).
    +226        # The following check is essentially a heuristic to detect tables based on whether or
    +227        # not they're qualified.
    +228        if isinstance(expression, exp.Identifier):
    +229            parent = expression.parent
    +230
    +231            while isinstance(parent, exp.Dot):
    +232                parent = parent.parent
    +233
    +234            if (
    +235                not isinstance(parent, exp.UserDefinedFunction)
    +236                and not (isinstance(parent, exp.Table) and parent.db)
    +237                and not expression.meta.get("is_table")
    +238            ):
    +239                expression.set("this", expression.this.lower())
    +240
    +241        return expression
     
    @@ -1956,39 +1969,39 @@ they will be normalized regardless of being quoted or not.

    -
    242    class Tokenizer(tokens.Tokenizer):
    -243        QUOTES = ["'", '"', '"""', "'''"]
    -244        COMMENTS = ["--", "#", ("/*", "*/")]
    -245        IDENTIFIERS = ["`"]
    -246        STRING_ESCAPES = ["\\"]
    -247
    -248        HEX_STRINGS = [("0x", ""), ("0X", "")]
    -249
    -250        BYTE_STRINGS = [
    -251            (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in ("b", "B")
    -252        ]
    -253
    -254        RAW_STRINGS = [
    -255            (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in ("r", "R")
    -256        ]
    -257
    -258        KEYWORDS = {
    -259            **tokens.Tokenizer.KEYWORDS,
    -260            "ANY TYPE": TokenType.VARIANT,
    -261            "BEGIN": TokenType.COMMAND,
    -262            "BEGIN TRANSACTION": TokenType.BEGIN,
    -263            "CURRENT_DATETIME": TokenType.CURRENT_DATETIME,
    -264            "BYTES": TokenType.BINARY,
    -265            "DECLARE": TokenType.COMMAND,
    -266            "FLOAT64": TokenType.DOUBLE,
    -267            "INT64": TokenType.BIGINT,
    -268            "RECORD": TokenType.STRUCT,
    -269            "TIMESTAMP": TokenType.TIMESTAMPTZ,
    -270            "NOT DETERMINISTIC": TokenType.VOLATILE,
    -271            "UNKNOWN": TokenType.NULL,
    -272            "FOR SYSTEM_TIME": TokenType.TIMESTAMP_SNAPSHOT,
    -273        }
    -274        KEYWORDS.pop("DIV")
    +            
    243    class Tokenizer(tokens.Tokenizer):
    +244        QUOTES = ["'", '"', '"""', "'''"]
    +245        COMMENTS = ["--", "#", ("/*", "*/")]
    +246        IDENTIFIERS = ["`"]
    +247        STRING_ESCAPES = ["\\"]
    +248
    +249        HEX_STRINGS = [("0x", ""), ("0X", "")]
    +250
    +251        BYTE_STRINGS = [
    +252            (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in ("b", "B")
    +253        ]
    +254
    +255        RAW_STRINGS = [
    +256            (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in ("r", "R")
    +257        ]
    +258
    +259        KEYWORDS = {
    +260            **tokens.Tokenizer.KEYWORDS,
    +261            "ANY TYPE": TokenType.VARIANT,
    +262            "BEGIN": TokenType.COMMAND,
    +263            "BEGIN TRANSACTION": TokenType.BEGIN,
    +264            "CURRENT_DATETIME": TokenType.CURRENT_DATETIME,
    +265            "BYTES": TokenType.BINARY,
    +266            "DECLARE": TokenType.COMMAND,
    +267            "FLOAT64": TokenType.DOUBLE,
    +268            "INT64": TokenType.BIGINT,
    +269            "RECORD": TokenType.STRUCT,
    +270            "TIMESTAMP": TokenType.TIMESTAMPTZ,
    +271            "NOT DETERMINISTIC": TokenType.VOLATILE,
    +272            "UNKNOWN": TokenType.NULL,
    +273            "FOR SYSTEM_TIME": TokenType.TIMESTAMP_SNAPSHOT,
    +274        }
    +275        KEYWORDS.pop("DIV")
     
    @@ -2129,151 +2142,149 @@ they will be normalized regardless of being quoted or not.

    -
    276    class Parser(parser.Parser):
    -277        PREFIXED_PIVOT_COLUMNS = True
    -278
    -279        LOG_BASE_FIRST = False
    -280        LOG_DEFAULTS_TO_LN = True
    -281
    -282        SUPPORTS_USER_DEFINED_TYPES = False
    -283
    -284        FUNCTIONS = {
    -285            **parser.Parser.FUNCTIONS,
    -286            "DATE": _parse_date,
    -287            "DATE_ADD": parse_date_delta_with_interval(exp.DateAdd),
    -288            "DATE_SUB": parse_date_delta_with_interval(exp.DateSub),
    -289            "DATE_TRUNC": lambda args: exp.DateTrunc(
    -290                unit=exp.Literal.string(str(seq_get(args, 1))),
    -291                this=seq_get(args, 0),
    -292            ),
    -293            "DATETIME_ADD": parse_date_delta_with_interval(exp.DatetimeAdd),
    -294            "DATETIME_SUB": parse_date_delta_with_interval(exp.DatetimeSub),
    -295            "DIV": binary_from_function(exp.IntDiv),
    -296            "GENERATE_ARRAY": exp.GenerateSeries.from_arg_list,
    -297            "MD5": exp.MD5Digest.from_arg_list,
    -298            "TO_HEX": _parse_to_hex,
    -299            "PARSE_DATE": lambda args: format_time_lambda(exp.StrToDate, "bigquery")(
    -300                [seq_get(args, 1), seq_get(args, 0)]
    -301            ),
    -302            "PARSE_TIMESTAMP": _parse_timestamp,
    -303            "REGEXP_CONTAINS": exp.RegexpLike.from_arg_list,
    -304            "REGEXP_EXTRACT": lambda args: exp.RegexpExtract(
    -305                this=seq_get(args, 0),
    -306                expression=seq_get(args, 1),
    -307                position=seq_get(args, 2),
    -308                occurrence=seq_get(args, 3),
    -309                group=exp.Literal.number(1)
    -310                if re.compile(str(seq_get(args, 1))).groups == 1
    -311                else None,
    -312            ),
    -313            "SHA256": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)),
    -314            "SHA512": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)),
    -315            "SPLIT": lambda args: exp.Split(
    -316                # https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#split
    -317                this=seq_get(args, 0),
    -318                expression=seq_get(args, 1) or exp.Literal.string(","),
    -319            ),
    -320            "TIME_ADD": parse_date_delta_with_interval(exp.TimeAdd),
    -321            "TIME_SUB": parse_date_delta_with_interval(exp.TimeSub),
    -322            "TIMESTAMP_ADD": parse_date_delta_with_interval(exp.TimestampAdd),
    -323            "TIMESTAMP_SUB": parse_date_delta_with_interval(exp.TimestampSub),
    -324            "TO_JSON_STRING": exp.JSONFormat.from_arg_list,
    -325        }
    -326
    -327        FUNCTION_PARSERS = {
    -328            **parser.Parser.FUNCTION_PARSERS,
    -329            "ARRAY": lambda self: self.expression(exp.Array, expressions=[self._parse_statement()]),
    -330        }
    -331        FUNCTION_PARSERS.pop("TRIM")
    -332
    -333        NO_PAREN_FUNCTIONS = {
    -334            **parser.Parser.NO_PAREN_FUNCTIONS,
    -335            TokenType.CURRENT_DATETIME: exp.CurrentDatetime,
    -336        }
    -337
    -338        NESTED_TYPE_TOKENS = {
    -339            *parser.Parser.NESTED_TYPE_TOKENS,
    -340            TokenType.TABLE,
    -341        }
    -342
    -343        ID_VAR_TOKENS = {
    -344            *parser.Parser.ID_VAR_TOKENS,
    -345            TokenType.VALUES,
    -346        }
    -347
    -348        PROPERTY_PARSERS = {
    -349            **parser.Parser.PROPERTY_PARSERS,
    -350            "NOT DETERMINISTIC": lambda self: self.expression(
    -351                exp.StabilityProperty, this=exp.Literal.string("VOLATILE")
    -352            ),
    -353            "OPTIONS": lambda self: self._parse_with_property(),
    -354        }
    -355
    -356        CONSTRAINT_PARSERS = {
    -357            **parser.Parser.CONSTRAINT_PARSERS,
    -358            "OPTIONS": lambda self: exp.Properties(expressions=self._parse_with_property()),
    -359        }
    -360
    -361        def _parse_table_part(self, schema: bool = False) -> t.Optional[exp.Expression]:
    -362            this = super()._parse_table_part(schema=schema) or self._parse_number()
    -363
    -364            # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#table_names
    -365            if isinstance(this, exp.Identifier):
    -366                table_name = this.name
    -367                while self._match(TokenType.DASH, advance=False) and self._next:
    -368                    self._advance(2)
    -369                    table_name += f"-{self._prev.text}"
    -370
    -371                this = exp.Identifier(this=table_name, quoted=this.args.get("quoted"))
    -372            elif isinstance(this, exp.Literal):
    -373                table_name = this.name
    -374
    -375                if (
    -376                    self._curr
    -377                    and self._prev.end == self._curr.start - 1
    -378                    and self._parse_var(any_token=True)
    -379                ):
    -380                    table_name += self._prev.text
    -381
    -382                this = exp.Identifier(this=table_name, quoted=True)
    -383
    -384            return this
    -385
    -386        def _parse_table_parts(self, schema: bool = False) -> exp.Table:
    -387            table = super()._parse_table_parts(schema=schema)
    -388            if isinstance(table.this, exp.Identifier) and "." in table.name:
    -389                catalog, db, this, *rest = (
    -390                    t.cast(t.Optional[exp.Expression], exp.to_identifier(x))
    -391                    for x in split_num_words(table.name, ".", 3)
    -392                )
    -393
    -394                if rest and this:
    -395                    this = exp.Dot.build(t.cast(t.List[exp.Expression], [this, *rest]))
    -396
    -397                table = exp.Table(this=this, db=db, catalog=catalog)
    -398
    -399            return table
    -400
    -401        def _parse_json_object(self) -> exp.JSONObject:
    -402            json_object = super()._parse_json_object()
    -403            array_kv_pair = seq_get(json_object.expressions, 0)
    -404
    -405            # Converts BQ's "signature 2" of JSON_OBJECT into SQLGlot's canonical representation
    -406            # https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_object_signature2
    -407            if (
    -408                array_kv_pair
    -409                and isinstance(array_kv_pair.this, exp.Array)
    -410                and isinstance(array_kv_pair.expression, exp.Array)
    -411            ):
    -412                keys = array_kv_pair.this.expressions
    -413                values = array_kv_pair.expression.expressions
    -414
    -415                json_object.set(
    -416                    "expressions",
    -417                    [exp.JSONKeyValue(this=k, expression=v) for k, v in zip(keys, values)],
    -418                )
    -419
    -420            return json_object
    +            
    277    class Parser(parser.Parser):
    +278        PREFIXED_PIVOT_COLUMNS = True
    +279
    +280        LOG_BASE_FIRST = False
    +281        LOG_DEFAULTS_TO_LN = True
    +282
    +283        FUNCTIONS = {
    +284            **parser.Parser.FUNCTIONS,
    +285            "DATE": _parse_date,
    +286            "DATE_ADD": parse_date_delta_with_interval(exp.DateAdd),
    +287            "DATE_SUB": parse_date_delta_with_interval(exp.DateSub),
    +288            "DATE_TRUNC": lambda args: exp.DateTrunc(
    +289                unit=exp.Literal.string(str(seq_get(args, 1))),
    +290                this=seq_get(args, 0),
    +291            ),
    +292            "DATETIME_ADD": parse_date_delta_with_interval(exp.DatetimeAdd),
    +293            "DATETIME_SUB": parse_date_delta_with_interval(exp.DatetimeSub),
    +294            "DIV": binary_from_function(exp.IntDiv),
    +295            "GENERATE_ARRAY": exp.GenerateSeries.from_arg_list,
    +296            "MD5": exp.MD5Digest.from_arg_list,
    +297            "TO_HEX": _parse_to_hex,
    +298            "PARSE_DATE": lambda args: format_time_lambda(exp.StrToDate, "bigquery")(
    +299                [seq_get(args, 1), seq_get(args, 0)]
    +300            ),
    +301            "PARSE_TIMESTAMP": _parse_timestamp,
    +302            "REGEXP_CONTAINS": exp.RegexpLike.from_arg_list,
    +303            "REGEXP_EXTRACT": lambda args: exp.RegexpExtract(
    +304                this=seq_get(args, 0),
    +305                expression=seq_get(args, 1),
    +306                position=seq_get(args, 2),
    +307                occurrence=seq_get(args, 3),
    +308                group=exp.Literal.number(1)
    +309                if re.compile(str(seq_get(args, 1))).groups == 1
    +310                else None,
    +311            ),
    +312            "SHA256": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)),
    +313            "SHA512": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)),
    +314            "SPLIT": lambda args: exp.Split(
    +315                # https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#split
    +316                this=seq_get(args, 0),
    +317                expression=seq_get(args, 1) or exp.Literal.string(","),
    +318            ),
    +319            "TIME_ADD": parse_date_delta_with_interval(exp.TimeAdd),
    +320            "TIME_SUB": parse_date_delta_with_interval(exp.TimeSub),
    +321            "TIMESTAMP_ADD": parse_date_delta_with_interval(exp.TimestampAdd),
    +322            "TIMESTAMP_SUB": parse_date_delta_with_interval(exp.TimestampSub),
    +323            "TO_JSON_STRING": exp.JSONFormat.from_arg_list,
    +324        }
    +325
    +326        FUNCTION_PARSERS = {
    +327            **parser.Parser.FUNCTION_PARSERS,
    +328            "ARRAY": lambda self: self.expression(exp.Array, expressions=[self._parse_statement()]),
    +329        }
    +330        FUNCTION_PARSERS.pop("TRIM")
    +331
    +332        NO_PAREN_FUNCTIONS = {
    +333            **parser.Parser.NO_PAREN_FUNCTIONS,
    +334            TokenType.CURRENT_DATETIME: exp.CurrentDatetime,
    +335        }
    +336
    +337        NESTED_TYPE_TOKENS = {
    +338            *parser.Parser.NESTED_TYPE_TOKENS,
    +339            TokenType.TABLE,
    +340        }
    +341
    +342        ID_VAR_TOKENS = {
    +343            *parser.Parser.ID_VAR_TOKENS,
    +344            TokenType.VALUES,
    +345        }
    +346
    +347        PROPERTY_PARSERS = {
    +348            **parser.Parser.PROPERTY_PARSERS,
    +349            "NOT DETERMINISTIC": lambda self: self.expression(
    +350                exp.StabilityProperty, this=exp.Literal.string("VOLATILE")
    +351            ),
    +352            "OPTIONS": lambda self: self._parse_with_property(),
    +353        }
    +354
    +355        CONSTRAINT_PARSERS = {
    +356            **parser.Parser.CONSTRAINT_PARSERS,
    +357            "OPTIONS": lambda self: exp.Properties(expressions=self._parse_with_property()),
    +358        }
    +359
    +360        def _parse_table_part(self, schema: bool = False) -> t.Optional[exp.Expression]:
    +361            this = super()._parse_table_part(schema=schema) or self._parse_number()
    +362
    +363            # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#table_names
    +364            if isinstance(this, exp.Identifier):
    +365                table_name = this.name
    +366                while self._match(TokenType.DASH, advance=False) and self._next:
    +367                    self._advance(2)
    +368                    table_name += f"-{self._prev.text}"
    +369
    +370                this = exp.Identifier(this=table_name, quoted=this.args.get("quoted"))
    +371            elif isinstance(this, exp.Literal):
    +372                table_name = this.name
    +373
    +374                if (
    +375                    self._curr
    +376                    and self._prev.end == self._curr.start - 1
    +377                    and self._parse_var(any_token=True)
    +378                ):
    +379                    table_name += self._prev.text
    +380
    +381                this = exp.Identifier(this=table_name, quoted=True)
    +382
    +383            return this
    +384
    +385        def _parse_table_parts(self, schema: bool = False) -> exp.Table:
    +386            table = super()._parse_table_parts(schema=schema)
    +387            if isinstance(table.this, exp.Identifier) and "." in table.name:
    +388                catalog, db, this, *rest = (
    +389                    t.cast(t.Optional[exp.Expression], exp.to_identifier(x))
    +390                    for x in split_num_words(table.name, ".", 3)
    +391                )
    +392
    +393                if rest and this:
    +394                    this = exp.Dot.build(t.cast(t.List[exp.Expression], [this, *rest]))
    +395
    +396                table = exp.Table(this=this, db=db, catalog=catalog)
    +397
    +398            return table
    +399
    +400        def _parse_json_object(self) -> exp.JSONObject:
    +401            json_object = super()._parse_json_object()
    +402            array_kv_pair = seq_get(json_object.expressions, 0)
    +403
    +404            # Converts BQ's "signature 2" of JSON_OBJECT into SQLGlot's canonical representation
    +405            # https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_object_signature2
    +406            if (
    +407                array_kv_pair
    +408                and isinstance(array_kv_pair.this, exp.Array)
    +409                and isinstance(array_kv_pair.expression, exp.Array)
    +410            ):
    +411                keys = array_kv_pair.this.expressions
    +412                values = array_kv_pair.expression.expressions
    +413
    +414                json_object.set(
    +415                    "expressions",
    +416                    [exp.JSONKeyValue(this=k, expression=v) for k, v in zip(keys, values)],
    +417                )
    +418
    +419            return json_object
     
    @@ -2329,18 +2340,6 @@ Default: 3 -
    -
    -
    - SUPPORTS_USER_DEFINED_TYPES = -False - - -
    - - - -
    @@ -2385,7 +2384,7 @@ Default: 3
    NESTED_TYPE_TOKENS = - {<TokenType.ARRAY: 'ARRAY'>, <TokenType.STRUCT: 'STRUCT'>, <TokenType.NESTED: 'NESTED'>, <TokenType.LOWCARDINALITY: 'LOWCARDINALITY'>, <TokenType.TABLE: 'TABLE'>, <TokenType.NULLABLE: 'NULLABLE'>, <TokenType.MAP: 'MAP'>} + {<TokenType.MAP: 'MAP'>, <TokenType.NULLABLE: 'NULLABLE'>, <TokenType.LOWCARDINALITY: 'LOWCARDINALITY'>, <TokenType.NESTED: 'NESTED'>, <TokenType.TABLE: 'TABLE'>, <TokenType.ARRAY: 'ARRAY'>, <TokenType.STRUCT: 'STRUCT'>}
    @@ -2398,7 +2397,7 @@ Default: 3
    ID_VAR_TOKENS = - {<TokenType.SET: 'SET'>, <TokenType.UINT128: 'UINT128'>, <TokenType.ENUM8: 'ENUM8'>, <TokenType.IMAGE: 'IMAGE'>, <TokenType.USERDEFINED: 'USERDEFINED'>, <TokenType.SEMI: 'SEMI'>, <TokenType.UPDATE: 'UPDATE'>, <TokenType.COMMAND: 'COMMAND'>, <TokenType.INET: 'INET'>, <TokenType.DATEMULTIRANGE: 'DATEMULTIRANGE'>, <TokenType.ROW: 'ROW'>, <TokenType.YEAR: 'YEAR'>, <TokenType.ALL: 'ALL'>, <TokenType.FILTER: 'FILTER'>, <TokenType.SCHEMA: 'SCHEMA'>, <TokenType.FIRST: 'FIRST'>, <TokenType.LONGTEXT: 'LONGTEXT'>, <TokenType.COLUMN: 'COLUMN'>, <TokenType.INT: 'INT'>, <TokenType.EXECUTE: 'EXECUTE'>, <TokenType.FALSE: 'FALSE'>, <TokenType.OFFSET: 'OFFSET'>, <TokenType.USMALLINT: 'USMALLINT'>, <TokenType.CACHE: 'CACHE'>, <TokenType.CURRENT_USER: 'CURRENT_USER'>, <TokenType.PSEUDO_TYPE: 'PSEUDO_TYPE'>, <TokenType.DATETIME64: 'DATETIME64'>, <TokenType.VARCHAR: 'VARCHAR'>, <TokenType.SERIAL: 'SERIAL'>, <TokenType.BEGIN: 'BEGIN'>, <TokenType.CONSTRAINT: 'CONSTRAINT'>, <TokenType.FORMAT: 'FORMAT'>, <TokenType.UINT: 'UINT'>, <TokenType.ORDINALITY: 'ORDINALITY'>, <TokenType.TEMPORARY: 'TEMPORARY'>, <TokenType.SMALLMONEY: 'SMALLMONEY'>, <TokenType.PROCEDURE: 'PROCEDURE'>, <TokenType.VIEW: 'VIEW'>, <TokenType.INDEX: 'INDEX'>, <TokenType.ROWVERSION: 'ROWVERSION'>, <TokenType.TSTZRANGE: 'TSTZRANGE'>, <TokenType.TINYINT: 'TINYINT'>, <TokenType.TIMESTAMP: 'TIMESTAMP'>, <TokenType.TIMESTAMPLTZ: 'TIMESTAMPLTZ'>, <TokenType.LOWCARDINALITY: 'LOWCARDINALITY'>, <TokenType.TSTZMULTIRANGE: 'TSTZMULTIRANGE'>, <TokenType.VOLATILE: 'VOLATILE'>, <TokenType.UINT256: 'UINT256'>, <TokenType.FULL: 'FULL'>, <TokenType.PERCENT: 'PERCENT'>, <TokenType.OVERWRITE: 'OVERWRITE'>, <TokenType.PRAGMA: 'PRAGMA'>, <TokenType.MERGE: 'MERGE'>, <TokenType.VAR: 'VAR'>, <TokenType.IPADDRESS: 'IPADDRESS'>, <TokenType.ENUM: 'ENUM'>, <TokenType.CASE: 'CASE'>, <TokenType.GEOMETRY: 'GEOMETRY'>, <TokenType.INT8RANGE: 'INT8RANGE'>, <TokenType.DATERANGE: 'DATERANGE'>, <TokenType.INT4RANGE: 'INT4RANGE'>, <TokenType.ANTI: 'ANTI'>, <TokenType.OBJECT_IDENTIFIER: 'OBJECT_IDENTIFIER'>, <TokenType.DOUBLE: 'DOUBLE'>, <TokenType.APPLY: 'APPLY'>, <TokenType.TSRANGE: 'TSRANGE'>, <TokenType.KEEP: 'KEEP'>, <TokenType.UBIGINT: 'UBIGINT'>, <TokenType.TABLE: 'TABLE'>, <TokenType.RANGE: 'RANGE'>, <TokenType.UNPIVOT: 'UNPIVOT'>, <TokenType.ANY: 'ANY'>, <TokenType.NVARCHAR: 'NVARCHAR'>, <TokenType.NULL: 'NULL'>, <TokenType.SHOW: 'SHOW'>, <TokenType.STRUCT: 'STRUCT'>, <TokenType.NESTED: 'NESTED'>, <TokenType.COMMENT: 'COMMENT'>, <TokenType.ASC: 'ASC'>, <TokenType.XML: 'XML'>, <TokenType.VARIANT: 'VARIANT'>, <TokenType.SETTINGS: 'SETTINGS'>, <TokenType.TOP: 'TOP'>, <TokenType.DECIMAL: 'DECIMAL'>, <TokenType.ENUM16: 'ENUM16'>, <TokenType.MEDIUMTEXT: 'MEDIUMTEXT'>, <TokenType.COLLATE: 'COLLATE'>, <TokenType.TINYTEXT: 'TINYTEXT'>, <TokenType.DELETE: 'DELETE'>, <TokenType.BIT: 'BIT'>, <TokenType.UNIQUE: 'UNIQUE'>, <TokenType.UNIQUEIDENTIFIER: 'UNIQUEIDENTIFIER'>, <TokenType.MEDIUMBLOB: 'MEDIUMBLOB'>, <TokenType.GEOGRAPHY: 'GEOGRAPHY'>, <TokenType.MEDIUMINT: 'MEDIUMINT'>, <TokenType.CURRENT_TIMESTAMP: 'CURRENT_TIMESTAMP'>, <TokenType.ISNULL: 'ISNULL'>, <TokenType.DATETIME: 'DATETIME'>, <TokenType.BIGDECIMAL: 'BIGDECIMAL'>, <TokenType.TRUE: 'TRUE'>, <TokenType.UMEDIUMINT: 'UMEDIUMINT'>, <TokenType.CHAR: 'CHAR'>, <TokenType.IS: 'IS'>, <TokenType.RIGHT: 'RIGHT'>, <TokenType.VARBINARY: 'VARBINARY'>, <TokenType.CURRENT_TIME: 'CURRENT_TIME'>, <TokenType.HSTORE: 'HSTORE'>, <TokenType.MAP: 'MAP'>, <TokenType.END: 'END'>, <TokenType.DESC: 'DESC'>, <TokenType.UTINYINT: 'UTINYINT'>, <TokenType.INT256: 'INT256'>, <TokenType.OBJECT: 'OBJECT'>, <TokenType.ESCAPE: 'ESCAPE'>, <TokenType.DESCRIBE: 'DESCRIBE'>, <TokenType.PARTITION: 'PARTITION'>, <TokenType.CURRENT_DATE: 'CURRENT_DATE'>, <TokenType.INT8MULTIRANGE: 'INT8MULTIRANGE'>, <TokenType.IPPREFIX: 'IPPREFIX'>, <TokenType.DATABASE: 'DATABASE'>, <TokenType.UUID: 'UUID'>, <TokenType.BIGINT: 'BIGINT'>, <TokenType.JSON: 'JSON'>, <TokenType.HLLSKETCH: 'HLLSKETCH'>, <TokenType.INT128: 'INT128'>, <TokenType.COMMIT: 'COMMIT'>, <TokenType.INT4MULTIRANGE: 'INT4MULTIRANGE'>, <TokenType.NEXT: 'NEXT'>, <TokenType.FLOAT: 'FLOAT'>, <TokenType.TIMESTAMPTZ: 'TIMESTAMPTZ'>, <TokenType.AUTO_INCREMENT: 'AUTO_INCREMENT'>, <TokenType.REFERENCES: 'REFERENCES'>, <TokenType.NATURAL: 'NATURAL'>, <TokenType.DICTIONARY: 'DICTIONARY'>, <TokenType.WINDOW: 'WINDOW'>, <TokenType.NULLABLE: 'NULLABLE'>, <TokenType.VALUES: 'VALUES'>, <TokenType.LONGBLOB: 'LONGBLOB'>, <TokenType.TEXT: 'TEXT'>, <TokenType.DIV: 'DIV'>, <TokenType.ARRAY: 'ARRAY'>, <TokenType.SMALLSERIAL: 'SMALLSERIAL'>, <TokenType.SOME: 'SOME'>, <TokenType.ROWS: 'ROWS'>, <TokenType.BIGSERIAL: 'BIGSERIAL'>, <TokenType.JSONB: 'JSONB'>, <TokenType.BOOLEAN: 'BOOLEAN'>, <TokenType.NUMMULTIRANGE: 'NUMMULTIRANGE'>, <TokenType.EXISTS: 'EXISTS'>, <TokenType.LEFT: 'LEFT'>, <TokenType.TINYBLOB: 'TINYBLOB'>, <TokenType.UNKNOWN: 'UNKNOWN'>, <TokenType.SUPER: 'SUPER'>, <TokenType.LOAD: 'LOAD'>, <TokenType.DATE: 'DATE'>, <TokenType.TSMULTIRANGE: 'TSMULTIRANGE'>, <TokenType.NUMRANGE: 'NUMRANGE'>, <TokenType.NCHAR: 'NCHAR'>, <TokenType.FIXEDSTRING: 'FIXEDSTRING'>, <TokenType.DEFAULT: 'DEFAULT'>, <TokenType.FUNCTION: 'FUNCTION'>, <TokenType.TIME: 'TIME'>, <TokenType.INTERVAL: 'INTERVAL'>, <TokenType.MONEY: 'MONEY'>, <TokenType.BINARY: 'BINARY'>, <TokenType.CURRENT_DATETIME: 'CURRENT_DATETIME'>, <TokenType.TIMETZ: 'TIMETZ'>, <TokenType.PIVOT: 'PIVOT'>, <TokenType.SMALLINT: 'SMALLINT'>} + {<TokenType.TEXT: 'TEXT'>, <TokenType.TINYTEXT: 'TINYTEXT'>, <TokenType.TSTZMULTIRANGE: 'TSTZMULTIRANGE'>, <TokenType.SMALLINT: 'SMALLINT'>, <TokenType.DEFAULT: 'DEFAULT'>, <TokenType.PROCEDURE: 'PROCEDURE'>, <TokenType.ROWS: 'ROWS'>, <TokenType.UUID: 'UUID'>, <TokenType.ALL: 'ALL'>, <TokenType.INT4RANGE: 'INT4RANGE'>, <TokenType.FIRST: 'FIRST'>, <TokenType.SETTINGS: 'SETTINGS'>, <TokenType.ANY: 'ANY'>, <TokenType.TIMESTAMPTZ: 'TIMESTAMPTZ'>, <TokenType.BEGIN: 'BEGIN'>, <TokenType.DESC: 'DESC'>, <TokenType.INT4MULTIRANGE: 'INT4MULTIRANGE'>, <TokenType.CURRENT_DATE: 'CURRENT_DATE'>, <TokenType.INT8RANGE: 'INT8RANGE'>, <TokenType.COLLATE: 'COLLATE'>, <TokenType.VAR: 'VAR'>, <TokenType.VALUES: 'VALUES'>, <TokenType.VIEW: 'VIEW'>, <TokenType.FALSE: 'FALSE'>, <TokenType.ENUM: 'ENUM'>, <TokenType.PRAGMA: 'PRAGMA'>, <TokenType.KEEP: 'KEEP'>, <TokenType.UINT256: 'UINT256'>, <TokenType.AUTO_INCREMENT: 'AUTO_INCREMENT'>, <TokenType.TSMULTIRANGE: 'TSMULTIRANGE'>, <TokenType.SEMI: 'SEMI'>, <TokenType.IS: 'IS'>, <TokenType.OBJECT: 'OBJECT'>, <TokenType.ASC: 'ASC'>, <TokenType.INTERVAL: 'INTERVAL'>, <TokenType.DELETE: 'DELETE'>, <TokenType.VARBINARY: 'VARBINARY'>, <TokenType.NEXT: 'NEXT'>, <TokenType.IPPREFIX: 'IPPREFIX'>, <TokenType.FUNCTION: 'FUNCTION'>, <TokenType.DATERANGE: 'DATERANGE'>, <TokenType.LONGTEXT: 'LONGTEXT'>, <TokenType.JSON: 'JSON'>, <TokenType.COLUMN: 'COLUMN'>, <TokenType.INT: 'INT'>, <TokenType.CURRENT_TIME: 'CURRENT_TIME'>, <TokenType.BIGINT: 'BIGINT'>, <TokenType.SCHEMA: 'SCHEMA'>, <TokenType.PSEUDO_TYPE: 'PSEUDO_TYPE'>, <TokenType.NULL: 'NULL'>, <TokenType.DATETIME64: 'DATETIME64'>, <TokenType.ISNULL: 'ISNULL'>, <TokenType.NUMMULTIRANGE: 'NUMMULTIRANGE'>, <TokenType.ANTI: 'ANTI'>, <TokenType.CURRENT_TIMESTAMP: 'CURRENT_TIMESTAMP'>, <TokenType.BIGDECIMAL: 'BIGDECIMAL'>, <TokenType.OFFSET: 'OFFSET'>, <TokenType.COMMENT: 'COMMENT'>, <TokenType.UNKNOWN: 'UNKNOWN'>, <TokenType.UMEDIUMINT: 'UMEDIUMINT'>, <TokenType.SET: 'SET'>, <TokenType.GEOMETRY: 'GEOMETRY'>, <TokenType.LEFT: 'LEFT'>, <TokenType.ARRAY: 'ARRAY'>, <TokenType.CURRENT_USER: 'CURRENT_USER'>, <TokenType.TINYINT: 'TINYINT'>, <TokenType.CHAR: 'CHAR'>, <TokenType.LOWCARDINALITY: 'LOWCARDINALITY'>, <TokenType.DATETIME: 'DATETIME'>, <TokenType.UNIQUE: 'UNIQUE'>, <TokenType.ESCAPE: 'ESCAPE'>, <TokenType.MEDIUMINT: 'MEDIUMINT'>, <TokenType.TEMPORARY: 'TEMPORARY'>, <TokenType.INT8MULTIRANGE: 'INT8MULTIRANGE'>, <TokenType.PIVOT: 'PIVOT'>, <TokenType.FULL: 'FULL'>, <TokenType.MAP: 'MAP'>, <TokenType.MEDIUMBLOB: 'MEDIUMBLOB'>, <TokenType.UPDATE: 'UPDATE'>, <TokenType.VARCHAR: 'VARCHAR'>, <TokenType.IMAGE: 'IMAGE'>, <TokenType.TIMESTAMP: 'TIMESTAMP'>, <TokenType.COMMIT: 'COMMIT'>, <TokenType.NATURAL: 'NATURAL'>, <TokenType.YEAR: 'YEAR'>, <TokenType.RANGE: 'RANGE'>, <TokenType.DOUBLE: 'DOUBLE'>, <TokenType.VARIANT: 'VARIANT'>, <TokenType.DICTIONARY: 'DICTIONARY'>, <TokenType.BIGSERIAL: 'BIGSERIAL'>, <TokenType.STRUCT: 'STRUCT'>, <TokenType.SUPER: 'SUPER'>, <TokenType.INT256: 'INT256'>, <TokenType.JSONB: 'JSONB'>, <TokenType.ENUM8: 'ENUM8'>, <TokenType.FIXEDSTRING: 'FIXEDSTRING'>, <TokenType.WINDOW: 'WINDOW'>, <TokenType.XML: 'XML'>, <TokenType.FORMAT: 'FORMAT'>, <TokenType.MONEY: 'MONEY'>, <TokenType.REFERENCES: 'REFERENCES'>, <TokenType.PERCENT: 'PERCENT'>, <TokenType.USMALLINT: 'USMALLINT'>, <TokenType.CACHE: 'CACHE'>, <TokenType.FILTER: 'FILTER'>, <TokenType.MERGE: 'MERGE'>, <TokenType.VOLATILE: 'VOLATILE'>, <TokenType.TOP: 'TOP'>, <TokenType.OBJECT_IDENTIFIER: 'OBJECT_IDENTIFIER'>, <TokenType.DATABASE: 'DATABASE'>, <TokenType.DATEMULTIRANGE: 'DATEMULTIRANGE'>, <TokenType.FLOAT: 'FLOAT'>, <TokenType.OVERWRITE: 'OVERWRITE'>, <TokenType.NVARCHAR: 'NVARCHAR'>, <TokenType.MEDIUMTEXT: 'MEDIUMTEXT'>, <TokenType.ROW: 'ROW'>, <TokenType.PARTITION: 'PARTITION'>, <TokenType.COMMAND: 'COMMAND'>, <TokenType.LOAD: 'LOAD'>, <TokenType.INET: 'INET'>, <TokenType.NUMRANGE: 'NUMRANGE'>, <TokenType.UINT128: 'UINT128'>, <TokenType.TRUE: 'TRUE'>, <TokenType.UNIQUEIDENTIFIER: 'UNIQUEIDENTIFIER'>, <TokenType.TIMETZ: 'TIMETZ'>, <TokenType.DESCRIBE: 'DESCRIBE'>, <TokenType.SMALLSERIAL: 'SMALLSERIAL'>, <TokenType.RIGHT: 'RIGHT'>, <TokenType.NULLABLE: 'NULLABLE'>, <TokenType.INT128: 'INT128'>, <TokenType.BIT: 'BIT'>, <TokenType.UTINYINT: 'UTINYINT'>, <TokenType.HSTORE: 'HSTORE'>, <TokenType.TABLE: 'TABLE'>, <TokenType.SMALLMONEY: 'SMALLMONEY'>, <TokenType.TSRANGE: 'TSRANGE'>, <TokenType.NCHAR: 'NCHAR'>, <TokenType.UNPIVOT: 'UNPIVOT'>, <TokenType.TIME: 'TIME'>, <TokenType.DIV: 'DIV'>, <TokenType.CURRENT_DATETIME: 'CURRENT_DATETIME'>, <TokenType.ROWVERSION: 'ROWVERSION'>, <TokenType.TINYBLOB: 'TINYBLOB'>, <TokenType.ENUM16: 'ENUM16'>, <TokenType.ORDINALITY: 'ORDINALITY'>, <TokenType.SHOW: 'SHOW'>, <TokenType.END: 'END'>, <TokenType.SOME: 'SOME'>, <TokenType.EXISTS: 'EXISTS'>, <TokenType.BOOLEAN: 'BOOLEAN'>, <TokenType.BINARY: 'BINARY'>, <TokenType.HLLSKETCH: 'HLLSKETCH'>, <TokenType.UINT: 'UINT'>, <TokenType.DECIMAL: 'DECIMAL'>, <TokenType.GEOGRAPHY: 'GEOGRAPHY'>, <TokenType.SERIAL: 'SERIAL'>, <TokenType.DATE: 'DATE'>, <TokenType.INDEX: 'INDEX'>, <TokenType.USERDEFINED: 'USERDEFINED'>, <TokenType.UBIGINT: 'UBIGINT'>, <TokenType.TSTZRANGE: 'TSTZRANGE'>, <TokenType.NESTED: 'NESTED'>, <TokenType.CASE: 'CASE'>, <TokenType.APPLY: 'APPLY'>, <TokenType.LONGBLOB: 'LONGBLOB'>, <TokenType.TIMESTAMPLTZ: 'TIMESTAMPLTZ'>, <TokenType.IPADDRESS: 'IPADDRESS'>, <TokenType.CONSTRAINT: 'CONSTRAINT'>, <TokenType.EXECUTE: 'EXECUTE'>}
    @@ -2456,6 +2455,18 @@ Default: 3 +
    +
    +
    + SUPPORTS_USER_DEFINED_TYPES = +False + + +
    + + + +
    @@ -2603,6 +2614,7 @@ Default: 3
    WINDOW_ALIAS_TOKENS
    WINDOW_BEFORE_PAREN_TOKENS
    WINDOW_SIDES
    +
    FETCH_TOKENS
    ADD_CONSTRAINT_TOKENS
    DISTINCT_TOKENS
    STRICT_CAST
    @@ -2642,280 +2654,280 @@ Default: 3
    -
    422    class Generator(generator.Generator):
    -423        EXPLICIT_UNION = True
    -424        INTERVAL_ALLOWS_PLURAL_FORM = False
    -425        JOIN_HINTS = False
    -426        QUERY_HINTS = False
    -427        TABLE_HINTS = False
    -428        LIMIT_FETCH = "LIMIT"
    -429        RENAME_TABLE_WITH_DB = False
    -430        ESCAPE_LINE_BREAK = True
    -431        NVL2_SUPPORTED = False
    -432
    -433        TRANSFORMS = {
    -434            **generator.Generator.TRANSFORMS,
    -435            exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"),
    -436            exp.ArraySize: rename_func("ARRAY_LENGTH"),
    -437            exp.Cast: transforms.preprocess([transforms.remove_precision_parameterized_types]),
    -438            exp.Create: _create_sql,
    -439            exp.CTE: transforms.preprocess([_pushdown_cte_column_names]),
    -440            exp.DateAdd: _date_add_sql("DATE", "ADD"),
    -441            exp.DateDiff: lambda self, e: f"DATE_DIFF({self.sql(e, 'this')}, {self.sql(e, 'expression')}, {self.sql(e.args.get('unit', 'DAY'))})",
    -442            exp.DateFromParts: rename_func("DATE"),
    -443            exp.DateStrToDate: datestrtodate_sql,
    -444            exp.DateSub: _date_add_sql("DATE", "SUB"),
    -445            exp.DatetimeAdd: _date_add_sql("DATETIME", "ADD"),
    -446            exp.DatetimeSub: _date_add_sql("DATETIME", "SUB"),
    -447            exp.DateTrunc: lambda self, e: self.func("DATE_TRUNC", e.this, e.text("unit")),
    -448            exp.GenerateSeries: rename_func("GENERATE_ARRAY"),
    -449            exp.GroupConcat: rename_func("STRING_AGG"),
    -450            exp.Hex: rename_func("TO_HEX"),
    -451            exp.ILike: no_ilike_sql,
    -452            exp.IntDiv: rename_func("DIV"),
    -453            exp.JSONFormat: rename_func("TO_JSON_STRING"),
    -454            exp.JSONKeyValue: json_keyvalue_comma_sql,
    -455            exp.Max: max_or_greatest,
    -456            exp.MD5: lambda self, e: self.func("TO_HEX", self.func("MD5", e.this)),
    -457            exp.MD5Digest: rename_func("MD5"),
    -458            exp.Min: min_or_least,
    -459            exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}",
    -460            exp.RegexpExtract: lambda self, e: self.func(
    -461                "REGEXP_EXTRACT",
    -462                e.this,
    -463                e.expression,
    -464                e.args.get("position"),
    -465                e.args.get("occurrence"),
    -466            ),
    -467            exp.RegexpReplace: regexp_replace_sql,
    -468            exp.RegexpLike: rename_func("REGEXP_CONTAINS"),
    -469            exp.ReturnsProperty: _returnsproperty_sql,
    -470            exp.Select: transforms.preprocess(
    -471                [
    -472                    transforms.explode_to_unnest,
    -473                    _unqualify_unnest,
    -474                    transforms.eliminate_distinct_on,
    -475                    _alias_ordered_group,
    -476                ]
    -477            ),
    -478            exp.SHA2: lambda self, e: self.func(
    -479                f"SHA256" if e.text("length") == "256" else "SHA512", e.this
    -480            ),
    -481            exp.StabilityProperty: lambda self, e: f"DETERMINISTIC"
    -482            if e.name == "IMMUTABLE"
    -483            else "NOT DETERMINISTIC",
    -484            exp.StrToDate: lambda self, e: f"PARSE_DATE({self.format_time(e)}, {self.sql(e, 'this')})",
    -485            exp.StrToTime: lambda self, e: self.func(
    -486                "PARSE_TIMESTAMP", self.format_time(e), e.this, e.args.get("zone")
    -487            ),
    -488            exp.TimeAdd: _date_add_sql("TIME", "ADD"),
    -489            exp.TimeSub: _date_add_sql("TIME", "SUB"),
    -490            exp.TimestampAdd: _date_add_sql("TIMESTAMP", "ADD"),
    -491            exp.TimestampSub: _date_add_sql("TIMESTAMP", "SUB"),
    -492            exp.TimeStrToTime: timestrtotime_sql,
    -493            exp.Trim: lambda self, e: self.func(f"TRIM", e.this, e.expression),
    -494            exp.TsOrDsAdd: _date_add_sql("DATE", "ADD"),
    -495            exp.TsOrDsToDate: ts_or_ds_to_date_sql("bigquery"),
    -496            exp.Unhex: rename_func("FROM_HEX"),
    -497            exp.Values: _derived_table_values_to_unnest,
    -498            exp.VariancePop: rename_func("VAR_POP"),
    -499        }
    -500
    -501        TYPE_MAPPING = {
    -502            **generator.Generator.TYPE_MAPPING,
    -503            exp.DataType.Type.BIGDECIMAL: "BIGNUMERIC",
    -504            exp.DataType.Type.BIGINT: "INT64",
    -505            exp.DataType.Type.BINARY: "BYTES",
    -506            exp.DataType.Type.BOOLEAN: "BOOL",
    -507            exp.DataType.Type.CHAR: "STRING",
    -508            exp.DataType.Type.DECIMAL: "NUMERIC",
    -509            exp.DataType.Type.DOUBLE: "FLOAT64",
    -510            exp.DataType.Type.FLOAT: "FLOAT64",
    -511            exp.DataType.Type.INT: "INT64",
    -512            exp.DataType.Type.NCHAR: "STRING",
    -513            exp.DataType.Type.NVARCHAR: "STRING",
    -514            exp.DataType.Type.SMALLINT: "INT64",
    -515            exp.DataType.Type.TEXT: "STRING",
    -516            exp.DataType.Type.TIMESTAMP: "DATETIME",
    -517            exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP",
    -518            exp.DataType.Type.TIMESTAMPLTZ: "TIMESTAMP",
    -519            exp.DataType.Type.TINYINT: "INT64",
    -520            exp.DataType.Type.VARBINARY: "BYTES",
    -521            exp.DataType.Type.VARCHAR: "STRING",
    -522            exp.DataType.Type.VARIANT: "ANY TYPE",
    -523        }
    -524
    -525        PROPERTIES_LOCATION = {
    -526            **generator.Generator.PROPERTIES_LOCATION,
    -527            exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,
    -528            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
    -529        }
    -530
    -531        # from: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
    -532        RESERVED_KEYWORDS = {
    -533            *generator.Generator.RESERVED_KEYWORDS,
    -534            "all",
    -535            "and",
    -536            "any",
    -537            "array",
    -538            "as",
    -539            "asc",
    -540            "assert_rows_modified",
    -541            "at",
    -542            "between",
    -543            "by",
    -544            "case",
    -545            "cast",
    -546            "collate",
    -547            "contains",
    -548            "create",
    -549            "cross",
    -550            "cube",
    -551            "current",
    -552            "default",
    -553            "define",
    -554            "desc",
    -555            "distinct",
    -556            "else",
    -557            "end",
    -558            "enum",
    -559            "escape",
    -560            "except",
    -561            "exclude",
    -562            "exists",
    -563            "extract",
    -564            "false",
    -565            "fetch",
    -566            "following",
    -567            "for",
    -568            "from",
    -569            "full",
    -570            "group",
    -571            "grouping",
    -572            "groups",
    -573            "hash",
    -574            "having",
    -575            "if",
    -576            "ignore",
    -577            "in",
    -578            "inner",
    -579            "intersect",
    -580            "interval",
    -581            "into",
    -582            "is",
    -583            "join",
    -584            "lateral",
    -585            "left",
    -586            "like",
    -587            "limit",
    -588            "lookup",
    -589            "merge",
    -590            "natural",
    -591            "new",
    -592            "no",
    -593            "not",
    -594            "null",
    -595            "nulls",
    -596            "of",
    -597            "on",
    -598            "or",
    -599            "order",
    -600            "outer",
    -601            "over",
    -602            "partition",
    -603            "preceding",
    -604            "proto",
    -605            "qualify",
    -606            "range",
    -607            "recursive",
    -608            "respect",
    -609            "right",
    -610            "rollup",
    -611            "rows",
    -612            "select",
    -613            "set",
    -614            "some",
    -615            "struct",
    -616            "tablesample",
    -617            "then",
    -618            "to",
    -619            "treat",
    -620            "true",
    -621            "unbounded",
    -622            "union",
    -623            "unnest",
    -624            "using",
    -625            "when",
    -626            "where",
    -627            "window",
    -628            "with",
    -629            "within",
    -630        }
    -631
    -632        def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
    -633            parent = expression.parent
    -634
    -635            # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).
    -636            # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.
    -637            if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"):
    -638                return self.func(
    -639                    "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone"))
    -640                )
    -641
    -642            return super().attimezone_sql(expression)
    -643
    -644        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
    -645            # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#json_literals
    -646            if expression.is_type("json"):
    -647                return f"JSON {self.sql(expression, 'this')}"
    -648
    -649            return super().cast_sql(expression, safe_prefix=safe_prefix)
    -650
    -651        def trycast_sql(self, expression: exp.TryCast) -> str:
    -652            return self.cast_sql(expression, safe_prefix="SAFE_")
    -653
    -654        def cte_sql(self, expression: exp.CTE) -> str:
    -655            if expression.alias_column_names:
    -656                self.unsupported("Column names in CTE definition are not supported.")
    -657            return super().cte_sql(expression)
    -658
    -659        def array_sql(self, expression: exp.Array) -> str:
    -660            first_arg = seq_get(expression.expressions, 0)
    -661            if isinstance(first_arg, exp.Subqueryable):
    -662                return f"ARRAY{self.wrap(self.sql(first_arg))}"
    -663
    -664            return inline_array_sql(self, expression)
    -665
    -666        def transaction_sql(self, *_) -> str:
    -667            return "BEGIN TRANSACTION"
    -668
    -669        def commit_sql(self, *_) -> str:
    -670            return "COMMIT TRANSACTION"
    -671
    -672        def rollback_sql(self, *_) -> str:
    -673            return "ROLLBACK TRANSACTION"
    -674
    -675        def in_unnest_op(self, expression: exp.Unnest) -> str:
    -676            return self.sql(expression)
    -677
    -678        def except_op(self, expression: exp.Except) -> str:
    -679            if not expression.args.get("distinct", False):
    -680                self.unsupported("EXCEPT without DISTINCT is not supported in BigQuery")
    -681            return f"EXCEPT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}"
    -682
    -683        def intersect_op(self, expression: exp.Intersect) -> str:
    -684            if not expression.args.get("distinct", False):
    -685                self.unsupported("INTERSECT without DISTINCT is not supported in BigQuery")
    -686            return f"INTERSECT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}"
    -687
    -688        def with_properties(self, properties: exp.Properties) -> str:
    -689            return self.properties(properties, prefix=self.seg("OPTIONS"))
    -690
    -691        def version_sql(self, expression: exp.Version) -> str:
    -692            if expression.name == "TIMESTAMP":
    -693                expression = expression.copy()
    -694                expression.set("this", "SYSTEM_TIME")
    -695            return super().version_sql(expression)
    +            
    421    class Generator(generator.Generator):
    +422        EXPLICIT_UNION = True
    +423        INTERVAL_ALLOWS_PLURAL_FORM = False
    +424        JOIN_HINTS = False
    +425        QUERY_HINTS = False
    +426        TABLE_HINTS = False
    +427        LIMIT_FETCH = "LIMIT"
    +428        RENAME_TABLE_WITH_DB = False
    +429        ESCAPE_LINE_BREAK = True
    +430        NVL2_SUPPORTED = False
    +431
    +432        TRANSFORMS = {
    +433            **generator.Generator.TRANSFORMS,
    +434            exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"),
    +435            exp.ArraySize: rename_func("ARRAY_LENGTH"),
    +436            exp.Cast: transforms.preprocess([transforms.remove_precision_parameterized_types]),
    +437            exp.Create: _create_sql,
    +438            exp.CTE: transforms.preprocess([_pushdown_cte_column_names]),
    +439            exp.DateAdd: _date_add_sql("DATE", "ADD"),
    +440            exp.DateDiff: lambda self, e: f"DATE_DIFF({self.sql(e, 'this')}, {self.sql(e, 'expression')}, {self.sql(e.args.get('unit', 'DAY'))})",
    +441            exp.DateFromParts: rename_func("DATE"),
    +442            exp.DateStrToDate: datestrtodate_sql,
    +443            exp.DateSub: _date_add_sql("DATE", "SUB"),
    +444            exp.DatetimeAdd: _date_add_sql("DATETIME", "ADD"),
    +445            exp.DatetimeSub: _date_add_sql("DATETIME", "SUB"),
    +446            exp.DateTrunc: lambda self, e: self.func("DATE_TRUNC", e.this, e.text("unit")),
    +447            exp.GenerateSeries: rename_func("GENERATE_ARRAY"),
    +448            exp.GroupConcat: rename_func("STRING_AGG"),
    +449            exp.Hex: rename_func("TO_HEX"),
    +450            exp.ILike: no_ilike_sql,
    +451            exp.IntDiv: rename_func("DIV"),
    +452            exp.JSONFormat: rename_func("TO_JSON_STRING"),
    +453            exp.JSONKeyValue: json_keyvalue_comma_sql,
    +454            exp.Max: max_or_greatest,
    +455            exp.MD5: lambda self, e: self.func("TO_HEX", self.func("MD5", e.this)),
    +456            exp.MD5Digest: rename_func("MD5"),
    +457            exp.Min: min_or_least,
    +458            exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}",
    +459            exp.RegexpExtract: lambda self, e: self.func(
    +460                "REGEXP_EXTRACT",
    +461                e.this,
    +462                e.expression,
    +463                e.args.get("position"),
    +464                e.args.get("occurrence"),
    +465            ),
    +466            exp.RegexpReplace: regexp_replace_sql,
    +467            exp.RegexpLike: rename_func("REGEXP_CONTAINS"),
    +468            exp.ReturnsProperty: _returnsproperty_sql,
    +469            exp.Select: transforms.preprocess(
    +470                [
    +471                    transforms.explode_to_unnest,
    +472                    _unqualify_unnest,
    +473                    transforms.eliminate_distinct_on,
    +474                    _alias_ordered_group,
    +475                ]
    +476            ),
    +477            exp.SHA2: lambda self, e: self.func(
    +478                f"SHA256" if e.text("length") == "256" else "SHA512", e.this
    +479            ),
    +480            exp.StabilityProperty: lambda self, e: f"DETERMINISTIC"
    +481            if e.name == "IMMUTABLE"
    +482            else "NOT DETERMINISTIC",
    +483            exp.StrToDate: lambda self, e: f"PARSE_DATE({self.format_time(e)}, {self.sql(e, 'this')})",
    +484            exp.StrToTime: lambda self, e: self.func(
    +485                "PARSE_TIMESTAMP", self.format_time(e), e.this, e.args.get("zone")
    +486            ),
    +487            exp.TimeAdd: _date_add_sql("TIME", "ADD"),
    +488            exp.TimeSub: _date_add_sql("TIME", "SUB"),
    +489            exp.TimestampAdd: _date_add_sql("TIMESTAMP", "ADD"),
    +490            exp.TimestampSub: _date_add_sql("TIMESTAMP", "SUB"),
    +491            exp.TimeStrToTime: timestrtotime_sql,
    +492            exp.Trim: lambda self, e: self.func(f"TRIM", e.this, e.expression),
    +493            exp.TsOrDsAdd: _date_add_sql("DATE", "ADD"),
    +494            exp.TsOrDsToDate: ts_or_ds_to_date_sql("bigquery"),
    +495            exp.Unhex: rename_func("FROM_HEX"),
    +496            exp.Values: _derived_table_values_to_unnest,
    +497            exp.VariancePop: rename_func("VAR_POP"),
    +498        }
    +499
    +500        TYPE_MAPPING = {
    +501            **generator.Generator.TYPE_MAPPING,
    +502            exp.DataType.Type.BIGDECIMAL: "BIGNUMERIC",
    +503            exp.DataType.Type.BIGINT: "INT64",
    +504            exp.DataType.Type.BINARY: "BYTES",
    +505            exp.DataType.Type.BOOLEAN: "BOOL",
    +506            exp.DataType.Type.CHAR: "STRING",
    +507            exp.DataType.Type.DECIMAL: "NUMERIC",
    +508            exp.DataType.Type.DOUBLE: "FLOAT64",
    +509            exp.DataType.Type.FLOAT: "FLOAT64",
    +510            exp.DataType.Type.INT: "INT64",
    +511            exp.DataType.Type.NCHAR: "STRING",
    +512            exp.DataType.Type.NVARCHAR: "STRING",
    +513            exp.DataType.Type.SMALLINT: "INT64",
    +514            exp.DataType.Type.TEXT: "STRING",
    +515            exp.DataType.Type.TIMESTAMP: "DATETIME",
    +516            exp.DataType.Type.TIMESTAMPTZ: "TIMESTAMP",
    +517            exp.DataType.Type.TIMESTAMPLTZ: "TIMESTAMP",
    +518            exp.DataType.Type.TINYINT: "INT64",
    +519            exp.DataType.Type.VARBINARY: "BYTES",
    +520            exp.DataType.Type.VARCHAR: "STRING",
    +521            exp.DataType.Type.VARIANT: "ANY TYPE",
    +522        }
    +523
    +524        PROPERTIES_LOCATION = {
    +525            **generator.Generator.PROPERTIES_LOCATION,
    +526            exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,
    +527            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
    +528        }
    +529
    +530        # from: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
    +531        RESERVED_KEYWORDS = {
    +532            *generator.Generator.RESERVED_KEYWORDS,
    +533            "all",
    +534            "and",
    +535            "any",
    +536            "array",
    +537            "as",
    +538            "asc",
    +539            "assert_rows_modified",
    +540            "at",
    +541            "between",
    +542            "by",
    +543            "case",
    +544            "cast",
    +545            "collate",
    +546            "contains",
    +547            "create",
    +548            "cross",
    +549            "cube",
    +550            "current",
    +551            "default",
    +552            "define",
    +553            "desc",
    +554            "distinct",
    +555            "else",
    +556            "end",
    +557            "enum",
    +558            "escape",
    +559            "except",
    +560            "exclude",
    +561            "exists",
    +562            "extract",
    +563            "false",
    +564            "fetch",
    +565            "following",
    +566            "for",
    +567            "from",
    +568            "full",
    +569            "group",
    +570            "grouping",
    +571            "groups",
    +572            "hash",
    +573            "having",
    +574            "if",
    +575            "ignore",
    +576            "in",
    +577            "inner",
    +578            "intersect",
    +579            "interval",
    +580            "into",
    +581            "is",
    +582            "join",
    +583            "lateral",
    +584            "left",
    +585            "like",
    +586            "limit",
    +587            "lookup",
    +588            "merge",
    +589            "natural",
    +590            "new",
    +591            "no",
    +592            "not",
    +593            "null",
    +594            "nulls",
    +595            "of",
    +596            "on",
    +597            "or",
    +598            "order",
    +599            "outer",
    +600            "over",
    +601            "partition",
    +602            "preceding",
    +603            "proto",
    +604            "qualify",
    +605            "range",
    +606            "recursive",
    +607            "respect",
    +608            "right",
    +609            "rollup",
    +610            "rows",
    +611            "select",
    +612            "set",
    +613            "some",
    +614            "struct",
    +615            "tablesample",
    +616            "then",
    +617            "to",
    +618            "treat",
    +619            "true",
    +620            "unbounded",
    +621            "union",
    +622            "unnest",
    +623            "using",
    +624            "when",
    +625            "where",
    +626            "window",
    +627            "with",
    +628            "within",
    +629        }
    +630
    +631        def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
    +632            parent = expression.parent
    +633
    +634            # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).
    +635            # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.
    +636            if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"):
    +637                return self.func(
    +638                    "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone"))
    +639                )
    +640
    +641            return super().attimezone_sql(expression)
    +642
    +643        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
    +644            # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#json_literals
    +645            if expression.is_type("json"):
    +646                return f"JSON {self.sql(expression, 'this')}"
    +647
    +648            return super().cast_sql(expression, safe_prefix=safe_prefix)
    +649
    +650        def trycast_sql(self, expression: exp.TryCast) -> str:
    +651            return self.cast_sql(expression, safe_prefix="SAFE_")
    +652
    +653        def cte_sql(self, expression: exp.CTE) -> str:
    +654            if expression.alias_column_names:
    +655                self.unsupported("Column names in CTE definition are not supported.")
    +656            return super().cte_sql(expression)
    +657
    +658        def array_sql(self, expression: exp.Array) -> str:
    +659            first_arg = seq_get(expression.expressions, 0)
    +660            if isinstance(first_arg, exp.Subqueryable):
    +661                return f"ARRAY{self.wrap(self.sql(first_arg))}"
    +662
    +663            return inline_array_sql(self, expression)
    +664
    +665        def transaction_sql(self, *_) -> str:
    +666            return "BEGIN TRANSACTION"
    +667
    +668        def commit_sql(self, *_) -> str:
    +669            return "COMMIT TRANSACTION"
    +670
    +671        def rollback_sql(self, *_) -> str:
    +672            return "ROLLBACK TRANSACTION"
    +673
    +674        def in_unnest_op(self, expression: exp.Unnest) -> str:
    +675            return self.sql(expression)
    +676
    +677        def except_op(self, expression: exp.Except) -> str:
    +678            if not expression.args.get("distinct", False):
    +679                self.unsupported("EXCEPT without DISTINCT is not supported in BigQuery")
    +680            return f"EXCEPT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}"
    +681
    +682        def intersect_op(self, expression: exp.Intersect) -> str:
    +683            if not expression.args.get("distinct", False):
    +684                self.unsupported("INTERSECT without DISTINCT is not supported in BigQuery")
    +685            return f"INTERSECT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}"
    +686
    +687        def with_properties(self, properties: exp.Properties) -> str:
    +688            return self.properties(properties, prefix=self.seg("OPTIONS"))
    +689
    +690        def version_sql(self, expression: exp.Version) -> str:
    +691            if expression.name == "TIMESTAMP":
    +692                expression = expression.copy()
    +693                expression.set("this", "SYSTEM_TIME")
    +694            return super().version_sql(expression)
     
    @@ -3109,7 +3121,7 @@ Default: True
    RESERVED_KEYWORDS = - {'right', 'when', 'default', 'collate', 'partition', 'merge', 'enum', 'as', 'order', 'select', 'groups', 'create', 'interval', 'natural', 'if', 'outer', 'over', 'is', 'exists', 'rows', 'true', 'except', 'ignore', 'cast', 'full', 'fetch', 'null', 'desc', 'set', 'false', 'some', 'treat', 'exclude', 'lateral', 'assert_rows_modified', 'group', 'qualify', 'for', 'not', 'define', 'nulls', 'extract', 'distinct', 'asc', 'on', 'range', 'struct', 'hash', 'proto', 'following', 'preceding', 'join', 'contains', 'at', 'case', 'into', 'limit', 'lookup', 'new', 'recursive', 'using', 'cross', 'escape', 'in', 'to', 'where', 'any', 'union', 'cube', 'unbounded', 'then', 'or', 'else', 'and', 'window', 'all', 'of', 'rollup', 'by', 'no', 'having', 'respect', 'current', 'unnest', 'within', 'between', 'like', 'tablesample', 'grouping', 'from', 'inner', 'with', 'end', 'array', 'intersect', 'left'} + {'at', 'else', 'some', 'order', 'union', 'set', 'like', 'groups', 'having', 'define', 'by', 'false', 'no', 'collate', 'in', 'using', 'where', 'range', 'with', 'when', 'and', 'between', 'merge', 'cube', 'qualify', 'lookup', 'join', 'hash', 'right', 'any', 'true', 'proto', 'null', 'window', 'all', 'asc', 'escape', 'limit', 'preceding', 'then', 'from', 'create', 'extract', 'unbounded', 'except', 'or', 'to', 'nulls', 'desc', 'enum', 'fetch', 'natural', 'as', 'recursive', 'respect', 'distinct', 'following', 'group', 'new', 'exists', 'treat', 'ignore', 'within', 'if', 'for', 'select', 'end', 'on', 'outer', 'grouping', 'unnest', 'tablesample', 'full', 'lateral', 'rows', 'cross', 'not', 'is', 'struct', 'assert_rows_modified', 'intersect', 'left', 'exclude', 'cast', 'case', 'inner', 'array', 'contains', 'of', 'current', 'partition', 'interval', 'default', 'rollup', 'into', 'over'}
    @@ -3129,17 +3141,17 @@ Default: True
    -
    632        def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
    -633            parent = expression.parent
    -634
    -635            # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).
    -636            # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.
    -637            if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"):
    -638                return self.func(
    -639                    "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone"))
    -640                )
    -641
    -642            return super().attimezone_sql(expression)
    +            
    631        def attimezone_sql(self, expression: exp.AtTimeZone) -> str:
    +632            parent = expression.parent
    +633
    +634            # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).
    +635            # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.
    +636            if not isinstance(parent, exp.Cast) or not parent.to.is_type("text"):
    +637                return self.func(
    +638                    "TIMESTAMP", self.func("DATETIME", expression.this, expression.args.get("zone"))
    +639                )
    +640
    +641            return super().attimezone_sql(expression)
     
    @@ -3157,12 +3169,12 @@ Default: True
    -
    644        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
    -645            # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#json_literals
    -646            if expression.is_type("json"):
    -647                return f"JSON {self.sql(expression, 'this')}"
    -648
    -649            return super().cast_sql(expression, safe_prefix=safe_prefix)
    +            
    643        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:
    +644            # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#json_literals
    +645            if expression.is_type("json"):
    +646                return f"JSON {self.sql(expression, 'this')}"
    +647
    +648            return super().cast_sql(expression, safe_prefix=safe_prefix)
     
    @@ -3180,8 +3192,8 @@ Default: True
    -
    651        def trycast_sql(self, expression: exp.TryCast) -> str:
    -652            return self.cast_sql(expression, safe_prefix="SAFE_")
    +            
    650        def trycast_sql(self, expression: exp.TryCast) -> str:
    +651            return self.cast_sql(expression, safe_prefix="SAFE_")
     
    @@ -3199,10 +3211,10 @@ Default: True
    -
    654        def cte_sql(self, expression: exp.CTE) -> str:
    -655            if expression.alias_column_names:
    -656                self.unsupported("Column names in CTE definition are not supported.")
    -657            return super().cte_sql(expression)
    +            
    653        def cte_sql(self, expression: exp.CTE) -> str:
    +654            if expression.alias_column_names:
    +655                self.unsupported("Column names in CTE definition are not supported.")
    +656            return super().cte_sql(expression)
     
    @@ -3220,12 +3232,12 @@ Default: True
    -
    659        def array_sql(self, expression: exp.Array) -> str:
    -660            first_arg = seq_get(expression.expressions, 0)
    -661            if isinstance(first_arg, exp.Subqueryable):
    -662                return f"ARRAY{self.wrap(self.sql(first_arg))}"
    -663
    -664            return inline_array_sql(self, expression)
    +            
    658        def array_sql(self, expression: exp.Array) -> str:
    +659            first_arg = seq_get(expression.expressions, 0)
    +660            if isinstance(first_arg, exp.Subqueryable):
    +661                return f"ARRAY{self.wrap(self.sql(first_arg))}"
    +662
    +663            return inline_array_sql(self, expression)
     
    @@ -3243,8 +3255,8 @@ Default: True
    -
    666        def transaction_sql(self, *_) -> str:
    -667            return "BEGIN TRANSACTION"
    +            
    665        def transaction_sql(self, *_) -> str:
    +666            return "BEGIN TRANSACTION"
     
    @@ -3262,8 +3274,8 @@ Default: True
    -
    669        def commit_sql(self, *_) -> str:
    -670            return "COMMIT TRANSACTION"
    +            
    668        def commit_sql(self, *_) -> str:
    +669            return "COMMIT TRANSACTION"
     
    @@ -3281,8 +3293,8 @@ Default: True
    -
    672        def rollback_sql(self, *_) -> str:
    -673            return "ROLLBACK TRANSACTION"
    +            
    671        def rollback_sql(self, *_) -> str:
    +672            return "ROLLBACK TRANSACTION"
     
    @@ -3300,8 +3312,8 @@ Default: True
    -
    675        def in_unnest_op(self, expression: exp.Unnest) -> str:
    -676            return self.sql(expression)
    +            
    674        def in_unnest_op(self, expression: exp.Unnest) -> str:
    +675            return self.sql(expression)
     
    @@ -3319,10 +3331,10 @@ Default: True
    -
    678        def except_op(self, expression: exp.Except) -> str:
    -679            if not expression.args.get("distinct", False):
    -680                self.unsupported("EXCEPT without DISTINCT is not supported in BigQuery")
    -681            return f"EXCEPT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}"
    +            
    677        def except_op(self, expression: exp.Except) -> str:
    +678            if not expression.args.get("distinct", False):
    +679                self.unsupported("EXCEPT without DISTINCT is not supported in BigQuery")
    +680            return f"EXCEPT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}"
     
    @@ -3340,10 +3352,10 @@ Default: True
    -
    683        def intersect_op(self, expression: exp.Intersect) -> str:
    -684            if not expression.args.get("distinct", False):
    -685                self.unsupported("INTERSECT without DISTINCT is not supported in BigQuery")
    -686            return f"INTERSECT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}"
    +            
    682        def intersect_op(self, expression: exp.Intersect) -> str:
    +683            if not expression.args.get("distinct", False):
    +684                self.unsupported("INTERSECT without DISTINCT is not supported in BigQuery")
    +685            return f"INTERSECT{' DISTINCT' if expression.args.get('distinct') else ' ALL'}"
     
    @@ -3361,8 +3373,8 @@ Default: True
    -
    688        def with_properties(self, properties: exp.Properties) -> str:
    -689            return self.properties(properties, prefix=self.seg("OPTIONS"))
    +            
    687        def with_properties(self, properties: exp.Properties) -> str:
    +688            return self.properties(properties, prefix=self.seg("OPTIONS"))
     
    @@ -3380,11 +3392,11 @@ Default: True
    -
    691        def version_sql(self, expression: exp.Version) -> str:
    -692            if expression.name == "TIMESTAMP":
    -693                expression = expression.copy()
    -694                expression.set("this", "SYSTEM_TIME")
    -695            return super().version_sql(expression)
    +            
    690        def version_sql(self, expression: exp.Version) -> str:
    +691            if expression.name == "TIMESTAMP":
    +692                expression = expression.copy()
    +693                expression.set("this", "SYSTEM_TIME")
    +694            return super().version_sql(expression)
     
    @@ -3451,26 +3463,26 @@ Default: True
    -
    257    @classmethod
    -258    def can_identify(cls, text: str, identify: str | bool = "safe") -> bool:
    -259        """Checks if text can be identified given an identify option.
    -260
    -261        Args:
    -262            text: The text to check.
    -263            identify:
    -264                "always" or `True`: Always returns true.
    -265                "safe": True if the identifier is case-insensitive.
    -266
    -267        Returns:
    -268            Whether or not the given text can be identified.
    -269        """
    -270        if identify is True or identify == "always":
    -271            return True
    -272
    -273        if identify == "safe":
    -274            return not cls.case_sensitive(text)
    +            
    260    @classmethod
    +261    def can_identify(cls, text: str, identify: str | bool = "safe") -> bool:
    +262        """Checks if text can be identified given an identify option.
    +263
    +264        Args:
    +265            text: The text to check.
    +266            identify:
    +267                "always" or `True`: Always returns true.
    +268                "safe": True if the identifier is case-insensitive.
    +269
    +270        Returns:
    +271            Whether or not the given text can be identified.
    +272        """
    +273        if identify is True or identify == "always":
    +274            return True
     275
    -276        return False
    +276        if identify == "safe":
    +277            return not cls.case_sensitive(text)
    +278
    +279        return False
     
    -- cgit v1.2.3