Repository: tobymao/sqlglot

Issue:
ClickHouse dialect does not support Map type
**Fully reproducible code snippet**

```
import sqlglot

sql = "INSERT INTO tab VALUES ({'key1':1, 'key2':10}), ({'key1':2,'key2':20}), ({'key1':3,'key2':30});"

ast = sqlglot.parse_one(sql, read="clickhouse") # fails
# sqlglot.errors.ParseError: Expecting a placeholder type or 'Identifier' for tables. Line 1, Col: 33.
#   INSERT INTO tab VALUES ({'key1':1, 'key2':10}), ({'key1':2,'key2':20}), ({'key1':3,'key2':30});

# WORKAROUND
ast = sqlglot.parse_one(sql, read="postgres")

ast.sql(dialect="clickhouse")
# 'INSERT INTO tab VALUES (STRUCT(1 AS key1, 10 AS key2)), (STRUCT(2 AS key1, 20 AS key2)), (STRUCT(3 AS key1, 30 AS key2))'
```

Expectation: round-trip works

**Official Documentation**

https://clickhouse.com/docs/en/sql-reference/data-types/map

Discussion:

Patch:
diff --git a/sqlglot/dialects/clickhouse.py b/sqlglot/dialects/clickhouse.py
index 7fc08d3f..9e7086f1 100644
--- a/sqlglot/dialects/clickhouse.py
+++ b/sqlglot/dialects/clickhouse.py
@@ -565,6 +565,8 @@ class ClickHouse(Dialect):
             Parse a placeholder expression like SELECT {abc: UInt32} or FROM {table: Identifier}
             https://clickhouse.com/docs/en/sql-reference/syntax#defining-and-using-query-parameters
             """
+            index = self._index
+
             this = self._parse_id_var()
             self._match(TokenType.COLON)
             kind = self._parse_types(check_func=False, allow_identifiers=False) or (
@@ -572,12 +574,32 @@ class ClickHouse(Dialect):
             )
 
             if not kind:
-                self.raise_error("Expecting a placeholder type or 'Identifier' for tables")
+                self._retreat(index)
+                return None
             elif not self._match(TokenType.R_BRACE):
                 self.raise_error("Expecting }")
 
             return self.expression(exp.Placeholder, this=this, kind=kind)
 
+        def _parse_bracket(
+            self, this: t.Optional[exp.Expression] = None
+        ) -> t.Optional[exp.Expression]:
+            l_brace = self._match(TokenType.L_BRACE, advance=False)
+            bracket = super()._parse_bracket(this)
+
+            if l_brace and isinstance(bracket, exp.Struct):
+                varmap = exp.VarMap(keys=exp.Array(), values=exp.Array())
+                for expression in bracket.expressions:
+                    if not isinstance(expression, exp.PropertyEQ):
+                        break
+
+                    varmap.args["keys"].append("expressions", exp.Literal.string(expression.name))
+                    varmap.args["values"].append("expressions", expression.expression)
+
+                return varmap
+
+            return bracket
+
         def _parse_in(self, this: t.Optional[exp.Expression], is_global: bool = False) -> exp.In:
             this = super()._parse_in(this)
             this.set("is_global", is_global)


Test patch:
diff --git a/tests/dialects/test_clickhouse.py b/tests/dialects/test_clickhouse.py
index 19b3ce39..d3d363ea 100644
--- a/tests/dialects/test_clickhouse.py
+++ b/tests/dialects/test_clickhouse.py
@@ -154,6 +154,10 @@ class TestClickhouse(Validator):
         self.validate_identity(
             "CREATE TABLE t (foo String CODEC(LZ4HC(9), ZSTD, DELTA), size String ALIAS formatReadableSize(size_bytes), INDEX idx1 a TYPE bloom_filter(0.001) GRANULARITY 1, INDEX idx2 a TYPE set(100) GRANULARITY 2, INDEX idx3 a TYPE minmax GRANULARITY 3)"
         )
+        self.validate_identity(
+            "INSERT INTO tab VALUES ({'key1': 1, 'key2': 10}), ({'key1': 2, 'key2': 20}), ({'key1': 3, 'key2': 30})",
+            "INSERT INTO tab VALUES (map('key1', 1, 'key2', 10)), (map('key1', 2, 'key2', 20)), (map('key1', 3, 'key2', 30))",
+        )
         self.validate_identity(
             "SELECT (toUInt8('1') + toUInt8('2')) IS NOT NULL",
             "SELECT NOT ((toUInt8('1') + toUInt8('2')) IS NULL)",