use rudb_common::{LogicalType, Result};
use rudb_plan::{ColumnBinding, Expr, ExprRef, Node, NodeRef, Plan, Slice};
use crate::pass::{Context, Pass};
use crate::walk;
pub const SET_DETERMINED: [&str; 5] = ["avg", "count", "max", "min", "sum"];
#[derive(Debug, Clone, Copy)]
pub struct DistinctAggregateRewrite;
impl Pass for DistinctAggregateRewrite {
fn name(&self) -> &'static str {
"distinct_aggregate_rewrite"
}
fn run(&self, plan: &mut Plan, _context: &Context) -> Result<()> {
split(plan);
Ok(())
}
}
pub fn split(plan: &mut Plan) {
let mut moved = false;
let root = walk::restack(plan, plan.root(), &mut moved, &mut stage);
if moved {
plan.set_root(root);
}
}
fn stage(plan: &mut Plan, at: NodeRef) -> Option<NodeRef> {
let Node::Aggregate { input, index, groups, aggregates } = *plan.node(at) else { return None };
let calls = plan.expr_list(aggregates).to_vec();
let args = shared_arguments(plan, &calls)?;
let keys = plan.expr_list(groups).to_vec();
if !keys.is_empty() && already_cheap(plan, &args) {
return None;
}
let mut below = keys.clone();
below.extend_from_slice(&args);
let below = plan.add_expr_list(&below);
let staged = walk::fresh_index(plan);
let inner = plan.add_node(Node::Aggregate {
input,
index: staged,
groups: below,
aggregates: Slice::EMPTY,
});
let column = &mut |plan: &mut Plan, at: usize, source: ExprRef| {
let ty = plan.expr_type(source).clone();
let at = u32::try_from(at).expect("an aggregate with this many expressions cannot bind");
plan.add_expr(Expr::Column(ColumnBinding::new(staged, at)), ty)
};
let outer_keys: Vec<ExprRef> =
keys.iter().enumerate().map(|(at, &key)| column(plan, at, key)).collect();
let outer_args: Vec<ExprRef> =
args.iter().enumerate().map(|(at, &arg)| column(plan, keys.len() + at, arg)).collect();
let outer_args = plan.add_expr_list(&outer_args);
let mut outer_calls = Vec::with_capacity(calls.len());
for &call in &calls {
let Expr::Aggregate { name, .. } = *plan.expr(call) else {
return None;
};
let ty = plan.expr_type(call).clone();
let plain = Expr::Aggregate { name, args: outer_args, distinct: false, filter: None };
outer_calls.push(plan.add_expr(plain, ty));
}
let outer_keys = plan.add_expr_list(&outer_keys);
let outer_calls = plan.add_expr_list(&outer_calls);
Some(plan.add_node(Node::Aggregate {
input: inner,
index,
groups: outer_keys,
aggregates: outer_calls,
}))
}
fn shared_arguments(plan: &Plan, calls: &[ExprRef]) -> Option<Vec<ExprRef>> {
let mut shared: Option<Vec<ExprRef>> = None;
for &call in calls {
let Expr::Aggregate { name, args, distinct, filter } = *plan.expr(call) else {
return None;
};
if !distinct || filter.is_some() || !SET_DETERMINED.contains(&plan.string(name)) {
return None;
}
let args = plan.expr_list(args).to_vec();
if args.is_empty() {
return None;
}
match &shared {
None => shared = Some(args),
Some(first) => {
if first.len() != args.len() {
return None;
}
if !first.iter().zip(&args).all(|(&one, &other)| walk::same(plan, one, other)) {
return None;
}
}
}
}
shared
}
fn already_cheap(plan: &Plan, args: &[ExprRef]) -> bool {
matches!(args, [only] if plan.expr_type(*only) == &LogicalType::BigInt)
}
#[cfg(test)]
mod tests {
use rudb_plan::Plan;
use super::split;
fn staged(text: &str) -> String {
let mut plan =
Plan::parse(text).unwrap_or_else(|error| panic!("{text} did not parse: {error}"));
split(&mut plan);
plan.validate().unwrap_or_else(|error| panic!("{text} did not stay valid: {error}"));
plan.to_string()
}
#[test]
fn an_ungrouped_count_distinct_becomes_a_grouping_with_a_count_over_it() {
assert_eq!(
staged(concat!(
"Aggregate #1 groups=[] aggregates=[count(DISTINCT #0.0::INTEGER)::BIGINT]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
)),
concat!(
"Aggregate #1 groups=[] aggregates=[count(#2.0::INTEGER)::BIGINT]\n",
" Aggregate #2 groups=[#0.0::INTEGER] aggregates=[]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
)
);
}
#[test]
fn a_grouped_count_distinct_groups_by_the_key_and_the_argument() {
assert_eq!(
staged(concat!(
"Aggregate #1 groups=[#0.0::INTEGER] aggregates=[count(DISTINCT #0.1::INTEGER)::BIGINT]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
)),
concat!(
"Aggregate #1 groups=[#2.0::INTEGER] aggregates=[count(#2.1::INTEGER)::BIGINT]\n",
" Aggregate #2 groups=[#0.0::INTEGER, #0.1::INTEGER] aggregates=[]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
)
);
}
#[test]
fn two_distinct_calls_over_the_same_argument_share_one_grouping() {
assert_eq!(
staged(concat!(
"Aggregate #1 groups=[] aggregates=[count(DISTINCT #0.0::INTEGER)::BIGINT, sum(DISTINCT #0.0::INTEGER)::HUGEINT]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
)),
concat!(
"Aggregate #1 groups=[] aggregates=[count(#2.0::INTEGER)::BIGINT, sum(#2.0::INTEGER)::HUGEINT]\n",
" Aggregate #2 groups=[#0.0::INTEGER] aggregates=[]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
)
);
}
#[test]
fn an_aggregate_with_no_distinct_in_it_is_left_alone() {
let text = concat!(
"Aggregate #1 groups=[#0.0::INTEGER] aggregates=[count_star()::BIGINT]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
);
assert_eq!(staged(text), text);
}
#[test]
fn a_plain_call_beside_a_distinct_one_is_left_alone() {
let text = concat!(
"Aggregate #1 groups=[] aggregates=[count(DISTINCT #0.0::INTEGER)::BIGINT, count_star()::BIGINT]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
);
assert_eq!(staged(text), text);
}
#[test]
fn two_distinct_calls_over_different_arguments_are_left_alone() {
let text = concat!(
"Aggregate #1 groups=[] aggregates=[count(DISTINCT #0.0::INTEGER)::BIGINT, count(DISTINCT #0.1::INTEGER)::BIGINT]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
);
assert_eq!(staged(text), text);
}
#[test]
fn a_distinct_call_with_a_filter_is_left_alone() {
let text = concat!(
"Aggregate #1 groups=[] aggregates=[count(DISTINCT #0.0::INTEGER FILTER #0.2::BOOLEAN)::BIGINT]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER, c::BOOLEAN]\n",
);
assert_eq!(staged(text), text);
}
#[test]
fn a_grouped_count_distinct_over_one_bigint_is_left_alone() {
let text = concat!(
"Aggregate #1 groups=[#0.0::INTEGER] aggregates=[count(DISTINCT #0.1::BIGINT)::BIGINT]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::BIGINT]\n",
);
assert_eq!(staged(text), text, "the row loop has a set of i64 for exactly this");
}
#[test]
fn an_ungrouped_count_distinct_over_one_bigint_is_rewritten_anyway() {
assert_eq!(
staged(concat!(
"Aggregate #1 groups=[] aggregates=[count(DISTINCT #0.1::BIGINT)::BIGINT]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::BIGINT]\n",
)),
concat!(
"Aggregate #1 groups=[] aggregates=[count(#2.0::BIGINT)::BIGINT]\n",
" Aggregate #2 groups=[#0.1::BIGINT] aggregates=[]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::BIGINT]\n",
),
"one set for the whole query is one set that never partitions"
);
}
#[test]
fn a_grouped_count_distinct_over_two_bigints_is_rewritten() {
assert_eq!(
staged(concat!(
"Aggregate #1 groups=[#0.0::INTEGER] aggregates=[count(DISTINCT #0.1::BIGINT, #0.0::INTEGER)::BIGINT]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::BIGINT]\n",
)),
concat!(
"Aggregate #1 groups=[#2.0::INTEGER] aggregates=[count(#2.1::BIGINT, #2.2::INTEGER)::BIGINT]\n",
" Aggregate #2 groups=[#0.0::INTEGER, #0.1::BIGINT, #0.0::INTEGER] aggregates=[]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::BIGINT]\n",
),
"two arguments are an encoded row either way"
);
}
#[test]
fn running_it_twice_is_running_it_once() {
let text = concat!(
"Aggregate #1 groups=[#0.0::INTEGER] aggregates=[count(DISTINCT #0.1::INTEGER)::BIGINT]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
);
let once = staged(text);
assert_eq!(staged(&once), once);
}
}