Refactor the query pipeline into a two-stage MPHF hit-detection pass followed by a column-major matrix fetch to improve cache efficiency. Introduce a QueryHit enum for event-driven callbacks, decoupling hit detection from data population. Add scan/fetch metrics to QueryStats, update Phase 4 architecture docs, and align tests with the new callback signature.
80 lines
2.9 KiB
Rust
80 lines
2.9 KiB
Rust
use super::*;
|
|
|
|
// ── QueryStats::AddAssign ───────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn query_stats_add_assign_sums_fields() {
|
|
let mut total = QueryStats {
|
|
n_unique_kmers: 3,
|
|
n_mphf_calls: 5,
|
|
n_hits: 2,
|
|
n_columns_scanned: 1,
|
|
n_col_get_calls: 7,
|
|
};
|
|
total += QueryStats {
|
|
n_unique_kmers: 1,
|
|
n_mphf_calls: 4,
|
|
n_hits: 1,
|
|
n_columns_scanned: 2,
|
|
n_col_get_calls: 3,
|
|
};
|
|
assert_eq!(total.n_unique_kmers, 4);
|
|
assert_eq!(total.n_mphf_calls, 9);
|
|
assert_eq!(total.n_hits, 3);
|
|
assert_eq!(total.n_columns_scanned, 3);
|
|
assert_eq!(total.n_col_get_calls, 10);
|
|
}
|
|
|
|
#[test]
|
|
fn query_stats_default_is_zero() {
|
|
let s = QueryStats::default();
|
|
assert_eq!(s.n_unique_kmers, 0);
|
|
assert_eq!(s.n_mphf_calls, 0);
|
|
assert_eq!(s.n_hits, 0);
|
|
assert_eq!(s.n_columns_scanned, 0);
|
|
assert_eq!(s.n_col_get_calls, 0);
|
|
}
|
|
|
|
// ── query_partition_with on a not-yet-indexed partition ─────────────────────
|
|
|
|
/// A `KmerPartition` created but never taken through `build_layers` has no
|
|
/// `index/` subdirectory under any partition — `query_partition_with` must
|
|
/// recognise this and return default (all-zero) stats rather than erroring,
|
|
/// exactly like an empty `kmers` map.
|
|
#[test]
|
|
fn query_partition_with_missing_index_dir_returns_default_stats() {
|
|
let tmp = tempfile::tempdir().expect("tempdir");
|
|
let partition = KmerPartition::create(tmp.path().join("idx"), 2, 21, 9, false)
|
|
.expect("create partition");
|
|
|
|
let mut kmers: HashMap<CanonicalKmer, Vec<KmerDesc>> = HashMap::new();
|
|
// Any well-formed canonical k-mer works here — the call must return
|
|
// before ever attempting an MPHF lookup, since `index/` doesn't exist.
|
|
let kmer = CanonicalKmer::from_raw_unchecked(0u64);
|
|
kmers.insert(kmer, vec![KmerDesc { seq_idx: 0, pos: 0 }]);
|
|
|
|
let stats = partition
|
|
.query_partition_with(0, &kmers, 1, false, |_event| {
|
|
panic!("on_event must not be called: no index was built");
|
|
})
|
|
.expect("query_partition_with should not error on a missing index dir");
|
|
|
|
assert_eq!(stats, QueryStats::default());
|
|
}
|
|
|
|
#[test]
|
|
fn query_partition_with_empty_kmers_is_a_noop() {
|
|
let tmp = tempfile::tempdir().expect("tempdir");
|
|
let partition = KmerPartition::create(tmp.path().join("idx"), 2, 21, 9, false)
|
|
.expect("create partition");
|
|
|
|
let kmers: HashMap<CanonicalKmer, Vec<KmerDesc>> = HashMap::new();
|
|
let stats = partition
|
|
.query_partition_with(0, &kmers, 1, false, |_event| {
|
|
panic!("on_event must not be called on an empty kmer map");
|
|
})
|
|
.expect("query_partition_with on an empty map should not error");
|
|
|
|
assert_eq!(stats, QueryStats::default());
|
|
}
|