mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-07-12 03:12:11 +02:00
test: lowering-boundary pins for Both + undirected under degraded coverage
Closes two coverage gaps from review of my own test surface: (1) the
lowering boundary — undirected lowers to Expand{direction: Both} both
top-level and inside not{} (the discarded-context-clone regression is
now pinned at the layer it lives at, not only end-to-end); (2) the
review-fix path — undirected both_modes equivalence with a
mutation-appended unindexed fragment degrading BTREE coverage, so
results stay correct and mode-equivalent whichever path the pessimistic
coverage pricing picks.
This commit is contained in:
parent
a41d6be88f
commit
2faa8dfa3e
2 changed files with 93 additions and 0 deletions
|
|
@ -39,6 +39,63 @@ return { $f.name, $f.age }
|
|||
assert_eq!(ir.return_exprs.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lower_undirected_traversal_to_direction_both() {
|
||||
let catalog = setup();
|
||||
let qf = parse_query(
|
||||
r#"
|
||||
query q($name: String) {
|
||||
match {
|
||||
$p: Person { name: $name }
|
||||
$p <knows> $f
|
||||
}
|
||||
return { $f.name }
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let tc = typecheck_query(&catalog, &qf.queries[0]).unwrap();
|
||||
let ir = lower_query(&catalog, &qf.queries[0], &tc).unwrap();
|
||||
match &ir.pipeline[1] {
|
||||
IROp::Expand { direction, .. } => assert_eq!(*direction, Direction::Both),
|
||||
op => panic!("expected Expand, got {op:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
// The discarded-context-clone regression: negation inners are typechecked into
|
||||
// a clone that never reaches lowering's ResolvedTraversal lookup, so direction
|
||||
// used to silently fall back to Out inside not{}. Undirectedness now travels
|
||||
// on the AST node; this pins Both surviving into the AntiJoin's inner Expand.
|
||||
#[test]
|
||||
fn test_lower_undirected_inside_negation_keeps_direction_both() {
|
||||
let catalog = setup();
|
||||
let qf = parse_query(
|
||||
r#"
|
||||
query q() {
|
||||
match {
|
||||
$p: Person
|
||||
not { $p <knows> $_ }
|
||||
}
|
||||
return { $p.name }
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let tc = typecheck_query(&catalog, &qf.queries[0]).unwrap();
|
||||
let ir = lower_query(&catalog, &qf.queries[0], &tc).unwrap();
|
||||
let IROp::AntiJoin { inner, .. } = &ir.pipeline[1] else {
|
||||
panic!("expected AntiJoin, got {:?}", ir.pipeline[1]);
|
||||
};
|
||||
match &inner[0] {
|
||||
IROp::Expand { direction, .. } => assert_eq!(
|
||||
*direction,
|
||||
Direction::Both,
|
||||
"negation inner must not fall back to Out"
|
||||
),
|
||||
op => panic!("expected inner Expand, got {op:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lower_negation() {
|
||||
let catalog = setup();
|
||||
|
|
|
|||
|
|
@ -141,6 +141,42 @@ query connected($name: String) {
|
|||
assert_eq!(got, vec!["Alice", "Diana"], "out ∪ in neighbors of Bob");
|
||||
}
|
||||
|
||||
/// The undirected cost fix (review follow-up): coverage is priced by the
|
||||
/// WORST of the two probed columns. This test degrades coverage via a
|
||||
/// mutation-appended unindexed fragment and asserts undirected results stay
|
||||
/// correct and mode-equivalent regardless of which path the cost model picks.
|
||||
#[tokio::test]
|
||||
async fn indexed_matches_csr_undirected_under_degraded_coverage() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let mut db = init_and_load(&dir).await;
|
||||
|
||||
// Append an edge through the mutation path — an unindexed fragment that
|
||||
// degrades BTREE coverage on the Knows table (pinned by the coverage test
|
||||
// above). Diana->Alice also gives Alice a NEW incoming edge that only the
|
||||
// undirected form can see from Alice's side.
|
||||
mutate_main(
|
||||
&mut db,
|
||||
MUTATION_QUERIES,
|
||||
"add_friend",
|
||||
¶ms(&[("$from", "Diana"), ("$to", "Alice")]),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let queries = r#"
|
||||
query connected($name: String) {
|
||||
match {
|
||||
$p: Person { name: $name }
|
||||
$p <knows> $f
|
||||
}
|
||||
return { $f.name }
|
||||
}
|
||||
"#;
|
||||
// Alice: outgoing Bob, Charlie; incoming Diana (the fresh unindexed edge).
|
||||
let got = both_modes(&mut db, queries, "connected", ¶ms(&[("$name", "Alice")])).await;
|
||||
assert_eq!(got, vec!["Bob", "Charlie", "Diana"]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn indexed_matches_csr_multi_hop_same_type() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue