use std::sync::{Arc, OnceLock};
use rudb_common::bounds::{Bound, Op};
use rudb_common::{LogicalType, Result, SessionTimeZone};
use rudb_graph::{KeyMap, Link, Pushed, Rids};
use rudb_metrics::Reduced;
use rudb_plan::{BuildSide, ColumnBinding, Expr, ExprRef, JoinKind, Node, NodeRef, Plan};
use rudb_storage::{Blocked, Range};
use rudb_vector::{Chunk, Vector};
use crate::expr::evaluate_all_in_time_zone;
use crate::lookup::has_nulls;
use crate::schema::Schema;
use crate::table::{Across, hash};
const BUDGET: usize = 32 << 20;
#[derive(Debug, Default)]
pub(crate) struct Sideways<'a> {
keyed: OnceLock<Keyed<'a>>,
binding: OnceLock<ColumnBinding>,
exact: OnceLock<Exact>,
found: OnceLock<Found>,
}
#[derive(Debug, Default)]
pub(crate) struct Found {
range: Option<(Bound, Bound)>,
filter: Option<Blocked>,
rows: Option<Rids>,
domain: Option<Domain>,
reduced: Option<Reduced>,
}
#[derive(Debug)]
pub(crate) struct Domain {
base: i128,
range: u64,
words: Vec<u64>,
}
impl Domain {
fn holds(&self, key: i64) -> bool {
let Ok(offset) = u64::try_from(i128::from(key) - self.base) else { return false };
offset < self.range && self.words[(offset / 64) as usize] >> (offset % 64) & 1 == 1
}
pub(crate) fn keep(&self, keys: &Vector, rows: usize, block: &mut Vec<i64>) -> Vec<u32> {
let mut kept = Vec::with_capacity(rows);
if keys.signed_block(block) && block.len() >= rows {
let none_null = keys.none_null();
if let (Ok(base), true, true) =
(i64::try_from(self.base), none_null, self.range < 1 << 62)
{
kept.resize(rows, 0);
let mut at = 0;
for (row, &key) in block[..rows].iter().enumerate() {
let offset = key.wrapping_sub(base) as u64;
let hit = offset < self.range
&& self.words[(offset / 64) as usize] >> (offset % 64) & 1 == 1;
kept[at] = row as u32;
at += usize::from(hit);
}
kept.truncate(at);
return kept;
}
for (row, &key) in block[..rows].iter().enumerate() {
if self.holds(key) && (none_null || !keys.is_null_at(row)) {
kept.push(row as u32);
}
}
return kept;
}
for row in 0..rows {
let key = keys.signed_at(row).and_then(|key| i64::try_from(key).ok());
if key.is_some_and(|key| self.holds(key)) {
kept.push(row as u32);
}
}
kept
}
}
#[derive(Debug)]
pub(crate) struct Exact {
keys: KeyMap,
link: Option<Link>,
}
impl Exact {
pub(crate) fn new(keys: KeyMap, link: Option<Link>) -> Self {
Self { keys, link }
}
}
#[derive(Debug)]
pub(crate) struct Keyed<'a> {
plan: &'a Plan,
expr: ExprRef,
schema: Schema,
time_zone: SessionTimeZone,
}
impl<'a> Keyed<'a> {
pub(crate) fn new(
plan: &'a Plan,
expr: ExprRef,
schema: Schema,
time_zone: SessionTimeZone,
) -> Self {
Self { plan, expr, schema, time_zone }
}
pub(crate) fn parts(&self) -> (&'a Plan, [ExprRef; 1], &Schema, SessionTimeZone) {
(self.plan, [self.expr], &self.schema, self.time_zone)
}
}
impl<'a> Sideways<'a> {
pub(crate) fn new() -> Arc<Self> {
Arc::new(Self::default())
}
pub(crate) fn keying(&self, keyed: Keyed<'a>) {
let _ = self.keyed.set(keyed);
}
pub(crate) fn about(&self, binding: ColumnBinding) {
let _ = self.binding.set(binding);
}
pub(crate) fn keyed(&self) -> Option<&Keyed<'a>> {
self.keyed.get()
}
pub(crate) fn exactly(&self, exact: Exact) {
let _ = self.exact.set(exact);
}
pub(crate) fn exact(&self) -> Option<&Exact> {
self.exact.get()
}
pub(crate) fn found(&self, found: Found) {
let _ = self.found.set(found);
}
pub(crate) fn tests(&self, index: u32) -> Vec<(usize, Op, Bound)> {
let (Some(binding), Some(Some((low, high)))) =
(self.binding.get(), self.found.get().map(|found| &found.range))
else {
return Vec::new();
};
if binding.table != index {
return Vec::new();
}
let column = binding.column as usize;
vec![(column, Op::GreaterOrEqual, low.clone()), (column, Op::LessOrEqual, high.clone())]
}
pub(crate) fn sifting(&self, index: u32) -> Option<(usize, &Blocked)> {
let binding = self.binding.get()?;
if binding.table != index {
return None;
}
Some((binding.column as usize, self.found.get()?.filter.as_ref()?))
}
pub(crate) fn rows(&self, index: u32) -> Option<&Rids> {
if self.binding.get()?.table != index {
return None;
}
self.found.get()?.rows.as_ref()
}
pub(crate) fn reduction(&self, index: u32) -> Option<Reduced> {
if self.binding.get()?.table != index {
return None;
}
self.found.get()?.reduced
}
pub(crate) fn domain(&self, index: u32) -> Option<(usize, &Domain)> {
let binding = self.binding.get()?;
if binding.table != index {
return None;
}
Some((binding.column as usize, self.found.get()?.domain.as_ref()?))
}
}
pub(crate) fn beneath(plan: &Plan, node: NodeRef, binding: ColumnBinding) -> Option<ColumnBinding> {
let mut at = node;
let mut binding = binding;
loop {
match *plan.node(at) {
Node::Get { index, .. } | Node::TableFunction { index, .. } => {
return (binding.table == index).then_some(binding);
}
Node::Filter { input, .. } => at = input,
Node::Project { input, index, exprs, .. } => {
if binding.table == index {
let exprs = plan.expr_list(exprs);
let at = exprs.get(binding.column as usize)?;
let Expr::Column(inner) = *plan.expr(*at) else { return None };
binding = inner;
}
at = input;
}
ref node @ Node::Join { .. } => at = through(node)?,
_ => return None,
}
}
}
pub(crate) fn through(node: &Node) -> Option<NodeRef> {
let Node::Join { left, right, kind, build, .. } = *node else { return None };
match (kind, build) {
(JoinKind::Inner, BuildSide::Left) => Some(right),
(JoinKind::Inner | JoinKind::Semi, BuildSide::Right) => Some(left),
_ => None,
}
}
pub(crate) fn found(keyed: &Keyed<'_>, exact: Option<&Exact>, chunks: &[Chunk]) -> Result<Found> {
let (plan, exprs, schema, time_zone) = keyed.parts();
let pushed = exact.map(|exact| reduce(keyed, exact, chunks)).transpose()?.flatten();
let mut reduced = pushed.as_ref().map(|pushed| Reduced {
kept: pushed.rids.len(),
rows: pushed.rids.rows(),
stopped: pushed.stopped,
by_key: false,
});
let mut domain = None;
if let Some((bitmap, held)) =
exact.map(|exact| domain_of(keyed, exact, chunks)).transpose()?.flatten()
{
let parents = exact.map_or(0, |exact| exact.keys.len());
reduced = Some(Reduced { kept: held, rows: parents, stopped: false, by_key: true });
domain = (held < parents).then_some(bitmap);
}
let stopped = pushed.as_ref().is_some_and(|pushed| pushed.stopped);
let exact = pushed.filter(|pushed| !pushed.stopped).map(|pushed| pushed.rids);
let rows: usize = chunks.iter().map(Chunk::len).sum();
let mut extremes = Extremes::default();
let settled = exact.is_some() || stopped || reduced.is_some_and(|reduced| reduced.by_key);
let mut keyed = Vec::with_capacity(chunks.len());
for chunk in chunks {
let keys = evaluate_all_in_time_zone(plan, &exprs, schema, chunk, time_zone)?;
keyed.push((keys.into_iter().next(), chunk.len()));
}
if !settled && domain.is_none() {
domain = dense(&keyed, rows);
}
let settled = settled || domain.is_some();
let mut filter = if settled { None } else { Blocked::sized(rows, BUDGET) };
let mut hashes = Vec::new();
for (keys, len) in &keyed {
let (Some(keys), len) = (keys, *len) else { continue };
extremes.widen(keys);
let Some(filter) = filter.as_mut() else { continue };
hash(std::slice::from_ref(keys), len, &mut hashes, Across::TwoInputs);
let nullable = has_nulls(keys, len);
for (row, &word) in hashes.iter().enumerate() {
if nullable && keys.is_null_at(row) {
continue;
}
filter.add(word);
}
}
Ok(Found { range: extremes.into_range(), filter, rows: exact, domain, reduced })
}
fn dense(keyed: &[(Option<Vector>, usize)], rows: usize) -> Option<Domain> {
let narrow = |ty: &LogicalType| {
matches!(
ty,
LogicalType::TinyInt
| LogicalType::SmallInt
| LogicalType::Integer
| LogicalType::BigInt
)
};
let mut block = Vec::new();
let mut low = i64::MAX;
let mut high = i64::MIN;
for (keys, len) in keyed {
let Some(keys) = keys else { continue };
if !narrow(keys.logical_type()) || !keys.signed_block(&mut block) || block.len() < *len {
return None;
}
let nullable = has_nulls(keys, *len);
for (row, &key) in block[..*len].iter().enumerate() {
if nullable && keys.is_null_at(row) {
continue;
}
low = low.min(key);
high = high.max(key);
}
}
if low > high {
return None;
}
let range = u64::try_from(i128::from(high) - i128::from(low) + 1).ok()?;
let bytes = usize::try_from(range.div_ceil(8)).ok()?;
if range > (rows as u64).saturating_mul(64) || bytes > BUDGET {
return None;
}
let mut words = vec![0_u64; usize::try_from(range.div_ceil(64)).ok()?];
for (keys, len) in keyed {
let Some(keys) = keys else { continue };
if !keys.signed_block(&mut block) {
return None;
}
let nullable = has_nulls(keys, *len);
for (row, &key) in block[..*len].iter().enumerate() {
if nullable && keys.is_null_at(row) {
continue;
}
let offset = key.wrapping_sub(low) as u64;
words[(offset / 64) as usize] |= 1 << (offset % 64);
}
}
Some(Domain { base: i128::from(low), range, words })
}
fn reduce(keyed: &Keyed<'_>, exact: &Exact, chunks: &[Chunk]) -> Result<Option<Pushed>> {
let Some(link) = exact.link.as_ref() else { return Ok(None) };
let (plan, exprs, schema, time_zone) = keyed.parts();
let parents = link.parents();
let mut words = vec![0_u64; usize::try_from(parents.div_ceil(64)).unwrap_or(usize::MAX)];
for chunk in chunks {
let keys = evaluate_all_in_time_zone(plan, &exprs, schema, chunk, time_zone)?;
let Some(keys) = keys.first() else { continue };
let nullable = has_nulls(keys, chunk.len());
for row in 0..chunk.len() {
if nullable && keys.is_null_at(row) {
continue;
}
let Some(key) = keys.signed_at(row) else { return Ok(None) };
let Some(rid) = exact.keys.lookup(key)? else { return Ok(None) };
let Some(word) = usize::try_from(rid / 64).ok().and_then(|at| words.get_mut(at)) else {
return Ok(None);
};
*word |= 1 << (rid % 64);
}
}
let held = Rids::from_words(parents, words)?;
Ok(Some(held.forward_or_stop(link)?))
}
fn domain_of(keyed: &Keyed<'_>, exact: &Exact, chunks: &[Chunk]) -> Result<Option<(Domain, u64)>> {
if exact.link.is_some() {
return Ok(None);
}
let Some((base, range)) = exact.keys.span() else { return Ok(None) };
let Ok(len) = usize::try_from(range.div_ceil(64)) else { return Ok(None) };
let (plan, exprs, schema, time_zone) = keyed.parts();
let mut words = vec![0_u64; len];
for chunk in chunks {
let keys = evaluate_all_in_time_zone(plan, &exprs, schema, chunk, time_zone)?;
let Some(keys) = keys.first() else { continue };
let nullable = has_nulls(keys, chunk.len());
for row in 0..chunk.len() {
if nullable && keys.is_null_at(row) {
continue;
}
let Some(key) = keys.signed_at(row) else { return Ok(None) };
let Some(offset) = key.checked_sub(base).and_then(|at| u64::try_from(at).ok()) else {
return Ok(None);
};
if offset >= range {
return Ok(None);
}
words[(offset / 64) as usize] |= 1 << (offset % 64);
}
}
let held = words.iter().map(|word| u64::from(word.count_ones())).sum();
Ok(Some((Domain { base, range, words }, held)))
}
#[derive(Debug, Default, Clone)]
pub(crate) struct Extremes {
low: Option<Bound>,
high: Option<Bound>,
}
impl Extremes {
pub(crate) fn widen(&mut self, keys: &Vector) {
let range = Range::of(keys);
if let Some(low) = range.low {
self.low = Some(match self.low.take() {
Some(held) => held.smaller(low),
None => low,
});
}
if let Some(high) = range.high {
self.high = Some(match self.high.take() {
Some(held) => held.larger(high),
None => high,
});
}
}
pub(crate) fn into_range(self) -> Option<(Bound, Bound)> {
Some((self.low?, self.high?))
}
}
impl Found {
#[cfg(test)]
pub(crate) fn of(range: Option<(Bound, Bound)>, filter: Option<Blocked>) -> Self {
Self { range, filter, rows: None, domain: None, reduced: None }
}
#[cfg(test)]
pub(crate) fn exactly(range: Option<(Bound, Bound)>, rows: Rids) -> Self {
Self { range, filter: None, rows: Some(rows), domain: None, reduced: None }
}
}
#[cfg(test)]
mod tests {
use rudb_common::bounds::{Bound, Op};
use rudb_common::{Field, LogicalType, SessionTimeZone, Value};
use rudb_plan::{ColumnBinding, Expr, ExprRef, Plan};
use rudb_storage::Blocked;
use rudb_vector::{Chunk, Vector};
use rudb_graph::{KeyMap, Link};
use super::{Across, Exact, Extremes, Found, Keyed, Schema, Sideways, beneath, found, hash};
fn column(values: &[Option<i32>]) -> Vector {
let values: Vec<Value> =
values.iter().map(|value| value.map_or(Value::Null, Value::Integer)).collect();
Vector::from_values(LogicalType::Integer, &values).expect("a column of integers")
}
#[test]
fn the_range_of_several_chunks_covers_every_one_of_them() {
let mut extremes = Extremes::default();
extremes.widen(&column(&[Some(5), Some(9)]));
extremes.widen(&column(&[Some(2), Some(7)]));
assert_eq!(extremes.into_range(), Some((Bound::Int(2), Bound::Int(9))));
}
#[test]
fn a_column_of_nulls_widens_nothing() {
let mut extremes = Extremes::default();
extremes.widen(&column(&[Some(4)]));
extremes.widen(&column(&[None, None]));
assert_eq!(extremes.into_range(), Some((Bound::Int(4), Bound::Int(4))));
}
#[test]
fn nothing_seen_is_no_range() {
assert_eq!(Extremes::default().into_range(), None);
}
#[test]
fn a_scan_is_told_only_about_its_own_column() {
let sideways = Sideways::new();
sideways.found(Found::of(Some((Bound::Int(1), Bound::Int(4))), None));
sideways.about(ColumnBinding::new(7, 2));
assert!(sideways.tests(8).is_empty(), "another table's scan");
assert_eq!(
sideways.tests(7),
vec![(2, Op::GreaterOrEqual, Bound::Int(1)), (2, Op::LessOrEqual, Bound::Int(4)),]
);
}
#[test]
fn an_unarmed_handoff_and_an_empty_build_side_both_say_nothing() {
let unarmed = Sideways::new();
assert!(unarmed.tests(1).is_empty());
assert!(unarmed.sifting(1).is_none());
let empty = Sideways::new();
empty.about(ColumnBinding::new(1, 0));
empty.found(Found::of(None, None));
assert!(empty.tests(1).is_empty());
assert!(empty.sifting(1).is_none());
}
fn chunk(values: &[Option<i32>]) -> Chunk {
Chunk::new(vec![column(values)]).expect("one column is one length")
}
fn chunks(values: &[Option<i32>]) -> Vec<Chunk> {
values.chunks(512).map(chunk).collect()
}
fn key(plan: &mut Plan) -> (ExprRef, Schema) {
let expr = plan.add_expr(Expr::Column(ColumnBinding::new(1, 0)), LogicalType::Integer);
let schema = Schema::numbered(vec![Field::new("k", LogicalType::Integer)], 1);
(expr, schema)
}
fn through(filter: &Blocked, values: &[Option<i32>]) -> Vec<bool> {
let probe = column(values);
let mut hashes = Vec::new();
hash(std::slice::from_ref(&probe), values.len(), &mut hashes, Across::TwoInputs);
hashes.iter().map(|&word| filter.holds(word)).collect()
}
#[test]
fn a_build_side_is_read_for_both_its_range_and_its_keys() {
let mut plan = Plan::new();
let (expr, schema) = key(&mut plan);
let keyed = Keyed::new(&plan, expr, schema, SessionTimeZone::default());
let found = found(&keyed, None, &[chunk(&[Some(5), Some(90_000)]), chunk(&[Some(2)])])
.expect("a column of integers");
assert_eq!(found.range, Some((Bound::Int(2), Bound::Int(90_000))));
let filter = found.filter.expect("a filter over three keys");
assert_eq!(through(&filter, &[Some(5), Some(90_000), Some(2)]), [true, true, true]);
}
#[test]
fn no_key_that_went_in_is_ever_turned_away() {
let mut plan = Plan::new();
let (expr, schema) = key(&mut plan);
let keyed = Keyed::new(&plan, expr, schema, SessionTimeZone::default());
let keys: Vec<Option<i32>> = (0..4_000).map(|value| Some(value * 1_000 + 11)).collect();
let found = found(&keyed, None, &chunks(&keys)).expect("a column of integers");
let filter = found.filter.expect("a filter over four thousand keys");
assert!(through(&filter, &keys).into_iter().all(|held| held), "a key it was given");
}
#[test]
fn a_key_the_build_side_never_held_is_nearly_always_turned_away() {
let mut plan = Plan::new();
let (expr, schema) = key(&mut plan);
let keyed = Keyed::new(&plan, expr, schema, SessionTimeZone::default());
let keys: Vec<Option<i32>> = (0..4_000).map(|value| Some(value * 1_000 + 11)).collect();
let absent: Vec<Option<i32>> =
(0..4_000).map(|value| Some(value * 7 + 10_000_000)).collect();
let found = found(&keyed, None, &chunks(&keys)).expect("a column of integers");
let filter = found.filter.expect("a filter over four thousand keys");
let through = through(&filter, &absent).into_iter().filter(|&held| held).count();
assert!(through < absent.len() / 10, "{through} of {} got through", absent.len());
}
#[test]
fn a_null_is_not_a_key_the_filter_holds() {
let mut plan = Plan::new();
let (expr, schema) = key(&mut plan);
let keyed = Keyed::new(&plan, expr, schema, SessionTimeZone::default());
let found =
found(&keyed, None, &[chunk(&[Some(3), None, Some(400_000)])]).expect("integers");
assert_eq!(found.range, Some((Bound::Int(3), Bound::Int(400_000))));
assert_eq!(through(&found.filter.expect("a filter"), &[None]), [false]);
}
#[test]
fn keys_close_together_are_kept_as_a_bitmap_instead_of_a_filter() {
let mut plan = Plan::new();
let (expr, schema) = key(&mut plan);
let keyed = Keyed::new(&plan, expr, schema, SessionTimeZone::default());
let found = found(&keyed, None, &[chunk(&[Some(-4), None, Some(60)]), chunk(&[Some(7)])])
.expect("integers");
assert!(found.filter.is_none(), "the bitmap is exact, so no filter is built beside it");
assert_eq!(found.range, Some((Bound::Int(-4), Bound::Int(60))));
let domain = found.domain.expect("a bitmap over sixty five values");
let driving = column(&[Some(7), Some(8), None, Some(-4), Some(-5), Some(61), Some(60)]);
let kept = domain.keep(&driving, driving.len(), &mut Vec::new());
assert_eq!(kept, [0, 3, 6]);
let whole = column(&[Some(60), Some(1), Some(7), Some(i32::MIN), Some(i32::MAX)]);
assert_eq!(domain.keep(&whole, whole.len(), &mut Vec::new()), [0, 2]);
}
#[test]
fn keys_spread_wide_still_get_a_filter() {
let mut plan = Plan::new();
let (expr, schema) = key(&mut plan);
let keyed = Keyed::new(&plan, expr, schema, SessionTimeZone::default());
let found = found(&keyed, None, &[chunk(&[Some(0), Some(64 * 2)])]).expect("integers");
assert!(found.domain.is_none() && found.filter.is_some());
}
#[test]
fn an_empty_build_side_turns_every_driving_row_away() {
let mut plan = Plan::new();
let (expr, schema) = key(&mut plan);
let keyed = Keyed::new(&plan, expr, schema, SessionTimeZone::default());
let found = found(&keyed, None, &[]).expect("nothing to read");
assert_eq!(found.range, None);
assert_eq!(through(&found.filter.expect("a filter of no keys"), &[Some(1)]), [false]);
}
#[test]
fn an_exact_side_keeps_the_children_of_the_parents_it_holds_and_no_others() {
let mut plan = Plan::new();
let (expr, schema) = key(&mut plan);
let keyed = Keyed::new(&plan, expr, schema, SessionTimeZone::default());
let parent_keys: Vec<Option<i128>> = (0..50).map(|rid| Some(100 + rid)).collect();
let parents_of: Vec<u64> = (0..5_000).map(|child| child / 100).collect();
let exact = Exact::new(
KeyMap::build(&parent_keys).expect("unique keys"),
Some(Link::build(&parents_of, 50).expect("every parent exists")),
);
let found = found(&keyed, Some(&exact), &[chunk(&[Some(103), None]), chunk(&[Some(140)])])
.expect("integers");
assert!(found.filter.is_none(), "the exact rows make the filter redundant");
let rows = found.rows.expect("an exact side");
let kept: Vec<u64> = rows.iter().collect();
let expected: Vec<u64> = (300..400).chain(4_000..4_100).collect();
assert_eq!(kept, expected);
assert_eq!(found.range, Some((Bound::Int(103), Bound::Int(140))));
}
#[test]
fn a_key_the_parent_does_not_hold_falls_back_to_the_filter() {
let mut plan = Plan::new();
let (expr, schema) = key(&mut plan);
let keyed = Keyed::new(&plan, expr, schema, SessionTimeZone::default());
let exact = Exact::new(
KeyMap::build(&[Some(1), Some(2)]).expect("unique keys"),
Some(Link::build(&[0, 1, 1], 2).expect("both parents exist")),
);
let found =
found(&keyed, Some(&exact), &[chunk(&[Some(1), Some(9_000_000)])]).expect("integers");
assert!(found.rows.is_none());
assert!(found.filter.is_some(), "the filter is what the join gets instead");
}
#[test]
fn a_side_with_no_link_keeps_the_keys_it_holds_as_a_bitmap() {
let mut plan = Plan::new();
let (expr, schema) = key(&mut plan);
let keyed = Keyed::new(&plan, expr, schema, SessionTimeZone::default());
let parent_keys: Vec<Option<i128>> = (0..200).map(|rid| Some(100 + rid)).collect();
let exact = Exact::new(KeyMap::build(&parent_keys).expect("unique keys"), None);
let found = found(&keyed, Some(&exact), &[chunk(&[Some(103), None]), chunk(&[Some(299)])])
.expect("integers");
assert!(found.filter.is_none() && found.rows.is_none(), "the bitmap is the whole answer");
let reduced = found.reduced.expect("a reduction to report");
assert_eq!((reduced.kept, reduced.rows, reduced.by_key), (2, 200, true));
let domain = found.domain.expect("a bitmap");
let driving = chunk(&[Some(103), Some(104), None, Some(299), Some(300), Some(99)]);
let mut block = Vec::new();
assert_eq!(domain.keep(&driving.columns()[0], driving.len(), &mut block), [0, 3]);
}
#[test]
fn a_side_that_holds_every_parent_key_tests_nothing() {
let mut plan = Plan::new();
let (expr, schema) = key(&mut plan);
let keyed = Keyed::new(&plan, expr, schema, SessionTimeZone::default());
let exact = Exact::new(KeyMap::build(&[Some(1), Some(2)]).expect("unique keys"), None);
let found = found(&keyed, Some(&exact), &[chunk(&[Some(2), Some(1)])]).expect("integers");
assert!(found.domain.is_none() && found.filter.is_none());
assert_eq!(found.reduced.map(|reduced| reduced.kept), Some(2));
}
fn driving(text: &str) -> Plan {
Plan::parse(text).expect("the plan text round trips")
}
#[test]
fn a_projection_between_the_join_and_the_scan_renames_the_column_the_filter_is_about() {
let plan = driving(
"Project #1 [#0.1::INTEGER AS k]\n \
TableFunction read_parquet args=['f'::VARCHAR] #0 [a::INTEGER, k::INTEGER]",
);
assert_eq!(
beneath(&plan, plan.root(), ColumnBinding::new(1, 0)),
Some(ColumnBinding::new(0, 1)),
"the scan's own name for the projection's column"
);
}
#[test]
fn a_filter_between_the_two_leaves_the_binding_alone() {
let plan = driving(
"Project #1 [#0.0::INTEGER AS k]\n \
Filter (#0.0::INTEGER > 3::INTEGER)::BOOLEAN\n \
TableFunction read_parquet args=['f'::VARCHAR] #0 [k::INTEGER]",
);
assert_eq!(
beneath(&plan, plan.root(), ColumnBinding::new(1, 0)),
Some(ColumnBinding::new(0, 0))
);
}
#[test]
fn a_computed_column_is_not_a_column_the_filter_can_be_about() {
let plan = driving(
"Project #1 [(#0.0::INTEGER > 3::INTEGER)::BOOLEAN AS k]\n \
TableFunction read_parquet args=['f'::VARCHAR] #0 [k::INTEGER]",
);
assert_eq!(beneath(&plan, plan.root(), ColumnBinding::new(1, 0)), None);
}
#[test]
fn a_walk_that_does_not_reach_the_scan_it_is_about_arms_nothing() {
let plan = driving(
"Limit 5 offset 0\n \
TableFunction read_parquet args=['f'::VARCHAR] #0 [k::INTEGER]",
);
assert_eq!(
beneath(&plan, plan.root(), ColumnBinding::new(0, 0)),
None,
"a node in the way"
);
let plan = driving("TableFunction read_parquet args=['f'::VARCHAR] #0 [k::INTEGER]");
assert_eq!(
beneath(&plan, plan.root(), ColumnBinding::new(3, 0)),
None,
"another table's column"
);
}
}