Skip to content

Commit 2d26599

Browse files
committed
Add supports for Hive's GROUP BY .. GROUPING SETS syntax and supports for Clickhouse's GROUP BY GROUPING SETS syntax
1 parent c3256a8 commit 2d26599

File tree

8 files changed

+166
-60
lines changed

8 files changed

+166
-60
lines changed

src/ast/query.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2522,13 +2522,18 @@ impl fmt::Display for SelectInto {
25222522
/// e.g. GROUP BY year WITH ROLLUP WITH TOTALS
25232523
///
25242524
/// [ClickHouse]: <https://clickhouse.com/docs/en/sql-reference/statements/select/group-by#rollup-modifier>
2525-
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2525+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
25262526
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25272527
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
25282528
pub enum GroupByWithModifier {
25292529
Rollup,
25302530
Cube,
25312531
Totals,
2532+
/// Hive supports GROUP BY GROUPING SETS syntax.
2533+
/// e.g. GROUP BY year , month GROUPING SETS((year,month),(year),(month))
2534+
///
2535+
/// [Hive]: <https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=30151323#EnhancedAggregation,Cube,GroupingandRollup-GROUPINGSETSclause>
2536+
GroupingSets(Expr),
25322537
}
25332538

25342539
impl fmt::Display for GroupByWithModifier {
@@ -2537,6 +2542,19 @@ impl fmt::Display for GroupByWithModifier {
25372542
GroupByWithModifier::Rollup => write!(f, "WITH ROLLUP"),
25382543
GroupByWithModifier::Cube => write!(f, "WITH CUBE"),
25392544
GroupByWithModifier::Totals => write!(f, "WITH TOTALS"),
2545+
GroupByWithModifier::GroupingSets(expr) => match expr {
2546+
Expr::GroupingSets(sets) => {
2547+
write!(f, "GROUPING SETS (")?;
2548+
let mut sep = "";
2549+
for set in sets {
2550+
write!(f, "{sep}")?;
2551+
sep = ", ";
2552+
write!(f, "({})", display_comma_separated(set))?;
2553+
}
2554+
write!(f, ")")
2555+
}
2556+
_ => unreachable!(),
2557+
},
25402558
}
25412559
}
25422560
}

src/dialect/clickhouse.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,14 @@ impl Dialect for ClickHouseDialect {
7575
fn supports_lambda_functions(&self) -> bool {
7676
true
7777
}
78+
79+
// See <https://clickhouse.com/docs/en/sql-reference/aggregate-functions/grouping_function#grouping-sets>
80+
fn supports_group_by_expr(&self) -> bool {
81+
true
82+
}
83+
84+
/// See <https://clickhouse.com/docs/en/sql-reference/statements/select/group-by#rollup-modifier>
85+
fn supports_group_by_with_modifier(&self) -> bool {
86+
true
87+
}
7888
}

src/dialect/generic.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ impl Dialect for GenericDialect {
4848
true
4949
}
5050

51+
fn supports_group_by_with_modifier(&self) -> bool {
52+
true
53+
}
54+
55+
fn supports_group_by_special_grouping_sets(&self) -> bool {
56+
true
57+
}
58+
5159
fn supports_connect_by(&self) -> bool {
5260
true
5361
}

src/dialect/hive.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,28 @@ impl Dialect for HiveDialect {
5252
true
5353
}
5454

55-
/// See Hive <https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27362061#Tutorial-BuiltInOperators>
55+
/// See <https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27362061#Tutorial-BuiltInOperators>
5656
fn supports_bang_not_operator(&self) -> bool {
5757
true
5858
}
5959

60-
/// See Hive <https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27362036#LanguageManualDML-Loadingfilesintotables>
60+
/// See <https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27362036#LanguageManualDML-Loadingfilesintotables>
6161
fn supports_load_data(&self) -> bool {
6262
true
6363
}
6464

65-
/// See Hive <https://cwiki.apache.org/confluence/display/hive/languagemanual+sampling>
65+
/// See <https://cwiki.apache.org/confluence/display/hive/languagemanual+sampling>
6666
fn supports_table_sample_before_alias(&self) -> bool {
6767
true
6868
}
69+
70+
/// See <https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=30151323#EnhancedAggregation,Cube,GroupingandRollup-CubesandRollupsr>
71+
fn supports_group_by_with_modifier(&self) -> bool {
72+
true
73+
}
74+
75+
/// See <https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=30151323#EnhancedAggregation,Cube,GroupingandRollup-GROUPINGSETSclause>
76+
fn supports_group_by_special_grouping_sets(&self) -> bool {
77+
true
78+
}
6979
}

src/dialect/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,16 @@ pub trait Dialect: Debug + Any {
245245
false
246246
}
247247

248+
/// Returns true if the dialects supports `with rollup/cube/all` expressions.
249+
fn supports_group_by_with_modifier(&self) -> bool {
250+
false
251+
}
252+
253+
/// Returns true if the dialects supports `group by .. grouping sets` expressions.
254+
fn supports_group_by_special_grouping_sets(&self) -> bool {
255+
false
256+
}
257+
248258
/// Returns true if the dialect supports CONNECT BY.
249259
fn supports_connect_by(&self) -> bool {
250260
false

src/parser/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9069,7 +9069,7 @@ impl<'a> Parser<'a> {
90699069
};
90709070

90719071
let mut modifiers = vec![];
9072-
if dialect_of!(self is ClickHouseDialect | GenericDialect) {
9072+
if self.dialect.supports_group_by_with_modifier() {
90739073
loop {
90749074
if !self.parse_keyword(Keyword::WITH) {
90759075
break;
@@ -9092,6 +9092,16 @@ impl<'a> Parser<'a> {
90929092
});
90939093
}
90949094
}
9095+
if self.dialect.supports_group_by_special_grouping_sets()
9096+
&& self.parse_keywords(&[Keyword::GROUPING, Keyword::SETS])
9097+
{
9098+
self.expect_token(&Token::LParen)?;
9099+
let result = self.parse_comma_separated(|p| p.parse_tuple(true, true))?;
9100+
self.expect_token(&Token::RParen)?;
9101+
modifiers.push(GroupByWithModifier::GroupingSets(Expr::GroupingSets(
9102+
result,
9103+
)));
9104+
};
90959105
let group_by = match expressions {
90969106
None => GroupByExpr::All(modifiers),
90979107
Some(exprs) => GroupByExpr::Expressions(exprs, modifiers),

tests/sqlparser_clickhouse.rs

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,61 +1068,6 @@ fn parse_create_materialized_view() {
10681068
clickhouse_and_generic().verified_stmt(sql);
10691069
}
10701070

1071-
#[test]
1072-
fn parse_group_by_with_modifier() {
1073-
let clauses = ["x", "a, b", "ALL"];
1074-
let modifiers = [
1075-
"WITH ROLLUP",
1076-
"WITH CUBE",
1077-
"WITH TOTALS",
1078-
"WITH ROLLUP WITH CUBE",
1079-
];
1080-
let expected_modifiers = [
1081-
vec![GroupByWithModifier::Rollup],
1082-
vec![GroupByWithModifier::Cube],
1083-
vec![GroupByWithModifier::Totals],
1084-
vec![GroupByWithModifier::Rollup, GroupByWithModifier::Cube],
1085-
];
1086-
for clause in &clauses {
1087-
for (modifier, expected_modifier) in modifiers.iter().zip(expected_modifiers.iter()) {
1088-
let sql = format!("SELECT * FROM t GROUP BY {clause} {modifier}");
1089-
match clickhouse_and_generic().verified_stmt(&sql) {
1090-
Statement::Query(query) => {
1091-
let group_by = &query.body.as_select().unwrap().group_by;
1092-
if clause == &"ALL" {
1093-
assert_eq!(group_by, &GroupByExpr::All(expected_modifier.to_vec()));
1094-
} else {
1095-
assert_eq!(
1096-
group_by,
1097-
&GroupByExpr::Expressions(
1098-
clause
1099-
.split(", ")
1100-
.map(|c| Identifier(Ident::new(c)))
1101-
.collect(),
1102-
expected_modifier.to_vec()
1103-
)
1104-
);
1105-
}
1106-
}
1107-
_ => unreachable!(),
1108-
}
1109-
}
1110-
}
1111-
1112-
// invalid cases
1113-
let invalid_cases = [
1114-
"SELECT * FROM t GROUP BY x WITH",
1115-
"SELECT * FROM t GROUP BY x WITH ROLLUP CUBE",
1116-
"SELECT * FROM t GROUP BY x WITH WITH ROLLUP",
1117-
"SELECT * FROM t GROUP BY WITH ROLLUP",
1118-
];
1119-
for sql in invalid_cases {
1120-
clickhouse_and_generic()
1121-
.parse_sql_statements(sql)
1122-
.expect_err("Expected: one of ROLLUP or CUBE or TOTALS, found: WITH");
1123-
}
1124-
}
1125-
11261071
#[test]
11271072
fn parse_select_order_by_with_fill_interpolate() {
11281073
let sql = "SELECT id, fname, lname FROM customer WHERE id < 5 \

tests/sqlparser_common.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2446,6 +2446,101 @@ fn parse_select_group_by_all() {
24462446
);
24472447
}
24482448

2449+
#[test]
2450+
fn parse_group_by_with_modifier() {
2451+
let clauses = ["x", "a, b", "ALL"];
2452+
let modifiers = [
2453+
"WITH ROLLUP",
2454+
"WITH CUBE",
2455+
"WITH TOTALS",
2456+
"WITH ROLLUP WITH CUBE",
2457+
];
2458+
let expected_modifiers = [
2459+
vec![GroupByWithModifier::Rollup],
2460+
vec![GroupByWithModifier::Cube],
2461+
vec![GroupByWithModifier::Totals],
2462+
vec![GroupByWithModifier::Rollup, GroupByWithModifier::Cube],
2463+
];
2464+
let dialects = all_dialects_where(|d| d.supports_group_by_with_modifier());
2465+
2466+
for clause in &clauses {
2467+
for (modifier, expected_modifier) in modifiers.iter().zip(expected_modifiers.iter()) {
2468+
let sql = format!("SELECT * FROM t GROUP BY {clause} {modifier}");
2469+
match dialects.verified_stmt(&sql) {
2470+
Statement::Query(query) => {
2471+
let group_by = &query.body.as_select().unwrap().group_by;
2472+
if clause == &"ALL" {
2473+
assert_eq!(group_by, &GroupByExpr::All(expected_modifier.to_vec()));
2474+
} else {
2475+
assert_eq!(
2476+
group_by,
2477+
&GroupByExpr::Expressions(
2478+
clause
2479+
.split(", ")
2480+
.map(|c| Identifier(Ident::new(c)))
2481+
.collect(),
2482+
expected_modifier.to_vec()
2483+
)
2484+
);
2485+
}
2486+
}
2487+
_ => unreachable!(),
2488+
}
2489+
}
2490+
}
2491+
2492+
// invalid cases
2493+
let invalid_cases = [
2494+
"SELECT * FROM t GROUP BY x WITH",
2495+
"SELECT * FROM t GROUP BY x WITH ROLLUP CUBE",
2496+
"SELECT * FROM t GROUP BY x WITH WITH ROLLUP",
2497+
"SELECT * FROM t GROUP BY WITH ROLLUP",
2498+
];
2499+
for sql in invalid_cases {
2500+
dialects
2501+
.parse_sql_statements(sql)
2502+
.expect_err("Expected: one of ROLLUP or CUBE or TOTALS, found: WITH");
2503+
}
2504+
}
2505+
2506+
#[test]
2507+
fn parse_select_grouping_sets() {
2508+
let dialects = all_dialects_where(|d| d.supports_group_by_special_grouping_sets());
2509+
2510+
let sql = "SELECT a, b, SUM(c) FROM tab1 GROUP BY a, b GROUPING SETS ((a, b), (a), (b), ())";
2511+
match dialects.verified_stmt(sql) {
2512+
Statement::Query(query) => {
2513+
let group_by = &query.body.as_select().unwrap().group_by;
2514+
assert_eq!(
2515+
group_by,
2516+
&GroupByExpr::Expressions(
2517+
vec![
2518+
Expr::Identifier(Ident::new("a")),
2519+
Expr::Identifier(Ident::new("b"))
2520+
],
2521+
vec![GroupByWithModifier::GroupingSets(Expr::GroupingSets(vec![
2522+
vec![
2523+
Expr::Identifier(Ident::new("a")),
2524+
Expr::Identifier(Ident::new("b"))
2525+
],
2526+
vec![Expr::Identifier(Ident::new("a")),],
2527+
vec![Expr::Identifier(Ident::new("b"))],
2528+
vec![]
2529+
]))]
2530+
)
2531+
);
2532+
}
2533+
_ => unreachable!(),
2534+
}
2535+
2536+
let dialects = all_dialects_where(|d| !d.supports_group_by_special_grouping_sets());
2537+
2538+
assert_eq!(
2539+
dialects.parse_sql_statements(sql).unwrap_err(),
2540+
ParserError::ParserError("Expected: end of statement, found: GROUPING".to_string())
2541+
);
2542+
}
2543+
24492544
#[test]
24502545
fn parse_select_having() {
24512546
let sql = "SELECT foo FROM bar GROUP BY foo HAVING COUNT(*) > 1";

0 commit comments

Comments
 (0)