Skip to content

Commit 0402fd2

Browse files
leiyangyouclaude
andcommitted
feat: add ternary operator support to ClickHouse dialect
Implements the ternary operator (condition ? true_expr : false_expr) for ClickHouse SQL dialect with the following characteristics: - Lower precedence than AND/OR operators - Does NOT support nested ternaries without parentheses (e.g., a ? b : c ? d : e will fail to parse) - Requires explicit parentheses for nesting: a ? b : (c ? d : e) - Matches ClickHouse's actual behavior This implementation avoids recursion issues by not supporting unparenthesized nested ternaries, which aligns with how ClickHouse itself handles these expressions. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f71222a commit 0402fd2

File tree

3 files changed

+216
-0
lines changed

3 files changed

+216
-0
lines changed

crates/lib-dialects/src/clickhouse.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2589,6 +2589,33 @@ pub fn dialect() -> Dialect {
25892589
.to_matchable(),
25902590
);
25912591

2592+
// Add ternary operator support for ClickHouse
2593+
// ClickHouse supports: condition ? true_expr : false_expr
2594+
//
2595+
// IMPORTANT: The ternary operator has LOWER precedence than AND/OR operators.
2596+
// This means `true ? 1 : 0 AND false ? 1 : 0` will fail to parse,
2597+
// while `(true ? 1 : 0) AND (false ? 1 : 0)` parses with explicit grouping.
2598+
//
2599+
// NOTE: This implementation does NOT support nested ternary operators without parentheses.
2600+
// For example, `a ? b : c ? d : e` is not supported and will fail to parse.
2601+
// You must use parentheses: `a ? b : (c ? d : e)` or `(a ? b : c) ? d : e`.
2602+
// This matches ClickHouse's actual behavior where nested ternaries require explicit grouping.
2603+
2604+
clickhouse_dialect.replace_grammar(
2605+
"ExpressionSegment",
2606+
Sequence::new(vec_of_erased![
2607+
Ref::new("Expression_A_Grammar"),
2608+
Sequence::new(vec_of_erased![
2609+
StringParser::new("?", SyntaxKind::QuestionMark),
2610+
Ref::new("Expression_A_Grammar"),
2611+
StringParser::new(":", SyntaxKind::Colon),
2612+
Ref::new("Expression_A_Grammar"),
2613+
])
2614+
.config(|this| this.optional()),
2615+
])
2616+
.to_matchable(),
2617+
);
2618+
25922619
clickhouse_dialect.expand();
25932620
clickhouse_dialect
25942621
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- Simple ternary expression
2+
SELECT 1 = 1 ? 'yes' : 'no' AS result;
3+
4+
-- Ternary with parentheses and AND
5+
SELECT (true ? 1 : 0) AND (false ? 1 : 0) AS ternary_with_and;
6+
7+
-- Ternary with comparison
8+
SELECT x > 5 ? 'greater' : 'not greater' AS comparison_result;
9+
10+
-- Ternary in WHERE with parentheses
11+
SELECT * FROM users WHERE (age >= 18 ? 1 : 0) = 1;
12+
13+
-- Ternary with arithmetic
14+
SELECT 2 + 3 > 4 ? 'yes' : 'no' AS arithmetic_test;
15+
16+
-- Nested ternary
17+
SELECT
18+
score >= 90 ? 'A' :
19+
(score >= 80 ? 'B' :
20+
(score >= 70 ? 'C' : 'F')) AS grade
21+
FROM students;
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
file:
2+
- statement:
3+
- select_statement:
4+
- select_clause:
5+
- keyword: SELECT
6+
- select_clause_element:
7+
- expression:
8+
- numeric_literal: '1'
9+
- comparison_operator:
10+
- raw_comparison_operator: =
11+
- numeric_literal: '1'
12+
- question_mark: '?'
13+
- quoted_literal: '''yes'''
14+
- colon: ':'
15+
- quoted_literal: '''no'''
16+
- alias_expression:
17+
- keyword: AS
18+
- naked_identifier: result
19+
- statement_terminator: ;
20+
- statement:
21+
- select_statement:
22+
- select_clause:
23+
- keyword: SELECT
24+
- select_clause_element:
25+
- expression:
26+
- bracketed:
27+
- start_bracket: (
28+
- expression:
29+
- boolean_literal: 'true'
30+
- question_mark: '?'
31+
- numeric_literal: '1'
32+
- colon: ':'
33+
- numeric_literal: '0'
34+
- end_bracket: )
35+
- binary_operator: AND
36+
- bracketed:
37+
- start_bracket: (
38+
- expression:
39+
- boolean_literal: 'false'
40+
- question_mark: '?'
41+
- numeric_literal: '1'
42+
- colon: ':'
43+
- numeric_literal: '0'
44+
- end_bracket: )
45+
- alias_expression:
46+
- keyword: AS
47+
- naked_identifier: ternary_with_and
48+
- statement_terminator: ;
49+
- statement:
50+
- select_statement:
51+
- select_clause:
52+
- keyword: SELECT
53+
- select_clause_element:
54+
- expression:
55+
- column_reference:
56+
- naked_identifier: x
57+
- comparison_operator:
58+
- raw_comparison_operator: '>'
59+
- numeric_literal: '5'
60+
- question_mark: '?'
61+
- quoted_literal: '''greater'''
62+
- colon: ':'
63+
- quoted_literal: '''not greater'''
64+
- alias_expression:
65+
- keyword: AS
66+
- naked_identifier: comparison_result
67+
- statement_terminator: ;
68+
- statement:
69+
- select_statement:
70+
- select_clause:
71+
- keyword: SELECT
72+
- select_clause_element:
73+
- wildcard_expression:
74+
- wildcard_identifier:
75+
- star: '*'
76+
- from_clause:
77+
- keyword: FROM
78+
- from_expression:
79+
- from_expression_element:
80+
- table_expression:
81+
- table_reference:
82+
- naked_identifier: users
83+
- where_clause:
84+
- keyword: WHERE
85+
- expression:
86+
- bracketed:
87+
- start_bracket: (
88+
- expression:
89+
- column_reference:
90+
- naked_identifier: age
91+
- comparison_operator: '>='
92+
- numeric_literal: '18'
93+
- question_mark: '?'
94+
- numeric_literal: '1'
95+
- colon: ':'
96+
- numeric_literal: '0'
97+
- end_bracket: )
98+
- comparison_operator:
99+
- raw_comparison_operator: =
100+
- numeric_literal: '1'
101+
- statement_terminator: ;
102+
- statement:
103+
- select_statement:
104+
- select_clause:
105+
- keyword: SELECT
106+
- select_clause_element:
107+
- expression:
108+
- numeric_literal: '2'
109+
- binary_operator: +
110+
- numeric_literal: '3'
111+
- comparison_operator:
112+
- raw_comparison_operator: '>'
113+
- numeric_literal: '4'
114+
- question_mark: '?'
115+
- quoted_literal: '''yes'''
116+
- colon: ':'
117+
- quoted_literal: '''no'''
118+
- alias_expression:
119+
- keyword: AS
120+
- naked_identifier: arithmetic_test
121+
- statement_terminator: ;
122+
- statement:
123+
- select_statement:
124+
- select_clause:
125+
- keyword: SELECT
126+
- select_clause_element:
127+
- expression:
128+
- column_reference:
129+
- naked_identifier: score
130+
- comparison_operator: '>='
131+
- numeric_literal: '90'
132+
- question_mark: '?'
133+
- quoted_literal: '''A'''
134+
- colon: ':'
135+
- bracketed:
136+
- start_bracket: (
137+
- expression:
138+
- column_reference:
139+
- naked_identifier: score
140+
- comparison_operator: '>='
141+
- numeric_literal: '80'
142+
- question_mark: '?'
143+
- quoted_literal: '''B'''
144+
- colon: ':'
145+
- bracketed:
146+
- start_bracket: (
147+
- expression:
148+
- column_reference:
149+
- naked_identifier: score
150+
- comparison_operator: '>='
151+
- numeric_literal: '70'
152+
- question_mark: '?'
153+
- quoted_literal: '''C'''
154+
- colon: ':'
155+
- quoted_literal: '''F'''
156+
- end_bracket: )
157+
- end_bracket: )
158+
- alias_expression:
159+
- keyword: AS
160+
- naked_identifier: grade
161+
- from_clause:
162+
- keyword: FROM
163+
- from_expression:
164+
- from_expression_element:
165+
- table_expression:
166+
- table_reference:
167+
- naked_identifier: students
168+
- statement_terminator: ;

0 commit comments

Comments
 (0)