subcog/services/
backend_factory.rs1use crate::embedding::{Embedder, FastEmbedEmbedder};
23use crate::storage::index::SqliteBackend;
24use crate::storage::traits::{IndexBackend, VectorBackend};
25use crate::storage::vector::UsearchBackend;
26use std::path::Path;
27use std::sync::Arc;
28
29#[derive(Default)]
33pub struct BackendSet {
34 pub embedder: Option<Arc<dyn Embedder>>,
36 pub index: Option<Arc<dyn IndexBackend + Send + Sync>>,
38 pub vector: Option<Arc<dyn VectorBackend + Send + Sync>>,
40}
41
42impl BackendSet {
43 #[must_use]
45 pub fn is_complete(&self) -> bool {
46 self.embedder.is_some() && self.index.is_some() && self.vector.is_some()
47 }
48
49 #[must_use]
51 pub fn has_embedder(&self) -> bool {
52 self.embedder.is_some()
53 }
54
55 #[must_use]
57 pub fn has_index(&self) -> bool {
58 self.index.is_some()
59 }
60
61 #[must_use]
63 pub fn has_vector(&self) -> bool {
64 self.vector.is_some()
65 }
66}
67
68pub struct BackendFactory;
85
86impl BackendFactory {
87 #[must_use]
101 pub fn create_all(index_path: &Path, vector_path: &Path) -> BackendSet {
102 let embedder = Self::create_embedder();
103 let index = Self::create_index_backend(index_path);
104 let vector = Self::create_vector_backend(vector_path);
105
106 BackendSet {
107 embedder,
108 index,
109 vector,
110 }
111 }
112
113 #[must_use]
118 pub fn create_embedder() -> Option<Arc<dyn Embedder>> {
119 Some(Arc::new(FastEmbedEmbedder::new()))
120 }
121
122 pub fn create_index_backend(path: &Path) -> Option<Arc<dyn IndexBackend + Send + Sync>> {
132 match SqliteBackend::new(path) {
133 Ok(backend) => {
134 tracing::debug!(path = %path.display(), "Created SQLite index backend");
135 Some(Arc::new(backend))
136 },
137 Err(e) => {
138 tracing::warn!(
139 path = %path.display(),
140 error = %e,
141 "Failed to create SQLite index backend"
142 );
143 None
144 },
145 }
146 }
147
148 pub fn create_vector_backend(path: &Path) -> Option<Arc<dyn VectorBackend + Send + Sync>> {
158 let dimensions = FastEmbedEmbedder::DEFAULT_DIMENSIONS;
159
160 #[cfg(feature = "usearch-hnsw")]
161 let result = UsearchBackend::new(path, dimensions);
162
163 #[cfg(not(feature = "usearch-hnsw"))]
164 let result: crate::Result<UsearchBackend> = Ok(UsearchBackend::new(path, dimensions));
165
166 match result {
167 Ok(backend) => {
168 if let Err(e) = backend.load() {
170 tracing::warn!(
171 path = %path.display(),
172 error = %e,
173 "Failed to load vector index, starting with empty index"
174 );
175 }
176 tracing::debug!(path = %path.display(), "Created usearch vector backend");
177 Some(Arc::new(backend))
178 },
179 Err(e) => {
180 tracing::warn!(
181 path = %path.display(),
182 error = %e,
183 "Failed to create usearch vector backend"
184 );
185 None
186 },
187 }
188 }
189
190 #[must_use]
200 pub fn create_with_dimensions(
201 index_path: &Path,
202 vector_path: &Path,
203 dimensions: usize,
204 ) -> BackendSet {
205 let embedder = Self::create_embedder();
206 let index = Self::create_index_backend(index_path);
207
208 #[cfg(feature = "usearch-hnsw")]
209 let vector_result = UsearchBackend::new(vector_path, dimensions);
210 #[cfg(not(feature = "usearch-hnsw"))]
211 let vector_result: crate::Result<UsearchBackend> =
212 Ok(UsearchBackend::new(vector_path, dimensions));
213
214 let vector = match vector_result {
215 Ok(backend) => Some(Arc::new(backend) as Arc<dyn VectorBackend + Send + Sync>),
216 Err(e) => {
217 tracing::warn!(
218 path = %vector_path.display(),
219 dimensions,
220 error = %e,
221 "Failed to create usearch vector backend with custom dimensions"
222 );
223 None
224 },
225 };
226
227 BackendSet {
228 embedder,
229 index,
230 vector,
231 }
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use tempfile::TempDir;
239
240 #[test]
241 fn test_backend_set_default() {
242 let set = BackendSet::default();
243 assert!(!set.is_complete());
244 assert!(!set.has_embedder());
245 assert!(!set.has_index());
246 assert!(!set.has_vector());
247 }
248
249 #[test]
250 fn test_create_embedder() {
251 let embedder = BackendFactory::create_embedder();
252 assert!(embedder.is_some());
253 }
254
255 #[test]
256 fn test_create_index_backend() {
257 let temp_dir = TempDir::new().expect("Failed to create temp dir");
258 let index_path = temp_dir.path().join("test_index.db");
259
260 let index = BackendFactory::create_index_backend(&index_path);
261 assert!(index.is_some());
262 }
263
264 #[test]
265 fn test_create_index_backend_invalid_path() {
266 let invalid_path = std::path::Path::new("/nonexistent/deeply/nested/path/index.db");
268 let index = BackendFactory::create_index_backend(invalid_path);
269 assert!(index.is_none());
270 }
271
272 #[test]
273 fn test_create_all() {
274 let temp_dir = TempDir::new().expect("Failed to create temp dir");
275 let index_path = temp_dir.path().join("index.db");
276 let vector_path = temp_dir.path().join("vectors");
277
278 let backends = BackendFactory::create_all(&index_path, &vector_path);
279
280 assert!(backends.has_embedder());
281 assert!(backends.has_index());
282 }
284
285 #[test]
286 fn test_backend_set_partial() {
287 let set = BackendSet {
288 embedder: BackendFactory::create_embedder(),
289 ..Default::default()
290 };
291
292 assert!(!set.is_complete());
293 assert!(set.has_embedder());
294 assert!(!set.has_index());
295 assert!(!set.has_vector());
296 }
297}