1pub mod storage;
2pub mod types;
3pub mod ingest;
4pub mod index;
5pub mod query;
6pub mod bench;
7pub mod fst_index;
8pub mod incremental;
9pub mod inverted_index;
10pub mod tombstone;
11pub mod locking;
12pub mod compaction;
13
14pub use types::*;
15
16use anyhow::Result;
17
18pub struct SiftDB {
20 path: std::path::PathBuf,
21}
22
23impl SiftDB {
24 pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
26 let path = path.as_ref().to_path_buf();
27 if !path.exists() {
28 anyhow::bail!("SiftDB collection does not exist at: {}", path.display());
29 }
30 Ok(Self { path })
31 }
32
33 pub fn init<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
35 let path = path.as_ref().to_path_buf();
36
37 std::fs::create_dir_all(&path)?;
39 std::fs::create_dir_all(path.join("store"))?;
40 std::fs::create_dir_all(path.join("index"))?;
41 std::fs::create_dir_all(path.join("tmp"))?;
42 std::fs::create_dir_all(path.join("gc"))?;
43
44 let manifest = Manifest::new(0);
46 manifest.write_to_file(&path.join("MANIFEST.a"))?;
47
48 Ok(Self { path })
49 }
50
51 pub fn snapshot(&self) -> Result<Snapshot> {
53 let manifest_path = self.path.join("MANIFEST.a");
54 let manifest = if manifest_path.exists() {
55 Manifest::read_from_file(&manifest_path)?
56 } else {
57 let manifest_path = self.path.join("MANIFEST.b");
58 if manifest_path.exists() {
59 Manifest::read_from_file(&manifest_path)?
60 } else {
61 anyhow::bail!("No valid manifest found");
62 }
63 };
64
65 let path_index = crate::index::PathIndex::read_from_file(&self.path.join("index/path.json"))?;
67 let handles_map = crate::index::HandlesMap::read_from_file(&self.path.join("index/handles.json"))?;
68
69 let inverted_index = if self.path.join("index/terms.fst").exists() && self.path.join("index/posting_lists.json").exists() {
71 crate::inverted_index::InvertedIndex::load_from_files(
72 &self.path.join("index/terms.fst"),
73 &self.path.join("index/posting_lists.json")
74 )?
75 } else {
76 crate::inverted_index::InvertedIndex::new()
77 };
78
79 Ok(Snapshot {
80 collection_path: self.path.clone(),
81 epoch: manifest.epoch,
82 path_index,
83 handles_map,
84 inverted_index,
85 segment_cache: std::collections::HashMap::new(),
86 })
87 }
88
89 pub fn incremental_update(
91 &self,
92 source_path: &std::path::Path,
93 includes: &[String],
94 excludes: &[String],
95 ) -> Result<crate::incremental::DeltaManifest> {
96 let updater = crate::incremental::IncrementalUpdater::new(&self.path);
97
98 let changes = updater.scan_for_changes(source_path, includes, excludes)?;
100
101 if changes.is_empty() {
102 anyhow::bail!("No changes detected since last import");
103 }
104
105 println!("Found {} changed files:", changes.len());
106 for change in &changes {
107 match change.change_type {
108 crate::incremental::ChangeType::Added => {
109 println!(" + {}", change.path.display());
110 }
111 crate::incremental::ChangeType::Modified => {
112 println!(" M {}", change.path.display());
113 }
114 crate::incremental::ChangeType::Deleted => {
115 println!(" - {}", change.path.display());
116 }
117 }
118 }
119
120 let delta_manifest = updater.apply_changes(changes, source_path)?;
122
123 Ok(delta_manifest)
124 }
125
126 pub fn has_incremental_cache(&self) -> bool {
128 let cache_path = self.path.join("index").join("file_cache.json");
129 cache_path.exists()
130 }
131}
132
133pub fn add(left: u64, right: u64) -> u64 {
134 left + right
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn it_works() {
143 let result = add(2, 2);
144 assert_eq!(result, 4);
145 }
146}