Skip to main content

lc_vector_stores/
sqlite_store.rs

1// lc-vector-stores/src/sqlite_store.rs
2//! SQLite 文档存储实现
3
4use async_trait::async_trait;
5use lc_shared::splitter::{RecursiveCharacterSplitter, TextSplitter};
6use rusqlite::Connection;
7use std::sync::Arc;
8use tokio::sync::Mutex;
9use uuid::Uuid;
10
11use crate::document_store::{ChunkDocument, ChunkedDocumentStoreTrait, DocumentStore};
12use crate::{Document, VectorStoreError};
13
14/// 解析 metadata 列;损坏时记日志并回退空映射,不再静默吞错。
15fn parse_metadata_or_default(raw: &str) -> std::collections::HashMap<String, serde_json::Value> {
16    serde_json::from_str(raw).unwrap_or_else(|e| {
17        log::warn!(
18            "SQLite metadata in store is corrupted, falling back to empty map: {}",
19            e
20        );
21        std::collections::HashMap::new()
22    })
23}
24
25/// SQLite 文档存储配置
26#[derive(Debug, Clone)]
27pub struct SQLiteStoreConfig {
28    /// SQLite 数据库文件路径
29    pub db_path: String,
30}
31
32impl Default for SQLiteStoreConfig {
33    fn default() -> Self {
34        Self {
35            db_path: "langchainrust.db".to_string(),
36        }
37    }
38}
39
40impl SQLiteStoreConfig {
41    /// 使用数据库文件路径创建配置。
42    pub fn new(path: impl Into<String>) -> Self {
43        Self {
44            db_path: path.into(),
45        }
46    }
47}
48
49/// SQLite 文档存储实现
50pub struct SQLiteDocumentStore {
51    conn: Arc<Mutex<Connection>>,
52}
53
54impl SQLiteDocumentStore {
55    /// 打开 SQLite 数据库并创建所需表与索引。
56    pub fn new(config: SQLiteStoreConfig) -> Result<Self, VectorStoreError> {
57        let conn = Connection::open(&config.db_path)
58            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
59        conn.execute_batch(
60            "CREATE TABLE IF NOT EXISTS documents (
61                id TEXT PRIMARY KEY, content TEXT NOT NULL,
62                metadata TEXT NOT NULL DEFAULT '{}'
63            );
64            CREATE TABLE IF NOT EXISTS chunks (
65                chunk_id TEXT PRIMARY KEY, parent_id TEXT NOT NULL,
66                content TEXT NOT NULL, segment INTEGER NOT NULL,
67                metadata TEXT NOT NULL DEFAULT '{}'
68            );
69            CREATE INDEX IF NOT EXISTS idx_chunks_parent ON chunks(parent_id);
70            CREATE INDEX IF NOT EXISTS idx_chunks_segment ON chunks(parent_id, segment);",
71        )
72        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
73        Ok(Self {
74            conn: Arc::new(Mutex::new(conn)),
75        })
76    }
77
78    /// 显式触发落盘。
79    ///
80    /// C3: 原 `ChunkedDocumentStoreTrait::save` 假默认方法已从 trait 删除;SQLite
81    /// 每次写入即自动持久化,本方法为兼容性 no-op(与 Redis 的 `save_to_disk` 对齐)。
82    pub async fn save(&self) -> Result<(), VectorStoreError> {
83        Ok(())
84    }
85}
86
87#[async_trait]
88impl DocumentStore for SQLiteDocumentStore {
89    async fn add_document(&self, document: Document) -> Result<String, VectorStoreError> {
90        let id = document
91            .id
92            .clone()
93            .unwrap_or_else(|| Uuid::new_v4().to_string());
94        let meta = serde_json::to_string(&document.metadata).unwrap_or_else(|_| "{}".to_string());
95        let conn = self.conn.lock().await;
96        conn.execute(
97            "INSERT OR REPLACE INTO documents (id, content, metadata) VALUES (?1, ?2, ?3)",
98            rusqlite::params![id, document.content, meta],
99        )
100        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
101        Ok(id)
102    }
103
104    async fn add_documents(
105        &self,
106        documents: Vec<Document>,
107    ) -> Result<Vec<String>, VectorStoreError> {
108        let conn = self.conn.lock().await;
109        let mut ids = Vec::new();
110        for doc in documents {
111            let id = doc.id.clone().unwrap_or_else(|| Uuid::new_v4().to_string());
112            let meta = serde_json::to_string(&doc.metadata).unwrap_or_else(|_| "{}".to_string());
113            conn.execute(
114                "INSERT OR REPLACE INTO documents (id, content, metadata) VALUES (?1, ?2, ?3)",
115                rusqlite::params![id, doc.content, meta],
116            )
117            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
118            ids.push(id);
119        }
120        Ok(ids)
121    }
122
123    async fn get_document(&self, id: &str) -> Result<Option<Document>, VectorStoreError> {
124        let conn = self.conn.lock().await;
125        let mut stmt = conn
126            .prepare("SELECT id, content, metadata FROM documents WHERE id = ?1")
127            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
128        let result = stmt.query_row(rusqlite::params![id], |row| {
129            let id: String = row.get(0)?;
130            let content: String = row.get(1)?;
131            let meta_str: String = row.get(2)?;
132            Ok(Document {
133                id: Some(id),
134                content,
135                metadata: parse_metadata_or_default(&meta_str),
136            })
137        });
138        match result {
139            Ok(doc) => Ok(Some(doc)),
140            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
141            Err(e) => Err(VectorStoreError::StorageError(e.to_string())),
142        }
143    }
144
145    async fn delete_document(&self, id: &str) -> Result<(), VectorStoreError> {
146        let conn = self.conn.lock().await;
147        conn.execute(
148            "DELETE FROM chunks WHERE parent_id = ?1",
149            rusqlite::params![id],
150        )
151        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
152        conn.execute("DELETE FROM documents WHERE id = ?1", rusqlite::params![id])
153            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
154        Ok(())
155    }
156
157    async fn count(&self) -> usize {
158        let conn = self.conn.lock().await;
159        match conn.query_row("SELECT COUNT(*) FROM documents", [], |r| r.get(0)) {
160            Ok(count) => count,
161            // M3: trait 返回 usize 无法传播错误——不再静默吞错返回 0,
162            // 记 error 暴露存储故障,与文件内"不再静默丢行"的既有模式一致。
163            Err(e) => {
164                log::error!("SQLite count(documents) query failed, returning 0: {}", e);
165                0
166            }
167        }
168    }
169
170    async fn clear(&self) -> Result<(), VectorStoreError> {
171        let conn = self.conn.lock().await;
172        conn.execute_batch("DELETE FROM chunks; DELETE FROM documents;")
173            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
174        Ok(())
175    }
176}
177
178#[async_trait]
179impl ChunkedDocumentStoreTrait for SQLiteDocumentStore {
180    async fn add_parent_document(
181        &self,
182        document: Document,
183        chunk_size: usize,
184    ) -> Result<(String, Vec<String>), VectorStoreError> {
185        let splitter = RecursiveCharacterSplitter::new(chunk_size, chunk_size / 10);
186        let chunks_text = splitter.split_text(&document.content);
187        let parent_id = document
188            .id
189            .clone()
190            .unwrap_or_else(|| Uuid::new_v4().to_string());
191        let meta = serde_json::to_string(&document.metadata).unwrap_or_else(|_| "{}".to_string());
192
193        let conn = self.conn.lock().await;
194        conn.execute(
195            "INSERT OR REPLACE INTO documents (id, content, metadata) VALUES (?1, ?2, ?3)",
196            rusqlite::params![parent_id, document.content, meta],
197        )
198        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
199
200        let mut chunk_ids = Vec::new();
201        for (i, text) in chunks_text.iter().enumerate() {
202            let cid = format!("{}:chunk:{}", parent_id, i);
203            conn.execute("INSERT OR REPLACE INTO chunks (chunk_id, parent_id, content, segment, metadata) VALUES (?1, ?2, ?3, ?4, ?5)",
204                rusqlite::params![cid, parent_id, text, i, "{}"])
205                .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
206            chunk_ids.push(cid);
207        }
208        Ok((parent_id, chunk_ids))
209    }
210
211    async fn add_parent_documents(
212        &self,
213        documents: Vec<Document>,
214        chunk_size: usize,
215    ) -> Result<Vec<(String, Vec<String>)>, VectorStoreError> {
216        let mut results = Vec::new();
217        for doc in documents {
218            results.push(self.add_parent_document(doc, chunk_size).await?);
219        }
220        Ok(results)
221    }
222
223    async fn get_parent_document(
224        &self,
225        parent_id: &str,
226    ) -> Result<Option<Document>, VectorStoreError> {
227        self.get_document(parent_id).await
228    }
229
230    async fn get_chunk(&self, chunk_id: &str) -> Result<Option<ChunkDocument>, VectorStoreError> {
231        let conn = self.conn.lock().await;
232        let mut stmt = conn.prepare("SELECT chunk_id, parent_id, content, segment, metadata FROM chunks WHERE chunk_id = ?1")
233            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
234        let result = stmt.query_row(rusqlite::params![chunk_id], |row| {
235            Ok(ChunkDocument {
236                chunk_id: row.get(0)?,
237                parent_id: row.get(1)?,
238                content: row.get(2)?,
239                segment: row.get(3)?,
240                metadata: parse_metadata_or_default(&row.get::<_, String>(4)?),
241            })
242        });
243        match result {
244            Ok(chunk) => Ok(Some(chunk)),
245            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
246            Err(e) => Err(VectorStoreError::StorageError(e.to_string())),
247        }
248    }
249
250    async fn get_chunk_document(
251        &self,
252        chunk_id: &str,
253    ) -> Result<Option<Document>, VectorStoreError> {
254        Ok(self.get_chunk(chunk_id).await?.map(|c| c.to_document()))
255    }
256
257    async fn get_chunks_for_parent(
258        &self,
259        parent_id: &str,
260    ) -> Result<Vec<ChunkDocument>, VectorStoreError> {
261        let conn = self.conn.lock().await;
262        let mut stmt = conn.prepare("SELECT chunk_id, parent_id, content, segment, metadata FROM chunks WHERE parent_id = ?1 ORDER BY segment")
263            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
264        let chunks = stmt
265            .query_map(rusqlite::params![parent_id], |row| {
266                Ok(ChunkDocument {
267                    chunk_id: row.get(0)?,
268                    parent_id: row.get(1)?,
269                    content: row.get(2)?,
270                    segment: row.get(3)?,
271                    metadata: parse_metadata_or_default(&row.get::<_, String>(4)?),
272                })
273            })
274            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
275            .filter_map(|r| match r {
276                Ok(chunk) => Some(chunk),
277                Err(e) => {
278                    // 不再静默丢行:记录到日志,暴露存储降级
279                    log::error!(
280                        "SQLite store: a row failed to deserialize and was dropped from the query result: {}",
281                        e
282                    );
283                    None
284                }
285            })
286            .collect();
287        Ok(chunks)
288    }
289
290    async fn get_chunk_documents_for_parent(
291        &self,
292        parent_id: &str,
293    ) -> Result<Vec<Document>, VectorStoreError> {
294        Ok(self
295            .get_chunks_for_parent(parent_id)
296            .await?
297            .into_iter()
298            .map(|c| c.to_document())
299            .collect())
300    }
301
302    async fn delete_parent_document(&self, parent_id: &str) -> Result<(), VectorStoreError> {
303        self.delete_document(parent_id).await
304    }
305
306    async fn parent_count(&self) -> usize {
307        let conn = self.conn.lock().await;
308        match conn.query_row("SELECT COUNT(*) FROM documents", [], |r| r.get(0)) {
309            Ok(count) => count,
310            // M3: 不静默吞错返回 0,记 error 暴露存储故障。
311            Err(e) => {
312                log::error!("SQLite parent_count query failed, returning 0: {}", e);
313                0
314            }
315        }
316    }
317
318    async fn chunk_count(&self) -> usize {
319        let conn = self.conn.lock().await;
320        match conn.query_row("SELECT COUNT(*) FROM chunks", [], |r| r.get(0)) {
321            Ok(count) => count,
322            // M3: 不静默吞错返回 0,记 error 暴露存储故障。
323            Err(e) => {
324                log::error!("SQLite chunk_count query failed, returning 0: {}", e);
325                0
326            }
327        }
328    }
329
330    async fn get_all_chunks(&self) -> Result<Vec<ChunkDocument>, VectorStoreError> {
331        let conn = self.conn.lock().await;
332        let mut stmt = conn
333            .prepare("SELECT chunk_id, parent_id, content, segment, metadata FROM chunks")
334            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
335        let chunks = stmt
336            .query_map([], |row| {
337                Ok(ChunkDocument {
338                    chunk_id: row.get(0)?,
339                    parent_id: row.get(1)?,
340                    content: row.get(2)?,
341                    segment: row.get(3)?,
342                    metadata: parse_metadata_or_default(&row.get::<_, String>(4)?),
343                })
344            })
345            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
346            .filter_map(|r| match r {
347                Ok(chunk) => Some(chunk),
348                Err(e) => {
349                    // 不再静默丢行:记录到日志,暴露存储降级
350                    log::error!(
351                        "SQLite store: a row failed to deserialize and was dropped from the query result: {}",
352                        e
353                    );
354                    None
355                }
356            })
357            .collect();
358        Ok(chunks)
359    }
360
361    async fn clear(&self) -> Result<(), VectorStoreError> {
362        let conn = self.conn.lock().await;
363        conn.execute_batch("DELETE FROM chunks; DELETE FROM documents;")
364            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
365        Ok(())
366    }
367
368    // Blocking methods
369    fn add_parent_document_blocking(
370        &self,
371        document: Document,
372        chunk_size: usize,
373    ) -> Result<(String, Vec<String>), VectorStoreError> {
374        let conn = self.conn.blocking_lock();
375        let splitter = RecursiveCharacterSplitter::new(chunk_size, chunk_size / 10);
376        let chunks_text = splitter.split_text(&document.content);
377        let parent_id = document
378            .id
379            .clone()
380            .unwrap_or_else(|| Uuid::new_v4().to_string());
381        let meta = serde_json::to_string(&document.metadata).unwrap_or_else(|_| "{}".to_string());
382
383        conn.execute(
384            "INSERT OR REPLACE INTO documents (id, content, metadata) VALUES (?1, ?2, ?3)",
385            rusqlite::params![parent_id, document.content, meta],
386        )
387        .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
388
389        let mut chunk_ids = Vec::new();
390        for (i, text) in chunks_text.iter().enumerate() {
391            let cid = format!("{}:chunk:{}", parent_id, i);
392            conn.execute("INSERT OR REPLACE INTO chunks (chunk_id, parent_id, content, segment, metadata) VALUES (?1, ?2, ?3, ?4, ?5)",
393                rusqlite::params![cid, parent_id, text, i, "{}"])
394                .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
395            chunk_ids.push(cid);
396        }
397        Ok((parent_id, chunk_ids))
398    }
399
400    fn get_parent_document_blocking(
401        &self,
402        parent_id: &str,
403    ) -> Result<Option<Document>, VectorStoreError> {
404        let conn = self.conn.blocking_lock();
405        let mut stmt = conn
406            .prepare("SELECT id, content, metadata FROM documents WHERE id = ?1")
407            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
408        let result = stmt.query_row(rusqlite::params![parent_id], |row| {
409            Ok(Document {
410                id: Some(row.get(0)?),
411                content: row.get(1)?,
412                metadata: parse_metadata_or_default(&row.get::<_, String>(2)?),
413            })
414        });
415        match result {
416            Ok(doc) => Ok(Some(doc)),
417            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
418            Err(e) => Err(VectorStoreError::StorageError(e.to_string())),
419        }
420    }
421
422    fn get_chunk_blocking(
423        &self,
424        chunk_id: &str,
425    ) -> Result<Option<ChunkDocument>, VectorStoreError> {
426        let conn = self.conn.blocking_lock();
427        let mut stmt = conn.prepare("SELECT chunk_id, parent_id, content, segment, metadata FROM chunks WHERE chunk_id = ?1")
428            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
429        let result = stmt.query_row(rusqlite::params![chunk_id], |row| {
430            Ok(ChunkDocument {
431                chunk_id: row.get(0)?,
432                parent_id: row.get(1)?,
433                content: row.get(2)?,
434                segment: row.get(3)?,
435                metadata: parse_metadata_or_default(&row.get::<_, String>(4)?),
436            })
437        });
438        match result {
439            Ok(chunk) => Ok(Some(chunk)),
440            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
441            Err(e) => Err(VectorStoreError::StorageError(e.to_string())),
442        }
443    }
444
445    fn blocking_get_chunks_for_parent(
446        &self,
447        parent_id: &str,
448    ) -> Result<Vec<ChunkDocument>, VectorStoreError> {
449        let conn = self.conn.blocking_lock();
450        let mut stmt = conn.prepare("SELECT chunk_id, parent_id, content, segment, metadata FROM chunks WHERE parent_id = ?1 ORDER BY segment")
451            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
452        let chunks = stmt
453            .query_map(rusqlite::params![parent_id], |row| {
454                Ok(ChunkDocument {
455                    chunk_id: row.get(0)?,
456                    parent_id: row.get(1)?,
457                    content: row.get(2)?,
458                    segment: row.get(3)?,
459                    metadata: parse_metadata_or_default(&row.get::<_, String>(4)?),
460                })
461            })
462            .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
463            .filter_map(|r| match r {
464                Ok(chunk) => Some(chunk),
465                Err(e) => {
466                    // 不再静默丢行:记录到日志,暴露存储降级
467                    log::error!(
468                        "SQLite store: a row failed to deserialize and was dropped from the query result: {}",
469                        e
470                    );
471                    None
472                }
473            })
474            .collect();
475        Ok(chunks)
476    }
477}