Skip to main content

sift_core/index/
kinds.rs

1use std::path::Path;
2
3use serde::{Deserialize, Serialize};
4
5use super::config::{CorpusKind, IndexConfig};
6use super::trigram;
7use super::{IndexDestination, IndexSource};
8
9/// How an index query plan resolves candidates.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
11pub enum PlanMode {
12    /// The query was narrowed using trigram candidates from the index.
13    #[default]
14    IndexedCandidates,
15    /// No trigrams were usable — all indexed files must be scanned.
16    FullScan,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct QueryPlanOutput {
21    pub pattern: String,
22    pub mode: PlanMode,
23}
24
25/// Tag identifying an index kind for lifecycle dispatch (build, open, update).
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
27#[serde(rename_all = "lowercase")]
28pub enum IndexKind {
29    Trigram,
30}
31
32impl IndexKind {
33    pub const ALL: &[Self] = &[Self::Trigram];
34
35    #[must_use]
36    pub const fn as_str(self) -> &'static str {
37        match self {
38            Self::Trigram => "trigram",
39        }
40    }
41
42    /// Return the artifact file names that this index kind produces.
43    #[must_use]
44    pub(crate) const fn artifact_names(self) -> &'static [&'static str] {
45        match self {
46            Self::Trigram => &[
47                crate::FILES_BIN,
48                crate::LEXICON_BIN,
49                crate::POSTINGS_BIN,
50                crate::TRIGRAMS_BIN,
51            ],
52        }
53    }
54
55    /// Build this index kind into the given destination.
56    pub(crate) fn build(
57        self,
58        config: &IndexConfig<'_>,
59        dest: IndexDestination,
60    ) -> crate::Result<()> {
61        match self {
62            Self::Trigram => {
63                let tables = trigram::builder::IndexTables::build(config)?;
64                let root = config.corpus.root.canonicalize()?;
65                trigram::TrigramIndex::persist_tables(&tables, &root, config.corpus.kind, dest)?;
66                Ok(())
67            }
68        }
69    }
70
71    /// Open this index kind from the given source.
72    pub(crate) fn open(
73        self,
74        source: IndexSource,
75        root: &Path,
76        corpus_kind: CorpusKind,
77    ) -> crate::Result<Index> {
78        match self {
79            Self::Trigram => Ok(Index::Trigram(trigram::TrigramIndex::open_tables(
80                source,
81                root,
82                corpus_kind,
83            )?)),
84        }
85    }
86
87    /// Update this index kind from the current snapshot, writing to `dest`.
88    ///
89    /// Returns `true` if a new index was written. If the index kind is not
90    /// present in the current snapshot, it is built from scratch.
91    pub(crate) fn update(
92        self,
93        current: IndexSource,
94        config: &IndexConfig<'_>,
95        dest: IndexDestination,
96    ) -> crate::Result<bool> {
97        let is_present = match &current {
98            IndexSource::Directory(dir) => dir.exists(),
99            IndexSource::Snapshot { reader, namespace } => {
100                reader.manifest().indexes.iter().any(|n| n == *namespace)
101            }
102        };
103        if !is_present {
104            self.build(config, dest)?;
105            return Ok(true);
106        }
107
108        let root = config
109            .corpus
110            .root
111            .canonicalize()
112            .unwrap_or_else(|_| config.corpus.root.to_path_buf());
113        match self {
114            Self::Trigram => {
115                let existing =
116                    trigram::TrigramIndex::open_tables(current, &root, config.corpus.kind)?;
117                let output = existing.rebuild(config, dest)?;
118                Ok(output.is_some())
119            }
120        }
121    }
122}
123
124impl std::fmt::Display for IndexKind {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        f.write_str(self.as_str())
127    }
128}
129
130impl std::str::FromStr for IndexKind {
131    type Err = String;
132
133    fn from_str(s: &str) -> Result<Self, Self::Err> {
134        match s {
135            "trigram" => Ok(Self::Trigram),
136            other => Err(format!("unknown index kind: {other}")),
137        }
138    }
139}
140
141/// An opened index instance, used for search-time dispatch.
142pub enum Index {
143    Trigram(trigram::TrigramIndex),
144}
145
146impl Index {
147    #[must_use]
148    pub fn root(&self) -> &Path {
149        match self {
150            Self::Trigram(idx) => idx.root(),
151        }
152    }
153
154    #[must_use]
155    pub const fn corpus_kind(&self) -> CorpusKind {
156        match self {
157            Self::Trigram(idx) => idx.corpus_kind(),
158        }
159    }
160
161    #[must_use]
162    pub fn candidates(&self, query: &crate::query::QuerySpec<'_>) -> Option<Vec<crate::Candidate>> {
163        match self {
164            Self::Trigram(idx) => idx.candidates(query),
165        }
166    }
167
168    #[must_use]
169    pub(crate) fn all_files(&self) -> Vec<crate::Candidate> {
170        match self {
171            Self::Trigram(idx) => idx.all_files(),
172        }
173    }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
177pub struct FileId(usize);
178
179impl FileId {
180    #[must_use]
181    pub const fn new(value: usize) -> Self {
182        Self(value)
183    }
184
185    #[must_use]
186    pub const fn get(self) -> usize {
187        self.0
188    }
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
192pub struct IndexId(usize);
193
194impl IndexId {
195    #[must_use]
196    pub const fn new(value: usize) -> Self {
197        Self(value)
198    }
199
200    #[must_use]
201    pub const fn get(self) -> usize {
202        self.0
203    }
204}