Repository: tobymao/sqlglot

Issue:
BigQuery `SELECT AS STRUCT` fails to parse
Reproducer:

```python
import sqlglot
sqlglot.transpile("SELECT AS STRUCT 1 a, 2 b", read="bigquery")
```

> sqlglot.errors.ParseError: Required keyword: 'this' missing for <class 'sqlglot.expressions.Alias'>. Line 1, Col: 18.
  SELECT AS STRUCT 1 a, 2 b

Note that the more general `SELECT AS VALUE` has the same issue.

Ref: https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#select_as_struct
Ref: https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#select_as_value


Discussion:

Resolving patch:
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -2058,6 +2058,7 @@ class Lock(Expression):
 class Select(Subqueryable):
     arg_types = {
         "with": False,
+        "kind": False,
         "expressions": False,
         "hint": False,
         "distinct": False,
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -1323,6 +1323,8 @@ def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str:
         )
 
     def select_sql(self, expression: exp.Select) -> str:
+        kind = expression.args.get("kind")
+        kind = f" AS {kind}" if kind else ""
         hint = self.sql(expression, "hint")
         distinct = self.sql(expression, "distinct")
         distinct = f" {distinct}" if distinct else ""
@@ -1330,7 +1332,7 @@ def select_sql(self, expression: exp.Select) -> str:
         expressions = f"{self.sep()}{expressions}" if expressions else expressions
         sql = self.query_modifiers(
             expression,
-            f"SELECT{hint}{distinct}{expressions}",
+            f"SELECT{kind}{hint}{distinct}{expressions}",
             self.sql(expression, "into", comment=False),
             self.sql(expression, "from", comment=False),
         )
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -1704,6 +1704,11 @@ def _parse_select(
         elif self._match(TokenType.SELECT):
             comments = self._prev_comments
 
+            kind = (
+                self._match(TokenType.ALIAS)
+                and self._match_texts(("STRUCT", "VALUE"))
+                and self._prev.text
+            )
             hint = self._parse_hint()
             all_ = self._match(TokenType.ALL)
             distinct = self._match(TokenType.DISTINCT)
@@ -1722,6 +1727,7 @@ def _parse_select(
 
             this = self.expression(
                 exp.Select,
+                kind=kind,
                 hint=hint,
                 distinct=distinct,
                 expressions=expressions,


Test patch:
diff --git a/tests/dialects/test_bigquery.py b/tests/dialects/test_bigquery.py
--- a/tests/dialects/test_bigquery.py
+++ b/tests/dialects/test_bigquery.py
@@ -6,6 +6,8 @@ class TestBigQuery(Validator):
     dialect = "bigquery"
 
     def test_bigquery(self):
+        self.validate_identity("SELECT AS STRUCT 1 AS a, 2 AS b")
+        self.validate_identity("SELECT AS VALUE STRUCT(1 AS a, 2 AS b)")
         self.validate_identity("SELECT STRUCT<ARRAY<STRING>>(['2023-01-17'])")
         self.validate_identity("SELECT * FROM q UNPIVOT(values FOR quarter IN (b, c))")
         self.validate_identity(
@@ -13,6 +15,12 @@ def test_bigquery(self):
         )
 
         self.validate_all("LEAST(x, y)", read={"sqlite": "MIN(x, y)"})
+        self.validate_all(
+            "SELECT ARRAY(SELECT AS STRUCT 1 a, 2 b)",
+            write={
+                "bigquery": "SELECT ARRAY(SELECT AS STRUCT 1 AS a, 2 AS b)",
+            },
+        )
         self.validate_all(
             "REGEXP_CONTAINS('foo', '.*')",
             read={"bigquery": "REGEXP_CONTAINS('foo', '.*')"},