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///
27/// Each variant corresponds to one type of on-disk index. The enum drives
28/// `build`, `open`, and `update` via match arms, so adding a new index type
29/// means adding a variant here and implementing those arms.
30///
31/// Today: `Trigram`. Future variants (AST, dependency graph, vector, etc.)
32/// will be added as the composable index architecture expands.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34#[serde(rename_all = "lowercase")]
35pub enum IndexKind {
36    /// Trigram index: inverted index of overlapping 3-byte sequences.
37    Trigram,
38}
39
40impl IndexKind {
41    pub const ALL: &[Self] = &[Self::Trigram];
42
43    #[must_use]
44    pub const fn as_str(self) -> &'static str {
45        match self {
46            Self::Trigram => "trigram",
47        }
48    }
49
50    /// Return the artifact file names that this index kind produces.
51    #[must_use]
52    pub(crate) const fn artifact_names(self) -> &'static [&'static str] {
53        match self {
54            Self::Trigram => &[
55                crate::FILES_BIN,
56                crate::LEXICON_BIN,
57                crate::POSTINGS_BIN,
58                crate::TRIGRAMS_BIN,
59            ],
60        }
61    }
62
63    /// Build this index kind into the given destination.
64    pub(crate) fn build(
65        self,
66        config: &IndexConfig<'_>,
67        dest: IndexDestination,
68        paths: &[PathBuf],
69    ) -> crate::Result<()> {
70        match self {
71            Self::Trigram => {
72                let tables = trigram::builder::IndexTables::build(config, paths)?;
73                let root = config.corpus.root.canonicalize()?;
74                trigram::TrigramIndex::persist_tables(&tables, &root, config.corpus.kind, dest)?;
75                Ok(())
76            }
77        }
78    }
79
80    /// Open this index kind from the given source.
81    pub(crate) fn open(
82        self,
83        source: IndexSource,
84        root: &Path,
85        corpus_kind: CorpusKind,
86    ) -> crate::Result<Index> {
87        match self {
88            Self::Trigram => Ok(Index::Trigram(trigram::TrigramIndex::open_tables(
89                source,
90                root,
91                corpus_kind,
92            )?)),
93        }
94    }
95
96    /// Update this index kind from the current snapshot, writing to `dest`.
97    ///
98    /// Returns `true` if a new index was written. If the index kind is not
99    /// present in the current snapshot, it is built from scratch.
100    pub(crate) fn update(
101        self,
102        current: IndexSource,
103        config: &IndexConfig<'_>,
104        dest: IndexDestination,
105        paths: &[PathBuf],
106    ) -> crate::Result<bool> {
107        let is_present = match &current {
108            IndexSource::Directory(dir) => dir.exists(),
109            IndexSource::Snapshot { reader, namespace } => {
110                reader.manifest().indexes.iter().any(|n| n == *namespace)
111            }
112        };
113        if !is_present {
114            self.build(config, dest, paths)?;
115            return Ok(true);
116        }
117
118        let root = config
119            .corpus
120            .root
121            .canonicalize()
122            .unwrap_or_else(|_| config.corpus.root.to_path_buf());
123        match self {
124            Self::Trigram => {
125                let existing =
126                    trigram::TrigramIndex::open_tables(current, &root, config.corpus.kind)?;
127                let output = existing.rebuild(config, dest, paths)?;
128                Ok(output.is_some())
129            }
130        }
131    }
132}
133
134impl std::fmt::Display for IndexKind {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        f.write_str(self.as_str())
137    }
138}
139
140impl std::str::FromStr for IndexKind {
141    type Err = String;
142
143    fn from_str(s: &str) -> Result<Self, Self::Err> {
144        match s {
145            "trigram" => Ok(Self::Trigram),
146            other => Err(format!("unknown index kind: {other}")),
147        }
148    }
149}
150
151/// An opened index instance, used for query-time candidate narrowing.
152///
153/// Each variant wraps a concrete index implementation. The `Indexes` registry
154/// opens all available indexes and calls `candidates()` on each, intersecting
155/// results to produce tighter narrowing than any single index alone.
156pub enum Index {
157    /// Opened trigram index with memory-mapped posting lists.
158    Trigram(trigram::TrigramIndex),
159}
160
161impl Index {
162    #[must_use]
163    pub fn root(&self) -> &Path {
164        match self {
165            Self::Trigram(idx) => idx.root(),
166        }
167    }
168
169    #[must_use]
170    pub const fn corpus_kind(&self) -> CorpusKind {
171        match self {
172            Self::Trigram(idx) => idx.corpus_kind(),
173        }
174    }
175
176    #[must_use]
177    pub fn candidates(&self, query: &crate::query::QuerySpec<'_>) -> Option<Vec<crate::Candidate>> {
178        match self {
179            Self::Trigram(idx) => idx.candidates(query),
180        }
181    }
182
183    #[must_use]
184    pub(crate) fn all_files(&self) -> Vec<crate::Candidate> {
185        match self {
186            Self::Trigram(idx) => idx.all_files(),
187        }
188    }
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
192pub struct FileId(usize);
193
194impl FileId {
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}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
207pub struct IndexId(usize);
208
209impl IndexId {
210    #[must_use]
211    pub const fn new(value: usize) -> Self {
212        Self(value)
213    }
214
215    #[must_use]
216    pub const fn get(self) -> usize {
217        self.0
218    }
219}