use relmath::{
BinaryRelation, ExactSupport, FiniteRelation, NaryRelation, NaryRelationError, RelationView,
ToExactBinaryRelation, ToExactNaryRelation, ToExactUnaryRelation, UnaryRelation,
annotated::{AnnotatedRelation, BooleanSemiring},
provenance::ProvenanceRelation,
temporal::{Interval, ValidTimeRelation},
};
fn exact_pairs<R>(relation: &R) -> BinaryRelation<&'static str, &'static str>
where
R: ExactSupport<(&'static str, &'static str)>
+ ToExactBinaryRelation<&'static str, &'static str>,
{
relation.to_binary_relation()
}
fn exact_values<R>(relation: &R) -> UnaryRelation<&'static str>
where
R: ExactSupport<&'static str> + ToExactUnaryRelation<&'static str>,
{
relation.to_unary_relation()
}
fn exact_rows<R>(relation: &R) -> Result<NaryRelation<&'static str>, NaryRelationError>
where
R: ToExactNaryRelation<&'static str>,
{
relation.to_nary_relation(["student", "course", "status"])
}
#[test]
fn exact_support_materialization_lines_up_across_add_on_binary_surfaces() {
let evidence = ProvenanceRelation::from_facts([
(("alice", "review"), "directory"),
(("bob", "approve"), "policy"),
(("alice", "review"), "directory"),
]);
let permissions = AnnotatedRelation::from_facts([
(("alice", "review"), BooleanSemiring::TRUE),
(("bob", "approve"), BooleanSemiring::TRUE),
(("carol", "audit"), BooleanSemiring::FALSE),
]);
let schedule = ValidTimeRelation::from_facts([
(
("alice", "review"),
Interval::new(1, 3).expect("expected valid interval"),
),
(
("alice", "review"),
Interval::new(3, 5).expect("expected valid interval"),
),
(
("bob", "approve"),
Interval::new(2, 4).expect("expected valid interval"),
),
]);
let expected = vec![("alice", "review"), ("bob", "approve")];
assert_eq!(evidence.exact_support().to_vec(), expected);
assert_eq!(permissions.exact_support().to_vec(), expected);
assert_eq!(schedule.exact_support().to_vec(), expected);
assert_eq!(exact_pairs(&evidence).to_vec(), expected);
assert_eq!(exact_pairs(&permissions).to_vec(), expected);
assert_eq!(exact_pairs(&schedule).to_vec(), expected);
}
#[test]
fn capability_terms_stay_distinct_while_exact_support_agrees() {
let evidence = ProvenanceRelation::from_facts([
(("alice", "review"), "directory"),
(("alice", "review"), "audit_log"),
]);
let permissions = AnnotatedRelation::from_facts([(("alice", "review"), BooleanSemiring::TRUE)]);
let schedule = ValidTimeRelation::from_facts([(
("alice", "review"),
Interval::new(1, 4).expect("expected valid interval"),
)]);
assert_eq!(
evidence
.why(&("alice", "review"))
.expect("expected witness")
.to_vec(),
vec!["audit_log", "directory"]
);
assert_eq!(
permissions.annotation_of(&("alice", "review")),
Some(&BooleanSemiring::TRUE)
);
assert_eq!(
schedule
.valid_time_of(&("alice", "review"))
.expect("expected interval support")
.to_vec(),
vec![Interval::new(1, 4).expect("expected valid interval")]
);
let expected = vec![("alice", "review")];
assert_eq!(evidence.exact_support().to_vec(), expected);
assert_eq!(permissions.exact_support().to_vec(), expected);
assert_eq!(schedule.exact_support().to_vec(), expected);
}
#[test]
fn generic_unary_and_nary_materialization_reuse_current_exact_rules() {
let concepts = AnnotatedRelation::from_facts([
("Closure", BooleanSemiring::TRUE),
("Relations", BooleanSemiring::TRUE),
]);
assert_eq!(
exact_values(&concepts).to_vec(),
vec!["Closure", "Relations"]
);
let rows = ProvenanceRelation::from_facts([
(vec!["Alice", "Math", "passed"], "gradebook"),
(vec!["Bob", "Physics", "passed"], "gradebook"),
]);
assert_eq!(
exact_rows(&rows).expect("expected valid exact rows"),
NaryRelation::from_rows(
["student", "course", "status"],
[["Alice", "Math", "passed"], ["Bob", "Physics", "passed"]],
)
.expect("expected valid exact relation")
);
assert_eq!(
rows.to_nary_relation(["student", "student", "status"])
.expect_err("expected duplicate column error"),
NaryRelationError::DuplicateColumn {
column: "student".to_string(),
}
);
}
#[test]
fn exact_support_of_empty_surfaces_stays_empty() -> Result<(), NaryRelationError> {
let evidence = ProvenanceRelation::<(&str, &str), &str>::new();
let permissions = AnnotatedRelation::<(&str, &str), BooleanSemiring>::new();
let schedule = ValidTimeRelation::<(&str, &str), i32>::new();
let empty_rows = ProvenanceRelation::<Vec<&str>, &str>::new();
assert!(evidence.exact_support().is_empty());
assert!(permissions.exact_support().is_empty());
assert!(schedule.exact_support().is_empty());
assert_eq!(
evidence.to_binary_relation().to_vec(),
Vec::<(&str, &str)>::new()
);
assert_eq!(
permissions.to_binary_relation().to_vec(),
Vec::<(&str, &str)>::new()
);
assert_eq!(
schedule.to_binary_relation().to_vec(),
Vec::<(&str, &str)>::new()
);
assert_eq!(
empty_rows.to_nary_relation(["student", "course"])?,
NaryRelation::new(["student", "course"])?
);
Ok(())
}
#[test]
fn exact_support_materialization_feeds_relation_view_workflow() {
let schedule = ValidTimeRelation::from_facts([
(
("bob", "approve"),
Interval::new(2, 4).expect("expected valid interval"),
),
(
("alice", "review"),
Interval::new(1, 3).expect("expected valid interval"),
),
]);
let exact = schedule.to_binary_relation();
assert_eq!(exact.len(), 2);
assert_eq!(
exact.tuples().copied().collect::<Vec<_>>(),
vec![("alice", "review"), ("bob", "approve")]
);
}