use std::collections::HashMap;
use rudb_common::{Field, LogicalType, Value};
use rudb_plan::{
Arm, BuildSide, ColumnBinding, CompareOp, Expr, ExprRef, JoinKind, Node, NodeRef, Plan, Slice,
SortKey, WindowBound, WindowFrame,
};
use crate::tables::{TableSet, produced};
use crate::walk;
struct Key {
binding: ColumnBinding,
expr: ExprRef,
}
struct Pushed {
node: NodeRef,
keys: Vec<ColumnBinding>,
moved: HashMap<ColumnBinding, ColumnBinding>,
}
pub(crate) fn lower(
plan: &mut Plan,
left: NodeRef,
right: NodeRef,
kind: JoinKind,
conditions: Slice,
) -> Option<NodeRef> {
let outer = produced(plan, left);
let keys = read_keys(plan, right, &outer);
if keys.is_empty() {
return None;
}
let group_exprs: Vec<ExprRef> = keys.iter().map(|key| key.expr).collect();
let groups = plan.add_expr_list(&group_exprs);
let aggregates = plan.add_expr_list(&[]);
let index = walk::fresh_index(plan);
let domain = plan.add_node(Node::Aggregate { input: left, index, groups, aggregates });
let pushed = push(plan, right, domain, index, &keys, &outer)?;
let held = plan.expr_list(conditions).to_vec();
let mut all: Vec<ExprRef> =
held.into_iter().map(|condition| remap(plan, condition, &pushed.moved)).collect();
for (key, &carried) in keys.iter().zip(&pushed.keys) {
let ty = plan.expr_type(key.expr).clone();
let span = plan.expr_span(key.expr);
let here = plan.add_expr_at(Expr::Column(key.binding), ty.clone(), span);
let there = plan.add_expr_at(Expr::Column(carried), ty, span);
all.push(plan.add_expr_at(
Expr::Compare { op: CompareOp::NotDistinctFrom, left: here, right: there },
LogicalType::Boolean,
span,
));
}
let conditions = plan.add_expr_list(&all);
Some(plan.add_node(Node::Join {
left,
right: pushed.node,
kind,
conditions,
build: BuildSide::default(),
}))
}
fn read_keys(plan: &Plan, at: NodeRef, outer: &TableSet) -> Vec<Key> {
let mut keys: Vec<Key> = Vec::new();
collect_keys(plan, at, outer, &mut keys);
keys
}
fn collect_keys(plan: &Plan, at: NodeRef, outer: &TableSet, keys: &mut Vec<Key>) {
walk::node_columns(plan, at, &mut |expr, binding| {
if outer.contains(binding.table) && !keys.iter().any(|key| key.binding == binding) {
keys.push(Key { binding, expr });
}
});
for child in plan.node(at).children().into_iter().flatten() {
collect_keys(plan, child, outer, keys);
}
}
fn correlated(plan: &Plan, at: NodeRef, outer: &TableSet) -> bool {
let mut yes = false;
walk::node_columns(plan, at, &mut |_, binding| yes |= outer.contains(binding.table));
yes || plan
.node(at)
.children()
.into_iter()
.flatten()
.any(|child| correlated(plan, child, outer))
}
fn push(
plan: &mut Plan,
at: NodeRef,
domain: NodeRef,
index: u32,
keys: &[Key],
outer: &TableSet,
) -> Option<Pushed> {
if !correlated(plan, at, outer) {
let node = plan.add_node(Node::CrossProduct { left: domain, right: at });
let carried = (0..keys.len())
.map(|position| ColumnBinding::new(index, u32::try_from(position).expect("key count")))
.collect();
return Some(Pushed { node, keys: carried, moved: HashMap::new() });
}
match *plan.node(at) {
Node::Filter { input, predicate } => {
let below = push(plan, input, domain, index, keys, outer)?;
let map = mapping(keys, &below);
let predicate = remap(plan, predicate, &map);
let node = plan.add_node(Node::Filter { input: below.node, predicate });
Some(Pushed { node, keys: below.keys, moved: below.moved })
}
Node::Project { input, index: at_index, exprs, names } => {
let below = push(plan, input, domain, index, keys, outer)?;
let map = mapping(keys, &below);
let held = plan.expr_list(exprs).to_vec();
let mut projected: Vec<ExprRef> =
held.into_iter().map(|expr| remap(plan, expr, &map)).collect();
let mut projected_names = plan.name_list(names).to_vec();
let width = projected.len();
let mut carried = Vec::new();
for (position, (key, &binding)) in keys.iter().zip(&below.keys).enumerate() {
let ty = plan.expr_type(key.expr).clone();
let span = plan.expr_span(key.expr);
projected.push(plan.add_expr_at(Expr::Column(binding), ty, span));
projected_names.push(plan.intern(&format!("__domain_{position}")));
carried.push(ColumnBinding::new(
at_index,
u32::try_from(width + position).expect("projection width"),
));
}
let exprs = plan.add_expr_list(&projected);
let names = plan.add_name_list(&projected_names);
let node =
plan.add_node(Node::Project { input: below.node, index: at_index, exprs, names });
Some(Pushed { node, keys: carried, moved: HashMap::new() })
}
Node::Aggregate { input, index: at_index, groups, aggregates } => {
let below = push(plan, input, domain, index, keys, outer)?;
aggregate(plan, below, at_index, groups, aggregates, domain, index, keys)
}
Node::Distinct { input, on } => {
let below = push(plan, input, domain, index, keys, outer)?;
let map = mapping(keys, &below);
let on = if on.is_empty() {
on
} else {
let held = plan.expr_list(on).to_vec();
let mut kept: Vec<ExprRef> =
held.into_iter().map(|expr| remap(plan, expr, &map)).collect();
for (key, &binding) in keys.iter().zip(&below.keys) {
let ty = plan.expr_type(key.expr).clone();
let span = plan.expr_span(key.expr);
kept.push(plan.add_expr_at(Expr::Column(binding), ty, span));
}
plan.add_expr_list(&kept)
};
let node = plan.add_node(Node::Distinct { input: below.node, on });
Some(Pushed { node, keys: below.keys, moved: below.moved })
}
Node::Sort { input, keys: order } => {
let below = push(plan, input, domain, index, keys, outer)?;
let map = mapping(keys, &below);
let mut ordering: Vec<SortKey> = Vec::new();
for (key, &binding) in keys.iter().zip(&below.keys) {
let ty = plan.expr_type(key.expr).clone();
let span = plan.expr_span(key.expr);
let expr = plan.add_expr_at(Expr::Column(binding), ty, span);
ordering.push(SortKey { expr, descending: false, nulls_first: false });
}
for key in plan.sort_key_list(order).to_vec() {
let expr = remap(plan, key.expr, &map);
ordering.push(SortKey { expr, ..key });
}
let order = plan.add_sort_keys(&ordering);
let node = plan.add_node(Node::Sort { input: below.node, keys: order });
Some(Pushed { node, keys: below.keys, moved: below.moved })
}
Node::Window { input, index: at_index, partition, order, frame, expressions } => {
let below = push(plan, input, domain, index, keys, outer)?;
window(plan, below, at_index, partition, order, frame, expressions, keys)
}
Node::Values { index: at_index, columns, rows } => {
values(plan, at_index, columns, rows, domain, index, keys)
}
Node::CrossProduct { left, right } => {
let empty = plan.add_expr_list(&[]);
sides(plan, left, right, JoinKind::Inner, empty, domain, index, keys, outer)
}
Node::Join {
left,
right,
kind: kind @ (JoinKind::Inner | JoinKind::Left),
conditions,
..
} => sides(plan, left, right, kind, conditions, domain, index, keys, outer),
_ => None,
}
}
#[allow(clippy::too_many_arguments)]
fn window(
plan: &mut Plan,
below: Pushed,
at_index: u32,
partition: Slice,
order: Slice,
frame: WindowFrame,
expressions: Slice,
keys: &[Key],
) -> Option<Pushed> {
let map = mapping(keys, &below);
let mut divided = Vec::new();
for (key, &binding) in keys.iter().zip(&below.keys) {
let ty = plan.expr_type(key.expr).clone();
let span = plan.expr_span(key.expr);
divided.push(plan.add_expr_at(Expr::Column(binding), ty, span));
}
for expr in plan.expr_list(partition).to_vec() {
divided.push(remap(plan, expr, &map));
}
let partition = plan.add_expr_list(÷d);
let mut ordering = Vec::new();
for key in plan.sort_key_list(order).to_vec() {
let expr = remap(plan, key.expr, &map);
ordering.push(SortKey { expr, ..key });
}
let order = plan.add_sort_keys(&ordering);
let frame = WindowFrame {
start: bound(plan, frame.start, &map),
end: bound(plan, frame.end, &map),
..frame
};
let held = plan.expr_list(expressions).to_vec();
let rewritten: Vec<ExprRef> = held.into_iter().map(|expr| remap(plan, expr, &map)).collect();
let expressions = plan.add_expr_list(&rewritten);
let node = plan.add_node(Node::Window {
input: below.node,
index: at_index,
partition,
order,
frame,
expressions,
});
Some(Pushed { node, keys: below.keys, moved: below.moved })
}
fn bound(
plan: &mut Plan,
at: WindowBound,
map: &HashMap<ColumnBinding, ColumnBinding>,
) -> WindowBound {
match at {
WindowBound::Preceding(expr) => WindowBound::Preceding(remap(plan, expr, map)),
WindowBound::Following(expr) => WindowBound::Following(remap(plan, expr, map)),
other => other,
}
}
fn values(
plan: &mut Plan,
at_index: u32,
columns: Slice,
rows: Slice,
domain: NodeRef,
index: u32,
keys: &[Key],
) -> Option<Pushed> {
let held: Vec<Vec<ExprRef>> =
plan.row_list(rows).to_vec().into_iter().map(|row| plan.expr_list(row).to_vec()).collect();
let fields = plan.field_list(columns).to_vec();
if held.is_empty() {
return None;
}
let span = plan.expr_span(keys.first()?.expr);
let mut map = HashMap::new();
for (position, key) in keys.iter().enumerate() {
let at = u32::try_from(position).expect("key count");
map.insert(key.binding, ColumnBinding::new(index, at));
}
let (input, chosen) = if held.len() == 1 {
let row = held.into_iter().next().expect("one row");
(domain, row.into_iter().map(|expr| remap(plan, expr, &map)).collect::<Vec<_>>())
} else {
let counter = LogicalType::Integer;
let count = i32::try_from(held.len()).ok()?;
let numbers: Vec<ExprRef> = (0..count)
.map(|at| {
let value = plan.add_value(Value::Integer(at));
plan.add_expr_at(Expr::Constant(value), counter.clone(), span)
})
.collect();
let numbered: Vec<Slice> =
numbers.iter().map(|&expr| plan.add_expr_list(&[expr])).collect();
let counted = plan.add_rows(&numbered);
let field = Field { name: "__row".to_string(), ty: counter.clone(), not_null: true };
let named = plan.add_fields(&[field]);
let table = walk::fresh_index(plan);
let source = plan.add_node(Node::Values { index: table, columns: named, rows: counted });
let input = plan.add_node(Node::CrossProduct { left: domain, right: source });
let which = plan.add_expr_at(Expr::Column(ColumnBinding::new(table, 0)), counter, span);
let mut chosen = Vec::new();
for (column, field) in fields.iter().enumerate() {
let (last, rest) = held.split_last().expect("more than one row");
let mut arms = Vec::new();
for (at, written) in rest.iter().enumerate() {
let when = plan.add_expr_at(
Expr::Compare { op: CompareOp::Equal, left: which, right: numbers[at] },
LogicalType::Boolean,
span,
);
let then = remap(plan, written[column], &map);
arms.push(Arm { when, then });
}
let otherwise = remap(plan, last[column], &map);
let arms = plan.add_arms(&arms);
chosen.push(plan.add_expr_at(
Expr::Case { arms, otherwise: Some(otherwise) },
field.ty.clone(),
span,
));
}
(input, chosen)
};
let mut projected = chosen;
let mut names: Vec<_> = fields.iter().map(|field| plan.intern(&field.name)).collect();
let width = projected.len();
let mut carried = Vec::new();
for (position, key) in keys.iter().enumerate() {
let ty = plan.expr_type(key.expr).clone();
let at = u32::try_from(position).expect("key count");
projected.push(plan.add_expr_at(Expr::Column(ColumnBinding::new(index, at)), ty, span));
names.push(plan.intern(&format!("__domain_{position}")));
carried.push(ColumnBinding::new(at_index, u32::try_from(width + position).expect("width")));
}
let exprs = plan.add_expr_list(&projected);
let names = plan.add_name_list(&names);
let node = plan.add_node(Node::Project { input, index: at_index, exprs, names });
Some(Pushed { node, keys: carried, moved: HashMap::new() })
}
#[allow(clippy::too_many_arguments)]
fn aggregate(
plan: &mut Plan,
below: Pushed,
at_index: u32,
groups: Slice,
aggregates: Slice,
domain: NodeRef,
index: u32,
keys: &[Key],
) -> Option<Pushed> {
let map = mapping(keys, &below);
let held = plan.expr_list(groups).to_vec();
let was_grouped = !held.is_empty();
let mut grouped: Vec<ExprRef> = held.into_iter().map(|expr| remap(plan, expr, &map)).collect();
let held = plan.expr_list(aggregates).to_vec();
let calls: Vec<ExprRef> = held.into_iter().map(|expr| remap(plan, expr, &map)).collect();
let width = grouped.len();
let mut carried = Vec::new();
for (position, (key, &binding)) in keys.iter().zip(&below.keys).enumerate() {
let ty = plan.expr_type(key.expr).clone();
let span = plan.expr_span(key.expr);
grouped.push(plan.add_expr_at(Expr::Column(binding), ty, span));
carried.push(ColumnBinding::new(
at_index,
u32::try_from(width + position).expect("grouping width"),
));
}
if was_grouped {
let mut moved = HashMap::new();
for position in 0..calls.len() {
let was = u32::try_from(width + position).expect("aggregate width");
let now = u32::try_from(width + keys.len() + position).expect("aggregate width");
moved.insert(ColumnBinding::new(at_index, was), ColumnBinding::new(at_index, now));
}
let groups = plan.add_expr_list(&grouped);
let aggregates = plan.add_expr_list(&calls);
let node = plan.add_node(Node::Aggregate {
input: below.node,
index: at_index,
groups,
aggregates,
});
return Some(Pushed { node, keys: carried, moved });
}
let inner = walk::fresh_index(plan);
let groups = plan.add_expr_list(&grouped);
let aggregates = plan.add_expr_list(&calls);
let node =
plan.add_node(Node::Aggregate { input: below.node, index: inner, groups, aggregates });
let marker = walk::fresh_index(plan);
let mut carried_exprs = Vec::new();
let mut carried_names = Vec::new();
for (position, key) in keys.iter().enumerate() {
let ty = plan.expr_type(key.expr).clone();
let span = plan.expr_span(key.expr);
let at = u32::try_from(position).expect("key count");
carried_exprs.push(plan.add_expr_at(Expr::Column(ColumnBinding::new(inner, at)), ty, span));
carried_names.push(plan.intern(&format!("__domain_{position}")));
}
for (position, &call) in calls.iter().enumerate() {
let ty = plan.expr_type(call).clone();
let span = plan.expr_span(call);
let at = u32::try_from(keys.len() + position).expect("aggregate width");
carried_exprs.push(plan.add_expr_at(Expr::Column(ColumnBinding::new(inner, at)), ty, span));
carried_names.push(plan.intern(&format!("__aggregate_{position}")));
}
let present = plan.add_value(Value::Boolean(true));
carried_exprs.push(plan.add_expr(Expr::Constant(present), LogicalType::Boolean));
carried_names.push(plan.intern("__present"));
let exprs = plan.add_expr_list(&carried_exprs);
let names = plan.add_name_list(&carried_names);
let answered = plan.add_node(Node::Project { input: node, index: marker, exprs, names });
let mut conditions = Vec::new();
for (position, key) in keys.iter().enumerate() {
let ty = plan.expr_type(key.expr).clone();
let span = plan.expr_span(key.expr);
let at = u32::try_from(position).expect("key count");
let here = plan.add_expr_at(Expr::Column(ColumnBinding::new(index, at)), ty.clone(), span);
let there = plan.add_expr_at(Expr::Column(ColumnBinding::new(marker, at)), ty, span);
conditions.push(plan.add_expr_at(
Expr::Compare { op: CompareOp::NotDistinctFrom, left: here, right: there },
LogicalType::Boolean,
span,
));
}
let conditions = plan.add_expr_list(&conditions);
let filled = plan.add_node(Node::Join {
left: domain,
right: answered,
kind: JoinKind::Left,
conditions,
build: BuildSide::default(),
});
let present = plan.add_expr(
Expr::Column(ColumnBinding::new(
marker,
u32::try_from(keys.len() + calls.len()).expect("projection width"),
)),
LogicalType::Boolean,
);
let mut repaired = Vec::new();
let mut repaired_names = Vec::new();
for (position, &call) in calls.iter().enumerate() {
let ty = plan.expr_type(call).clone();
let span = plan.expr_span(call);
let at = u32::try_from(keys.len() + position).expect("aggregate width");
let column =
plan.add_expr_at(Expr::Column(ColumnBinding::new(marker, at)), ty.clone(), span);
repaired.push(match empty_answer(plan, call) {
None => column,
Some(value) => {
if ty != value.logical_type() {
return None;
}
let reference = plan.add_value(value);
let otherwise = plan.add_expr_at(Expr::Constant(reference), ty.clone(), span);
let arms = plan.add_arms(&[Arm { when: present, then: column }]);
plan.add_expr_at(Expr::Case { arms, otherwise: Some(otherwise) }, ty, span)
}
});
repaired_names.push(plan.intern(&format!("__aggregate_{position}")));
}
let mut repaired_keys = Vec::new();
for (position, key) in keys.iter().enumerate() {
let ty = plan.expr_type(key.expr).clone();
let span = plan.expr_span(key.expr);
let at = u32::try_from(position).expect("key count");
repaired.push(plan.add_expr_at(Expr::Column(ColumnBinding::new(index, at)), ty, span));
repaired_names.push(plan.intern(&format!("__domain_{position}")));
repaired_keys.push(ColumnBinding::new(
at_index,
u32::try_from(calls.len() + position).expect("projection width"),
));
}
let exprs = plan.add_expr_list(&repaired);
let names = plan.add_name_list(&repaired_names);
let node = plan.add_node(Node::Project { input: filled, index: at_index, exprs, names });
Some(Pushed { node, keys: repaired_keys, moved: HashMap::new() })
}
fn empty_answer(plan: &Plan, call: ExprRef) -> Option<Value> {
let Expr::Aggregate { name, .. } = *plan.expr(call) else {
return None;
};
matches!(plan.string(name), "count" | "count_star").then_some(Value::BigInt(0))
}
#[allow(clippy::too_many_arguments)]
fn sides(
plan: &mut Plan,
left: NodeRef,
right: NodeRef,
kind: JoinKind,
conditions: Slice,
domain: NodeRef,
index: u32,
keys: &[Key],
outer: &TableSet,
) -> Option<Pushed> {
let left_reads = correlated(plan, left, outer);
let right_reads = correlated(plan, right, outer);
if left_reads && right_reads {
let first = push(plan, left, domain, index, keys, outer)?;
let second = push(plan, right, domain, index, keys, outer)?;
let mut moved = first.moved.clone();
moved.extend(second.moved.clone());
let map = mapping(keys, &first);
let held = plan.expr_list(conditions).to_vec();
let mut all: Vec<ExprRef> = held.into_iter().map(|expr| remap(plan, expr, &map)).collect();
for (key, (&here, &there)) in keys.iter().zip(first.keys.iter().zip(&second.keys)) {
let ty = plan.expr_type(key.expr).clone();
let span = plan.expr_span(key.expr);
let one = plan.add_expr_at(Expr::Column(here), ty.clone(), span);
let other = plan.add_expr_at(Expr::Column(there), ty, span);
all.push(plan.add_expr_at(
Expr::Compare { op: CompareOp::NotDistinctFrom, left: one, right: other },
LogicalType::Boolean,
span,
));
}
let conditions = plan.add_expr_list(&all);
let node = plan.add_node(Node::Join {
left: first.node,
right: second.node,
kind,
conditions,
build: BuildSide::default(),
});
return Some(Pushed { node, keys: first.keys, moved });
}
if right_reads && kind != JoinKind::Inner {
return None;
}
let (correlated_side, other) = if right_reads { (right, left) } else { (left, right) };
let below = push(plan, correlated_side, domain, index, keys, outer)?;
let map = mapping(keys, &below);
let held = plan.expr_list(conditions).to_vec();
let rewritten: Vec<ExprRef> = held.into_iter().map(|expr| remap(plan, expr, &map)).collect();
let conditions = plan.add_expr_list(&rewritten);
let (left, right) = if right_reads { (other, below.node) } else { (below.node, other) };
let node =
plan.add_node(Node::Join { left, right, kind, conditions, build: BuildSide::default() });
Some(Pushed { node, keys: below.keys, moved: below.moved })
}
fn mapping(keys: &[Key], below: &Pushed) -> HashMap<ColumnBinding, ColumnBinding> {
let mut map = below.moved.clone();
for (key, &binding) in keys.iter().zip(&below.keys) {
map.insert(key.binding, binding);
}
map
}
fn remap(plan: &mut Plan, expr: ExprRef, map: &HashMap<ColumnBinding, ColumnBinding>) -> ExprRef {
if map.is_empty() {
return expr;
}
if let Expr::Column(binding) = *plan.expr(expr) {
let Some(&moved) = map.get(&binding) else {
return expr;
};
let ty = plan.expr_type(expr).clone();
let span = plan.expr_span(expr);
return plan.add_expr_at(Expr::Column(moved), ty, span);
}
walk::rebuild(plan, expr, &mut |plan, child| remap(plan, child, map))
}
#[cfg(test)]
mod tests {
use crate::unnest;
use rudb_plan::Plan;
fn correlated(above: &str) -> Plan {
let last = above.lines().next_back().expect("at least one operator above the filter");
let depth = last.len() - last.trim_start().len() + 2;
let inner = " ".repeat(depth);
let deeper = " ".repeat(depth + 2);
let text = format!(
"DependentJoin SINGLE on=[]\n Get memory.main.outer AS o #0 [k::INTEGER]\n{above}{inner}Filter (#1.0::INTEGER = #0.0::INTEGER)::BOOLEAN\n{deeper}Get memory.main.inner AS i #1 [k::INTEGER, value::INTEGER]\n"
);
Plan::parse(&text).expect("a correlated plan")
}
#[test]
fn a_distinct_inside_the_subquery_is_pushed_through() {
let mut plan = correlated(" Distinct on=[]\n Project #2 [#1.1::INTEGER AS value]\n");
unnest::lower(&mut plan).expect("unnesting succeeds");
plan.validate().expect("the rewritten plan is valid");
let after = plan.to_string();
assert!(!after.contains("DependentJoin"), "{after}");
assert!(after.contains("__domain_0"), "{after}");
assert!(after.contains("CrossProduct"), "{after}");
}
#[test]
fn a_distinct_on_gains_the_domain_columns() {
let mut plan =
correlated(" Distinct on=[#2.0::INTEGER]\n Project #2 [#1.1::INTEGER AS value]\n");
unnest::lower(&mut plan).expect("unnesting succeeds");
plan.validate().expect("the rewritten plan is valid");
let after = plan.to_string();
assert!(!after.contains("DependentJoin"), "{after}");
assert!(after.contains("Distinct on=[#2.0::INTEGER, #2.1::INTEGER]"), "{after}");
}
#[test]
fn a_sort_inside_the_subquery_orders_by_the_domain_first() {
let mut plan = correlated(
" Sort [#2.0::INTEGER ASC NULLS LAST]\n Project #2 [#1.1::INTEGER AS value]\n",
);
unnest::lower(&mut plan).expect("unnesting succeeds");
plan.validate().expect("the rewritten plan is valid");
let after = plan.to_string();
assert!(!after.contains("DependentJoin"), "{after}");
assert!(
after.contains("Sort [#2.1::INTEGER ASC NULLS LAST, #2.0::INTEGER ASC NULLS LAST]"),
"{after}"
);
}
const FRAME: &str = "frame=RANGE UNBOUNDED PRECEDING TO CURRENT ROW EXCLUDE NO OTHERS";
#[test]
fn a_window_inside_the_subquery_partitions_by_the_domain() {
let mut plan = correlated(&format!(
" Window #2 partition=[] order=[#1.1::INTEGER ASC NULLS LAST] {FRAME} expressions=[row_number()::BIGINT]\n"
));
unnest::lower(&mut plan).expect("unnesting succeeds");
plan.validate().expect("the rewritten plan is valid");
let after = plan.to_string();
assert!(!after.contains("DependentJoin"), "{after}");
assert!(after.contains("partition=[#3.0::INTEGER]"), "{after}");
}
#[test]
fn a_window_that_already_partitions_puts_the_domain_in_front() {
let mut plan = correlated(&format!(
" Window #2 partition=[#1.1::INTEGER] order=[] {FRAME} expressions=[rank()::BIGINT]\n"
));
unnest::lower(&mut plan).expect("unnesting succeeds");
plan.validate().expect("the rewritten plan is valid");
let after = plan.to_string();
assert!(!after.contains("DependentJoin"), "{after}");
assert!(after.contains("partition=[#3.0::INTEGER, #1.1::INTEGER]"), "{after}");
}
#[test]
fn a_grouped_aggregate_adds_the_domain_to_the_groups() {
let mut plan =
correlated(" Aggregate #2 groups=[#1.1::INTEGER] aggregates=[count_star()::BIGINT]\n");
unnest::lower(&mut plan).expect("unnesting succeeds");
plan.validate().expect("the rewritten plan is valid");
let after = plan.to_string();
assert!(!after.contains("DependentJoin"), "{after}");
assert!(after.contains("groups=[#1.1::INTEGER, #3.0::INTEGER]"), "{after}");
assert!(!after.contains("__present"), "{after}");
}
#[test]
fn an_ungrouped_count_keeps_its_answer_over_no_rows() {
let mut plan = correlated(" Aggregate #2 groups=[] aggregates=[count_star()::BIGINT]\n");
unnest::lower(&mut plan).expect("unnesting succeeds");
plan.validate().expect("the rewritten plan is valid");
let after = plan.to_string();
assert!(!after.contains("DependentJoin"), "{after}");
assert!(after.contains("Join LEFT"), "{after}");
assert!(after.contains("TRUE::BOOLEAN AS __present"), "{after}");
assert!(after.contains("CASE WHEN"), "{after}");
assert!(after.contains("ELSE 0::BIGINT"), "{after}");
}
#[test]
fn an_ungrouped_sum_is_left_alone_because_null_is_already_its_answer() {
let mut plan =
correlated(" Aggregate #2 groups=[] aggregates=[sum(#1.1::INTEGER)::HUGEINT]\n");
unnest::lower(&mut plan).expect("unnesting succeeds");
plan.validate().expect("the rewritten plan is valid");
let after = plan.to_string();
assert!(!after.contains("DependentJoin"), "{after}");
assert!(after.contains("Join LEFT"), "{after}");
assert!(!after.contains("CASE WHEN"), "{after}");
}
#[test]
fn a_limit_inside_the_subquery_is_refused() {
let mut plan = correlated(" Limit 1 offset 0\n Project #2 [#1.1::INTEGER AS value]\n");
unnest::lower(&mut plan).expect("unnesting runs");
let after = plan.to_string();
assert!(after.contains("DependentJoin"), "{after}");
}
fn correlated_values(rows: &str) -> Plan {
let text = format!(
"DependentJoin INNER on=[]\n Get memory.main.outer AS o #0 [k::INTEGER]\n Values #1 [col0::INTEGER] rows=[{rows}]\n"
);
Plan::parse(&text).expect("a correlated plan")
}
#[test]
fn one_values_row_reading_the_outer_row_becomes_a_projection_over_the_domain() {
let mut plan = correlated_values("[\"*\"(#0.0::INTEGER, 3::INTEGER)::INTEGER]");
unnest::lower(&mut plan).expect("unnesting succeeds");
plan.validate().expect("the rewritten plan is valid");
let after = plan.to_string();
assert!(!after.contains("DependentJoin"), "{after}");
assert!(!after.contains("CrossProduct"), "{after}");
assert!(!after.contains("CASE WHEN"), "{after}");
assert!(after.contains("__domain_0"), "{after}");
}
#[test]
fn several_values_rows_are_chosen_between_by_a_row_number() {
let mut plan = correlated_values(
"[\"*\"(#0.0::INTEGER, 3::INTEGER)::INTEGER], [\"+\"(#0.0::INTEGER, 1::INTEGER)::INTEGER]",
);
unnest::lower(&mut plan).expect("unnesting succeeds");
plan.validate().expect("the rewritten plan is valid");
let after = plan.to_string();
assert!(!after.contains("DependentJoin"), "{after}");
assert!(
after.contains("Values #3 [__row::INTEGER] rows=[[0::INTEGER], [1::INTEGER]]"),
"{after}"
);
assert!(after.contains("CASE WHEN (#3.0::INTEGER = 0::INTEGER)"), "{after}");
assert!(after.contains("ELSE \"+\"(#2.0::INTEGER, 1::INTEGER)::INTEGER"), "{after}");
}
}