From 145eb08a24b3f362cdf0f473cd094eafe0769841 Mon Sep 17 00:00:00 2001 From: s5dsn-eqee Date: Mon, 19 Oct 2026 13:19:30 +0200 Subject: [PATCH 1/2] ClickHouse: Allow $ inside identifiers --- .../clickhouse_dollar_identifier.sql | 1 + src/dialect/clickhouse.rs | 4 +++- tests/sqlparser_clickhouse.rs | 24 +++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 fuzz/fuzz_seeds/clickhouse_dollar_identifier.sql diff --git a/fuzz/fuzz_seeds/clickhouse_dollar_identifier.sql b/fuzz/fuzz_seeds/clickhouse_dollar_identifier.sql new file mode 100644 index 0000000000..c9ab8dde80 --- /dev/null +++ b/fuzz/fuzz_seeds/clickhouse_dollar_identifier.sql @@ -0,0 +1 @@ +SELECT a$b, c$$, d$e$, $$text$$, $tag$text$tag$ FROM t$1 WHERE x$y = 1 \ No newline at end of file diff --git a/src/dialect/clickhouse.rs b/src/dialect/clickhouse.rs index d256da28ea..f07e98e13c 100644 --- a/src/dialect/clickhouse.rs +++ b/src/dialect/clickhouse.rs @@ -29,7 +29,9 @@ impl Dialect for ClickHouseDialect { } fn is_identifier_part(&self, ch: char) -> bool { - self.is_identifier_start(ch) || ch.is_ascii_digit() + // Undocumented, but the server accepts `$` in identifiers for MySQL compatibility + // since https://github.com/ClickHouse/ClickHouse/pull/14232 + self.is_identifier_start(ch) || ch.is_ascii_digit() || ch == '$' } /// ClickHouse accepts both backticks and double quotes, but its own diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 39e00bf38f..5fc00a1300 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -2366,6 +2366,30 @@ fn parse_update_on_cluster_and_settings() { ); } +#[test] +fn parse_dollar_in_identifier() { + let select = clickhouse_and_generic().verified_only_select("SELECT a$b, c$$, d$e$ FROM t"); + assert_eq!( + select.projection, + vec![ + UnnamedExpr(Identifier(Ident::new("a$b"))), + UnnamedExpr(Identifier(Ident::new("c$$"))), + UnnamedExpr(Identifier(Ident::new("d$e$"))), + ] + ); + // Dollar-quoted strings still take precedence at the start of a token. + clickhouse_and_generic().verified_stmt("SELECT $$abc$$, $tag$abc$tag$"); + // Tokenizes like ClickHouse does; only the parser rejects it. + let err = clickhouse() + .parse_sql_statements("INSERT I$$$$$$$$$$$$$$$$$$$$$$$$$$") + .unwrap_err(); + assert!(matches!(err, ParserError(_)), "{err}"); + + all_dialects_where(|d| !d.is_identifier_part('$')) + .parse_sql_statements("SELECT a$b FROM t") + .unwrap_err(); +} + fn clickhouse() -> TestedDialects { TestedDialects::new(vec![Box::new(ClickHouseDialect {})]) } From 6291da9c4eee5d6b70694e5c555e7afcea1e1ba3 Mon Sep 17 00:00:00 2001 From: "Dmitri B." Date: Mon, 19 Oct 2026 17:45:14 +0200 Subject: [PATCH 2/2] Rename clickhouse_dollar_identifier.sql to clickhouse_2686_dollar_identifier.sql --- ...lar_identifier.sql => clickhouse_2686_dollar_identifier.sql} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename fuzz/fuzz_seeds/{clickhouse_dollar_identifier.sql => clickhouse_2686_dollar_identifier.sql} (90%) diff --git a/fuzz/fuzz_seeds/clickhouse_dollar_identifier.sql b/fuzz/fuzz_seeds/clickhouse_2686_dollar_identifier.sql similarity index 90% rename from fuzz/fuzz_seeds/clickhouse_dollar_identifier.sql rename to fuzz/fuzz_seeds/clickhouse_2686_dollar_identifier.sql index c9ab8dde80..a500e47c3f 100644 --- a/fuzz/fuzz_seeds/clickhouse_dollar_identifier.sql +++ b/fuzz/fuzz_seeds/clickhouse_2686_dollar_identifier.sql @@ -1 +1 @@ -SELECT a$b, c$$, d$e$, $$text$$, $tag$text$tag$ FROM t$1 WHERE x$y = 1 \ No newline at end of file +SELECT a$b, c$$, d$e$, $$text$$, $tag$text$tag$ FROM t$1 WHERE x$y = 1