This is an automated email from the ASF dual-hosted git repository.

iffyio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 50c605a4 Support for Map values in ClickHouse settings (#1896)
50c605a4 is described below

commit 50c605a47138b1602575731c196d04b0d280ad3d
Author: Sergey Olontsev <ser...@olontsev.io>
AuthorDate: Sat Jun 28 07:13:11 2025 +0100

    Support for Map values in ClickHouse settings (#1896)
    
    Co-authored-by: Ifeanyi Ubah <ify1...@yahoo.com>
---
 src/ast/query.rs              |   2 +-
 src/ast/value.rs              |   1 -
 src/parser/mod.rs             |  17 +++---
 src/test_utils.rs             |   5 ++
 tests/sqlparser_clickhouse.rs | 130 +++++++++++++++++++++++++++++++-----------
 5 files changed, 112 insertions(+), 43 deletions(-)

diff --git a/src/ast/query.rs b/src/ast/query.rs
index c79ec110..99cd2ef2 100644
--- a/src/ast/query.rs
+++ b/src/ast/query.rs
@@ -1047,7 +1047,7 @@ impl fmt::Display for ConnectBy {
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
 pub struct Setting {
     pub key: Ident,
-    pub value: Value,
+    pub value: Expr,
 }
 
 impl fmt::Display for Setting {
diff --git a/src/ast/value.rs b/src/ast/value.rs
index 90dbccbf..fdfa6a67 100644
--- a/src/ast/value.rs
+++ b/src/ast/value.rs
@@ -116,7 +116,6 @@ impl From<ValueWithSpan> for Value {
     derive(Visit, VisitMut),
     visit(with = "visit_value")
 )]
-
 pub enum Value {
     /// Numeric literal
     #[cfg(not(feature = "bigdecimal"))]
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 68d89a1e..adf50a8f 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -2770,7 +2770,7 @@ impl<'a> Parser<'a> {
 
         if self.dialect.supports_dictionary_syntax() {
             self.prev_token(); // Put back the '{'
-            return self.parse_duckdb_struct_literal();
+            return self.parse_dictionary();
         }
 
         self.expected("an expression", token)
@@ -3139,7 +3139,7 @@ impl<'a> Parser<'a> {
         Ok(fields)
     }
 
-    /// DuckDB specific: Parse a duckdb [dictionary]
+    /// DuckDB and ClickHouse specific: Parse a duckdb [dictionary] or a 
clickhouse [map] setting
     ///
     /// Syntax:
     ///
@@ -3148,18 +3148,18 @@ impl<'a> Parser<'a> {
     /// ```
     ///
     /// [dictionary]: 
https://duckdb.org/docs/sql/data_types/struct#creating-structs
-    fn parse_duckdb_struct_literal(&mut self) -> Result<Expr, ParserError> {
+    /// [map]: 
https://clickhouse.com/docs/operations/settings/settings#additional_table_filters
+    fn parse_dictionary(&mut self) -> Result<Expr, ParserError> {
         self.expect_token(&Token::LBrace)?;
 
-        let fields =
-            self.parse_comma_separated0(Self::parse_duckdb_dictionary_field, 
Token::RBrace)?;
+        let fields = self.parse_comma_separated0(Self::parse_dictionary_field, 
Token::RBrace)?;
 
         self.expect_token(&Token::RBrace)?;
 
         Ok(Expr::Dictionary(fields))
     }
 
-    /// Parse a field for a duckdb [dictionary]
+    /// Parse a field for a duckdb [dictionary] or a clickhouse [map] setting
     ///
     /// Syntax
     ///
@@ -3168,7 +3168,8 @@ impl<'a> Parser<'a> {
     /// ```
     ///
     /// [dictionary]: 
https://duckdb.org/docs/sql/data_types/struct#creating-structs
-    fn parse_duckdb_dictionary_field(&mut self) -> Result<DictionaryField, 
ParserError> {
+    /// [map]: 
https://clickhouse.com/docs/operations/settings/settings#additional_table_filters
+    fn parse_dictionary_field(&mut self) -> Result<DictionaryField, 
ParserError> {
         let key = self.parse_identifier()?;
 
         self.expect_token(&Token::Colon)?;
@@ -11216,7 +11217,7 @@ impl<'a> Parser<'a> {
             let key_values = self.parse_comma_separated(|p| {
                 let key = p.parse_identifier()?;
                 p.expect_token(&Token::Eq)?;
-                let value = p.parse_value()?.value;
+                let value = p.parse_expr()?;
                 Ok(Setting { key, value })
             })?;
             Some(key_values)
diff --git a/src/test_utils.rs b/src/test_utils.rs
index c7965c3f..db7b3dd6 100644
--- a/src/test_utils.rs
+++ b/src/test_utils.rs
@@ -366,6 +366,11 @@ pub fn number(n: &str) -> Value {
     Value::Number(n.parse().unwrap(), false)
 }
 
+/// Creates a [Value::SingleQuotedString]
+pub fn single_quoted_string(s: impl Into<String>) -> Value {
+    Value::SingleQuotedString(s.into())
+}
+
 pub fn table_alias(name: impl Into<String>) -> Option<TableAlias> {
     Some(TableAlias {
         name: Ident::new(name),
diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs
index 0a60c9c4..0288c6d2 100644
--- a/tests/sqlparser_clickhouse.rs
+++ b/tests/sqlparser_clickhouse.rs
@@ -28,7 +28,7 @@ use test_utils::*;
 use sqlparser::ast::Expr::{BinaryOp, Identifier};
 use sqlparser::ast::SelectItem::UnnamedExpr;
 use sqlparser::ast::TableFactor::Table;
-use sqlparser::ast::Value::Number;
+use sqlparser::ast::Value::Boolean;
 use sqlparser::ast::*;
 use sqlparser::dialect::ClickHouseDialect;
 use sqlparser::dialect::GenericDialect;
@@ -965,38 +965,103 @@ fn parse_limit_by() {
 
 #[test]
 fn parse_settings_in_query() {
-    match clickhouse_and_generic()
-        .verified_stmt(r#"SELECT * FROM t SETTINGS max_threads = 1, 
max_block_size = 10000"#)
-    {
-        Statement::Query(query) => {
-            assert_eq!(
-                query.settings,
-                Some(vec![
-                    Setting {
-                        key: Ident::new("max_threads"),
-                        value: Number("1".parse().unwrap(), false)
-                    },
-                    Setting {
-                        key: Ident::new("max_block_size"),
-                        value: Number("10000".parse().unwrap(), false)
-                    },
-                ])
-            );
+    fn check_settings(sql: &str, expected: Vec<Setting>) {
+        match clickhouse_and_generic().verified_stmt(sql) {
+            Statement::Query(q) => {
+                assert_eq!(q.settings, Some(expected));
+            }
+            _ => unreachable!(),
         }
-        _ => unreachable!(),
+    }
+
+    for (sql, expected_settings) in [
+        (
+            r#"SELECT * FROM t SETTINGS max_threads = 1, max_block_size = 
10000"#,
+            vec![
+                Setting {
+                    key: Ident::new("max_threads"),
+                    value: Expr::value(number("1")),
+                },
+                Setting {
+                    key: Ident::new("max_block_size"),
+                    value: Expr::value(number("10000")),
+                },
+            ],
+        ),
+        (
+            r#"SELECT * FROM t SETTINGS additional_table_filters = {'table_1': 
'x != 2'}"#,
+            vec![Setting {
+                key: Ident::new("additional_table_filters"),
+                value: Expr::Dictionary(vec![DictionaryField {
+                    key: Ident::with_quote('\'', "table_1"),
+                    value: Expr::value(single_quoted_string("x != 2")).into(),
+                }]),
+            }],
+        ),
+        (
+            r#"SELECT * FROM t SETTINGS additional_result_filter = 'x != 2', 
query_plan_optimize_lazy_materialization = false"#,
+            vec![
+                Setting {
+                    key: Ident::new("additional_result_filter"),
+                    value: Expr::value(single_quoted_string("x != 2")),
+                },
+                Setting {
+                    key: 
Ident::new("query_plan_optimize_lazy_materialization"),
+                    value: Expr::value(Boolean(false)),
+                },
+            ],
+        ),
+    ] {
+        check_settings(sql, expected_settings);
     }
 
     let invalid_cases = vec![
-        "SELECT * FROM t SETTINGS a",
-        "SELECT * FROM t SETTINGS a=",
-        "SELECT * FROM t SETTINGS a=1, b",
-        "SELECT * FROM t SETTINGS a=1, b=",
-        "SELECT * FROM t SETTINGS a=1, b=c",
+        ("SELECT * FROM t SETTINGS a", "Expected: =, found: EOF"),
+        (
+            "SELECT * FROM t SETTINGS a=",
+            "Expected: an expression, found: EOF",
+        ),
+        ("SELECT * FROM t SETTINGS a=1, b", "Expected: =, found: EOF"),
+        (
+            "SELECT * FROM t SETTINGS a=1, b=",
+            "Expected: an expression, found: EOF",
+        ),
+        (
+            "SELECT * FROM t SETTINGS a = {",
+            "Expected: identifier, found: EOF",
+        ),
+        (
+            "SELECT * FROM t SETTINGS a = {'b'",
+            "Expected: :, found: EOF",
+        ),
+        (
+            "SELECT * FROM t SETTINGS a = {'b': ",
+            "Expected: an expression, found: EOF",
+        ),
+        (
+            "SELECT * FROM t SETTINGS a = {'b': 'c',}",
+            "Expected: identifier, found: }",
+        ),
+        (
+            "SELECT * FROM t SETTINGS a = {'b': 'c', 'd'}",
+            "Expected: :, found: }",
+        ),
+        (
+            "SELECT * FROM t SETTINGS a = {'b': 'c', 'd': }",
+            "Expected: an expression, found: }",
+        ),
+        (
+            "SELECT * FROM t SETTINGS a = {ANY(b)}",
+            "Expected: :, found: (",
+        ),
     ];
-    for sql in invalid_cases {
-        clickhouse_and_generic()
-            .parse_sql_statements(sql)
-            .expect_err("Expected: SETTINGS key = value, found: ");
+    for (sql, error_msg) in invalid_cases {
+        assert_eq!(
+            clickhouse_and_generic()
+                .parse_sql_statements(sql)
+                .unwrap_err(),
+            ParserError(error_msg.to_string())
+        );
     }
 }
 #[test]
@@ -1550,11 +1615,11 @@ fn parse_select_table_function_settings() {
             settings: Some(vec![
                 Setting {
                     key: "s0".into(),
-                    value: Value::Number("3".parse().unwrap(), false),
+                    value: Expr::value(number("3")),
                 },
                 Setting {
                     key: "s1".into(),
-                    value: Value::SingleQuotedString("s".into()),
+                    value: Expr::value(single_quoted_string("s")),
                 },
             ]),
         },
@@ -1575,11 +1640,11 @@ fn parse_select_table_function_settings() {
             settings: Some(vec![
                 Setting {
                     key: "s0".into(),
-                    value: Value::Number("3".parse().unwrap(), false),
+                    value: Expr::value(number("3")),
                 },
                 Setting {
                     key: "s1".into(),
-                    value: Value::SingleQuotedString("s".into()),
+                    value: Expr::value(single_quoted_string("s")),
                 },
             ]),
         },
@@ -1589,7 +1654,6 @@ fn parse_select_table_function_settings() {
         "SELECT * FROM t(SETTINGS a=)",
         "SELECT * FROM t(SETTINGS a=1, b)",
         "SELECT * FROM t(SETTINGS a=1, b=)",
-        "SELECT * FROM t(SETTINGS a=1, b=c)",
     ];
     for sql in invalid_cases {
         clickhouse_and_generic()


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@datafusion.apache.org
For additional commands, e-mail: commits-h...@datafusion.apache.org

Reply via email to