Repository: tobymao/sqlglot

Issue:
CopyInto with Named File Format in Snowflake hanging on parse
Related to:
#3434 & #3604

When a named `FILE_FORMAT` is provided in the `COPY INTO ...` query, it seems as if the Parser is hanging infinitely, without raising any error. 

Example:
```python
from sqlglot import parse_one

query = """
    COPY INTO MY_DATABASE.MY_SCHEMA.MY_TABLE
    FROM @MY_DATABASE.MY_SCHEMA.MY_STAGE/my_path
    FILE_FORMAT=(FORMAT_NAME = MY_DATABASE.MY_SCHEMA.MY_FILE_FORMAT)
"""
# will remain on this line
parsed = parse_one(query, dialect='snowflake')
```

If commenting out the file format, the parse result is as expected.

Docs: https://docs.snowflake.com/en/sql-reference/sql/copy-into-table

Hints from issue comments:

Solution patch:
diff --git a/sqlglot/dialects/dialect.py b/sqlglot/dialects/dialect.py
index 97dfe6350f..d7fed7cdcc 100644
--- a/sqlglot/dialects/dialect.py
+++ b/sqlglot/dialects/dialect.py
@@ -1836,7 +1836,7 @@ def groupconcat_sql(
     on_overflow: bool = False,
 ) -> str:
     this = expression.this
-    separator = expression.args.get("separator") or exp.Literal.string(sep)
+    separator = self.sql(expression, "separator") or exp.Literal.string(sep)
 
     on_overflow_sql = self.sql(expression, "on_overflow")
     on_overflow_sql = f" ON OVERFLOW {on_overflow_sql}" if (on_overflow and on_overflow_sql) else ""
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index fc844c5179..199bf72a92 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -7897,10 +7897,9 @@ def _parse_wrapped_options(self) -> t.List[t.Optional[exp.Expression]]:
         opts: t.List[t.Optional[exp.Expression]] = []
         while self._curr and not self._match(TokenType.R_PAREN):
             if self._match_text_seq("FORMAT_NAME", "="):
-                # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL,
-                # so we parse it separately to use _parse_field()
+                # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL
                 prop = self.expression(
-                    exp.Property, this=exp.var("FORMAT_NAME"), value=self._parse_field()
+                    exp.Property, this=exp.var("FORMAT_NAME"), value=self._parse_table_parts()
                 )
                 opts.append(prop)
             else:


Test patch:
diff --git a/tests/dialects/test_bigquery.py b/tests/dialects/test_bigquery.py
index ec0c5f2975..ad654a9adb 100644
--- a/tests/dialects/test_bigquery.py
+++ b/tests/dialects/test_bigquery.py
@@ -2346,6 +2346,7 @@ def test_string_agg(self):
         self.validate_identity("STRING_AGG(a, ' & ')")
         self.validate_identity("STRING_AGG(DISTINCT a, ' & ')")
         self.validate_identity("STRING_AGG(a, ' & ' ORDER BY LENGTH(a))")
+        self.validate_identity("STRING_AGG(foo, b'|' ORDER BY bar)")
 
         self.validate_identity("STRING_AGG(a)", "STRING_AGG(a, ',')")
         self.validate_identity(
diff --git a/tests/dialects/test_snowflake.py b/tests/dialects/test_snowflake.py
index cc4e6257da..7957d159c4 100644
--- a/tests/dialects/test_snowflake.py
+++ b/tests/dialects/test_snowflake.py
@@ -2326,6 +2326,9 @@ def test_copy(self):
         self.validate_identity(
             """COPY INTO @my_stage/result/data FROM (SELECT * FROM orderstiny) FILE_FORMAT = (TYPE='csv')"""
         )
+        self.validate_identity(
+            """COPY INTO MY_DATABASE.MY_SCHEMA.MY_TABLE FROM @MY_DATABASE.MY_SCHEMA.MY_STAGE/my_path FILE_FORMAT = (FORMAT_NAME=MY_DATABASE.MY_SCHEMA.MY_FILE_FORMAT)"""
+        )
         self.validate_all(
             """COPY INTO 's3://example/data.csv'
     FROM EXTRA.EXAMPLE.TABLE