Skip to content

Commit dacdbc9

Browse files
authored
ClickHouse: Support GLOBAL IN / GLOBAL NOT IN (#2598)
1 parent e790703 commit dacdbc9

8 files changed

Lines changed: 246 additions & 9 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
SELECT * FROM t WHERE x GLOBAL IN (1, 2);
2+
SELECT * FROM t WHERE x GLOBAL NOT IN (1, 2);
3+
SELECT * FROM t WHERE x GLOBAL IN (SELECT y FROM t2);
4+
SELECT * FROM t WHERE x GLOBAL NOT IN (SELECT y FROM t2);
5+
SELECT * FROM t WHERE x GLOBAL IN t2;
6+
SELECT * FROM t WHERE x GLOBAL IN (t2);
7+
SELECT * FROM t WHERE x GLOBAL IN db.t2;
8+
SELECT * FROM t WHERE x GLOBAL IN t2 AND y = 2;
9+
SELECT * FROM t WHERE x global in (1) AND y Global Not In (2);
10+
SELECT * FROM t WHERE x GLOBAL IN (1);
11+
SELECT * FROM t WHERE x GLOBAL IN (NULL, 1, 'a', 1.5, -1, 1 + 2, y * 2);
12+
SELECT * FROM t WHERE x GLOBAL IN ('', '''', 'GLOBAL IN');
13+
SELECT * FROM t WHERE x GLOBAL IN ((1), ((2)), (((3))));
14+
SELECT * FROM t WHERE x GLOBAL IN ((SELECT 1), 2);
15+
SELECT * FROM t WHERE x GLOBAL IN ((SELECT 1));
16+
SELECT * FROM t WHERE x GLOBAL NOT IN (((SELECT 1)), (SELECT 2) + 1);
17+
SELECT * FROM t WHERE x GLOBAL IN (SELECT 1 UNION ALL SELECT 2);
18+
SELECT * FROM t WHERE x GLOBAL IN (SELECT y FROM t2 WHERE z GLOBAL IN (SELECT w FROM t3 WHERE v GLOBAL NOT IN (1)));
19+
SELECT * FROM t WHERE (a, b) GLOBAL IN ((1, 2), (3, 4));
20+
SELECT * FROM t WHERE (a, b) GLOBAL NOT IN (SELECT a, b FROM t2);
21+
SELECT * FROM t WHERE tuple(a, b) GLOBAL IN (SELECT a, b FROM t2);
22+
SELECT * FROM t WHERE (SELECT 1) GLOBAL IN (1, 2);
23+
SELECT * FROM t WHERE toInt32(x) GLOBAL IN (lower('a'), upper('b'));
24+
SELECT * FROM t WHERE CAST(x AS String) GLOBAL IN ('1', '2');
25+
SELECT * FROM t WHERE x GLOBAL IN ([1, 2], [3]);
26+
SELECT * FROM t WHERE arr[1] GLOBAL IN (1, 2);
27+
SELECT * FROM t WHERE tup.1 GLOBAL IN (1, 2);
28+
SELECT * FROM t WHERE NOT x GLOBAL IN (1, 2);
29+
SELECT * FROM t WHERE NOT (x GLOBAL NOT IN (1, 2));
30+
SELECT * FROM t WHERE x GLOBAL IN (1) AND y = 2 OR z GLOBAL NOT IN (3) AND w = 4;
31+
SELECT * FROM t WHERE a = 1 OR x GLOBAL IN (1) AND y BETWEEN 1 AND 2;
32+
SELECT * FROM t WHERE (x GLOBAL IN (1)) IS NULL;
33+
SELECT * FROM t WHERE (x GLOBAL IN (1)) = (y GLOBAL IN (2));
34+
SELECT * FROM t WHERE x GLOBAL IN (1) AND y IN (2) AND z NOT IN (3) AND w GLOBAL NOT IN (4);
35+
SELECT * FROM t WHERE x GLOBAL IN (1) AND EXISTS (SELECT 1 FROM u WHERE u.x GLOBAL IN (1));
36+
SELECT x GLOBAL IN (1, 2) AS flag, x GLOBAL NOT IN (SELECT y FROM t2) FROM t;
37+
SELECT CASE WHEN x GLOBAL IN (1, 2) THEN 'a' WHEN x GLOBAL NOT IN (SELECT y FROM t2) THEN 'b' ELSE 'c' END FROM t;
38+
SELECT if(x GLOBAL IN (1), 1, 0), multiIf(x GLOBAL NOT IN (2), 1, 0) FROM t;
39+
SELECT count() FROM t GROUP BY x GLOBAL IN (1, 2) HAVING count() GLOBAL IN (SELECT c FROM counts);
40+
SELECT * FROM t ORDER BY x GLOBAL IN (1, 2) DESC, y GLOBAL NOT IN (3) ASC LIMIT 10;
41+
SELECT * FROM t PREWHERE x GLOBAL IN (SELECT y FROM t2) WHERE z GLOBAL NOT IN (1);
42+
SELECT * FROM t JOIN u ON t.x = u.x AND u.y GLOBAL IN (SELECT y FROM t2);
43+
SELECT * FROM t LEFT JOIN u ON t.x GLOBAL IN (1, 2) WHERE u.y GLOBAL NOT IN (3);
44+
SELECT * FROM t ARRAY JOIN arr WHERE arr GLOBAL IN (1, 2);
45+
SELECT arrayFilter(v -> v GLOBAL IN (1, 2), arr) FROM t;
46+
SELECT * FROM (SELECT * FROM t WHERE x GLOBAL IN (1)) AS sub WHERE sub.x GLOBAL NOT IN (2);
47+
WITH c AS (SELECT 1 AS v) SELECT * FROM t WHERE x GLOBAL IN (SELECT v FROM c);
48+
SELECT * FROM t WHERE x GLOBAL IN (1, 2) SETTINGS max_threads = 1;
49+
SELECT * FROM t WHERE x GLOBAL IN (1) UNION ALL SELECT * FROM t WHERE x GLOBAL NOT IN (2);
50+
SELECT * FROM t WHERE x GLOBAL IN (SELECT y FROM t2) ORDER BY x LIMIT 1 BY x;
51+
SELECT sum(x) OVER (PARTITION BY y GLOBAL IN (1, 2)) FROM t;
52+
INSERT INTO t2 SELECT * FROM t WHERE x GLOBAL IN (1, 2);
53+
DELETE FROM t WHERE x GLOBAL IN (SELECT y FROM t2);
54+
UPDATE t SET y = 1 WHERE x GLOBAL IN (SELECT y FROM t2);
55+
CREATE VIEW v AS SELECT * FROM t WHERE x GLOBAL IN (1, 2);
56+
SELECT /* GLOBAL IN */ * FROM t WHERE x GLOBAL -- NOT
57+
IN (1, 2);
58+
SELECT * FROM t WHERE x
59+
GLOBAL
60+
NOT
61+
IN
62+
(1,
63+
2)

‎src/ast/mod.rs‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -975,23 +975,27 @@ pub enum Expr {
975975
/// `true` when `NOT` is present.
976976
negated: bool,
977977
},
978-
/// `[ NOT ] IN (val1, val2, ...)`
978+
/// `[ GLOBAL ] [ NOT ] IN (val1, val2, ...)`
979979
InList {
980980
/// Left-hand expression to test for membership.
981981
expr: Box<Expr>,
982982
/// Literal list of expressions to check against.
983983
list: Vec<Expr>,
984984
/// `true` when the `NOT` modifier is present.
985985
negated: bool,
986+
/// `true` when the ClickHouse `GLOBAL` modifier is present.
987+
global: bool,
986988
},
987-
/// `[ NOT ] IN (SELECT ...)`
989+
/// `[ GLOBAL ] [ NOT ] IN (SELECT ...)`
988990
InSubquery {
989991
/// Left-hand expression to test for membership.
990992
expr: Box<Expr>,
991993
/// The subquery providing the candidate values.
992994
subquery: Box<Query>,
993995
/// `true` when the `NOT` modifier is present.
994996
negated: bool,
997+
/// `true` when the ClickHouse `GLOBAL` modifier is present.
998+
global: bool,
995999
},
9961000
/// `[ NOT ] IN UNNEST(array_expression)`
9971001
InUnnest {
@@ -1801,21 +1805,25 @@ impl fmt::Display for Expr {
18011805
expr,
18021806
list,
18031807
negated,
1808+
global,
18041809
} => write!(
18051810
f,
1806-
"{} {}IN ({})",
1811+
"{} {}{}IN ({})",
18071812
expr,
1813+
if *global { "GLOBAL " } else { "" },
18081814
if *negated { "NOT " } else { "" },
18091815
display_comma_separated(list)
18101816
),
18111817
Expr::InSubquery {
18121818
expr,
18131819
subquery,
18141820
negated,
1821+
global,
18151822
} => write!(
18161823
f,
1817-
"{} {}IN ({})",
1824+
"{} {}{}IN ({})",
18181825
expr,
1826+
if *global { "GLOBAL " } else { "" },
18191827
if *negated { "NOT " } else { "" },
18201828
subquery
18211829
),

‎src/ast/spans.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,13 +1513,15 @@ impl Spanned for Expr {
15131513
expr,
15141514
list,
15151515
negated: _,
1516+
global: _,
15161517
} => union_spans(
15171518
core::iter::once(expr.span()).chain(list.iter().map(|item| item.span())),
15181519
),
15191520
Expr::InSubquery {
15201521
expr,
15211522
subquery,
15221523
negated: _,
1524+
global: _,
15231525
} => expr.span().union(&subquery.span()),
15241526
Expr::InUnnest {
15251527
expr,

‎src/dialect/clickhouse.rs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ impl Dialect for ClickHouseDialect {
100100
true
101101
}
102102

103+
// See <https://clickhouse.com/docs/sql-reference/operators/in#distributed-subqueries>
104+
fn supports_global_in(&self) -> bool {
105+
true
106+
}
107+
103108
/// See <https://clickhouse.com/docs/en/sql-reference/functions#higher-order-functions---operator-and-lambdaparams-expr-function>
104109
fn supports_lambda_functions(&self) -> bool {
105110
true

‎src/dialect/mod.rs‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,11 @@ pub trait Dialect: Debug + Any {
461461
false
462462
}
463463

464+
/// Returns true if the dialect supports `GLOBAL IN` and `GLOBAL NOT IN`.
465+
fn supports_global_in(&self) -> bool {
466+
false
467+
}
468+
464469
/// Returns true if the dialect supports `BEGIN {DEFERRED | IMMEDIATE | EXCLUSIVE | TRY | CATCH} [TRANSACTION]` statements
465470
fn supports_start_transaction_modifier(&self) -> bool {
466471
false
@@ -914,6 +919,20 @@ pub trait Dialect: Debug + Any {
914919
Ok(p!(Is))
915920
}
916921
Token::Word(w) if w.keyword == Keyword::IS => Ok(p!(Is)),
922+
Token::Word(w) if w.keyword == Keyword::GLOBAL && self.supports_global_in() => {
923+
match (
924+
&parser.peek_nth_token_ref(1).token,
925+
&parser.peek_nth_token_ref(2).token,
926+
) {
927+
(Token::Word(w), _) if w.keyword == Keyword::IN => Ok(p!(Between)),
928+
(Token::Word(w), Token::Word(w2))
929+
if w.keyword == Keyword::NOT && w2.keyword == Keyword::IN =>
930+
{
931+
Ok(p!(Between))
932+
}
933+
_ => Ok(self.prec_unknown()),
934+
}
935+
}
917936
Token::Word(w) if w.keyword == Keyword::IN => Ok(p!(Between)),
918937
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(p!(Between)),
919938
Token::Word(w) if w.keyword == Keyword::OVERLAPS => Ok(p!(Between)),

‎src/parser/mod.rs‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4170,6 +4170,11 @@ impl<'a> Parser<'a> {
41704170
time_zone: Box::new(self.parse_subexpr(precedence)?),
41714171
})
41724172
}
4173+
Keyword::GLOBAL if self.dialect.supports_global_in() => {
4174+
let negated = self.parse_keyword(Keyword::NOT);
4175+
self.expect_keyword_is(Keyword::IN)?;
4176+
self.parse_in(expr, negated, true)
4177+
}
41734178
Keyword::NOT
41744179
| Keyword::IN
41754180
| Keyword::BETWEEN
@@ -4199,7 +4204,7 @@ impl<'a> Parser<'a> {
41994204
} else if negated && null {
42004205
Ok(Expr::IsNotNull(Box::new(expr)))
42014206
} else if self.parse_keyword(Keyword::IN) {
4202-
self.parse_in(expr, negated)
4207+
self.parse_in(expr, negated, false)
42034208
} else if self.parse_keyword(Keyword::BETWEEN) {
42044209
self.parse_between(expr, negated)
42054210
} else if self.parse_keyword(Keyword::LIKE) {
@@ -4462,11 +4467,16 @@ impl<'a> Parser<'a> {
44624467
Ok(JsonPath { path })
44634468
}
44644469

4465-
/// Parses the parens following the `[ NOT ] IN` operator.
4466-
pub fn parse_in(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParserError> {
4470+
/// Parses the parens following the `[ GLOBAL ] [ NOT ] IN` operator.
4471+
pub fn parse_in(
4472+
&mut self,
4473+
expr: Expr,
4474+
negated: bool,
4475+
global: bool,
4476+
) -> Result<Expr, ParserError> {
44674477
// BigQuery allows `IN UNNEST(array_expression)`
44684478
// https://cloud.google.com/bigquery/docs/reference/standard-sql/operators#in_operators
4469-
if self.parse_keyword(Keyword::UNNEST) {
4479+
if !global && self.parse_keyword(Keyword::UNNEST) {
44704480
self.expect_token(&Token::LParen)?;
44714481
let array_expr = self.parse_expr()?;
44724482
self.expect_token(&Token::RParen)?;
@@ -4481,8 +4491,9 @@ impl<'a> Parser<'a> {
44814491
{
44824492
return Ok(Expr::InList {
44834493
expr: Box::new(expr),
4484-
list: vec![self.parse_expr()?],
4494+
list: vec![self.parse_subexpr(self.dialect.prec_value(Precedence::Eq))?],
44854495
negated,
4496+
global,
44864497
});
44874498
}
44884499
self.expect_token(&Token::LParen)?;
@@ -4501,6 +4512,7 @@ impl<'a> Parser<'a> {
45014512
expr: Box::new(expr),
45024513
subquery,
45034514
negated,
4515+
global,
45044516
});
45054517
}
45064518
missing_rparen = Some(self.index);
@@ -4521,6 +4533,7 @@ impl<'a> Parser<'a> {
45214533
expr: Box::new(expr),
45224534
list,
45234535
negated,
4536+
global,
45244537
}),
45254538
Err(ParserError::RecursionLimitExceeded) => Err(ParserError::RecursionLimitExceeded),
45264539
Err(e) => match missing_rparen {

‎tests/sqlparser_clickhouse.rs‎

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,6 +2084,125 @@ fn parse_tuple_element_access() {
20842084
}
20852085
}
20862086

2087+
#[test]
2088+
fn parse_global_in() {
2089+
let select = clickhouse().verified_only_select("SELECT * FROM t WHERE x GLOBAL IN (1, 2)");
2090+
assert_eq!(
2091+
select.selection.unwrap(),
2092+
Expr::InList {
2093+
expr: Box::new(Identifier(Ident::new("x"))),
2094+
list: vec![
2095+
Expr::Value(number("1").with_empty_span()),
2096+
Expr::Value(number("2").with_empty_span()),
2097+
],
2098+
negated: false,
2099+
global: true,
2100+
}
2101+
);
2102+
2103+
let select = clickhouse()
2104+
.verified_only_select("SELECT * FROM t WHERE x GLOBAL NOT IN (SELECT y FROM t2)");
2105+
assert_eq!(
2106+
select.selection.unwrap(),
2107+
Expr::InSubquery {
2108+
expr: Box::new(Identifier(Ident::new("x"))),
2109+
subquery: Box::new(clickhouse().verified_query("SELECT y FROM t2")),
2110+
negated: true,
2111+
global: true,
2112+
}
2113+
);
2114+
2115+
clickhouse().one_statement_parses_to(
2116+
"SELECT * FROM t WHERE x GLOBAL IN t2",
2117+
"SELECT * FROM t WHERE x GLOBAL IN (t2)",
2118+
);
2119+
2120+
let select =
2121+
clickhouse().verified_only_select("SELECT * FROM t WHERE x GLOBAL IN (1) AND y = 2");
2122+
assert!(matches!(
2123+
select.selection.unwrap(),
2124+
BinaryOp {
2125+
op: BinaryOperator::And,
2126+
..
2127+
}
2128+
));
2129+
2130+
assert!(clickhouse()
2131+
.parse_sql_statements("SELECT * FROM t WHERE x GLOBAL y")
2132+
.is_err());
2133+
2134+
for dialect in all_dialects_where(|d| !d.supports_global_in()).dialects {
2135+
assert!(TestedDialects::new(vec![dialect])
2136+
.parse_sql_statements("SELECT * FROM t WHERE x GLOBAL IN (1)")
2137+
.is_err());
2138+
}
2139+
}
2140+
2141+
#[test]
2142+
fn parse_in_unparenthesized_expr_precedence() {
2143+
for (sql, canonical, op) in [
2144+
(
2145+
"SELECT * FROM t WHERE x IN t2 AND y = 2",
2146+
"SELECT * FROM t WHERE x IN (t2) AND y = 2",
2147+
BinaryOperator::And,
2148+
),
2149+
(
2150+
"SELECT * FROM t WHERE x GLOBAL IN t2 AND y = 2",
2151+
"SELECT * FROM t WHERE x GLOBAL IN (t2) AND y = 2",
2152+
BinaryOperator::And,
2153+
),
2154+
(
2155+
"SELECT * FROM t WHERE x GLOBAL NOT IN t2 OR y = 2",
2156+
"SELECT * FROM t WHERE x GLOBAL NOT IN (t2) OR y = 2",
2157+
BinaryOperator::Or,
2158+
),
2159+
(
2160+
"SELECT * FROM t WHERE x IN t2 = 1",
2161+
"SELECT * FROM t WHERE x IN (t2) = 1",
2162+
BinaryOperator::Eq,
2163+
),
2164+
] {
2165+
let select = clickhouse().verified_only_select_with_canonical(sql, canonical);
2166+
match select.selection.unwrap() {
2167+
BinaryOp { op: got, .. } => assert_eq!(got, op, "{sql}"),
2168+
other => panic!("{sql}: expected BinaryOp, got {other:?}"),
2169+
}
2170+
}
2171+
2172+
// Higher-precedence operators still belong to the IN operand.
2173+
clickhouse().one_statement_parses_to(
2174+
"SELECT * FROM t WHERE x IN 1 + 2",
2175+
"SELECT * FROM t WHERE x IN (1 + 2)",
2176+
);
2177+
}
2178+
2179+
#[test]
2180+
fn global_in_bare_table_binds_before_and() {
2181+
let expr =
2182+
clickhouse().expr_parses_to("x GLOBAL IN t2 AND y = 2", "x GLOBAL IN (t2) AND y = 2");
2183+
assert!(matches!(
2184+
expr,
2185+
BinaryOp {
2186+
left,
2187+
op: BinaryOperator::And,
2188+
..
2189+
} if matches!(*left, Expr::InList { global: true, .. })
2190+
));
2191+
}
2192+
2193+
#[test]
2194+
fn global_in_bare_table_binds_before_equality() {
2195+
let expr = clickhouse().expr_parses_to("x GLOBAL IN t2 = 1", "x GLOBAL IN (t2) = 1");
2196+
assert!(matches!(
2197+
expr,
2198+
BinaryOp {
2199+
left,
2200+
op: BinaryOperator::Eq,
2201+
..
2202+
} if matches!(*left, Expr::InList { global: true, .. })
2203+
));
2204+
}
2205+
20872206
fn clickhouse() -> TestedDialects {
20882207
TestedDialects::new(vec![Box::new(ClickHouseDialect {})])
20892208
}

0 commit comments

Comments
 (0)