Skip to main content

sift_core/index/
kinds.rs

1use std::path::{Path, PathBuf};
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        paths: &[PathBuf],
61    ) -> crate::Result<()> {
62        match self {
63            Self::Trigram => {
64                let tables = trigram::builder::IndexTables::build(config, paths)?;
65                let root = config.corpus.root.canonicalize()?;
66                trigram::TrigramIndex::persist_tables(&tables, &root, config.corpus.kind, dest)?;
67                Ok(())
68            }
69        }
70    }
71
72    /// Open this index kind from the given source.
73    pub(crate) fn open(
74        self,
75        source: IndexSource,
76        root: &Path,
77        corpus_kind: CorpusKind,
78    ) -> crate::Result<Index> {
79        match self {
80            Self::Trigram => Ok(Index::Trigram(trigram::TrigramIndex::open_tables(
81                source,
82                root,
83                corpus_kind,
84            )?)),
85        }
86    }
87
88    /// Update this index kind from the current snapshot, writing to `dest`.
89    ///
90    /// Returns `true` if a new index was written. If the index kind is not
91    /// present in the current snapshot, it is built from scratch.
92    pub(crate) fn update(
93        self,
94        current: IndexSource,
95        config: &IndexConfig<'_>,
96        dest: IndexDestination,
97        paths: &[PathBuf],
98    ) -> crate::Result<bool> {
99        let is_present = match &current {
100            IndexSource::Directory(dir) => dir.exists(),
101            IndexSource::Snapshot { reader, namespace } => {
102                reader.manifest().indexes.iter().any(|n| n == *namespace)
103            }
104        };
105        if !is_present {
106            self.build(config, dest, paths)?;
107            return Ok(true);
108        }
109
110        let root = config
111            .corpus
112            .root
113            .canonicalize()
114            .unwrap_or_else(|_| config.corpus.root.to_path_buf());
115        match self {
116            Self::Trigram => {
117                let existing =
118                    trigram::TrigramIndex::open_tables(current, &root, config.corpus.kind)?;
119                let output = existing.rebuild(config, dest, paths)?;
120                Ok(output.is_some())
121            }
122        }
123    }
124}
125
126impl std::fmt::Display for IndexKind {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        f.write_str(self.as_str())
129    }
130}
131
132impl std::str::FromStr for IndexKind {
133    type Err = String;
134
135    fn from_str(s: &str) -> Result<Self, Self::Err> {
136        match s {
137            "trigram" => Ok(Self::Trigram),
138            other => Err(format!("unknown index kind: {other}")),
139        }
140    }
141}
142
143/// An opened index instance, used for search-time dispatch.
144pub enum Index {
145    Trigram(trigram::TrigramIndex),
146}
147
148impl Index {
149    #[must_use]
150    pub fn root(&self) -> &Path {
151        match self {
152            Self::Trigram(idx) => idx.root(),
153        }
154    }
155
156    #[must_use]
157    pub const fn corpus_kind(&self) -> CorpusKind {
158        match self {
159            Self::Trigram(idx) => idx.corpus_kind(),
160        }
161    }
162
163    #[must_use]
164    pub fn candidates(&self, query: &crate::query::QuerySpec<'_>) -> Option<Vec<crate::Candidate>> {
165        match self {
166            Self::Trigram(idx) => idx.candidates(query),
167        }
168    }
169
170    #[must_use]
171    pub(crate) fn all_files(&self) -> Vec<crate::Candidate> {
172        match self {
173            Self::Trigram(idx) => idx.all_files(),
174        }
175    }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
179pub struct FileId(usize);
180
181impl FileId {
182    #[must_use]
183    pub const fn new(value: usize) -> Self {
184        Self(value)
185    }
186
187    #[must_use]
188    pub const fn get(self) -> usize {
189        self.0
190    }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
194pub struct IndexId(usize);
195
196impl IndexId {
197    #[must_use]
198    pub const fn new(value: usize) -> Self {
199        Self(value)
200    }
201
202    #[must_use]
203    pub const fn get(self) -> usize {
204        self.0
205    }
206}