#![forbid(unsafe_code)]
pub mod columns;
pub mod empty;
pub mod filter;
pub mod fold;
pub mod limit;
pub mod nulls;
pub mod pass;
pub mod tables;
pub mod topn;
mod transitive;
mod walk;
use rudb_common::{Error, Result};
use rudb_plan::{Node, NodeRef, Plan};
use crate::pass::{Context, Pass};
pub const RANK: u8 = 11;
pub static PASSES: [&(dyn Pass + Sync); 6] = [
&fold::ExpressionRewriter,
&filter::FilterPushdown,
&empty::EmptyResultPullup,
&columns::UnusedColumns,
&limit::LimitPushdown,
&topn::TopN,
];
pub fn optimize(plan: &mut Plan) -> Result<()> {
optimize_with(plan, &Context::new())
}
pub fn optimize_with(plan: &mut Plan, context: &Context) -> Result<()> {
run(plan, context, &PASSES)
}
fn run(plan: &mut Plan, context: &Context, passes: &[&(dyn Pass + Sync)]) -> Result<()> {
let before = output_columns(plan, plan.root());
once(plan, context, passes)?;
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}"
)));
}
let settled = plan.to_string();
once(plan, context, passes)?;
let again = plan.to_string();
if again != settled {
return Err(Error::internal(format!(
"the passes did not settle, since running them again gave a different plan\n\n{settled}\n{again}"
)));
}
}
Ok(())
}
fn once(plan: &mut Plan, context: &Context, passes: &[&(dyn Pass + Sync)]) -> Result<()> {
for pass in passes {
if context.is_disabled(pass.name()) {
continue;
}
pass.run(plan, context)?;
}
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::TopN { 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));
}
#[test]
fn no_two_passes_answer_to_the_same_name() {
let mut names: Vec<&str> = PASSES.iter().map(|pass| pass.name()).collect();
names.sort_unstable();
let held = names.len();
names.dedup();
assert_eq!(names.len(), held, "{names:?}");
}
#[test]
fn a_pass_that_is_turned_off_does_not_run() {
let text = "Project #1 [\"+\"(1::INTEGER, 1::INTEGER)::INTEGER AS n]\n Get memory.main.t AS t #0 [a::INTEGER]\n";
let mut plan = Plan::parse(text).expect("a well formed plan");
let context = Context::without("expression_rewriter").expect("a name that is a pass");
optimize_with(&mut plan, &context).expect("the other pass still runs");
assert_eq!(
plan.to_string(),
"Project #1 [\"+\"(1::INTEGER, 1::INTEGER)::INTEGER AS n]\n Get memory.main.t AS t #0 []\n"
);
}
#[derive(Debug)]
#[cfg(debug_assertions)]
struct Restless;
#[cfg(debug_assertions)]
impl Pass for Restless {
fn name(&self) -> &'static str {
"restless"
}
fn run(&self, plan: &mut Plan, _context: &Context) -> Result<()> {
let root = plan.root();
if !matches!(*plan.node(root), Node::Limit { .. }) {
return Ok(());
}
let stacked = plan.add_node(Node::Limit { input: root, count: Some(1), offset: 0 });
plan.set_root(stacked);
Ok(())
}
}
#[test]
#[cfg(debug_assertions)]
fn a_pass_that_never_settles_is_a_reported_error_and_not_a_plan() {
let text = "Limit 1 offset 0\n Get memory.main.t AS t #0 [a::INTEGER]\n";
let mut plan = Plan::parse(text).expect("a well formed plan");
let error = run(&mut plan, &Context::new(), &[&Restless]).expect_err("it never settles");
assert!(error.message().starts_with("the passes did not settle"), "{}", error.message());
}
#[test]
fn folding_runs_first_so_that_pruning_sees_the_columns_it_freed() {
let text = "Project #1 [CASE WHEN FALSE::BOOLEAN THEN #0.1::INTEGER ELSE #0.0::INTEGER END::INTEGER AS n]\n Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n";
assert_eq!(
optimized(text),
"Project #1 [#0.0::INTEGER AS n]\n Get memory.main.t AS t #0 [a::INTEGER]\n"
);
}
}