use std::collections::HashMap;
use rudb_common::{Result, Value};
use rudb_plan::{
BuildSide, ColumnBinding, CompareOp, Expr, ExprRef, JoinKind, Node, NodeRef, Plan, Slice,
};
use crate::domain::remap;
use crate::pass::{Context, Pass, top_down};
use crate::tables::{TableSet, produced};
use crate::walk;
#[derive(Debug, Clone, Copy)]
pub struct Deliminator;
impl Pass for Deliminator {
fn name(&self) -> &'static str {
"deliminator"
}
fn run(&self, plan: &mut Plan, _context: &Context) -> Result<()> {
remove(plan);
Ok(())
}
}
pub fn remove(plan: &mut Plan) {
for node in top_down(plan) {
let Some(found) = matched(plan, node).or_else(|| flattened(plan, node)) else {
continue;
};
let held = plan.expr_list(found.conditions).to_vec();
let moved: Vec<ExprRef> =
held.into_iter().map(|condition| remap(plan, condition, &found.moved)).collect();
let conditions = plan.add_expr_list(&moved);
*plan.node_mut(node) = Node::Join {
left: found.left,
right: found.right,
kind: found.kind,
conditions,
build: BuildSide::default(),
};
}
}
struct Found {
left: NodeRef,
right: NodeRef,
conditions: Slice,
kind: JoinKind,
moved: HashMap<ColumnBinding, ColumnBinding>,
}
fn matched(plan: &Plan, node: NodeRef) -> Option<Found> {
let Node::Filter { input, predicate } = *plan.node(node) else {
return None;
};
let (kind, tested) = asked(plan, predicate)?;
let Node::Join { left, right, kind: JoinKind::Single, conditions, .. } = *plan.node(input)
else {
return None;
};
let Node::Project { input: distinct, index: marker, exprs, .. } = *plan.node(right) else {
return None;
};
if tested != ColumnBinding::new(marker, 0) {
return None;
}
let projected = plan.expr_list(exprs).to_vec();
let (&flag, carried) = projected.split_first()?;
let Expr::Constant(value) = *plan.expr(flag) else {
return None;
};
if matches!(plan.value(value), Value::Null) {
return None;
}
let Node::Aggregate { input: answers, index: distinct_index, groups, aggregates } =
*plan.node(distinct)
else {
return None;
};
if !plan.expr_list(aggregates).is_empty() || !columns(plan, carried, distinct_index) {
return None;
}
let Node::Join { left: domain, right: inner, kind: inside, conditions: correlated, .. } =
*plan.node(answers)
else {
return None;
};
if !matches!(inside, JoinKind::Inner | JoinKind::Semi) {
return None;
}
let Node::Aggregate { index: domain_index, groups: keys, aggregates: none, .. } =
*plan.node(domain)
else {
return None;
};
let grouped = plan.expr_list(groups).to_vec();
if !plan.expr_list(none).is_empty() || !columns(plan, &grouped, domain_index) {
return None;
}
let outer = produced(plan, left);
let mut moved = HashMap::new();
let mut keyed = Vec::new();
for (position, &key) in plan.expr_list(keys).iter().enumerate() {
let Expr::Column(binding) = *plan.expr(key) else {
return None;
};
if !outer.contains(binding.table) {
return None;
}
moved.insert(ColumnBinding::new(domain_index, at(position)?), binding);
keyed.push(binding);
}
if keyed.len() != grouped.len() || keyed.len() != carried.len() {
return None;
}
if !lines_up(plan, conditions, marker, &keyed) {
return None;
}
if reads(plan, inner, &outer) || read_above(plan, marker, node, input) {
return None;
}
Some(Found { left, right: inner, conditions: correlated, kind, moved })
}
fn flattened(plan: &Plan, node: NodeRef) -> Option<Found> {
let Node::Filter { input, predicate } = *plan.node(node) else {
return None;
};
let (kind, tested) = asked(plan, predicate)?;
let Node::Join { left, right, kind: JoinKind::Single, conditions, .. } = *plan.node(input)
else {
return None;
};
let Node::Project { input: distinct, index: marker, exprs, .. } = *plan.node(right) else {
return None;
};
if tested != ColumnBinding::new(marker, 0) {
return None;
}
let projected = plan.expr_list(exprs).to_vec();
let (&flag, carried) = projected.split_first()?;
let Expr::Constant(value) = *plan.expr(flag) else {
return None;
};
if matches!(plan.value(value), Value::Null) {
return None;
}
let Node::Aggregate { input: answers, index: distinct_index, groups, aggregates } =
*plan.node(distinct)
else {
return None;
};
if !plan.expr_list(aggregates).is_empty() || !columns(plan, carried, distinct_index) {
return None;
}
let grouped = plan.expr_list(groups).to_vec();
if grouped.len() != carried.len() {
return None;
}
let mut moved = HashMap::new();
for (position, &group) in grouped.iter().enumerate() {
let Expr::Column(binding) = *plan.expr(group) else {
return None;
};
moved.insert(ColumnBinding::new(marker, at(position + 1)?), binding);
}
if !covers(plan, conditions, marker, carried.len())
|| reads(plan, answers, &produced(plan, left))
{
return None;
}
if read_above(plan, marker, node, input)
|| read_beside(plan, distinct_index, [node, input, right])
{
return None;
}
Some(Found { left, right: answers, conditions, kind, moved })
}
fn asked(plan: &Plan, predicate: ExprRef) -> Option<(JoinKind, ColumnBinding)> {
if let Expr::Function { name, args } = *plan.expr(predicate) {
if plan.string(name) != "not" {
return None;
}
let [only] = *plan.expr_list(args) else {
return None;
};
return match asked(plan, only)? {
(JoinKind::Semi, binding) => Some((JoinKind::Anti, binding)),
_ => None,
};
}
let Expr::Compare { op: CompareOp::DistinctFrom, left, right } = *plan.expr(predicate) else {
return None;
};
let Expr::Column(binding) = *plan.expr(left) else {
return None;
};
let Expr::Constant(value) = *plan.expr(right) else {
return None;
};
matches!(plan.value(value), Value::Null).then_some((JoinKind::Semi, binding))
}
fn columns(plan: &Plan, exprs: &[ExprRef], index: u32) -> bool {
exprs.iter().enumerate().all(|(position, &expr)| {
let Expr::Column(binding) = *plan.expr(expr) else {
return false;
};
at(position).is_some_and(|column| binding == ColumnBinding::new(index, column))
})
}
fn lines_up(plan: &Plan, conditions: Slice, marker: u32, keyed: &[ColumnBinding]) -> bool {
let held = plan.expr_list(conditions);
if held.len() != keyed.len() {
return false;
}
let mut seen = vec![false; keyed.len()];
for &condition in held {
let Expr::Compare { op: CompareOp::NotDistinctFrom, left, right } = *plan.expr(condition)
else {
return false;
};
let Expr::Column(here) = *plan.expr(left) else {
return false;
};
let Expr::Column(there) = *plan.expr(right) else {
return false;
};
let (outer, carried) = if there.table == marker { (here, there) } else { (there, here) };
if carried.table != marker || carried.column == 0 {
return false;
}
let Ok(position) = usize::try_from(carried.column - 1) else {
return false;
};
if position >= keyed.len() || seen[position] || keyed[position] != outer {
return false;
}
seen[position] = true;
}
seen.into_iter().all(|found| found)
}
fn covers(plan: &Plan, conditions: Slice, marker: u32, carried: usize) -> bool {
let held = plan.expr_list(conditions);
if held.len() != carried {
return false;
}
let mut seen = vec![false; carried];
for &condition in held {
let Expr::Compare { op: CompareOp::Equal | CompareOp::NotDistinctFrom, left, right } =
*plan.expr(condition)
else {
return false;
};
let Expr::Column(here) = *plan.expr(left) else {
return false;
};
let Expr::Column(there) = *plan.expr(right) else {
return false;
};
let named = match (here.table == marker, there.table == marker) {
(true, false) => here,
(false, true) => there,
_ => return false,
};
if named.column == 0 {
return false;
}
let Ok(position) = usize::try_from(named.column - 1) else {
return false;
};
if position >= carried || seen[position] {
return false;
}
seen[position] = true;
}
seen.into_iter().all(|found| found)
}
fn read_beside(plan: &Plan, distinct: u32, going: [NodeRef; 3]) -> bool {
let mut found = false;
for at in top_down(plan) {
if going.contains(&at) {
continue;
}
walk::node_columns(plan, at, &mut |_, binding| found |= binding.table == distinct);
}
found
}
fn reads(plan: &Plan, at: NodeRef, outer: &TableSet) -> bool {
let mut found = false;
walk::node_columns(plan, at, &mut |_, binding| found |= outer.contains(binding.table));
found || plan.node(at).children().into_iter().flatten().any(|child| reads(plan, child, outer))
}
fn read_above(plan: &Plan, marker: u32, filter: NodeRef, join: NodeRef) -> bool {
let mut found = false;
for at in top_down(plan) {
if at == filter || at == join {
continue;
}
walk::node_columns(plan, at, &mut |_, binding| found |= binding.table == marker);
}
found
}
fn at(position: usize) -> Option<u32> {
u32::try_from(position).ok()
}
#[cfg(test)]
mod tests {
use rudb_plan::Plan;
use super::remove;
fn removed(text: &str) -> String {
let mut plan =
Plan::parse(text).unwrap_or_else(|error| panic!("{text} did not parse: {error}"));
remove(&mut plan);
plan.validate().unwrap_or_else(|error| panic!("{text} did not stay valid: {error}"));
plan.to_string()
}
fn existence(head: &str) -> String {
format!(
concat!(
"Filter {head}\n",
" Join SINGLE on=[(#0.0::BIGINT IS NOT DISTINCT FROM #5.1::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Project #5 [TRUE::BOOLEAN AS exists, #4.0::BIGINT AS __correlated_1]\n",
" Aggregate #4 groups=[#3.0::BIGINT] aggregates=[]\n",
" Join SEMI on=[(#1.0::BIGINT = #3.0::BIGINT)::BOOLEAN]\n",
" Aggregate #3 groups=[#0.0::BIGINT] aggregates=[]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
),
head = head
)
}
const TESTED: &str = "(#5.0::BOOLEAN IS DISTINCT FROM NULL::BOOLEAN)::BOOLEAN";
#[test]
fn an_existence_test_over_a_domain_becomes_a_semi_join_against_the_outer_side() {
assert_eq!(
removed(&existence(TESTED)),
concat!(
"Join SEMI on=[(#1.0::BIGINT = #0.0::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
)
);
}
#[test]
fn the_same_test_with_not_in_front_of_it_becomes_an_anti_join() {
assert_eq!(
removed(&existence(&format!("not({TESTED})::BOOLEAN"))),
concat!(
"Join ANTI on=[(#1.0::BIGINT = #0.0::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
)
);
}
#[test]
fn a_filter_on_something_other_than_the_marker_is_left_alone() {
let text = existence("(#0.0::BIGINT > 3::BIGINT)::BOOLEAN");
assert_eq!(removed(&text), text);
}
#[test]
fn a_marker_column_read_above_the_filter_stops_the_collapse() {
let text = concat!(
"Project #6 [#5.1::BIGINT AS k]\n",
" Filter (#5.0::BOOLEAN IS DISTINCT FROM NULL::BOOLEAN)::BOOLEAN\n",
" Join SINGLE on=[(#0.0::BIGINT IS NOT DISTINCT FROM #5.1::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Project #5 [TRUE::BOOLEAN AS exists, #4.0::BIGINT AS __correlated_1]\n",
" Aggregate #4 groups=[#3.0::BIGINT] aggregates=[]\n",
" Join SEMI on=[(#1.0::BIGINT = #3.0::BIGINT)::BOOLEAN]\n",
" Aggregate #3 groups=[#0.0::BIGINT] aggregates=[]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
);
assert_eq!(removed(text), text);
}
#[test]
fn a_real_aggregate_under_the_marker_is_not_a_domain_and_is_left_alone() {
let text = concat!(
"Filter (#5.0::BOOLEAN IS DISTINCT FROM NULL::BOOLEAN)::BOOLEAN\n",
" Join SINGLE on=[(#0.0::BIGINT IS NOT DISTINCT FROM #5.1::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Project #5 [TRUE::BOOLEAN AS exists, #4.0::BIGINT AS __correlated_1]\n",
" Aggregate #4 groups=[#3.0::BIGINT] aggregates=[count_star()::BIGINT]\n",
" Join SEMI on=[(#1.0::BIGINT = #3.0::BIGINT)::BOOLEAN]\n",
" Aggregate #3 groups=[#0.0::BIGINT] aggregates=[]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
);
assert_eq!(removed(text), text);
}
#[test]
fn a_domain_grouped_on_an_expression_is_left_alone() {
let text = concat!(
"Filter (#5.0::BOOLEAN IS DISTINCT FROM NULL::BOOLEAN)::BOOLEAN\n",
" Join SINGLE on=[(#0.0::BIGINT IS NOT DISTINCT FROM #5.1::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Project #5 [TRUE::BOOLEAN AS exists, #4.0::BIGINT AS __correlated_1]\n",
" Aggregate #4 groups=[#3.0::BIGINT] aggregates=[]\n",
" Join SEMI on=[(#1.0::BIGINT = #3.0::BIGINT)::BOOLEAN]\n",
" Aggregate #3 groups=[\"+\"(#0.0::BIGINT, 1::BIGINT)::BIGINT] aggregates=[]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
);
assert_eq!(removed(text), text);
}
#[test]
fn a_subquery_relation_that_still_reads_the_outer_side_is_left_alone() {
let text = concat!(
"Filter (#5.0::BOOLEAN IS DISTINCT FROM NULL::BOOLEAN)::BOOLEAN\n",
" Join SINGLE on=[(#0.0::BIGINT IS NOT DISTINCT FROM #5.1::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Project #5 [TRUE::BOOLEAN AS exists, #4.0::BIGINT AS __correlated_1]\n",
" Aggregate #4 groups=[#3.0::BIGINT] aggregates=[]\n",
" Join SEMI on=[(#1.0::BIGINT = #3.0::BIGINT)::BOOLEAN]\n",
" Aggregate #3 groups=[#0.0::BIGINT] aggregates=[]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Filter (#1.0::BIGINT > #0.1::BIGINT)::BOOLEAN\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
);
assert_eq!(removed(text), text);
}
#[test]
fn a_join_back_that_leaves_a_key_unjoined_is_left_alone() {
let text = concat!(
"Filter (#5.0::BOOLEAN IS DISTINCT FROM NULL::BOOLEAN)::BOOLEAN\n",
" Join SINGLE on=[(#0.0::BIGINT IS NOT DISTINCT FROM #5.1::BIGINT)::BOOLEAN, \
(#0.0::BIGINT IS NOT DISTINCT FROM #5.1::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Project #5 [TRUE::BOOLEAN AS exists, #4.0::BIGINT AS __correlated_1, \
#4.1::BIGINT AS __correlated_2]\n",
" Aggregate #4 groups=[#3.0::BIGINT, #3.1::BIGINT] aggregates=[]\n",
" Join SEMI on=[(#1.0::BIGINT = #3.0::BIGINT)::BOOLEAN]\n",
" Aggregate #3 groups=[#0.0::BIGINT, #0.1::BIGINT] aggregates=[]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
);
assert_eq!(removed(text), text);
}
fn grouped(head: &str) -> String {
format!(
concat!(
"Filter {head}\n",
" Join SINGLE on=[(#5.1::BIGINT = #0.0::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Project #5 [TRUE::BOOLEAN AS exists, #4.0::BIGINT AS __correlated_1]\n",
" Aggregate #4 groups=[#1.0::BIGINT] aggregates=[]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
),
head = head
)
}
#[test]
fn an_existence_test_over_a_grouping_becomes_a_semi_join_against_the_relation() {
assert_eq!(
removed(&grouped(TESTED)),
concat!(
"Join SEMI on=[(#1.0::BIGINT = #0.0::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
)
);
}
#[test]
fn the_same_grouping_with_not_in_front_of_the_test_becomes_an_anti_join() {
assert_eq!(
removed(&grouped(&format!("not({TESTED})::BOOLEAN"))),
concat!(
"Join ANTI on=[(#1.0::BIGINT = #0.0::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
)
);
}
#[test]
fn a_grouping_with_a_real_aggregate_in_it_is_left_alone() {
let text = concat!(
"Filter (#5.0::BOOLEAN IS DISTINCT FROM NULL::BOOLEAN)::BOOLEAN\n",
" Join SINGLE on=[(#5.1::BIGINT = #0.0::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Project #5 [TRUE::BOOLEAN AS exists, #4.0::BIGINT AS __correlated_1]\n",
" Aggregate #4 groups=[#1.0::BIGINT] aggregates=[count_star()::BIGINT]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
);
assert_eq!(removed(text), text);
}
#[test]
fn a_grouping_on_an_expression_is_left_alone() {
let text = concat!(
"Filter (#5.0::BOOLEAN IS DISTINCT FROM NULL::BOOLEAN)::BOOLEAN\n",
" Join SINGLE on=[(#5.1::BIGINT = #0.0::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Project #5 [TRUE::BOOLEAN AS exists, #4.0::BIGINT AS __correlated_1]\n",
" Aggregate #4 groups=[abs(#1.0::BIGINT)::BIGINT] aggregates=[]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
);
assert_eq!(removed(text), text);
}
#[test]
fn a_join_back_that_is_not_an_equality_is_left_alone() {
let text = concat!(
"Filter (#5.0::BOOLEAN IS DISTINCT FROM NULL::BOOLEAN)::BOOLEAN\n",
" Join SINGLE on=[(#5.1::BIGINT > #0.0::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Project #5 [TRUE::BOOLEAN AS exists, #4.0::BIGINT AS __correlated_1]\n",
" Aggregate #4 groups=[#1.0::BIGINT] aggregates=[]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
);
assert_eq!(removed(text), text);
}
#[test]
fn a_grouped_column_the_join_back_does_not_name_is_left_alone() {
let text = concat!(
"Filter (#5.0::BOOLEAN IS DISTINCT FROM NULL::BOOLEAN)::BOOLEAN\n",
" Join SINGLE on=[(#5.1::BIGINT = #0.0::BIGINT)::BOOLEAN, \
(#5.1::BIGINT = #0.1::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Project #5 [TRUE::BOOLEAN AS exists, #4.0::BIGINT AS __correlated_1, \
#4.1::BIGINT AS __correlated_2]\n",
" Aggregate #4 groups=[#1.0::BIGINT, #1.1::BIGINT] aggregates=[]\n",
" Get memory.main.u AS u #1 [k::BIGINT, j::BIGINT]\n",
);
assert_eq!(removed(text), text);
}
#[test]
fn a_grouped_column_read_above_the_filter_stops_the_collapse() {
let text = concat!(
"Project #6 [#5.1::BIGINT AS k]\n",
" Filter (#5.0::BOOLEAN IS DISTINCT FROM NULL::BOOLEAN)::BOOLEAN\n",
" Join SINGLE on=[(#5.1::BIGINT = #0.0::BIGINT)::BOOLEAN]\n",
" Get memory.main.t AS t #0 [a::BIGINT, b::BIGINT]\n",
" Project #5 [TRUE::BOOLEAN AS exists, #4.0::BIGINT AS __correlated_1]\n",
" Aggregate #4 groups=[#1.0::BIGINT] aggregates=[]\n",
" Get memory.main.u AS u #1 [k::BIGINT]\n",
);
assert_eq!(removed(text), text);
}
}