Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 0 additions & 1 deletion src/ast/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand Down
25 changes: 17 additions & 8 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2771,7 +2771,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_duckdb_and_clickhouse_struct_literal();
}

self.expected("an expression", token)
Expand Down Expand Up @@ -3147,7 +3147,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:
///
Expand All @@ -3156,18 +3156,21 @@ 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_duckdb_and_clickhouse_struct_literal(&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_duckdb_and_clickhouse_struct_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
///
Expand All @@ -3176,7 +3179,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_duckdb_and_clickhouse_struct_field(&mut self) -> Result<DictionaryField, ParserError> {
let key = self.parse_identifier()?;

self.expect_token(&Token::Colon)?;
Expand Down Expand Up @@ -11190,7 +11194,12 @@ 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 = if p.peek_token_ref().token == Token::LBrace {
p.parse_duckdb_and_clickhouse_struct_literal()?
} else {
Expr::Value(p.parse_value()?)
};
Ok(Setting { key, value })
})?;
Some(key_values)
Expand Down
5 changes: 5 additions & 0 deletions src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: &str) -> Value {
Value::SingleQuotedString(s.to_string())
}

pub fn table_alias(name: impl Into<String>) -> Option<TableAlias> {
Some(TableAlias {
name: Ident::new(name),
Expand Down
133 changes: 101 additions & 32 deletions tests/sqlparser_clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -965,38 +965,107 @@ 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: a value, found: EOF",
),
("SELECT * FROM t SETTINGS a=1, b", "Expected: =, found: EOF"),
(
"SELECT * FROM t SETTINGS a=1, b=",
"Expected: a value, found: EOF",
),
(
"SELECT * FROM t SETTINGS a=1, b=c",
"Expected: a concrete value, found: c",
),
(
"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]
Expand Down Expand Up @@ -1550,11 +1619,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")),
},
]),
},
Expand All @@ -1575,11 +1644,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")),
},
]),
},
Expand Down
Loading