#![forbid(unsafe_code)]
pub mod columns;
use rudb_common::{Error, Result};
use rudb_plan::{Node, NodeRef, Plan};
pub const RANK: u8 = 11;
pub fn optimize(plan: &mut Plan) -> Result<()> {
let before = output_columns(plan, plan.root());
columns::prune(plan);
if cfg!(debug_assertions) {
plan.validate()?;
let after = output_columns(plan, plan.root());
if after != before {
return Err(Error::internal(format!(
"a pass turned a query of {before} columns into one of {after}"
)));
}
}
Ok(())
}
fn output_columns(plan: &Plan, reference: NodeRef) -> usize {
match *plan.node(reference) {
Node::Get { columns, .. }
| Node::Values { columns, .. }
| Node::TableFunction { columns, .. } => plan.field_list(columns).len(),
Node::Project { exprs, .. } => plan.expr_list(exprs).len(),
Node::Aggregate { groups, aggregates, .. } => {
plan.expr_list(groups).len() + plan.expr_list(aggregates).len()
}
Node::Dummy => 0,
Node::Filter { input, .. }
| Node::Sort { input, .. }
| Node::Limit { input, .. }
| Node::Distinct { input, .. } => output_columns(plan, input),
Node::SetOp { left, .. } => output_columns(plan, left),
Node::Join { left, right, .. } | Node::CrossProduct { left, right } => {
output_columns(plan, left) + output_columns(plan, right)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn width(text: &str) -> usize {
let plan =
Plan::parse(text).unwrap_or_else(|error| panic!("{text} did not parse: {error}"));
output_columns(&plan, plan.root())
}
fn optimized(text: &str) -> String {
let mut plan =
Plan::parse(text).unwrap_or_else(|error| panic!("{text} did not parse: {error}"));
optimize(&mut plan).unwrap_or_else(|error| panic!("{text} did not optimize: {error}"));
plan.to_string()
}
#[test]
fn the_width_of_a_plan_is_the_width_of_whatever_produces_its_columns() {
assert_eq!(
width(
"Project #1 [#0.0::INTEGER AS a]\n Get memory.main.t AS t #0 [a::INTEGER, b::VARCHAR]\n"
),
1
);
assert_eq!(width("Get memory.main.t AS t #0 [a::INTEGER, b::VARCHAR]\n"), 2);
assert_eq!(width("Dummy\n"), 0);
assert_eq!(
width(
"Aggregate #1 groups=[#0.0::INTEGER] aggregates=[count_star()::BIGINT]\n Get memory.main.t AS t #0 [a::INTEGER]\n"
),
2
);
}
#[test]
fn an_operator_that_passes_its_input_through_is_as_wide_as_its_input() {
assert_eq!(
width("Limit 1 offset 0\n Get memory.main.t AS t #0 [a::INTEGER, b::VARCHAR]\n"),
2
);
}
#[test]
fn a_join_is_both_sides_together_and_a_set_operation_is_one_of_them() {
assert_eq!(
width(
"Join INNER on=[]\n Get memory.main.t AS t #0 [a::INTEGER, b::VARCHAR]\n Get memory.main.u AS u #1 [x::INTEGER]\n"
),
3
);
assert_eq!(
width(
"SetOp UNION ALL #2\n Get memory.main.t AS t #0 [a::INTEGER]\n Get memory.main.u AS u #1 [x::INTEGER]\n"
),
1
);
}
#[test]
fn optimizing_keeps_a_query_as_wide_as_it_was() {
let before = "Project #1 [#0.1::VARCHAR AS b]\n Get memory.main.t AS t #0 [a::INTEGER, b::VARCHAR]\n";
let after = "Project #1 [#0.0::VARCHAR AS b]\n Get memory.main.t AS t #0 [b::VARCHAR]\n";
assert_eq!(optimized(before), after);
assert_eq!(width(before), width(after));
}
}