use super::{Duration, Instant, Path, open_lexical_searcher};
use crate::core::cass_prefetch::{
CassPrefetchCandidate, DEFAULT_PREFETCH_TOP_K, MAX_PREFETCH_TOPIC_ID_BYTES,
};
use crate::core::index::IndexGenerationLease;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(crate) enum PrefetchStop {
#[default]
Complete,
Interrupted,
BudgetExceeded,
Unavailable,
StaleGeneration,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(crate) struct LexicalPrefetchReport {
pub completed_queries: usize,
pub matching_queries: usize,
pub stop: PrefetchStop,
}
pub(crate) fn warm_prefetch_lexical(
index_dir: &Path,
expected_generation: u64,
candidates: &[CassPrefetchCandidate],
budget: Duration,
should_stop: impl Fn() -> bool,
) -> LexicalPrefetchReport {
let started = Instant::now();
let checkpoint = || {
if should_stop() {
Some(PrefetchStop::Interrupted)
} else if started.elapsed() >= budget {
Some(PrefetchStop::BudgetExceeded)
} else {
None
}
};
if let Some(stop) = checkpoint() {
return LexicalPrefetchReport {
stop,
..Default::default()
};
}
if candidates.is_empty() {
return LexicalPrefetchReport::default();
}
crate::core::run_cli_with_cx(budget, |cx| async move {
let mut report = LexicalPrefetchReport::default();
let _lease =
match IndexGenerationLease::try_read_for_prefetch(&cx, index_dir, expected_generation)
.await
{
Ok(Some(lease)) => lease,
Ok(None) => {
return LexicalPrefetchReport {
stop: PrefetchStop::StaleGeneration,
..report
};
}
Err(_) => {
return LexicalPrefetchReport {
stop: checkpoint().unwrap_or(PrefetchStop::Unavailable),
..report
};
}
};
if let Some(stop) = checkpoint() {
return LexicalPrefetchReport { stop, ..report };
}
let reader = match open_lexical_searcher(index_dir) {
Ok(Some(reader)) => reader,
_ => {
return LexicalPrefetchReport {
stop: PrefetchStop::Unavailable,
..report
};
}
};
for candidate in candidates.iter().take(DEFAULT_PREFETCH_TOP_K) {
if let Some(stop) = checkpoint() {
report.stop = stop;
break;
}
let query = candidate.topic_id.as_str();
if query.trim().is_empty() || query.len() > MAX_PREFETCH_TOPIC_ID_BYTES {
continue;
}
if cx.checkpoint().is_err() {
report.stop = PrefetchStop::BudgetExceeded;
break;
}
match reader.search(&cx, query, 16).await {
Ok(matches) => {
report.completed_queries += 1;
report.matching_queries += usize::from(!matches.is_empty());
}
Err(_) => {
report.stop = checkpoint().unwrap_or(PrefetchStop::Unavailable);
break;
}
}
if let Some(stop) = checkpoint() {
report.stop = stop;
break;
}
}
report
})
.unwrap_or(LexicalPrefetchReport {
stop: PrefetchStop::Unavailable,
..Default::default()
})
}
#[cfg(all(test, feature = "lexical-bm25"))]
#[path = "search_prefetch_tests.rs"]
mod tests;