Repository: tobymao/sqlglot

Issue:
duckdb unnest to snowflake generates incorrect code
**Fully reproducible code snippet**

```
In [7]: import sqlglot as sg

In [8]: sg.__version__
Out[8]: '18.0.0'

In [9]: sg.parse_one("select unnest([1, 2, 3])", read="duckdb").sql(dialect="snowflake")
Out[9]: 'SELECT EXPLODE([1, 2, 3])'
```

As far as I can tell, `EXPLODE` is not a function that exists in Snowflake.

Array flattening operations are done with their `FLATTEN` function

**Official Documentation**

https://docs.snowflake.com/en/sql-reference/functions/flatten



Discussion:
It seems like `select unnest([1, 2, 3])` is equivalent to `select value from table(flatten(input => [1, 2, 3]))`, so perhaps this needs a two-step process to fix:

- Use something like the unnest-to-cross-join transformation (i.e. this might require #2227 to be solved first)
- Convert `unnest(x)` to `table(flatten(input => x))` @ generation time

Resolving patch:
diff --git a/sqlglot/dialects/snowflake.py b/sqlglot/dialects/snowflake.py
--- a/sqlglot/dialects/snowflake.py
+++ b/sqlglot/dialects/snowflake.py
@@ -407,7 +407,9 @@ class Generator(generator.Generator):
             exp.Min: min_or_least,
             exp.PartitionedByProperty: lambda self, e: f"PARTITION BY {self.sql(e, 'this')}",
             exp.RegexpILike: _regexpilike_sql,
-            exp.Select: transforms.preprocess([transforms.eliminate_distinct_on]),
+            exp.Select: transforms.preprocess(
+                [transforms.eliminate_distinct_on, transforms.explode_to_unnest]
+            ),
             exp.StarMap: rename_func("OBJECT_CONSTRUCT"),
             exp.StartsWith: rename_func("STARTSWITH"),
             exp.StrPosition: lambda self, e: self.func(
@@ -449,6 +451,16 @@ class Generator(generator.Generator):
             exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
         }
 
+        def unnest_sql(self, expression: exp.Unnest) -> str:
+            subquery = exp.Subquery(
+                this=exp.select("value").from_(
+                    f"TABLE(FLATTEN(INPUT => {self.sql(expression.expressions[0])}))"
+                ),
+            )
+            alias = self.sql(expression, "alias")
+            alias = f" AS {alias}" if alias else ""
+            return f"{self.sql(subquery)}{alias}"
+
         def show_sql(self, expression: exp.Show) -> str:
             scope = self.sql(expression, "scope")
             scope = f" {scope}" if scope else ""


Test patch:
diff --git a/tests/dialects/test_duckdb.py b/tests/dialects/test_duckdb.py
--- a/tests/dialects/test_duckdb.py
+++ b/tests/dialects/test_duckdb.py
@@ -61,6 +61,13 @@ def test_duckdb(self):
         self.validate_all("0x1010", write={"": "0 AS x1010"})
         self.validate_all("x ~ y", write={"duckdb": "REGEXP_MATCHES(x, y)"})
         self.validate_all("SELECT * FROM 'x.y'", write={"duckdb": 'SELECT * FROM "x.y"'})
+        self.validate_all(
+            "SELECT UNNEST([1, 2, 3])",
+            write={
+                "duckdb": "SELECT UNNEST([1, 2, 3])",
+                "snowflake": "SELECT col FROM (SELECT value FROM TABLE(FLATTEN(INPUT => [1, 2, 3]))) AS _u(col)",
+            },
+        )
         self.validate_all(
             "VAR_POP(x)",
             read={