sift_core/query/
planner.rs1use std::collections::HashSet;
2use std::path::PathBuf;
3
4use crate::Candidate;
5use crate::CandidateFilter;
6use crate::StoreMeta;
7use crate::index::Indexes;
8use crate::query::QuerySpec;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum CandidateRequirement {
13 Complete,
14 PotentialMatches,
15}
16
17pub struct QueryPlanner<'a> {
19 spec: QuerySpec<'a>,
20}
21
22impl<'a> QueryPlanner<'a> {
23 #[must_use]
24 pub const fn new(spec: QuerySpec<'a>) -> Self {
25 Self { spec }
26 }
27
28 pub fn candidates(
37 &self,
38 indexes: &Indexes,
39 requirement: CandidateRequirement,
40 filter: &CandidateFilter,
41 store_meta: Option<&StoreMeta>,
42 walk_unindexed: bool,
43 base: impl FnOnce() -> crate::Result<Vec<Candidate>>,
44 ) -> crate::Result<Vec<Candidate>> {
45 match requirement {
46 CandidateRequirement::Complete => {
47 if indexes.is_empty() {
48 return base();
49 }
50 if store_meta.is_some_and(|meta| !meta.matches_search_filter(filter)) {
51 return base();
52 }
53 Ok(indexes.complete_candidates())
54 }
55 CandidateRequirement::PotentialMatches => {
56 if indexes.is_empty() {
57 return base();
58 }
59 match indexes.candidates(&self.spec) {
60 None => base(),
61 Some(snapshot_hits) if !walk_unindexed => Ok(snapshot_hits),
62 Some(mut snapshot_hits) => {
63 let indexed_paths = indexes.indexed_rel_paths();
64 let walked = base()?;
65 let mut seen: HashSet<PathBuf> = snapshot_hits
66 .iter()
67 .map(|c| c.rel_path().to_path_buf())
68 .collect();
69 for candidate in walked {
70 if indexed_paths.contains(candidate.rel_path()) {
71 continue;
72 }
73 if seen.insert(candidate.rel_path().to_path_buf()) {
74 snapshot_hits.push(candidate);
75 }
76 }
77 Ok(snapshot_hits)
78 }
79 }
80 }
81 }
82 }
83}