use rudb_common::rules::Rule;
use rudb_common::{Class, Direction, Result, Stat};
use rudb_plan::{Node, Plan};
use crate::estimate::{self, DISTINCT, Facts};
use crate::pass::{Context, Pass, top_down};
const MOST: u64 = 8 << 20;
const ALREADY: u64 = 32;
#[derive(Debug)]
pub struct AggregatePresize;
impl Pass for AggregatePresize {
fn name(&self) -> &'static str {
"aggregate_presize"
}
fn run(&self, plan: &mut Plan, context: &Context) -> Result<()> {
if context.allows(Rule::Presize) {
size(plan, context.facts());
}
Ok(())
}
}
fn size(plan: &mut Plan, stats: &Facts) {
let mut found = Vec::new();
for node in top_down(plan) {
let Node::Aggregate { input, index, groups, .. } = *plan.node(node) else {
continue;
};
let keys = plan.expr_list(groups);
if keys.is_empty() {
continue;
}
let Some(ceiling) = ceiling(plan, input, keys, stats) else {
continue;
};
if ceiling <= ALREADY {
continue;
}
found.push((index, ceiling.min(MOST)));
}
for (index, ceiling) in found {
plan.presize(index, ceiling);
}
}
fn ceiling(
plan: &Plan,
input: rudb_plan::NodeRef,
keys: &[rudb_plan::ExprRef],
stats: &Facts,
) -> Option<u64> {
let bindings = estimate::keyed(plan, keys)?;
let mut values: u64 = 1;
for binding in bindings {
values = values.saturating_mul(bounded(estimate::stated(plan, binding, stats))?);
}
(values <= estimate::rows(plan, input, stats)?).then_some(values)
}
fn bounded(stat: Stat<u64>) -> Option<u64> {
let value = *stat.read(DISTINCT)?;
match stat.class()? {
Class::Exact => Some(value),
Class::Certified { direction: Direction::AtMost, .. } => Some(value),
Class::Certified { bound, .. } if bound <= 1.0 => Some(value),
Class::Certified { .. } | Class::Estimated => None,
}
}
#[cfg(test)]
mod tests {
use rudb_common::Provenance;
use rudb_plan::Plan;
use super::{AggregatePresize, MOST, Rule};
use crate::estimate::Facts;
use crate::pass::{Context, Pass};
const SCAN: &str = "Get memory.main.t AS t #0 [a::INTEGER]";
fn grouped(filter: Option<&str>) -> Plan {
let scan = SCAN;
let text = match filter {
None => format!("Aggregate #1 groups=[#0.0::INTEGER] aggregates=[]\n {scan}\n"),
Some(predicate) => format!(
"Aggregate #1 groups=[#0.0::INTEGER] aggregates=[]\n Filter {predicate}\n \
{scan}\n"
),
};
Plan::parse(&text).expect("a plan that parses")
}
fn counted(rows: u64, distinct: u64) -> Context {
let mut facts = Facts::new();
facts.record("memory", "main", "t", rows);
facts.record_distinct("memory", "main", "t", "a", distinct, Provenance::Dictionary);
let mut context = Context::new();
context.measure(std::sync::Arc::new(facts));
context
}
fn run(plan: &mut Plan, context: &Context) {
AggregatePresize.run(plan, context).expect("a pass that cannot fail");
}
fn without(rule: Rule) -> Context {
let mut context = counted(1_000_000, 50_000);
let mut rules = rudb_common::rules::Rules::default();
rules.set(rule, false);
context.govern(rules);
context
}
#[test]
fn the_rule_s_own_setting_turns_it_off() {
let mut plan = grouped(None);
run(&mut plan, &without(Rule::Presize));
assert_eq!(plan.presized_count(), 0, "stats_presize = off asked for no room");
}
#[test]
fn the_master_setting_turns_it_off_too() {
let mut plan = grouped(None);
run(&mut plan, &without(Rule::StatsAll));
assert_eq!(plan.presized_count(), 0, "statistics = off asked for no room");
}
#[test]
fn a_counted_key_column_sizes_the_table() {
let mut plan = grouped(None);
run(&mut plan, &counted(1_000_000, 50_000));
assert_eq!(plan.presized(1), Some(50_000));
}
#[test]
fn a_key_the_rows_bound_more_tightly_than_its_count_does_is_left_alone() {
let mut plan = grouped(None);
run(&mut plan, &counted(4_000, 50_000));
assert_eq!(plan.presized_count(), 0);
}
#[test]
fn a_filter_underneath_does_not_raise_the_ceiling() {
let mut plan = grouped(Some("(#0.0::INTEGER > 10::INTEGER)::BOOLEAN"));
run(&mut plan, &counted(1_000_000, 50_000));
let sized = plan.presized(1).unwrap_or(0);
assert!(sized <= 50_000, "{sized} is above the key's own ceiling");
}
#[test]
fn a_key_of_several_columns_is_left_alone_because_the_product_is_not_a_size() {
let text = "Aggregate #1 groups=[#0.0::INTEGER, #0.1::INTEGER] aggregates=[]\n Get \
memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n";
let mut plan = Plan::parse(text).expect("a plan that parses");
let mut facts = Facts::new();
facts.record("memory", "main", "t", 1_000_000);
facts.record_distinct("memory", "main", "t", "a", 50_000, Provenance::Dictionary);
facts.record_distinct("memory", "main", "t", "b", 50_000, Provenance::Dictionary);
let mut context = Context::new();
context.measure(std::sync::Arc::new(facts));
run(&mut plan, &context);
assert_eq!(plan.presized_count(), 0);
}
#[test]
fn a_column_nobody_counted_is_left_alone() {
let mut plan = grouped(None);
let mut facts = Facts::new();
facts.record("memory", "main", "t", 1_000_000);
let mut context = Context::new();
context.measure(std::sync::Arc::new(facts));
run(&mut plan, &context);
assert_eq!(plan.presized(1), None);
assert_eq!(plan.presized_count(), 0);
}
#[test]
fn an_ungrouped_aggregate_has_no_table_to_size() {
let text = format!("Aggregate #1 groups=[] aggregates=[count_star()::BIGINT]\n {SCAN}\n");
let mut plan = Plan::parse(&text).expect("a plan that parses");
run(&mut plan, &counted(1_000_000, 50_000));
assert_eq!(plan.presized_count(), 0);
}
#[test]
fn a_key_that_never_fills_the_first_buckets_is_left_alone() {
let mut plan = grouped(None);
run(&mut plan, &counted(1_000_000, 8));
assert_eq!(plan.presized_count(), 0);
}
#[test]
fn a_ceiling_past_the_cap_is_capped() {
let mut plan = grouped(None);
run(&mut plan, &counted(u64::MAX, u64::MAX));
assert_eq!(plan.presized(1), Some(MOST));
}
#[test]
fn an_input_whose_rows_nobody_can_say_is_left_alone() {
let mut plan = grouped(None);
let mut facts = Facts::new();
facts.record_distinct("memory", "main", "t", "a", 50_000, Provenance::Dictionary);
let mut context = Context::new();
context.measure(std::sync::Arc::new(facts));
run(&mut plan, &context);
assert_eq!(plan.presized_count(), 0);
}
#[test]
fn a_second_run_writes_what_the_first_one_wrote() {
let mut plan = grouped(None);
let context = counted(1_000_000, 50_000);
run(&mut plan, &context);
let once = plan.presized(1);
run(&mut plan, &context);
assert_eq!(plan.presized(1), once);
assert_eq!(plan.presized_count(), 1);
}
#[test]
fn the_pass_is_off_when_it_is_named() {
let context = Context::without("aggregate_presize").expect("a name that is a pass");
assert!(context.is_disabled("aggregate_presize"));
}
}