use std::collections::{BTreeSet, HashMap};
use rudb_common::rules::Rule;
use rudb_common::{Field, LogicalType, Result};
use rudb_plan::{
ColumnBinding, CompareOp, ConjunctionOp, Edge, Expr, ExprRef, Extreme, JoinKind, Key, Leaf,
Node, NodeRef, Plan, Reducer,
};
use crate::pass::{Context, Pass};
use crate::walk;
#[derive(Debug, Clone, Copy)]
pub struct ConsistentExtremes;
impl Pass for ConsistentExtremes {
fn name(&self) -> &'static str {
"consistent_extremes"
}
fn run(&self, plan: &mut Plan, context: &Context) -> Result<()> {
if !context.allows(Rule::Consistent) {
return Ok(());
}
crate::eliminate::inner_joins(plan, context);
let mut changed = false;
let root = plan.root();
let mut next = walk::fresh_index(plan);
let rewritten = walk::restack(plan, root, &mut changed, &mut |plan, at| match rewrite(
plan, at, &mut next,
) {
Ok(done) => Some(done),
Err(Declined::Silently) => None,
Err(Declined::Because(reason)) => {
let index = match *plan.node(at) {
Node::Aggregate { index, .. } => index,
_ => 0,
};
plan.note_declined(format!("aggregate #{index} {reason}"));
None
}
});
if changed {
plan.set_root(rewritten);
}
Ok(())
}
}
#[derive(Debug)]
enum Declined {
Silently,
Because(String),
}
fn because<T>(reason: impl Into<String>) -> std::result::Result<T, Declined> {
Err(Declined::Because(reason.into()))
}
struct Relation {
get: NodeRef,
index: u32,
local: Vec<ExprRef>,
}
type Place = (usize, u32);
fn rewrite(plan: &mut Plan, at: NodeRef, next: &mut u32) -> std::result::Result<NodeRef, Declined> {
let Node::Aggregate { input, index, groups, aggregates } = *plan.node(at) else {
return Err(Declined::Silently);
};
let mut above: Vec<ExprRef> = Vec::new();
let mut projections: HashMap<u32, NodeRef> = HashMap::new();
let mut region = input;
loop {
match *plan.node(region) {
Node::Filter { input, predicate } => {
conjuncts(plan, predicate, &mut above);
region = input;
}
Node::Project { input, index, .. } => {
projections.insert(index, region);
region = input;
}
_ => break,
}
}
if !is_join(plan.node(region)) {
return Err(Declined::Silently);
}
if !groups.is_empty() {
return because("groups by a key, so its answer is per group rather than one extreme");
}
let mut bottoms = Vec::new();
let mut conditions = above;
gather(plan, region, &mut bottoms, &mut conditions)?;
let mut relations = Vec::with_capacity(bottoms.len());
for &bottom in &bottoms {
relations.push(relation(plan, bottom)?);
}
let by_index: HashMap<u32, usize> =
relations.iter().enumerate().map(|(at, relation)| (relation.index, at)).collect();
let find = Resolver { projections: &projections, by_index: &by_index };
let mut extremes = Vec::new();
for &aggregate in plan.expr_list(aggregates) {
let Expr::Aggregate { name, args, filter, .. } = *plan.expr(aggregate) else {
return because("holds something that is not an aggregate call");
};
let called = plan.string(name).to_ascii_lowercase();
let max = match called.as_str() {
"min" => false,
"max" => true,
_ => return because(format!("computes {called}, which is not a MIN or a MAX")),
};
if filter.is_some() {
return because(format!("has a FILTER on a {called}"));
}
let [argument] = plan.expr_list(args) else {
return because(format!("calls {called} with other than one argument"));
};
let Some(place) = find.plain(plan, *argument) else {
return because(format!("takes a {called} of something other than a column"));
};
if field(plan, &relations, place).ty != *plan.expr_type(aggregate) {
return because(format!("takes a {called} whose type is not its column's"));
}
extremes.push((place, max, called));
}
let mut equalities: Vec<(Place, Place)> = Vec::new();
for &condition in &conditions {
let mut read: BTreeSet<usize> = BTreeSet::new();
let mut outside = false;
walk::columns(plan, condition, &mut |binding| match find.place(plan, binding) {
Some((relation, _)) => {
read.insert(relation);
}
None => outside = true,
});
if outside {
return because("has a predicate that reads a column from outside the join");
}
match read.len() {
0 => relations[0].local.push(condition),
1 => relations[*read.first().unwrap_or(&0)].local.push(condition),
_ => {
let Some(pair) = equality(plan, &find, &relations, condition) else {
return because(
"has a predicate between relations that is not an equality of integer columns",
);
};
equalities.push(pair);
}
}
}
let mut classes = Classes::default();
for &(one, other) in &equalities {
classes.union(one, other);
}
let (class_of, count) = classes.numbered();
let mut edges: Vec<BTreeSet<u32>> = vec![BTreeSet::new(); relations.len()];
for (&(relation, _), &class) in &class_of {
if !edges[relation].insert(class) {
return because("joins two columns of one relation to each other");
}
}
let parents = match gyo(&edges) {
Ok(parents) => parents,
Err(reason) => return because(reason),
};
let mut wanted = vec![0usize; relations.len()];
for &((relation, _), _, _) in &extremes {
wanted[relation] += 1;
}
let order = rooted(&parents, &wanted);
let mut position = vec![0usize; relations.len()];
for (at, &(relation, _)) in order.iter().enumerate() {
position[relation] = at;
}
let mut read: Vec<BTreeSet<u32>> = vec![BTreeSet::new(); relations.len()];
for &(relation, column) in class_of.keys() {
read[relation].insert(column);
}
for &((relation, column), _, _) in &extremes {
read[relation].insert(column);
}
for (at, relation) in relations.iter().enumerate() {
for &predicate in &relation.local {
walk::columns(plan, predicate, &mut |binding| {
if let Some((_, column)) = find.place(plan, binding) {
read[at].insert(column);
}
});
}
}
let mut narrowed: Vec<(u32, HashMap<u32, u32>)> = Vec::with_capacity(relations.len());
let mut inputs = Vec::with_capacity(relations.len());
for (at, relation) in relations.iter().enumerate() {
let fresh = *next;
*next += 1;
let moved: HashMap<u32, u32> =
read[at].iter().enumerate().map(|(new, &old)| (old, count_u32(new))).collect();
let input = narrow(plan, relation, fresh, &read[at], &moved, &find);
narrowed.push((fresh, moved));
inputs.push(input);
}
let mut leaves = Vec::with_capacity(relations.len());
for &(relation, parent) in &order {
let moved = &narrowed[relation].1;
let mut keys: Vec<Key> = class_of
.iter()
.filter(|((held, _), _)| *held == relation)
.map(|(&(_, column), &class)| Key { class, column: moved[&column] })
.collect();
keys.sort_by_key(|key| key.class);
let parent = parent.map(|parent| {
let shared = edges[relation]
.intersection(&edges[parent])
.next()
.copied()
.expect("the tree only joins relations that share a class");
Edge { leaf: count_u32(position[parent]), class: shared }
});
leaves.push(Leaf { input: inputs[relation], keys, parent });
}
let produced: Vec<Extreme> = extremes
.iter()
.map(|&((relation, column), max, _)| Extreme {
leaf: count_u32(position[relation]),
column: narrowed[relation].1[&column],
max,
})
.collect();
let fields: Vec<Field> = extremes
.iter()
.zip(plan.expr_list(aggregates).to_vec())
.map(|((_, _, called), aggregate)| Field {
name: called.clone(),
ty: plan.expr_type(aggregate).clone(),
not_null: false,
})
.collect();
let reducer = Reducer { leaves, classes: count, extremes: produced };
if reducer.validate().is_err() {
return because("built a join tree that does not hold together, which is a bug");
}
let reducer = plan.add_reducer(reducer);
let columns = plan.add_fields(&fields);
let span = plan.node_span(at);
Ok(plan.add_node_at(Node::Consistent { index, columns, reducer }, span))
}
fn is_join(node: &Node) -> bool {
matches!(
node,
Node::Join { .. }
| Node::CrossProduct { .. }
| Node::LinkJoin { .. }
| Node::DependentJoin { .. }
)
}
fn gather(
plan: &Plan,
at: NodeRef,
bottoms: &mut Vec<NodeRef>,
conditions: &mut Vec<ExprRef>,
) -> std::result::Result<(), Declined> {
match *plan.node(at) {
Node::CrossProduct { left, right } => {
gather(plan, left, bottoms, conditions)?;
gather(plan, right, bottoms, conditions)
}
Node::Join { left, right, kind: JoinKind::Inner, conditions: list, .. } => {
for &condition in plan.expr_list(list) {
conjuncts(plan, condition, conditions);
}
gather(plan, left, bottoms, conditions)?;
gather(plan, right, bottoms, conditions)
}
Node::Join { .. } | Node::DependentJoin { .. } | Node::LinkJoin { .. } => {
because("is over a join that is not an inner join")
}
_ => {
bottoms.push(at);
Ok(())
}
}
}
fn conjuncts(plan: &Plan, predicate: ExprRef, into: &mut Vec<ExprRef>) {
match *plan.expr(predicate) {
Expr::Conjunction { op: ConjunctionOp::And, children } => {
for &child in plan.expr_list(children) {
conjuncts(plan, child, into);
}
}
_ => into.push(predicate),
}
}
fn relation(plan: &Plan, bottom: NodeRef) -> std::result::Result<Relation, Declined> {
let mut local = Vec::new();
let mut at = bottom;
loop {
match *plan.node(at) {
Node::Filter { input, predicate } => {
conjuncts(plan, predicate, &mut local);
at = input;
}
Node::Get { index, .. } => return Ok(Relation { get: at, index, local }),
ref other => {
return because(format!(
"joins a relation that is a {} rather than a table scan",
other.keyword()
));
}
}
}
}
fn field<'a>(plan: &'a Plan, relations: &[Relation], (relation, column): Place) -> &'a Field {
let Node::Get { columns, .. } = *plan.node(relations[relation].get) else {
unreachable!("a relation is a scan, which `relation` checked")
};
&plan.field_list(columns)[column as usize]
}
struct Resolver<'a> {
projections: &'a HashMap<u32, NodeRef>,
by_index: &'a HashMap<u32, usize>,
}
impl Resolver<'_> {
fn place(&self, plan: &Plan, binding: ColumnBinding) -> Option<Place> {
if let Some(&relation) = self.by_index.get(&binding.table) {
return Some((relation, binding.column));
}
let &projection = self.projections.get(&binding.table)?;
let Node::Project { exprs, .. } = *plan.node(projection) else { return None };
let &expr = plan.expr_list(exprs).get(binding.column as usize)?;
self.plain(plan, expr)
}
fn plain(&self, plan: &Plan, expr: ExprRef) -> Option<Place> {
match *plan.expr(expr) {
Expr::Column(binding) => self.place(plan, binding),
_ => None,
}
}
}
fn equality(
plan: &Plan,
find: &Resolver<'_>,
relations: &[Relation],
condition: ExprRef,
) -> Option<(Place, Place)> {
let Expr::Compare { op: CompareOp::Equal, left, right } = *plan.expr(condition) else {
return None;
};
let one = key(plan, find, relations, left)?;
let other = key(plan, find, relations, right)?;
(one.0 != other.0).then_some((one, other))
}
fn key(plan: &Plan, find: &Resolver<'_>, relations: &[Relation], side: ExprRef) -> Option<Place> {
let place = match *plan.expr(side) {
Expr::Cast { input, try_cast: false } => {
let place = find.plain(plan, input)?;
if !widens(&field(plan, relations, place).ty, plan.expr_type(side)) {
return None;
}
place
}
_ => find.plain(plan, side)?,
};
keyed(&field(plan, relations, place).ty).then_some(place)
}
fn keyed(ty: &LogicalType) -> bool {
matches!(
ty,
LogicalType::TinyInt | LogicalType::SmallInt | LogicalType::Integer | LogicalType::BigInt
)
}
fn widens(from: &LogicalType, to: &LogicalType) -> bool {
fn range(ty: &LogicalType) -> Option<(i128, i128)> {
Some(match ty {
LogicalType::TinyInt => (i128::from(i8::MIN), i128::from(i8::MAX)),
LogicalType::SmallInt => (i128::from(i16::MIN), i128::from(i16::MAX)),
LogicalType::Integer => (i128::from(i32::MIN), i128::from(i32::MAX)),
LogicalType::BigInt => (i128::from(i64::MIN), i128::from(i64::MAX)),
LogicalType::UTinyInt => (0, i128::from(u8::MAX)),
LogicalType::USmallInt => (0, i128::from(u16::MAX)),
LogicalType::UInteger => (0, i128::from(u32::MAX)),
LogicalType::UBigInt => (0, i128::from(u64::MAX)),
_ => return None,
})
}
match (range(from), range(to)) {
(Some((low, high)), Some((floor, ceiling))) => floor <= low && high <= ceiling,
_ => false,
}
}
fn count_u32(at: usize) -> u32 {
u32::try_from(at).expect("a plan has fewer than u32::MAX of anything")
}
#[derive(Default)]
struct Classes {
numbers: HashMap<Place, usize>,
parent: Vec<usize>,
seen: Vec<Place>,
}
impl Classes {
fn number(&mut self, place: Place) -> usize {
if let Some(&number) = self.numbers.get(&place) {
return number;
}
let number = self.parent.len();
self.parent.push(number);
self.numbers.insert(place, number);
self.seen.push(place);
number
}
fn root(&mut self, mut number: usize) -> usize {
while self.parent[number] != number {
self.parent[number] = self.parent[self.parent[number]];
number = self.parent[number];
}
number
}
fn union(&mut self, one: Place, other: Place) {
let one = self.number(one);
let other = self.number(other);
let (one, other) = (self.root(one), self.root(other));
if one != other {
self.parent[one.max(other)] = one.min(other);
}
}
fn numbered(mut self) -> (std::collections::BTreeMap<Place, u32>, u32) {
let mut class_of = std::collections::BTreeMap::new();
let mut numbers: HashMap<usize, u32> = HashMap::new();
for place in self.seen.clone() {
let number = self.numbers[&place];
let root = self.root(number);
let fresh = count_u32(numbers.len());
let class = *numbers.entry(root).or_insert(fresh);
class_of.insert(place, class);
}
(class_of, count_u32(numbers.len()))
}
}
fn gyo(edges: &[BTreeSet<u32>]) -> std::result::Result<Vec<Option<usize>>, String> {
let mut parents: Vec<Option<usize>> = vec![None; edges.len()];
let mut left: Vec<usize> = (0..edges.len()).collect();
while !left.is_empty() {
let mut removed = None;
for (slot, &ear) in left.iter().enumerate() {
let shared: BTreeSet<u32> = edges[ear]
.iter()
.copied()
.filter(|class| {
left.iter().any(|&other| other != ear && edges[other].contains(class))
})
.collect();
if shared.is_empty() {
removed = Some((slot, None));
break;
}
let holder =
left.iter().copied().find(|&other| other != ear && shared.is_subset(&edges[other]));
if let Some(holder) = holder {
if shared.len() > 1 {
return Err("joins two relations on more than one column".to_owned());
}
removed = Some((slot, Some(holder)));
break;
}
}
let Some((slot, parent)) = removed else {
return Err("is over a join with a cycle in it".to_owned());
};
let ear = left.remove(slot);
parents[ear] = parent;
}
Ok(parents)
}
fn rooted(parents: &[Option<usize>], wanted: &[usize]) -> Vec<(usize, Option<usize>)> {
let count = parents.len();
let mut next_to: Vec<Vec<usize>> = vec![Vec::new(); count];
for (child, parent) in parents.iter().enumerate() {
if let Some(parent) = *parent {
next_to[child].push(parent);
next_to[parent].push(child);
}
}
let mut placed = vec![false; count];
let mut order = Vec::with_capacity(count);
for start in 0..count {
if placed[start] {
continue;
}
let mut tree = vec![start];
placed[start] = true;
let mut at = 0;
while at < tree.len() {
for &other in &next_to[tree[at]] {
if !placed[other] {
placed[other] = true;
tree.push(other);
}
}
at += 1;
}
let root = tree
.iter()
.copied()
.max_by_key(|&relation| (wanted[relation], std::cmp::Reverse(relation)))
.unwrap_or(start);
let mut stack: Vec<(usize, Option<usize>, bool)> = vec![(root, None, false)];
while let Some((relation, parent, expanded)) = stack.pop() {
if expanded {
order.push((relation, parent));
continue;
}
stack.push((relation, parent, true));
for &other in next_to[relation].iter().rev() {
if Some(other) != parent {
stack.push((other, Some(relation), false));
}
}
}
}
order
}
fn narrow(
plan: &mut Plan,
relation: &Relation,
fresh: u32,
read: &BTreeSet<u32>,
moved: &HashMap<u32, u32>,
find: &Resolver<'_>,
) -> NodeRef {
let Node::Get { catalog, schema, table, alias, columns, .. } = *plan.node(relation.get) else {
unreachable!("a relation is a scan, which `relation` checked")
};
let kept: Vec<Field> =
read.iter().map(|&column| plan.field_list(columns)[column as usize].clone()).collect();
let kept = plan.add_fields(&kept);
let span = plan.node_span(relation.get);
let scan = plan.add_node_at(
Node::Get { catalog, schema, table, alias, index: fresh, columns: kept },
span,
);
if relation.local.is_empty() {
return scan;
}
let predicates: Vec<ExprRef> = relation
.local
.iter()
.map(|&predicate| rebound(plan, predicate, fresh, moved, find))
.collect();
let predicate = match predicates.as_slice() {
[one] => *one,
_ => {
let span = plan.expr_span(relation.local[0]);
let children = plan.add_expr_list(&predicates);
plan.add_expr_at(
Expr::Conjunction { op: ConjunctionOp::And, children },
LogicalType::Boolean,
span,
)
}
};
plan.add_node(Node::Filter { input: scan, predicate })
}
fn rebound(
plan: &mut Plan,
expr: ExprRef,
fresh: u32,
moved: &HashMap<u32, u32>,
find: &Resolver<'_>,
) -> ExprRef {
if let Expr::Column(binding) = *plan.expr(expr) {
let (_, column) =
find.place(plan, binding).expect("the predicate was checked to read this");
let ty = plan.expr_type(expr).clone();
let span = plan.expr_span(expr);
return plan.add_expr_at(Expr::Column(ColumnBinding::new(fresh, moved[&column])), ty, span);
}
walk::rebuild(plan, expr, &mut |plan, child| rebound(plan, child, fresh, moved, find))
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use super::{ConsistentExtremes, gyo, rooted, widens};
use crate::pass::{Context, Pass};
use rudb_common::LogicalType;
use rudb_common::rules::{Rule, Rules};
use rudb_plan::Plan;
fn joined(aggregates: &str, groups: &str, join: &str) -> Plan {
let text = format!(
"Aggregate #3 groups=[{groups}] aggregates=[{aggregates}]\n \
Join {join}\n \
Get memory.main.t AS t #0 [id::INTEGER, name::VARCHAR, n::INTEGER]\n \
Get memory.main.u AS u #1 [t_id::INTEGER, note::VARCHAR]\n"
);
Plan::parse(&text).unwrap_or_else(|error| panic!("{text} did not parse: {error}"))
}
const EQUAL: &str = "INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]";
fn rewritten(mut plan: Plan) -> (String, Vec<String>) {
ConsistentExtremes.run(&mut plan, &Context::new()).expect("the pass does not fail");
(plan.to_string(), plan.declined().to_vec())
}
#[test]
fn a_min_and_a_max_over_an_equality_join_become_one_node() {
let plan = joined("min(#0.1::VARCHAR)::VARCHAR, max(#1.1::VARCHAR)::VARCHAR", "", EQUAL);
let (text, declined) = rewritten(plan);
assert!(text.starts_with("Consistent #3"), "{text}");
assert!(!text.contains("Join"), "{text}");
assert!(declined.is_empty(), "{declined:?}");
}
#[test]
fn the_switch_keeps_the_join() {
let mut plan = joined("min(#0.1::VARCHAR)::VARCHAR", "", EQUAL);
let mut rules = Rules::new();
rules.set(Rule::Consistent, false);
let mut context = Context::new();
context.govern(rules);
ConsistentExtremes.run(&mut plan, &context).expect("the pass does not fail");
assert!(plan.to_string().contains("Join INNER"), "{plan}");
}
#[test]
fn every_other_shape_is_declined_with_its_reason() {
let cases = [
("count_star()::BIGINT", "", EQUAL, "not a MIN or a MAX"),
("sum(#0.2::INTEGER)::HUGEINT", "", EQUAL, "not a MIN or a MAX"),
("min(#0.2::INTEGER)::INTEGER, count_star()::BIGINT", "", EQUAL, "not a MIN or a MAX"),
("string_agg(#0.1::VARCHAR, ','::VARCHAR)::VARCHAR", "", EQUAL, "not a MIN or a MAX"),
("min(#0.2::INTEGER)::INTEGER", "#1.1::VARCHAR", EQUAL, "groups by a key"),
(
"min(#0.2::INTEGER)::INTEGER",
"",
"LEFT on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]",
"not an inner join",
),
(
"min(#0.2::INTEGER)::INTEGER",
"",
"INNER on=[(#0.0::INTEGER < #1.0::INTEGER)::BOOLEAN]",
"not an equality",
),
(
"min(#0.2::INTEGER)::INTEGER",
"",
"INNER on=[(#0.1::VARCHAR = #1.1::VARCHAR)::BOOLEAN]",
"not an equality",
),
];
for (aggregates, groups, join, reason) in cases {
let (text, declined) = rewritten(joined(aggregates, groups, join));
assert!(!text.contains("Consistent"), "{aggregates} {groups} {join}: {text}");
assert!(
declined.iter().any(|written| written.contains(reason)),
"{aggregates} {groups} {join}: {declined:?}"
);
}
}
#[test]
fn a_cyclic_join_is_declined() {
let text = "Aggregate #3 groups=[] aggregates=[min(#0.1::INTEGER)::INTEGER]\n \
Filter (((#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN AND (#1.1::INTEGER = #2.0::INTEGER)::BOOLEAN)::BOOLEAN AND (#2.1::INTEGER = #0.1::INTEGER)::BOOLEAN)::BOOLEAN\n \
CrossProduct\n \
CrossProduct\n \
Get memory.main.a AS a #0 [x::INTEGER, y::INTEGER]\n \
Get memory.main.b AS b #1 [x::INTEGER, y::INTEGER]\n \
Get memory.main.c AS c #2 [x::INTEGER, y::INTEGER]\n";
let plan =
Plan::parse(text).unwrap_or_else(|error| panic!("{text} did not parse: {error}"));
let (text, declined) = rewritten(plan);
assert!(!text.contains("Consistent"), "{text}");
assert!(declined.iter().any(|written| written.contains("cycle")), "{declined:?}");
}
fn edges(list: &[&[u32]]) -> Vec<BTreeSet<u32>> {
list.iter().map(|classes| classes.iter().copied().collect()).collect()
}
#[test]
fn a_star_is_one_tree_with_the_points_under_the_middle() {
let parents = gyo(&edges(&[&[0], &[0, 1, 2], &[1], &[2]])).expect("a star is acyclic");
assert_eq!(parents.iter().filter(|parent| parent.is_none()).count(), 1, "{parents:?}");
assert_eq!(parents[0], Some(1), "{parents:?}");
assert_eq!(parents[2], Some(1), "{parents:?}");
}
#[test]
fn a_triangle_is_a_cycle() {
let found = gyo(&edges(&[&[0, 1], &[1, 2], &[2, 0]]));
assert!(found.is_err_and(|reason| reason.contains("cycle")));
}
#[test]
fn one_class_shared_by_many_is_not_a_cycle() {
assert!(gyo(&edges(&[&[0], &[0], &[0], &[0]])).is_ok());
}
#[test]
fn two_relations_sharing_two_classes_are_a_composite_key() {
let found = gyo(&edges(&[&[0, 1], &[0, 1]]));
assert!(found.is_err_and(|reason| reason.contains("more than one")));
}
#[test]
fn relations_that_share_nothing_are_each_a_tree() {
let parents = gyo(&edges(&[&[0], &[1], &[]])).expect("a forest is acyclic");
assert_eq!(parents, [None, None, None]);
}
#[test]
fn the_root_is_the_relation_most_extremes_are_read_from_and_comes_last() {
let parents = gyo(&edges(&[&[0], &[0, 1], &[1]])).expect("a line is acyclic");
let order = rooted(&parents, &[0, 0, 2]);
assert_eq!(order.last(), Some(&(2, None)));
let position = |relation: usize| order.iter().position(|&(at, _)| at == relation);
for &(relation, parent) in &order {
if let Some(parent) = parent {
assert!(position(relation) < position(parent), "{order:?}");
}
}
}
#[test]
fn only_a_cast_that_keeps_every_value_is_a_key() {
assert!(widens(&LogicalType::Integer, &LogicalType::BigInt));
assert!(widens(&LogicalType::UInteger, &LogicalType::BigInt));
assert!(!widens(&LogicalType::BigInt, &LogicalType::Integer));
assert!(!widens(&LogicalType::Integer, &LogicalType::UInteger));
assert!(!widens(&LogicalType::Integer, &LogicalType::Double));
}
}