1use crate::error::{Result, VaultError};
4use crate::rag::documents::{ChunkInfo, Document};
5use std::collections::HashMap;
6
7pub trait Database {
9 fn query(&self, query: &str) -> Result<Vec<HashMap<String, String>>>;
11
12 fn insert(&mut self, table: &str, data: HashMap<String, String>) -> Result<()>;
14
15 fn update(&mut self, table: &str, id: &str, data: HashMap<String, String>) -> Result<()>;
17
18 fn delete(&mut self, table: &str, id: &str) -> Result<()>;
20}
21
22pub struct InMemoryDatabase {
24 tables: HashMap<String, Vec<HashMap<String, String>>>,
25}
26
27impl InMemoryDatabase {
28 pub fn new() -> Self {
30 Self {
31 tables: HashMap::new(),
32 }
33 }
34
35 pub fn create_table(&mut self, name: String) {
37 self.tables.insert(name, Vec::new());
38 }
39}
40
41impl Default for InMemoryDatabase {
42 fn default() -> Self {
43 Self::new()
44 }
45}
46
47impl Database for InMemoryDatabase {
48 fn query(&self, query: &str) -> Result<Vec<HashMap<String, String>>> {
49 let parts: Vec<&str> = query.split_whitespace().collect();
51 if parts.is_empty() {
52 return Ok(Vec::new());
53 }
54
55 let table_name = parts[0];
56
57 if let Some(table) = self.tables.get(table_name) {
58 if parts.len() > 2 && parts[1] == "WHERE" {
59 let condition = parts[2];
61 let cond_parts: Vec<&str> = condition.split('=').collect();
62 if cond_parts.len() == 2 {
63 let key = cond_parts[0];
64 let value = cond_parts[1];
65
66 let results: Vec<HashMap<String, String>> = table
67 .iter()
68 .filter(|row| row.get(key).map(|v| v == value).unwrap_or(false))
69 .cloned()
70 .collect();
71
72 return Ok(results);
73 }
74 }
75
76 Ok(table.clone())
78 } else {
79 Ok(Vec::new())
80 }
81 }
82
83 fn insert(&mut self, table: &str, data: HashMap<String, String>) -> Result<()> {
84 if let Some(table_data) = self.tables.get_mut(table) {
85 table_data.push(data);
86 Ok(())
87 } else {
88 Err(VaultError::InvalidInput(format!(
89 "Table {} not found",
90 table
91 )))
92 }
93 }
94
95 fn update(&mut self, table: &str, id: &str, data: HashMap<String, String>) -> Result<()> {
96 if let Some(table_data) = self.tables.get_mut(table) {
97 if let Some(row) = table_data
98 .iter_mut()
99 .find(|r| r.get("id").map(|v| v == id).unwrap_or(false))
100 {
101 for (k, v) in data {
102 row.insert(k, v);
103 }
104 Ok(())
105 } else {
106 Err(VaultError::InvalidInput(format!(
107 "Row with id {} not found",
108 id
109 )))
110 }
111 } else {
112 Err(VaultError::InvalidInput(format!(
113 "Table {} not found",
114 table
115 )))
116 }
117 }
118
119 fn delete(&mut self, table: &str, id: &str) -> Result<()> {
120 if let Some(table_data) = self.tables.get_mut(table) {
121 table_data.retain(|r| !r.get("id").map(|v| v == id).unwrap_or(false));
122 Ok(())
123 } else {
124 Err(VaultError::InvalidInput(format!(
125 "Table {} not found",
126 table
127 )))
128 }
129 }
130}
131
132#[cfg(feature = "sqlite")]
134pub struct SQLiteDatabase {
135 conn: std::sync::Arc<std::sync::Mutex<rusqlite::Connection>>,
136}
137
138#[cfg(feature = "sqlite")]
141fn validate_sql_identifier(name: &str) -> Result<()> {
142 if name.is_empty() {
143 return Err(VaultError::InvalidInput(
144 "SQL identifier cannot be empty".to_string(),
145 ));
146 }
147 if name.len() > 128 {
148 return Err(VaultError::InvalidInput(
149 "SQL identifier too long (max 128 chars)".to_string(),
150 ));
151 }
152 let first = name
155 .chars()
156 .next()
157 .expect("BUG: empty check above should have returned");
158 if !first.is_ascii_alphabetic() && first != '_' {
159 return Err(VaultError::InvalidInput(format!(
160 "SQL identifier '{}' must start with a letter or underscore",
161 name
162 )));
163 }
164 if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
165 return Err(VaultError::InvalidInput(format!(
166 "SQL identifier '{}' contains invalid characters (only alphanumeric and underscore allowed)",
167 name
168 )));
169 }
170 Ok(())
171}
172
173#[cfg(feature = "sqlite")]
174impl SQLiteDatabase {
175 pub fn new(path: &std::path::Path) -> Result<Self> {
177 let conn = rusqlite::Connection::open(path).map_err(|e| {
178 VaultError::StorageError(format!("Failed to open SQLite database: {}", e))
179 })?;
180
181 Ok(Self {
182 conn: std::sync::Arc::new(std::sync::Mutex::new(conn)),
183 })
184 }
185
186 pub fn in_memory() -> Result<Self> {
188 let conn = rusqlite::Connection::open_in_memory().map_err(|e| {
189 VaultError::StorageError(format!("Failed to create in-memory database: {}", e))
190 })?;
191
192 Ok(Self {
193 conn: std::sync::Arc::new(std::sync::Mutex::new(conn)),
194 })
195 }
196
197 pub fn create_table(&self, name: &str, columns: &[(&str, &str)]) -> Result<()> {
199 validate_sql_identifier(name)?;
200 for (col_name, col_type) in columns {
201 validate_sql_identifier(col_name)?;
202 validate_sql_identifier(col_type)?;
203 }
204
205 let conn = self
206 .conn
207 .lock()
208 .map_err(|e| VaultError::StorageError(format!("Lock poisoned: {}", e)))?;
209
210 let mut sql = format!("CREATE TABLE IF NOT EXISTS {} (", name);
211 sql.push_str("id TEXT PRIMARY KEY, ");
212 for (i, (col_name, col_type)) in columns.iter().enumerate() {
213 sql.push_str(&format!("{} {}", col_name, col_type));
214 if i < columns.len() - 1 {
215 sql.push_str(", ");
216 }
217 }
218 sql.push(')');
219
220 conn.execute(&sql, [])
221 .map_err(|e| VaultError::StorageError(format!("Failed to create table: {}", e)))?;
222
223 Ok(())
224 }
225
226 pub fn store_document(&self, doc: &Document) -> Result<()> {
228 let conn = self
229 .conn
230 .lock()
231 .map_err(|e| VaultError::StorageError(format!("Lock poisoned: {}", e)))?;
232
233 conn.execute(
235 "CREATE TABLE IF NOT EXISTS documents (
236 id TEXT PRIMARY KEY,
237 content TEXT NOT NULL,
238 metadata TEXT,
239 embedding BLOB,
240 chunk_parent_id TEXT,
241 chunk_index INTEGER,
242 chunk_total INTEGER,
243 chunk_overlap INTEGER,
244 created_at TEXT DEFAULT CURRENT_TIMESTAMP
245 )",
246 [],
247 )
248 .map_err(|e| {
249 VaultError::StorageError(format!("Failed to create documents table: {}", e))
250 })?;
251
252 let metadata_json = serde_json::to_string(&doc.metadata)
254 .map_err(|e| VaultError::SerializationError(e.to_string()))?;
255
256 let embedding_blob = doc.embedding.as_ref().map(|emb| {
257 emb.iter()
258 .flat_map(|f| f.to_le_bytes())
259 .collect::<Vec<u8>>()
260 });
261
262 let (chunk_parent, chunk_idx, chunk_total, chunk_overlap) =
263 if let Some(chunk_info) = &doc.chunk_info {
264 (
265 chunk_info.parent_id.clone(),
266 Some(chunk_info.chunk_index as i64),
267 Some(chunk_info.total_chunks as i64),
268 Some(chunk_info.overlap as i64),
269 )
270 } else {
271 (None, None, None, None)
272 };
273
274 conn.execute(
275 "INSERT OR REPLACE INTO documents
276 (id, content, metadata, embedding, chunk_parent_id, chunk_index, chunk_total, chunk_overlap)
277 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
278 rusqlite::params![
279 &doc.id,
280 &doc.content,
281 &metadata_json,
282 &embedding_blob,
283 &chunk_parent,
284 &chunk_idx,
285 &chunk_total,
286 &chunk_overlap,
287 ],
288 ).map_err(|e| VaultError::StorageError(format!("Failed to insert document: {}", e)))?;
289
290 Ok(())
291 }
292
293 pub fn get_document(&self, id: &str) -> Result<Option<Document>> {
295 let conn = self
296 .conn
297 .lock()
298 .map_err(|e| VaultError::StorageError(format!("Lock poisoned: {}", e)))?;
299
300 let mut stmt = conn.prepare(
301 "SELECT id, content, metadata, embedding, chunk_parent_id, chunk_index, chunk_total, chunk_overlap
302 FROM documents WHERE id = ?1"
303 ).map_err(|e| VaultError::StorageError(format!("Failed to prepare statement: {}", e)))?;
304
305 let result = stmt.query_row([id], |row| {
306 let id: String = row.get(0)?;
307 let content: String = row.get(1)?;
308 let metadata_json: String = row.get(2)?;
309 let embedding_blob: Option<Vec<u8>> = row.get(3)?;
310 let chunk_parent: Option<String> = row.get(4)?;
311 let chunk_idx: Option<i64> = row.get(5)?;
312 let chunk_total: Option<i64> = row.get(6)?;
313 let chunk_overlap: Option<i64> = row.get(7)?;
314
315 let metadata: HashMap<String, String> =
316 serde_json::from_str(&metadata_json).unwrap_or_default();
317
318 let embedding = embedding_blob.map(|blob| {
319 let (whole, _trailing) = blob.as_chunks::<4>();
322 whole.iter().copied().map(f32::from_le_bytes).collect()
323 });
324
325 let chunk_info = if let (Some(parent), Some(idx), Some(total), Some(overlap)) =
326 (chunk_parent, chunk_idx, chunk_total, chunk_overlap)
327 {
328 Some(ChunkInfo {
329 parent_id: Some(parent),
330 chunk_index: idx as usize,
331 total_chunks: total as usize,
332 overlap: overlap as usize,
333 })
334 } else {
335 None
336 };
337
338 Ok(Document {
339 id,
340 content,
341 metadata,
342 embedding,
343 chunk_info,
344 })
345 });
346
347 match result {
348 Ok(doc) => Ok(Some(doc)),
349 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
350 Err(e) => Err(VaultError::StorageError(format!(
351 "Failed to query document: {}",
352 e
353 ))),
354 }
355 }
356
357 pub fn search_documents(&self, query: &str, limit: usize) -> Result<Vec<Document>> {
359 let conn = self
360 .conn
361 .lock()
362 .map_err(|e| VaultError::StorageError(format!("Lock poisoned: {}", e)))?;
363
364 let mut stmt = conn.prepare(
365 "SELECT id, content, metadata, embedding, chunk_parent_id, chunk_index, chunk_total, chunk_overlap
366 FROM documents
367 WHERE content LIKE ?1
368 LIMIT ?2"
369 ).map_err(|e| VaultError::StorageError(format!("Failed to prepare statement: {}", e)))?;
370
371 let search_pattern = format!("%{}%", query);
372 let mut rows = stmt
373 .query(rusqlite::params![&search_pattern, limit as i64])
374 .map_err(|e| VaultError::StorageError(format!("Failed to query: {}", e)))?;
375
376 let mut documents = Vec::new();
377 while let Some(row) = rows
378 .next()
379 .map_err(|e| VaultError::StorageError(format!("Failed to iterate rows: {}", e)))?
380 {
381 let id: String = row
382 .get(0)
383 .map_err(|e| VaultError::StorageError(format!("Failed to get id: {}", e)))?;
384 let content: String = row
385 .get(1)
386 .map_err(|e| VaultError::StorageError(format!("Failed to get content: {}", e)))?;
387 let metadata_json: String = row
388 .get(2)
389 .map_err(|e| VaultError::StorageError(format!("Failed to get metadata: {}", e)))?;
390 let embedding_blob: Option<Vec<u8>> = row
391 .get(3)
392 .map_err(|e| VaultError::StorageError(format!("Failed to get embedding: {}", e)))?;
393
394 let metadata: HashMap<String, String> =
395 serde_json::from_str(&metadata_json).unwrap_or_default();
396
397 let embedding = embedding_blob.map(|blob| {
398 let (whole, _trailing) = blob.as_chunks::<4>();
401 whole.iter().copied().map(f32::from_le_bytes).collect()
402 });
403
404 documents.push(Document {
405 id,
406 content,
407 metadata,
408 embedding,
409 chunk_info: None, });
411 }
412
413 Ok(documents)
414 }
415}
416
417#[cfg(feature = "sqlite")]
418impl Database for SQLiteDatabase {
419 fn query(&self, query: &str) -> Result<Vec<HashMap<String, String>>> {
420 let conn = self
421 .conn
422 .lock()
423 .map_err(|e| VaultError::StorageError(format!("Lock poisoned: {}", e)))?;
424
425 let mut stmt = conn
426 .prepare(query)
427 .map_err(|e| VaultError::StorageError(format!("Failed to prepare query: {}", e)))?;
428
429 let column_names: Vec<String> = stmt.column_names().iter().map(|s| s.to_string()).collect();
430
431 let mut rows = stmt
432 .query([])
433 .map_err(|e| VaultError::StorageError(format!("Failed to execute query: {}", e)))?;
434
435 let mut results = Vec::new();
436 while let Some(row) = rows
437 .next()
438 .map_err(|e| VaultError::StorageError(format!("Failed to iterate rows: {}", e)))?
439 {
440 let mut record = HashMap::new();
441 for (i, col_name) in column_names.iter().enumerate() {
442 let value: String = row.get(i).unwrap_or_else(|_| String::new());
443 record.insert(col_name.clone(), value);
444 }
445 results.push(record);
446 }
447
448 Ok(results)
449 }
450
451 fn insert(&mut self, table: &str, data: HashMap<String, String>) -> Result<()> {
452 validate_sql_identifier(table)?;
453 let conn = self
454 .conn
455 .lock()
456 .map_err(|e| VaultError::StorageError(format!("Lock poisoned: {}", e)))?;
457
458 let columns: Vec<String> = data.keys().cloned().collect();
459 for col in &columns {
460 validate_sql_identifier(col)?;
461 }
462 let placeholders: Vec<String> = (1..=columns.len()).map(|i| format!("?{}", i)).collect();
463
464 let sql = format!(
465 "INSERT INTO {} ({}) VALUES ({})",
466 table,
467 columns.join(", "),
468 placeholders.join(", ")
469 );
470
471 let values: Vec<&str> = columns
472 .iter()
473 .map(|k| data.get(k).map(|s| s.as_str()).unwrap_or(""))
474 .collect();
475
476 conn.execute(&sql, rusqlite::params_from_iter(values))
477 .map_err(|e| VaultError::StorageError(format!("Failed to insert: {}", e)))?;
478
479 Ok(())
480 }
481
482 fn update(&mut self, table: &str, id: &str, data: HashMap<String, String>) -> Result<()> {
483 validate_sql_identifier(table)?;
484 for col in data.keys() {
485 validate_sql_identifier(col)?;
486 }
487
488 let conn = self
489 .conn
490 .lock()
491 .map_err(|e| VaultError::StorageError(format!("Lock poisoned: {}", e)))?;
492
493 let set_clause: Vec<String> = data.keys().map(|k| format!("{} = ?", k)).collect();
494 let sql = format!(
495 "UPDATE {} SET {} WHERE id = ?",
496 table,
497 set_clause.join(", ")
498 );
499
500 let mut values: Vec<&str> = data.values().map(|s| s.as_str()).collect();
501 values.push(id);
502
503 conn.execute(&sql, rusqlite::params_from_iter(values))
504 .map_err(|e| VaultError::StorageError(format!("Failed to update: {}", e)))?;
505
506 Ok(())
507 }
508
509 fn delete(&mut self, table: &str, id: &str) -> Result<()> {
510 validate_sql_identifier(table)?;
511
512 let conn = self
513 .conn
514 .lock()
515 .map_err(|e| VaultError::StorageError(format!("Lock poisoned: {}", e)))?;
516
517 let sql = format!("DELETE FROM {} WHERE id = ?", table);
518 conn.execute(&sql, [id])
519 .map_err(|e| VaultError::StorageError(format!("Failed to delete: {}", e)))?;
520
521 Ok(())
522 }
523}
524
525#[cfg(feature = "kv-store")]
527pub struct SledDatabase {
528 db: sled::Db,
529}
530
531#[cfg(feature = "kv-store")]
532impl SledDatabase {
533 pub fn new(path: &std::path::Path) -> Result<Self> {
535 let db = sled::open(path).map_err(|e| {
536 VaultError::StorageError(format!("Failed to open Sled database: {}", e))
537 })?;
538
539 Ok(Self { db })
540 }
541
542 pub fn temporary() -> Result<Self> {
544 let config = sled::Config::new().temporary(true);
545 let db = config.open().map_err(|e| {
546 VaultError::StorageError(format!("Failed to create temporary database: {}", e))
547 })?;
548
549 Ok(Self { db })
550 }
551
552 pub fn store_document(&self, doc: &Document) -> Result<()> {
554 let doc_json =
555 serde_json::to_vec(doc).map_err(|e| VaultError::SerializationError(e.to_string()))?;
556
557 self.db
558 .insert(doc.id.as_bytes(), doc_json)
559 .map_err(|e| VaultError::StorageError(format!("Failed to insert document: {}", e)))?;
560
561 self.db
562 .flush()
563 .map_err(|e| VaultError::StorageError(format!("Failed to flush: {}", e)))?;
564
565 Ok(())
566 }
567
568 pub fn get_document(&self, id: &str) -> Result<Option<Document>> {
570 if let Some(doc_bytes) = self
571 .db
572 .get(id.as_bytes())
573 .map_err(|e| VaultError::StorageError(format!("Failed to get document: {}", e)))?
574 {
575 let doc: Document = serde_json::from_slice(&doc_bytes).map_err(|e| {
576 VaultError::SerializationError(format!("Failed to deserialize: {}", e))
577 })?;
578
579 Ok(Some(doc))
580 } else {
581 Ok(None)
582 }
583 }
584
585 pub fn list_documents(&self) -> Result<Vec<String>> {
587 let mut ids = Vec::new();
588
589 for (key, _) in self.db.iter().flatten() {
590 if let Ok(id) = String::from_utf8(key.to_vec()) {
591 ids.push(id);
592 }
593 }
594
595 Ok(ids)
596 }
597
598 pub fn search_prefix(&self, prefix: &str) -> Result<Vec<Document>> {
600 let mut documents = Vec::new();
601
602 for (_, value) in self.db.scan_prefix(prefix.as_bytes()).flatten() {
603 if let Ok(doc) = serde_json::from_slice::<Document>(&value) {
604 documents.push(doc);
605 }
606 }
607
608 Ok(documents)
609 }
610}
611
612#[cfg(feature = "kv-store")]
613impl Database for SledDatabase {
614 fn query(&self, query: &str) -> Result<Vec<HashMap<String, String>>> {
615 let parts: Vec<&str> = query.split_whitespace().collect();
617 if parts.is_empty() {
618 return Ok(Vec::new());
619 }
620
621 let prefix = parts[0];
622 let mut results = Vec::new();
623
624 for (key, value) in self.db.scan_prefix(prefix.as_bytes()).flatten() {
625 let mut record = HashMap::new();
626 record.insert("key".to_string(), String::from_utf8_lossy(&key).to_string());
627 record.insert(
628 "value".to_string(),
629 String::from_utf8_lossy(&value).to_string(),
630 );
631 results.push(record);
632 }
633
634 Ok(results)
635 }
636
637 fn insert(&mut self, table: &str, data: HashMap<String, String>) -> Result<()> {
638 if let Some(id) = data.get("id") {
640 let key = format!("{}:{}", table, id);
641 let value = serde_json::to_vec(&data)
642 .map_err(|e| VaultError::SerializationError(e.to_string()))?;
643
644 self.db
645 .insert(key.as_bytes(), value)
646 .map_err(|e| VaultError::StorageError(format!("Failed to insert: {}", e)))?;
647
648 self.db
649 .flush()
650 .map_err(|e| VaultError::StorageError(format!("Failed to flush: {}", e)))?;
651
652 Ok(())
653 } else {
654 Err(VaultError::InvalidInput(
655 "Data must contain 'id' field".to_string(),
656 ))
657 }
658 }
659
660 fn update(&mut self, table: &str, id: &str, data: HashMap<String, String>) -> Result<()> {
661 let key = format!("{}:{}", table, id);
662
663 if let Some(existing) = self
665 .db
666 .get(key.as_bytes())
667 .map_err(|e| VaultError::StorageError(format!("Failed to get: {}", e)))?
668 {
669 let mut existing_data: HashMap<String, String> =
670 serde_json::from_slice(&existing).unwrap_or_default();
671
672 existing_data.extend(data);
673
674 let value = serde_json::to_vec(&existing_data)
675 .map_err(|e| VaultError::SerializationError(e.to_string()))?;
676
677 self.db
678 .insert(key.as_bytes(), value)
679 .map_err(|e| VaultError::StorageError(format!("Failed to update: {}", e)))?;
680
681 self.db
682 .flush()
683 .map_err(|e| VaultError::StorageError(format!("Failed to flush: {}", e)))?;
684
685 Ok(())
686 } else {
687 Err(VaultError::InvalidInput(format!(
688 "Record {}:{} not found",
689 table, id
690 )))
691 }
692 }
693
694 fn delete(&mut self, table: &str, id: &str) -> Result<()> {
695 let key = format!("{}:{}", table, id);
696 self.db
697 .remove(key.as_bytes())
698 .map_err(|e| VaultError::StorageError(format!("Failed to delete: {}", e)))?;
699
700 self.db
701 .flush()
702 .map_err(|e| VaultError::StorageError(format!("Failed to flush: {}", e)))?;
703
704 Ok(())
705 }
706}
707
708#[cfg(test)]
709mod tests {
710 use super::*;
711
712 #[test]
715 fn test_in_memory_create_and_insert() {
716 let mut db = InMemoryDatabase::new();
717 db.create_table("models".to_string());
718 let mut data = HashMap::new();
719 data.insert("id".to_string(), "m1".to_string());
720 data.insert("name".to_string(), "gpt".to_string());
721 db.insert("models", data).unwrap();
722
723 let results = db.query("models").unwrap();
724 assert_eq!(results.len(), 1);
725 assert_eq!(results[0].get("name").unwrap(), "gpt");
726 }
727
728 #[test]
729 fn test_in_memory_update() {
730 let mut db = InMemoryDatabase::new();
731 db.create_table("t".to_string());
732 let mut data = HashMap::new();
733 data.insert("id".to_string(), "r1".to_string());
734 data.insert("val".to_string(), "old".to_string());
735 db.insert("t", data).unwrap();
736
737 let mut upd = HashMap::new();
738 upd.insert("val".to_string(), "new".to_string());
739 db.update("t", "r1", upd).unwrap();
740
741 let results = db.query("t").unwrap();
742 assert_eq!(results[0].get("val").unwrap(), "new");
743 }
744
745 #[test]
746 fn test_in_memory_delete() {
747 let mut db = InMemoryDatabase::new();
748 db.create_table("t".to_string());
749 let mut data = HashMap::new();
750 data.insert("id".to_string(), "r1".to_string());
751 db.insert("t", data).unwrap();
752
753 db.delete("t", "r1").unwrap();
754 let results = db.query("t").unwrap();
755 assert!(results.is_empty());
756 }
757
758 #[test]
759 fn test_in_memory_insert_missing_table() {
760 let mut db = InMemoryDatabase::new();
761 let data = HashMap::new();
762 let err = db.insert("nonexistent", data).unwrap_err();
763 assert!(format!("{err}").contains("not found"));
764 }
765
766 #[test]
767 fn test_in_memory_update_missing_table() {
768 let mut db = InMemoryDatabase::new();
769 let err = db.update("nonexistent", "x", HashMap::new()).unwrap_err();
770 assert!(format!("{err}").contains("not found"));
771 }
772
773 #[test]
774 fn test_in_memory_update_missing_row() {
775 let mut db = InMemoryDatabase::new();
776 db.create_table("t".to_string());
777 let err = db.update("t", "missing", HashMap::new()).unwrap_err();
778 assert!(format!("{err}").contains("not found"));
779 }
780
781 #[test]
782 fn test_in_memory_delete_missing_table() {
783 let mut db = InMemoryDatabase::new();
784 let err = db.delete("nonexistent", "x").unwrap_err();
785 assert!(format!("{err}").contains("not found"));
786 }
787
788 #[test]
789 fn test_in_memory_query_missing_table() {
790 let db = InMemoryDatabase::new();
791 let results = db.query("nonexistent").unwrap();
792 assert!(results.is_empty());
793 }
794
795 #[test]
796 fn test_in_memory_query_where() {
797 let mut db = InMemoryDatabase::new();
798 db.create_table("t".to_string());
799 let mut d1 = HashMap::new();
800 d1.insert("id".to_string(), "1".to_string());
801 d1.insert("color".to_string(), "red".to_string());
802 db.insert("t", d1).unwrap();
803
804 let mut d2 = HashMap::new();
805 d2.insert("id".to_string(), "2".to_string());
806 d2.insert("color".to_string(), "blue".to_string());
807 db.insert("t", d2).unwrap();
808
809 let results = db.query("t WHERE color=red").unwrap();
811 assert_eq!(results.len(), 1);
812 assert_eq!(results[0].get("color").unwrap(), "red");
813 }
814
815 #[cfg(feature = "sqlite")]
818 #[test]
819 fn test_validate_sql_identifier_empty() {
820 let err = validate_sql_identifier("").unwrap_err();
821 assert!(format!("{err}").contains("cannot be empty"));
822 }
823
824 #[cfg(feature = "sqlite")]
825 #[test]
826 fn test_validate_sql_identifier_too_long() {
827 let long = "a".repeat(129);
828 let err = validate_sql_identifier(&long).unwrap_err();
829 assert!(format!("{err}").contains("too long"));
830 }
831
832 #[cfg(feature = "sqlite")]
833 #[test]
834 fn test_validate_sql_identifier_bad_start() {
835 let err = validate_sql_identifier("1abc").unwrap_err();
836 assert!(format!("{err}").contains("must start with"));
837 }
838
839 #[cfg(feature = "sqlite")]
840 #[test]
841 fn test_validate_sql_identifier_invalid_chars() {
842 let err = validate_sql_identifier("abc def").unwrap_err();
843 assert!(format!("{err}").contains("invalid characters"));
844 }
845
846 #[cfg(feature = "sqlite")]
847 #[test]
848 fn test_validate_sql_identifier_valid() {
849 assert!(validate_sql_identifier("_my_table_1").is_ok());
850 assert!(validate_sql_identifier("users").is_ok());
851 }
852
853 #[cfg(feature = "sqlite")]
856 #[test]
857 fn test_sqlite_new_on_disk() {
858 let dir = tempfile::tempdir().unwrap();
859 let path = dir.path().join("test.db");
860 let db = SQLiteDatabase::new(&path).unwrap();
861 assert!(path.exists());
862
863 db.create_table("t", &[("name", "TEXT")]).unwrap();
865 let mut data = HashMap::new();
866 data.insert("id".to_string(), "1".to_string());
867 data.insert("name".to_string(), "alice".to_string());
868 let mut db = db;
869 db.insert("t", data).unwrap();
870 }
871
872 #[cfg(feature = "sqlite")]
873 #[test]
874 fn test_sqlite_in_memory() {
875 let db = SQLiteDatabase::in_memory().unwrap();
876 db.create_table("items", &[("val", "TEXT")]).unwrap();
877 }
878
879 #[cfg(feature = "sqlite")]
880 #[test]
881 fn test_sqlite_create_table_and_crud() {
882 let mut db = SQLiteDatabase::in_memory().unwrap();
883 db.create_table("users", &[("name", "TEXT"), ("age", "TEXT")])
884 .unwrap();
885
886 let mut data = HashMap::new();
888 data.insert("id".to_string(), "u1".to_string());
889 data.insert("name".to_string(), "Alice".to_string());
890 data.insert("age".to_string(), "30".to_string());
891 db.insert("users", data).unwrap();
892
893 let rows = db.query("SELECT * FROM users").unwrap();
895 assert_eq!(rows.len(), 1);
896 assert_eq!(rows[0].get("name").unwrap(), "Alice");
897
898 let mut upd = HashMap::new();
900 upd.insert("name".to_string(), "Bob".to_string());
901 db.update("users", "u1", upd).unwrap();
902 let rows = db.query("SELECT name FROM users WHERE id='u1'").unwrap();
903 assert_eq!(rows[0].get("name").unwrap(), "Bob");
904
905 db.delete("users", "u1").unwrap();
907 let rows = db.query("SELECT * FROM users").unwrap();
908 assert!(rows.is_empty());
909 }
910
911 #[cfg(feature = "sqlite")]
912 #[test]
913 fn test_sqlite_store_and_get_document() {
914 let db = SQLiteDatabase::in_memory().unwrap();
915 let doc = Document {
916 id: "d1".to_string(),
917 content: "Test document content".to_string(),
918 metadata: {
919 let mut m = HashMap::new();
920 m.insert("author".to_string(), "test".to_string());
921 m
922 },
923 embedding: Some(vec![1.0, 2.0, 3.0]),
924 chunk_info: Some(ChunkInfo {
925 parent_id: Some("parent1".to_string()),
926 chunk_index: 0,
927 total_chunks: 3,
928 overlap: 50,
929 }),
930 };
931 db.store_document(&doc).unwrap();
932
933 let retrieved = db.get_document("d1").unwrap().unwrap();
934 assert_eq!(retrieved.id, "d1");
935 assert_eq!(retrieved.content, "Test document content");
936 assert_eq!(retrieved.metadata.get("author").unwrap(), "test");
937 assert!(retrieved.embedding.is_some());
938 assert!(retrieved.chunk_info.is_some());
939 let ci = retrieved.chunk_info.unwrap();
940 assert_eq!(ci.parent_id.as_deref(), Some("parent1"));
941 assert_eq!(ci.chunk_index, 0);
942 assert_eq!(ci.total_chunks, 3);
943 }
944
945 #[cfg(feature = "sqlite")]
946 #[test]
947 fn test_sqlite_get_document_not_found() {
948 let db = SQLiteDatabase::in_memory().unwrap();
949 let dummy = Document {
951 id: "setup".to_string(),
952 content: "x".to_string(),
953 metadata: HashMap::new(),
954 embedding: None,
955 chunk_info: None,
956 };
957 db.store_document(&dummy).unwrap();
958
959 let result = db.get_document("nonexistent").unwrap();
960 assert!(result.is_none());
961 }
962
963 #[cfg(feature = "sqlite")]
964 #[test]
965 fn test_sqlite_search_documents() {
966 let db = SQLiteDatabase::in_memory().unwrap();
967 let doc1 = Document {
968 id: "s1".to_string(),
969 content: "Machine learning with neural networks".to_string(),
970 metadata: HashMap::new(),
971 embedding: None,
972 chunk_info: None,
973 };
974 let doc2 = Document {
975 id: "s2".to_string(),
976 content: "Database management systems".to_string(),
977 metadata: HashMap::new(),
978 embedding: None,
979 chunk_info: None,
980 };
981 db.store_document(&doc1).unwrap();
982 db.store_document(&doc2).unwrap();
983
984 let results = db.search_documents("neural", 10).unwrap();
985 assert!(!results.is_empty());
986 assert!(results.iter().any(|d| d.id == "s1"));
987 }
988
989 #[cfg(feature = "sqlite")]
990 #[test]
991 fn test_sqlite_store_document_no_embedding_no_chunk() {
992 let db = SQLiteDatabase::in_memory().unwrap();
993 let doc = Document {
994 id: "plain".to_string(),
995 content: "Plain text".to_string(),
996 metadata: HashMap::new(),
997 embedding: None,
998 chunk_info: None,
999 };
1000 db.store_document(&doc).unwrap();
1001
1002 let retrieved = db.get_document("plain").unwrap().unwrap();
1003 assert_eq!(retrieved.content, "Plain text");
1004 assert!(retrieved.embedding.is_none());
1005 assert!(retrieved.chunk_info.is_none());
1006 }
1007
1008 #[test]
1009 fn test_sqlite_get_document_nonexistent() {
1010 let db = SQLiteDatabase::in_memory().unwrap();
1011 let doc = Document {
1012 id: "existing".to_string(),
1013 content: "some content".to_string(),
1014 metadata: HashMap::new(),
1015 embedding: None,
1016 chunk_info: None,
1017 };
1018 db.store_document(&doc).unwrap();
1019 let result = db.get_document("does_not_exist").unwrap();
1020 assert!(result.is_none(), "Nonexistent document should return None");
1021 }
1022
1023 #[test]
1024 fn test_sqlite_database_crud() {
1025 let mut db = SQLiteDatabase::in_memory().unwrap();
1026 db.create_table("items", &[("name", "TEXT")]).unwrap();
1027 let mut data = std::collections::HashMap::new();
1028 data.insert("id".to_string(), "item1".to_string());
1029 data.insert("name".to_string(), "widget".to_string());
1030 db.insert("items", data).unwrap();
1031 let rows = db.query("SELECT * FROM items").unwrap();
1032 assert!(!rows.is_empty());
1033 let mut upd = std::collections::HashMap::new();
1034 upd.insert("name".to_string(), "gizmo".to_string());
1035 db.update("items", "item1", upd).unwrap();
1036 db.delete("items", "item1").unwrap();
1037 let rows2 = db.query("SELECT * FROM items").unwrap();
1038 assert!(rows2.is_empty());
1039 }
1040
1041 #[test]
1042 fn test_sqlite_search_documents_no_results() {
1043 let db = SQLiteDatabase::in_memory().unwrap();
1044 let doc = Document {
1046 id: "seed".to_string(),
1047 content: "seed content".to_string(),
1048 metadata: HashMap::new(),
1049 embedding: None,
1050 chunk_info: None,
1051 };
1052 db.store_document(&doc).unwrap();
1053 let results = db.search_documents("nonexistent query", 10).unwrap();
1054 assert!(results.is_empty());
1055 }
1056
1057 #[test]
1058 fn test_in_memory_default() {
1059 let mut db = InMemoryDatabase::default();
1060 db.create_table("t".to_string());
1061 let mut data = std::collections::HashMap::new();
1062 data.insert("id".to_string(), "1".to_string());
1063 data.insert("col".to_string(), "v".to_string());
1064 db.insert("t", data).unwrap();
1065 let rows = db.query("t").unwrap();
1066 assert_eq!(rows.len(), 1);
1067 }
1068
1069 #[test]
1070 fn test_in_memory_empty_query() {
1071 let db = InMemoryDatabase::new();
1072 let rows = db.query("").unwrap();
1073 assert!(rows.is_empty());
1074 }
1075
1076 #[cfg(feature = "sqlite")]
1077 #[test]
1078 fn test_sqlite_search_documents_with_embedding() {
1079 let db = SQLiteDatabase::in_memory().unwrap();
1080 db.create_table("items", &[("name", "TEXT")]).unwrap();
1081 let doc = crate::rag::documents::Document {
1083 id: "doc_emb".to_string(),
1084 content: "embedding search test".to_string(),
1085 metadata: std::collections::HashMap::new(),
1086 embedding: Some(vec![0.1, 0.2, 0.3]),
1087 chunk_info: None,
1088 };
1089 db.store_document(&doc).unwrap();
1090 let results = db.search_documents("embedding", 10).unwrap();
1092 assert_eq!(results.len(), 1);
1093 assert_eq!(results[0].id, "doc_emb");
1094 assert!(results[0].embedding.is_some());
1096 let emb = results[0].embedding.as_ref().unwrap();
1097 assert_eq!(emb.len(), 3);
1098 assert!((emb[0] - 0.1).abs() < 1e-6);
1099 }
1100}