use rudb_common::Result;
use rudb_common::rules::Rule;
use rudb_plan::{ColumnBinding, CompareOp, Expr, JoinKind, Node, NodeRef, Plan};
use crate::link::{Linked, absorbed, consumers};
use crate::pass::{Context, Pass};
use crate::walk;
#[derive(Debug)]
pub struct JoinElimination;
impl Pass for JoinElimination {
fn name(&self) -> &'static str {
"join_elimination"
}
fn run(&self, plan: &mut Plan, context: &Context) -> Result<()> {
sweep(plan, context);
Ok(())
}
}
pub(crate) fn sweep(plan: &mut Plan, context: &Context) {
if context.links().is_empty() || !context.allows(Rule::JoinElimination) {
return;
}
let consumers = consumers(plan);
for node in 0..u32::try_from(plan.node_count()).unwrap_or(u32::MAX) {
rewrite(plan, node, &consumers, context);
}
}
fn rewrite(plan: &mut Plan, at: NodeRef, consumers: &[Option<NodeRef>], context: &Context) {
let Node::Join { left, right, kind, conditions, .. } = *plan.node(at) else {
return;
};
let Some(keys) = equated_pair(plan, conditions) else {
return;
};
let sides: &[(NodeRef, NodeRef)] = match kind {
JoinKind::Inner => &[(left, right), (right, left)],
JoinKind::Left | JoinKind::Semi => &[(left, right)],
_ => return,
};
for &(child, parent) in sides {
if !verified(plan, child, parent, keys, context) {
continue;
}
match kind {
JoinKind::Inner if unread(plan, parent, at) && absorbed(plan, consumers, at) => {
stand_in(plan, at, child, consumers);
}
JoinKind::Left => {
if let Node::Join { kind, .. } = plan.node_mut(at) {
*kind = JoinKind::Inner;
}
}
JoinKind::Semi => stand_in(plan, at, child, consumers),
JoinKind::Inner => return,
_ => return,
}
return;
}
}
pub(crate) fn stand_in(
plan: &mut Plan,
at: NodeRef,
child: NodeRef,
consumers: &[Option<NodeRef>],
) {
let Some(above) = consumers.get(at as usize).copied().flatten() else {
if plan.root() == at {
plan.set_root(child);
}
return;
};
let rebuilt: Vec<NodeRef> = plan
.node(above)
.children()
.into_iter()
.flatten()
.map(|was| if was == at { child } else { was })
.collect();
let mut node = plan.node(above).clone();
walk::replace_children(&mut node, &rebuilt);
*plan.node_mut(above) = node;
}
fn verified(
plan: &Plan,
child: NodeRef,
parent: NodeRef,
keys: [ColumnBinding; 2],
context: &Context,
) -> bool {
let Node::Get { table: parent_name, index: parent_index, columns: projected, .. } =
*plan.node(parent)
else {
return false;
};
let [child_key, parent_key] =
match (keys[0].table == parent_index, keys[1].table == parent_index) {
(false, true) => [keys[0], keys[1]],
(true, false) => [keys[1], keys[0]],
_ => return false,
};
let Some(scan) = walk::scan_of(plan, child, child_key.table) else {
return false;
};
let Node::Get { table: child_name, columns: child_columns, .. } = *plan.node(scan) else {
return false;
};
let (Some(child_column), Some(parent_column)) = (
plan.field_list(child_columns).get(child_key.column as usize),
plan.field_list(projected).get(parent_key.column as usize),
) else {
return false;
};
let relationship = (
(plan.string(child_name), child_column.name.as_str()),
(plan.string(parent_name), parent_column.name.as_str()),
);
context
.links()
.iter()
.find(|link| link.between(relationship.0, relationship.1))
.is_some_and(Linked::exactly_one)
}
fn unread(plan: &Plan, parent: NodeRef, join: NodeRef) -> bool {
let mut produced = Vec::new();
indices(plan, parent, &mut produced);
let mut inside = vec![false; plan.node_count()];
mark(plan, parent, &mut inside);
let mut clear = true;
for node in 0..u32::try_from(plan.node_count()).unwrap_or(u32::MAX) {
if node == join || inside.get(node as usize).copied().unwrap_or(false) {
continue;
}
walk::node_columns(plan, node, &mut |_, binding| {
clear &= !produced.contains(&binding.table);
});
}
clear
}
fn indices(plan: &Plan, at: NodeRef, found: &mut Vec<u32>) {
if let Some(outputs) = walk::outputs(plan, at) {
for (binding, _) in outputs {
if !found.contains(&binding.table) {
found.push(binding.table);
}
}
}
for child in plan.node(at).children().into_iter().flatten() {
indices(plan, child, found);
}
}
fn mark(plan: &Plan, at: NodeRef, inside: &mut [bool]) {
if let Some(slot) = inside.get_mut(at as usize) {
*slot = true;
}
for child in plan.node(at).children().into_iter().flatten() {
mark(plan, child, inside);
}
}
fn equated_pair(plan: &Plan, conditions: rudb_plan::Slice) -> Option<[ColumnBinding; 2]> {
let [condition] = plan.expr_list(conditions) else {
return None;
};
let Expr::Compare { op: CompareOp::Equal, left, right } = *plan.expr(*condition) else {
return None;
};
match (plan.expr(left), plan.expr(right)) {
(&Expr::Column(left), &Expr::Column(right)) => Some([left, right]),
_ => None,
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use rudb_common::rules::{Rule, Rules};
use rudb_plan::Plan;
use super::JoinElimination;
use crate::link::Linked;
use crate::pass::{Context, Pass};
fn context(links: Vec<Linked>) -> Context {
let mut context = Context::new();
context.relate(Arc::new(links));
context
}
fn verified() -> Vec<Linked> {
vec![Linked::verified("orders", "o_custkey", "customer", "c_custkey")]
}
fn joined(kind: &str, projected: &str) -> Plan {
let text = format!(
"Project #2 [{projected}]\n \
Join {kind} on=[(#0.1::BIGINT = #1.0::BIGINT)::BOOLEAN]\n \
Get memory.main.orders AS orders #0 [o_orderkey::BIGINT, o_custkey::BIGINT]\n \
Get memory.main.customer AS customer #1 [c_custkey::BIGINT, c_name::VARCHAR]\n"
);
Plan::parse(&text).unwrap_or_else(|error| panic!("{text} did not parse: {error}"))
}
fn rewritten(plan: &mut Plan, context: &Context) -> String {
JoinElimination.run(plan, context).expect("the pass does not fail");
plan.to_string()
}
#[test]
fn an_inner_join_nobody_reads_the_parent_of_is_deleted() {
let mut plan = joined("INNER", "#0.0::BIGINT AS k");
let text = rewritten(&mut plan, &context(verified()));
assert!(!text.contains("Join"), "the join does nothing to the row set: {text}");
assert!(text.contains("orders"), "and the child is what is left: {text}");
assert!(!text.contains("customer"), "the parent scan went with it: {text}");
}
#[test]
fn a_parent_column_the_query_projects_keeps_the_join() {
let mut plan = joined("INNER", "#1.1::VARCHAR AS n");
let text = rewritten(&mut plan, &context(verified()));
assert!(text.contains("Join INNER"), "the parent's name is in the answer: {text}");
}
#[test]
fn a_relationship_with_only_the_uniqueness_certificate_licenses_nothing() {
let built = vec![Linked::built("orders", "o_custkey", "customer", "c_custkey")];
let mut plan = joined("INNER", "#0.0::BIGINT AS k");
let text = rewritten(&mut plan, &context(built));
assert!(text.contains("Join INNER"), "at most one is not exactly one: {text}");
}
#[test]
fn a_left_join_over_a_total_relationship_becomes_an_inner_join() {
let mut plan = joined("LEFT", "#1.1::VARCHAR AS n");
let text = rewritten(&mut plan, &context(verified()));
assert!(text.contains("Join INNER"), "no row is padded, so nothing is preserved: {text}");
assert!(text.contains("customer"), "the parent's column is still in the answer: {text}");
}
#[test]
fn a_semi_join_over_a_total_relationship_keeps_every_child_row() {
let text = "Project #2 [#0.0::BIGINT AS k]\n \
Join SEMI on=[(#0.1::BIGINT = #1.0::BIGINT)::BOOLEAN]\n \
Get memory.main.orders AS orders #0 [o_orderkey::BIGINT, o_custkey::BIGINT]\n \
Get memory.main.customer AS customer #1 [c_custkey::BIGINT]\n";
let mut plan = Plan::parse(text).expect("it parses");
let text = rewritten(&mut plan, &context(verified()));
assert!(!text.contains("Join"), "EXISTS is true for every child row: {text}");
assert!(text.contains("orders"), "which leaves the child: {text}");
}
#[test]
fn the_relationship_has_to_be_the_way_round_it_was_declared() {
let backwards = vec![Linked::verified("customer", "c_custkey", "orders", "o_custkey")];
let mut plan = joined("INNER", "#0.0::BIGINT AS k");
let text = rewritten(&mut plan, &context(backwards));
assert!(text.contains("Join INNER"), "the direction is part of the fact: {text}");
}
#[test]
fn a_parent_behind_a_filter_is_not_the_table_the_certificate_is_about() {
let text = "Project #3 [#0.0::BIGINT AS k]\n \
Join INNER on=[(#0.1::BIGINT = #1.0::BIGINT)::BOOLEAN]\n \
Get memory.main.orders AS orders #0 [o_orderkey::BIGINT, o_custkey::BIGINT]\n \
Filter (#1.0::BIGINT > 5::BIGINT)::BOOLEAN\n \
Get memory.main.customer AS customer #1 [c_custkey::BIGINT]\n";
let mut plan = Plan::parse(text).expect("it parses");
let text = rewritten(&mut plan, &context(verified()));
assert!(text.contains("Join INNER"), "the filter drops child rows too: {text}");
}
#[test]
fn the_rule_turns_the_whole_pass_off() {
let mut rules = Rules::new();
rules.set(Rule::JoinElimination, false);
let mut context = context(verified());
context.govern(rules);
let mut plan = joined("INNER", "#0.0::BIGINT AS k");
let text = rewritten(&mut plan, &context);
assert!(text.contains("Join INNER"), "the switch is what the per rule table needs: {text}");
}
#[test]
fn running_the_pass_twice_gives_the_same_plan() {
let context = context(verified());
let mut plan = joined("INNER", "#0.0::BIGINT AS k");
let once = rewritten(&mut plan, &context);
let twice = rewritten(&mut plan, &context);
assert_eq!(once, twice, "a deleted join stays deleted and nothing else goes");
}
}