Repository: tobymao/sqlglot

Issue:
Clickhouse INSERT INTO FUNCTION s3 raises ParseError 
**Fully reproducible code snippet**
```
import unittest

from sqlglot import parse_one
from sqlglot.dialects import ClickHouse


class TestClickhouseInsertIntoS3Select(unittest.TestCase):
    def test_parse_one_insert_into_s3_select(self):
        sql = """
              INSERT INTO FUNCTION s3('https://clickhouse-public-datasets.s3.amazonaws.com/my-test-bucket-768/test-data.csv.gz', 'CSV', 'name String, value UInt32', 'gzip')
              SELECT name, value FROM existing_table;
              """
        ast = parse_one(sql=sql, dialect=ClickHouse)
        self.assertIsNotNone(ast)


if __name__ == '__main__':
    unittest.main()
```

**Exception**
```
sqlglot.errors.ParseError: Invalid expression / Unexpected token. Line 2, Col: 31.

        INSERT INTO FUNCTION s3('https://clickhouse-public-datasets.s3.amazonaws.com/my-test-bucket-768/test-data.csv.gz', 'CSV', ' 
```

**Official Documentation**
        
SQL statement example taken from: https://clickhouse.com/docs/en/sql-reference/table-functions/s3 - 

> Insert data into file test-data.csv.gz from existing table

Thanks in advance.


Discussion:

Resolving patch:
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -1931,6 +1931,7 @@ class Insert(DDL, DML):
     arg_types = {
         "hint": False,
         "with": False,
+        "is_function": False,
         "this": True,
         "expression": False,
         "conflict": False,
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -1512,7 +1512,9 @@ def insert_sql(self, expression: exp.Insert) -> str:
         alternative = expression.args.get("alternative")
         alternative = f" OR {alternative}" if alternative else ""
         ignore = " IGNORE" if expression.args.get("ignore") else ""
-
+        is_function = expression.args.get("is_function")
+        if is_function:
+            this = f"{this} FUNCTION"
         this = f"{this} {self.sql(expression, 'this')}"
 
         exists = " IF EXISTS" if expression.args.get("exists") else ""
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -2185,7 +2185,9 @@ def _parse_insert(self) -> exp.Insert:
             self._match(TokenType.INTO)
             comments += ensure_list(self._prev_comments)
             self._match(TokenType.TABLE)
-            this = self._parse_table(schema=True)
+            is_function = self._match(TokenType.FUNCTION)
+
+            this = self._parse_table(schema=True) if not is_function else self._parse_function()
 
         returning = self._parse_returning()
 
@@ -2193,6 +2195,7 @@ def _parse_insert(self) -> exp.Insert:
             exp.Insert,
             comments=comments,
             hint=hint,
+            is_function=is_function,
             this=this,
             by_name=self._match_text_seq("BY", "NAME"),
             exists=self._parse_exists(),


Test patch:
diff --git a/tests/dialects/test_clickhouse.py b/tests/dialects/test_clickhouse.py
--- a/tests/dialects/test_clickhouse.py
+++ b/tests/dialects/test_clickhouse.py
@@ -390,6 +390,17 @@ def test_clickhouse(self):
         )
         self.validate_identity("SYSTEM STOP MERGES foo.bar", check_command_warning=True)
 
+        self.validate_identity(
+            "INSERT INTO FUNCTION s3('url', 'CSV', 'name String, value UInt32', 'gzip') SELECT name, value FROM existing_table"
+        )
+        self.validate_identity(
+            "INSERT INTO FUNCTION remote('localhost', default.simple_table) VALUES (100, 'inserted via remote()')"
+        )
+        self.validate_identity(
+            """INSERT INTO TABLE FUNCTION hdfs('hdfs://hdfs1:9000/test', 'TSV', 'name String, column2 UInt32, column3 UInt32') VALUES ('test', 1, 2)""",
+            """INSERT INTO FUNCTION hdfs('hdfs://hdfs1:9000/test', 'TSV', 'name String, column2 UInt32, column3 UInt32') VALUES ('test', 1, 2)""",
+        )
+
     def test_cte(self):
         self.validate_identity("WITH 'x' AS foo SELECT foo")
         self.validate_identity("WITH ['c'] AS field_names SELECT field_names")