Repository: tobymao/sqlglot
Issue category: Bug Report

Issue:
User defined function recognized as column (Snowflake dialect)
This query:
`select COL1,COL2 from some_table,TABLE(SOME_DB.SOME_SCHEMA.TABLE_FUNC(value1, value2) over (PARTITION BY value1))`
`p = sqlglot.parse_one(query, dialect=sqlglot.Dialects.SNOWFLAKE)`
creates this ast:
`Select(
  expressions=[
    Column(
      this=Identifier(this=COL1, quoted=False)),
    Column(
      this=Identifier(this=COL2, quoted=False))],
  from=From(
    this=Table(
      this=Identifier(this=some_table, quoted=False))),
  joins=[
    Join(
      this=Table(
        this=Anonymous(
          this=TABLE,
          expressions=[
            Column(
              this=Window(
                this=Anonymous(
                  this=TABLE_FUNC,
                  expressions=[
                    Column(
                      this=Identifier(this=value1, quoted=False)),
                    Column(
                      this=Identifier(this=value2, quoted=False))]),
                partition_by=[
                  Column(
                    this=Identifier(this=value1, quoted=False))],
                over=OVER),
              table=Identifier(this=SOME_SCHEMA, quoted=False),
              db=Identifier(this=SOME_DB, quoted=False))])))])`

Inside the anonymous TABLE (which is also a function that returns values as a table you can select on) we can see the expressions contain only one expression which should be another function but is recognized as a Column.

Verified this still happens on 25.31.4.
dialect: Snowflake


Hints:

Patch:
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index 5fa7d1ef13..6e7b21a6cc 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -5109,9 +5109,8 @@ def _parse_column_ops(self, this: t.Optional[exp.Expression]) -> t.Optional[exp.
             else:
                 field = self._parse_field(any_token=True, anonymous_func=True)
 
-            if isinstance(field, exp.Func) and this:
-                # bigquery allows function calls like x.y.count(...)
-                # SAFE.SUBSTR(...)
+            if isinstance(field, (exp.Func, exp.Window)) and this:
+                # BQ & snowflake allow function calls like x.y.count(...), SAFE.SUBSTR(...) etc
                 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules
                 this = exp.replace_tree(
                     this,
@@ -5135,6 +5134,11 @@ def _parse_column_ops(self, this: t.Optional[exp.Expression]) -> t.Optional[exp.
                     db=this.args.get("table"),
                     catalog=this.args.get("db"),
                 )
+            elif isinstance(field, exp.Window):
+                # Move the exp.Dot's to the window's function
+                window_func = self.expression(exp.Dot, this=this, expression=field.this)
+                field.set("this", window_func)
+                this = field
             else:
                 this = self.expression(exp.Dot, this=this, expression=field)
 


Test patch:
diff --git a/tests/dialects/test_snowflake.py b/tests/dialects/test_snowflake.py
index 515a07c4f4..157947df46 100644
--- a/tests/dialects/test_snowflake.py
+++ b/tests/dialects/test_snowflake.py
@@ -2250,3 +2250,13 @@ def test_grant(self):
         self.validate_identity(
             "GRANT ALL PRIVILEGES ON FUNCTION mydb.myschema.ADD5(number) TO ROLE analyst"
         )
+
+    def test_window_function_arg(self):
+        query = "SELECT * FROM TABLE(db.schema.FUNC(a) OVER ())"
+
+        ast = self.parse_one(query)
+        window = ast.find(exp.Window)
+
+        self.assertEqual(ast.sql("snowflake"), query)
+        self.assertEqual(len(list(ast.find_all(exp.Column))), 1)
+        self.assertEqual(window.this.sql("snowflake"), "db.schema.FUNC(a)")