use super::*;
use crate::{AccessPathPlan, JoinExecutionStrategy};
use uqa_core::Value;
use uqa_sql::ast::{BinaryOp, JoinKind};
fn equality(left: &str, right: &str) -> ScalarExpr {
ScalarExpr::Binary {
op: BinaryOp::Equal,
lhs: Box::new(ScalarExpr::Column(left.into())),
rhs: Box::new(ScalarExpr::Column(right.into())),
}
}
fn qualified_literal_equality(qualifier: &str, column: &str, value: &str) -> ScalarExpr {
ScalarExpr::Binary {
op: BinaryOp::Equal,
lhs: Box::new(ScalarExpr::qualified_column(qualifier, column)),
rhs: Box::new(ScalarExpr::Literal(Value::Str(value.into()))),
}
}
fn joined_source(kind: JoinKind, on: ScalarExpr) -> SourcePlan {
SourcePlan::Join {
left: Box::new(SourcePlan::Table {
bound_columns: None,
name: "left_table".into(),
qualifier: "left_table".into(),
alias: Some("l".into()),
column_aliases: Vec::new(),
include_descendants: true,
}),
right: Box::new(SourcePlan::Table {
bound_columns: None,
name: "right_table".into(),
qualifier: "right_table".into(),
alias: Some("r".into()),
column_aliases: Vec::new(),
include_descendants: true,
}),
kind,
on: Some(on),
using: None,
natural: false,
alias: None,
column_aliases: Vec::new(),
lateral: false,
strategy: JoinExecutionStrategy::Hash,
}
}
fn query_block(filter: ScalarExpr, from: SourcePlan) -> QueryBlockPlan {
QueryBlockPlan {
projections: Vec::new(),
from: Some(from),
r#where: Some(filter),
compute: ComputePlan::Project,
group_by: Vec::new(),
grouping_sets: Vec::new(),
group_distinct: false,
having: None,
order_by: Vec::new(),
limit: None,
with_ties: false,
offset: None,
distinct: false,
distinct_on: Vec::new(),
subqueries: Vec::new(),
access: AccessPathPlan::Row,
locking: Vec::new(),
}
}
#[test]
fn unique_unqualified_owner_enables_safe_filter_pushdown() {
let fixture = Fixture::new();
let context = fixture.context();
let owners = BTreeMap::from([
("p_name".into(), Some("part".into())),
("shared".into(), None),
]);
let qualifiers = BTreeSet::from(["part".into(), "lineitem".into()]);
let predicate = ScalarExpr::Binary {
op: BinaryOp::Equal,
lhs: Box::new(ScalarExpr::Column("p_name".into())),
rhs: Box::new(ScalarExpr::Literal(Value::Str("green".into()))),
};
let (qualifier, pushed) =
qualifier_filter_for_part(context, &predicate, &qualifiers, None, &owners, &[]).unwrap();
assert_eq!(qualifier, "part");
let ScalarExpr::Binary { lhs, .. } = pushed else {
panic!("pushdown changed the predicate shape");
};
assert!(matches!(
lhs.as_ref(),
ScalarExpr::QualifiedColumn { qualifier, column, .. }
if qualifier == "part" && column == "p_name"
));
let ambiguous = ScalarExpr::Column("shared".into());
assert!(
qualifier_filter_for_part(context, &ambiguous, &qualifiers, None, &owners, &[]).is_none()
);
}
#[test]
fn table_range_aliases_are_the_filter_ownership_names() {
let fixture = Fixture::new();
let scope = fixture.scope();
let source = SourcePlan::Table {
bound_columns: None,
name: "filter_alias_source".into(),
qualifier: "filter_alias_source".into(),
alias: Some("source".into()),
column_aliases: vec!["key".into(), "value".into()],
include_descendants: true,
};
let owners = source_column_owners(scope, &source).unwrap();
assert_eq!(owners.get("key"), Some(&Some("source".into())));
assert_eq!(owners.get("value"), Some(&Some("source".into())));
assert!(!owners.contains_key("id"));
assert!(!owners.contains_key("label"));
}
#[test]
fn internal_relation_attributes_never_push_into_catalog_sources() {
let fixture = Fixture::new();
let context = fixture.context();
let qualifiers = BTreeSet::from(["source".into()]);
let predicate = ScalarExpr::Binary {
op: BinaryOp::Equal,
lhs: Box::new(ScalarExpr::qualified_column("source", "id")),
rhs: Box::new(ScalarExpr::InternalColumn(
uqa_sql::ast::InternalRelationId::allocate().column(0),
)),
};
assert!(qualifier_filter_for_part(
context,
&predicate,
&qualifiers,
Some("source"),
&BTreeMap::new(),
&[],
)
.is_none());
}
#[test]
fn disjunction_derives_a_necessary_filter_for_every_complete_source_projection() {
let fixture = Fixture::new();
let context = fixture.context();
let qualifiers = BTreeSet::from(["n1".into(), "n2".into()]);
let predicate = ScalarExpr::Or(vec![
ScalarExpr::And(vec![
qualified_literal_equality("n1", "name", "FRANCE"),
qualified_literal_equality("n2", "name", "GERMANY"),
]),
ScalarExpr::And(vec![
qualified_literal_equality("n1", "name", "GERMANY"),
qualified_literal_equality("n2", "name", "FRANCE"),
]),
]);
let derived = derived_disjunctive_qualifier_filters(
context,
&predicate,
&qualifiers,
None,
&BTreeMap::new(),
&[],
);
assert_eq!(derived.len(), 2);
for (qualifier, predicate) in derived {
let ScalarExpr::Or(disjuncts) = predicate else {
panic!("expected a projected disjunction")
};
assert_eq!(disjuncts.len(), 2);
assert!(disjuncts
.iter()
.all(|part| expr_qualifiers(part) == BTreeSet::from([qualifier.clone()])));
}
}
#[test]
fn disjunction_does_not_push_a_projection_missing_from_any_branch() {
let fixture = Fixture::new();
let context = fixture.context();
let qualifiers = BTreeSet::from(["n1".into(), "n2".into()]);
let predicate = ScalarExpr::Or(vec![
qualified_literal_equality("n1", "name", "FRANCE"),
qualified_literal_equality("n2", "name", "GERMANY"),
]);
assert!(derived_disjunctive_qualifier_filters(
context,
&predicate,
&qualifiers,
None,
&BTreeMap::new(),
&[],
)
.is_empty());
}
#[test]
fn inner_join_guarantee_elides_duplicate_where_conjunct() {
let fixture = Fixture::new();
let context = fixture.context();
let scope = fixture.scope();
let join_equality = equality("l.key", "r.key");
let residual = ScalarExpr::Literal(Value::Bool(true));
let from = joined_source(JoinKind::Inner, join_equality.clone());
let block = query_block(
ScalarExpr::And(vec![join_equality, residual.clone()]),
from.clone(),
);
assert_eq!(
final_filter_after_qualifier_pushdown(context, &block, &from, None, scope).unwrap(),
Some(residual)
);
}
#[test]
fn outer_join_keeps_duplicate_where_conjunct() {
let fixture = Fixture::new();
let context = fixture.context();
let scope = fixture.scope();
let join_equality = equality("l.key", "r.key");
let filter = ScalarExpr::And(vec![
join_equality.clone(),
ScalarExpr::Literal(Value::Bool(true)),
]);
let from = joined_source(JoinKind::Left, join_equality);
let block = query_block(filter.clone(), from.clone());
assert_eq!(
final_filter_after_qualifier_pushdown(context, &block, &from, None, scope).unwrap(),
Some(filter)
);
}
#[test]
fn outer_join_marks_only_null_extended_qualifiers_as_unsafe_for_pushdown() {
let join_equality = equality("l.key", "r.key");
assert_eq!(
outer_join_nullable_qualifiers(&joined_source(JoinKind::Left, join_equality.clone())),
BTreeSet::from(["r".into()])
);
assert_eq!(
outer_join_nullable_qualifiers(&joined_source(JoinKind::Right, join_equality.clone())),
BTreeSet::from(["l".into()])
);
assert_eq!(
outer_join_nullable_qualifiers(&joined_source(JoinKind::Full, join_equality.clone())),
BTreeSet::from(["l".into(), "r".into()])
);
assert!(
outer_join_nullable_qualifiers(&joined_source(JoinKind::Inner, join_equality)).is_empty()
);
let nested_outer = joined_source(JoinKind::Left, equality("l.key", "r.key"));
let nested_alias = SourcePlan::Join {
left: Box::new(nested_outer),
right: Box::new(SourcePlan::Table {
bound_columns: None,
name: "marker_table".into(),
qualifier: "marker_table".into(),
alias: Some("marker".into()),
column_aliases: Vec::new(),
include_descendants: true,
}),
kind: JoinKind::Cross,
on: None,
using: None,
natural: false,
alias: Some("joined".into()),
column_aliases: Vec::new(),
lateral: false,
strategy: JoinExecutionStrategy::Auto,
};
assert_eq!(
outer_join_nullable_qualifiers(&nested_alias),
BTreeSet::from(["joined".into()]),
"an alias around an inner join must retain a nested outer join's nullable output"
);
let mut aliased_inner = joined_source(JoinKind::Inner, equality("l.key", "r.key"));
let SourcePlan::Join { alias, .. } = &mut aliased_inner else {
unreachable!()
};
*alias = Some("joined".into());
assert!(outer_join_nullable_qualifiers(&aliased_inner).is_empty());
}
mod fixture;
use fixture::Fixture;