use rudb_plan::{ColumnBinding, CompareOp, Expr, ExprRef, JoinKind, Plan};
use crate::filter::kept;
use crate::tables::{TableSet, Tables};
use crate::walk;
pub(crate) fn within(plan: &mut Plan, parts: &mut Vec<ExprRef>) {
let pairs = equalities(plan, parts);
if pairs.is_empty() {
return;
}
let sources = parts.clone();
for (one, other) in pairs {
for &source in &sources {
for (from, to) in [(one, other), (other, one)] {
let Some(made) = copy(plan, source, from, to) else { continue };
if !parts.iter().any(|&held| walk::same(plan, held, made)) {
parts.push(made);
}
}
}
}
}
pub(crate) fn across(
plan: &mut Plan,
tables: &mut Tables,
kind: JoinKind,
conditions: &[ExprRef],
pending: &[ExprRef],
below: (&TableSet, &TableSet),
) -> (Vec<ExprRef>, Vec<ExprRef>) {
let drop = droppable(kind);
let empty = (Vec::new(), Vec::new());
if !drop.0 && !drop.1 {
return empty;
}
let pairs = equalities(plan, conditions);
if pairs.is_empty() {
return empty;
}
let keep = kept(kind);
let mut sources = Vec::new();
for &part in pending {
let read = tables.of(plan, part);
let held = (keep.0 && read.is_subset_of(below.0)) || (keep.1 && read.is_subset_of(below.1));
if held {
sources.push(part);
}
}
let (mut to_left, mut to_right) = empty;
for (one, other) in pairs {
for &source in &sources {
for (from, to) in [(one, other), (other, one)] {
let Expr::Column(binding) = *plan.expr(to) else { continue };
let (allowed, into) = if below.0.contains(binding.table) {
(drop.0, &mut to_left)
} else if below.1.contains(binding.table) {
(drop.1, &mut to_right)
} else {
continue;
};
if !allowed {
continue;
}
let Some(made) = copy(plan, source, from, to) else { continue };
let seen = |plan: &Plan, held: &[ExprRef]| {
held.iter().any(|&held| walk::same(plan, held, made))
};
if !seen(plan, pending) && !seen(plan, conditions) && !seen(plan, into) {
into.push(made);
}
}
}
}
(to_left, to_right)
}
fn droppable(kind: JoinKind) -> (bool, bool) {
match kind {
JoinKind::Inner => (true, true),
JoinKind::Left | JoinKind::Semi | JoinKind::Anti | JoinKind::Single => (false, true),
JoinKind::Right => (true, false),
JoinKind::Full | JoinKind::Positional => (false, false),
}
}
fn equalities(plan: &Plan, parts: &[ExprRef]) -> Vec<(ExprRef, ExprRef)> {
let mut pairs = Vec::new();
for &part in parts {
let Expr::Compare { op: CompareOp::Equal, left, right } = *plan.expr(part) else {
continue;
};
let (Expr::Column(one), Expr::Column(other)) = (plan.expr(left), plan.expr(right)) else {
continue;
};
if one != other && plan.expr_type(left) == plan.expr_type(right) {
pairs.push((left, right));
}
}
pairs
}
fn copy(plan: &mut Plan, source: ExprRef, from: ExprRef, to: ExprRef) -> Option<ExprRef> {
let Expr::Column(from) = *plan.expr(from) else { return None };
if !only(plan, source, from) || walk::volatile(plan, source) {
return None;
}
Some(replace(plan, source, from, to))
}
fn only(plan: &Plan, expr: ExprRef, binding: ColumnBinding) -> bool {
let mut found = false;
let mut other = false;
walk::columns(plan, expr, &mut |read| {
if read == binding {
found = true;
} else {
other = true;
}
});
found && !other
}
fn replace(plan: &mut Plan, expr: ExprRef, from: ColumnBinding, to: ExprRef) -> ExprRef {
if matches!(*plan.expr(expr), Expr::Column(binding) if binding == from) {
return to;
}
walk::rebuild(plan, expr, &mut |plan, child| replace(plan, child, from, to))
}
#[cfg(test)]
mod tests {
use crate::filter::FilterPushdown;
use crate::pass::{Context, Pass};
use rudb_plan::Plan;
fn pushed(text: &str) -> String {
let mut plan =
Plan::parse(text).unwrap_or_else(|error| panic!("{text} did not parse: {error}"));
let mut once = String::new();
for _ in 0..2 {
FilterPushdown
.run(&mut plan, &Context::new())
.unwrap_or_else(|error| panic!("{text} did not push: {error}"));
plan.validate().unwrap_or_else(|error| panic!("{text} pushed to a bad plan: {error}"));
if once.is_empty() {
once = plan.to_string();
}
}
assert_eq!(once, plan.to_string(), "{text} did not print the same the second time");
once
}
#[test]
fn an_equality_between_two_columns_of_one_table_copies_a_predicate_across_it() {
let before = "\
Filter ((#0.0::INTEGER = #0.1::INTEGER)::BOOLEAN AND (#0.0::INTEGER > 5::INTEGER)::BOOLEAN)::BOOLEAN
Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]
";
let after = "\
Filter ((#0.0::INTEGER = #0.1::INTEGER)::BOOLEAN AND (#0.0::INTEGER > 5::INTEGER)::BOOLEAN AND (#0.1::INTEGER > 5::INTEGER)::BOOLEAN)::BOOLEAN
Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]
";
assert_eq!(pushed(before), after);
}
#[test]
fn an_equality_over_a_cross_product_sends_the_predicate_into_both_sides() {
let before = "\
Filter ((#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN AND (#0.0::INTEGER > 5::INTEGER)::BOOLEAN)::BOOLEAN
CrossProduct
Get memory.main.t AS a #0 [a::INTEGER]
Get memory.main.t AS b #1 [a::INTEGER]
";
let after = "\
Filter (#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN
CrossProduct
Filter (#0.0::INTEGER > 5::INTEGER)::BOOLEAN
Get memory.main.t AS a #0 [a::INTEGER]
Filter (#1.0::INTEGER > 5::INTEGER)::BOOLEAN
Get memory.main.t AS b #1 [a::INTEGER]
";
assert_eq!(pushed(before), after);
}
#[test]
fn a_join_condition_sends_a_predicate_over_one_side_into_the_other() {
let before = "\
Filter (#0.0::INTEGER > 5::INTEGER)::BOOLEAN
Join INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]
Get memory.main.t AS a #0 [a::INTEGER]
Get memory.main.t AS b #1 [a::INTEGER]
";
let after = "\
Join INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]
Filter (#0.0::INTEGER > 5::INTEGER)::BOOLEAN
Get memory.main.t AS a #0 [a::INTEGER]
Filter (#1.0::INTEGER > 5::INTEGER)::BOOLEAN
Get memory.main.t AS b #1 [a::INTEGER]
";
assert_eq!(pushed(before), after);
}
#[test]
fn a_left_join_takes_the_derived_predicate_on_the_side_it_pads() {
let before = "\
Filter (#0.0::INTEGER > 5::INTEGER)::BOOLEAN
Join LEFT on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]
Get memory.main.t AS a #0 [a::INTEGER]
Get memory.main.t AS b #1 [a::INTEGER]
";
let after = "\
Join LEFT on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]
Filter (#0.0::INTEGER > 5::INTEGER)::BOOLEAN
Get memory.main.t AS a #0 [a::INTEGER]
Filter (#1.0::INTEGER > 5::INTEGER)::BOOLEAN
Get memory.main.t AS b #1 [a::INTEGER]
";
assert_eq!(pushed(before), after);
}
#[test]
fn a_condition_over_the_padded_side_of_a_left_join_derives_nothing_for_the_other() {
let before = "\
Join LEFT on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN, (#1.0::INTEGER > 5::INTEGER)::BOOLEAN]
Get memory.main.t AS a #0 [a::INTEGER]
Get memory.main.t AS b #1 [a::INTEGER]
";
assert_eq!(pushed(before), before);
}
#[test]
fn a_predicate_written_into_the_on_clause_is_not_a_source() {
let before = "\
Join LEFT on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN, (#0.0::INTEGER > 5::INTEGER)::BOOLEAN]
Get memory.main.t AS a #0 [a::INTEGER]
Get memory.main.t AS b #1 [a::INTEGER]
";
assert_eq!(pushed(before), before);
}
#[test]
fn a_full_join_derives_nothing_because_both_of_its_sides_come_out_padded() {
let before = "\
Filter (#0.0::INTEGER IS NOT DISTINCT FROM NULL::\"NULL\")::BOOLEAN
Join FULL on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]
Get memory.main.t AS a #0 [a::INTEGER]
Get memory.main.t AS b #1 [a::INTEGER]
";
assert_eq!(pushed(before), before);
}
#[test]
fn an_equality_between_an_expression_and_a_column_derives_nothing() {
let before = "\
Filter (#0.0::INTEGER > 5::INTEGER)::BOOLEAN
Join INNER on=[(\"+\"(#0.0::INTEGER, 1::INTEGER)::INTEGER = #1.0::INTEGER)::BOOLEAN]
Get memory.main.t AS a #0 [a::INTEGER]
Get memory.main.t AS b #1 [a::INTEGER]
";
let after = "\
Join INNER on=[(\"+\"(#0.0::INTEGER, 1::INTEGER)::INTEGER = #1.0::INTEGER)::BOOLEAN]
Filter (#0.0::INTEGER > 5::INTEGER)::BOOLEAN
Get memory.main.t AS a #0 [a::INTEGER]
Get memory.main.t AS b #1 [a::INTEGER]
";
assert_eq!(pushed(before), after);
}
#[test]
fn a_predicate_reading_a_second_column_derives_nothing() {
let before = "\
Filter (#0.0::INTEGER > #0.1::INTEGER)::BOOLEAN
Join INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]
Get memory.main.t AS a #0 [a::INTEGER, b::INTEGER]
Get memory.main.t AS b #1 [a::INTEGER]
";
let after = "\
Join INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]
Filter (#0.0::INTEGER > #0.1::INTEGER)::BOOLEAN
Get memory.main.t AS a #0 [a::INTEGER, b::INTEGER]
Get memory.main.t AS b #1 [a::INTEGER]
";
assert_eq!(pushed(before), after);
}
#[test]
fn a_derived_predicate_reaches_the_next_table_and_stops_there() {
let before = "\
Filter (#0.0::INTEGER > 5::INTEGER)::BOOLEAN
Join INNER on=[(#1.0::INTEGER = #2.0::INTEGER)::BOOLEAN]
Join INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]
Get memory.main.t AS a #0 [a::INTEGER]
Get memory.main.t AS b #1 [a::INTEGER]
Get memory.main.t AS c #2 [a::INTEGER]
";
let after = "\
Join INNER on=[(#1.0::INTEGER = #2.0::INTEGER)::BOOLEAN]
Join INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]
Filter (#0.0::INTEGER > 5::INTEGER)::BOOLEAN
Get memory.main.t AS a #0 [a::INTEGER]
Filter (#1.0::INTEGER > 5::INTEGER)::BOOLEAN
Get memory.main.t AS b #1 [a::INTEGER]
Get memory.main.t AS c #2 [a::INTEGER]
";
assert_eq!(pushed(before), after);
}
}