use vibesql_ast::{Expression, GroupByClause, GroupingElement, GroupingSet, MixedGroupingItem};
use super::{expression_utils::expressions_equal, ResolvedGroupingSet};
pub fn expand_group_by_clause(clause: &GroupByClause) -> Vec<ResolvedGroupingSet> {
match clause {
GroupByClause::Simple(exprs) => {
vec![ResolvedGroupingSet {
group_by_exprs: exprs.clone(),
rolled_up: vec![false; exprs.len()],
}]
}
GroupByClause::Rollup(elements) => expand_rollup(elements),
GroupByClause::Cube(elements) => expand_cube(elements),
GroupByClause::GroupingSets(sets) => expand_grouping_sets(sets),
GroupByClause::Mixed(items) => expand_mixed(items),
}
}
pub fn get_base_expressions(clause: &GroupByClause) -> Vec<Expression> {
match clause {
GroupByClause::Simple(exprs) => exprs.clone(),
GroupByClause::Rollup(elements) | GroupByClause::Cube(elements) => {
elements.iter().flat_map(element_to_expressions).collect()
}
GroupByClause::GroupingSets(sets) => {
sets.iter()
.find(|s| !s.columns.is_empty())
.map(|s| s.columns.clone())
.unwrap_or_default()
}
GroupByClause::Mixed(items) => {
items
.iter()
.flat_map(|item| match item {
MixedGroupingItem::Simple(expr) => vec![expr.clone()],
MixedGroupingItem::Rollup(elements) | MixedGroupingItem::Cube(elements) => {
elements.iter().flat_map(element_to_expressions).collect()
}
MixedGroupingItem::GroupingSets(sets) => {
sets.iter().flat_map(|s| s.columns.clone()).collect()
}
})
.collect()
}
}
}
fn expand_rollup(elements: &[GroupingElement]) -> Vec<ResolvedGroupingSet> {
let base_exprs: Vec<Expression> = elements.iter().flat_map(element_to_expressions).collect();
let mut result = Vec::with_capacity(elements.len() + 1);
for prefix_len in (0..=elements.len()).rev() {
let group_by_exprs: Vec<Expression> =
elements[0..prefix_len].iter().flat_map(element_to_expressions).collect();
let mut rolled_up = Vec::with_capacity(base_exprs.len());
for (i, element) in elements.iter().enumerate() {
let element_expr_count = match element {
GroupingElement::Single(_) => 1,
GroupingElement::Composite(exprs) => exprs.len(),
};
for _ in 0..element_expr_count {
rolled_up.push(i >= prefix_len);
}
}
result.push(ResolvedGroupingSet { group_by_exprs, rolled_up });
}
result
}
fn expand_cube(elements: &[GroupingElement]) -> Vec<ResolvedGroupingSet> {
let base_exprs: Vec<Expression> = elements.iter().flat_map(element_to_expressions).collect();
let n = elements.len();
let num_sets = 1 << n;
let mut result = Vec::with_capacity(num_sets);
for mask in (0..num_sets).rev() {
let mut group_by_exprs = Vec::new();
let mut rolled_up = Vec::with_capacity(base_exprs.len());
for (i, element) in elements.iter().enumerate() {
let include = (mask & (1 << (n - 1 - i))) != 0;
let element_exprs = element_to_expressions(element);
for expr in &element_exprs {
if include {
group_by_exprs.push(expr.clone());
}
rolled_up.push(!include);
}
}
result.push(ResolvedGroupingSet { group_by_exprs, rolled_up });
}
result
}
fn expand_grouping_sets(sets: &[GroupingSet]) -> Vec<ResolvedGroupingSet> {
let all_exprs: Vec<Expression> = sets.iter().flat_map(|s| s.columns.clone()).collect();
let mut base_exprs: Vec<Expression> = Vec::new();
for expr in &all_exprs {
if !base_exprs.iter().any(|e| expressions_equal(e, expr)) {
base_exprs.push(expr.clone());
}
}
sets.iter()
.map(|set| {
let rolled_up: Vec<bool> = base_exprs
.iter()
.map(|base| !set.columns.iter().any(|e| expressions_equal(e, base)))
.collect();
ResolvedGroupingSet { group_by_exprs: set.columns.clone(), rolled_up }
})
.collect()
}
fn expand_mixed(items: &[MixedGroupingItem]) -> Vec<ResolvedGroupingSet> {
let mut simple_exprs: Vec<Expression> = Vec::new();
let mut item_expansions: Vec<Vec<ResolvedGroupingSet>> = Vec::new();
for item in items {
match item {
MixedGroupingItem::Simple(expr) => {
simple_exprs.push(expr.clone());
}
MixedGroupingItem::Rollup(elements) => {
item_expansions.push(expand_rollup(elements));
}
MixedGroupingItem::Cube(elements) => {
item_expansions.push(expand_cube(elements));
}
MixedGroupingItem::GroupingSets(sets) => {
item_expansions.push(expand_grouping_sets(sets));
}
}
}
if item_expansions.is_empty() {
return vec![ResolvedGroupingSet {
group_by_exprs: simple_exprs.clone(),
rolled_up: vec![false; simple_exprs.len()],
}];
}
let combined = cross_product_grouping_sets(item_expansions);
combined
.into_iter()
.map(|set| {
let mut group_by_exprs = simple_exprs.clone();
group_by_exprs.extend(set.group_by_exprs);
let mut rolled_up = vec![false; simple_exprs.len()];
rolled_up.extend(set.rolled_up);
ResolvedGroupingSet { group_by_exprs, rolled_up }
})
.collect()
}
fn cross_product_grouping_sets(
expansions: Vec<Vec<ResolvedGroupingSet>>,
) -> Vec<ResolvedGroupingSet> {
if expansions.is_empty() {
return vec![ResolvedGroupingSet { group_by_exprs: vec![], rolled_up: vec![] }];
}
let mut result = expansions[0].clone();
for expansion in expansions.into_iter().skip(1) {
let mut new_result = Vec::with_capacity(result.len() * expansion.len());
for left in &result {
for right in &expansion {
let mut combined_exprs = left.group_by_exprs.clone();
combined_exprs.extend(right.group_by_exprs.clone());
let mut combined_rolled_up = left.rolled_up.clone();
combined_rolled_up.extend(right.rolled_up.clone());
new_result.push(ResolvedGroupingSet {
group_by_exprs: combined_exprs,
rolled_up: combined_rolled_up,
});
}
}
result = new_result;
}
result
}
fn element_to_expressions(element: &GroupingElement) -> Vec<Expression> {
match element {
GroupingElement::Single(expr) => vec![expr.clone()],
GroupingElement::Composite(exprs) => exprs.clone(),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn col(name: &str) -> Expression {
Expression::ColumnRef(vibesql_ast::ColumnIdentifier::simple(&name, false))
}
#[test]
fn test_expand_rollup() {
let elements = vec![
GroupingElement::Single(col("a")),
GroupingElement::Single(col("b")),
GroupingElement::Single(col("c")),
];
let sets = expand_rollup(&elements);
assert_eq!(sets.len(), 4);
assert_eq!(sets[0].group_by_exprs.len(), 3);
assert_eq!(sets[0].rolled_up, vec![false, false, false]);
assert_eq!(sets[1].group_by_exprs.len(), 2);
assert_eq!(sets[1].rolled_up, vec![false, false, true]);
assert_eq!(sets[2].group_by_exprs.len(), 1);
assert_eq!(sets[2].rolled_up, vec![false, true, true]);
assert_eq!(sets[3].group_by_exprs.len(), 0);
assert_eq!(sets[3].rolled_up, vec![true, true, true]);
}
#[test]
fn test_expand_cube() {
let elements = vec![GroupingElement::Single(col("a")), GroupingElement::Single(col("b"))];
let sets = expand_cube(&elements);
assert_eq!(sets.len(), 4);
let set_sizes: Vec<usize> = sets.iter().map(|s| s.group_by_exprs.len()).collect();
assert!(set_sizes.contains(&2)); assert!(set_sizes.iter().filter(|&&s| s == 1).count() == 2); assert!(set_sizes.contains(&0)); }
#[test]
fn test_expand_grouping_sets() {
let sets = vec![
GroupingSet { columns: vec![col("a"), col("b")] },
GroupingSet { columns: vec![col("a")] },
GroupingSet { columns: vec![] },
];
let resolved = expand_grouping_sets(&sets);
assert_eq!(resolved.len(), 3);
}
#[test]
fn test_expand_mixed_simple_with_rollup() {
let items = vec![
MixedGroupingItem::Simple(col("a")),
MixedGroupingItem::Rollup(vec![
GroupingElement::Single(col("b")),
GroupingElement::Single(col("c")),
]),
];
let sets = expand_mixed(&items);
assert_eq!(sets.len(), 3);
assert_eq!(sets[0].group_by_exprs.len(), 3);
assert_eq!(sets[0].rolled_up, vec![false, false, false]);
assert_eq!(sets[1].group_by_exprs.len(), 2);
assert_eq!(sets[1].rolled_up, vec![false, false, true]);
assert_eq!(sets[2].group_by_exprs.len(), 1);
assert_eq!(sets[2].rolled_up, vec![false, true, true]); }
#[test]
fn test_expand_mixed_simple_with_cube() {
let items = vec![
MixedGroupingItem::Simple(col("a")),
MixedGroupingItem::Cube(vec![GroupingElement::Single(col("b"))]),
];
let sets = expand_mixed(&items);
assert_eq!(sets.len(), 2);
assert_eq!(sets[0].group_by_exprs.len(), 2);
assert_eq!(sets[0].rolled_up, vec![false, false]);
assert_eq!(sets[1].group_by_exprs.len(), 1);
assert_eq!(sets[1].rolled_up, vec![false, true]);
}
#[test]
fn test_expand_mixed_multiple_simple() {
let items = vec![
MixedGroupingItem::Simple(col("a")),
MixedGroupingItem::Simple(col("b")),
MixedGroupingItem::Rollup(vec![GroupingElement::Single(col("c"))]),
];
let sets = expand_mixed(&items);
assert_eq!(sets.len(), 2);
assert_eq!(sets[0].group_by_exprs.len(), 3);
assert_eq!(sets[0].rolled_up, vec![false, false, false]);
assert_eq!(sets[1].group_by_exprs.len(), 2);
assert_eq!(sets[1].rolled_up, vec![false, false, true]);
}
#[test]
fn test_expand_mixed_cross_product_of_rollups() {
let items = vec![
MixedGroupingItem::Rollup(vec![GroupingElement::Single(col("a"))]),
MixedGroupingItem::Rollup(vec![GroupingElement::Single(col("b"))]),
];
let sets = expand_mixed(&items);
assert_eq!(sets.len(), 4);
let set_sizes: Vec<usize> = sets.iter().map(|s| s.group_by_exprs.len()).collect();
assert!(set_sizes.contains(&2)); assert!(set_sizes.iter().filter(|&&s| s == 1).count() == 2); assert!(set_sizes.contains(&0)); }
#[test]
fn test_expand_mixed_only_simple() {
let items = vec![MixedGroupingItem::Simple(col("a")), MixedGroupingItem::Simple(col("b"))];
let sets = expand_mixed(&items);
assert_eq!(sets.len(), 1);
assert_eq!(sets[0].group_by_exprs.len(), 2);
assert_eq!(sets[0].rolled_up, vec![false, false]);
}
}