1use crate::document_store::{ChunkDocument, ChunkedDocumentStoreTrait};
11use crate::{Document, VectorStoreError};
12use async_trait::async_trait;
13use lc_shared::splitter::{RecursiveCharacterSplitter, TextSplitter};
14use mongodb::{bson::doc, options::ClientOptions, Client, Collection};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19struct MongoParentDoc {
20 #[serde(rename = "_id")]
21 id: String,
22 content: String,
23 metadata: HashMap<String, serde_json::Value>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27struct MongoChunkDoc {
28 #[serde(rename = "_id")]
29 chunk_id: String,
30 parent_id: String,
31 content: String,
32 segment: i32,
33 metadata: HashMap<String, serde_json::Value>,
34}
35
36impl From<MongoParentDoc> for Document {
37 fn from(m: MongoParentDoc) -> Self {
38 Document {
39 content: m.content,
40 metadata: m.metadata,
41 id: Some(m.id),
42 }
43 }
44}
45
46impl From<Document> for MongoParentDoc {
47 fn from(d: Document) -> Self {
48 MongoParentDoc {
49 id: d
50 .id
51 .clone()
52 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
53 content: d.content,
54 metadata: d.metadata,
55 }
56 }
57}
58
59impl From<MongoChunkDoc> for ChunkDocument {
60 fn from(m: MongoChunkDoc) -> Self {
61 ChunkDocument {
62 chunk_id: m.chunk_id,
63 parent_id: m.parent_id,
64 content: m.content,
65 segment: m.segment as usize,
66 metadata: m.metadata,
67 }
68 }
69}
70
71impl From<ChunkDocument> for MongoChunkDoc {
72 fn from(c: ChunkDocument) -> Self {
73 MongoChunkDoc {
74 chunk_id: c.chunk_id,
75 parent_id: c.parent_id,
76 content: c.content,
77 segment: c.segment as i32,
78 metadata: c.metadata,
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
85pub struct MongoStoreConfig {
86 pub uri: String,
88 pub database: String,
90 pub parent_collection: String,
92 pub chunk_collection: String,
94}
95
96impl Default for MongoStoreConfig {
97 fn default() -> Self {
98 Self {
99 uri: "mongodb://localhost:27017".to_string(),
100 database: "langchainrust".to_string(),
101 parent_collection: "parent_docs".to_string(),
102 chunk_collection: "chunks".to_string(),
103 }
104 }
105}
106
107impl MongoStoreConfig {
108 pub fn new(uri: impl Into<String>, database: impl Into<String>) -> Self {
110 Self {
111 uri: uri.into(),
112 database: database.into(),
113 parent_collection: "parent_docs".to_string(),
114 chunk_collection: "chunks".to_string(),
115 }
116 }
117
118 pub fn with_collections(mut self, parent: impl Into<String>, chunk: impl Into<String>) -> Self {
120 self.parent_collection = parent.into();
121 self.chunk_collection = chunk.into();
122 self
123 }
124}
125
126pub struct MongoChunkedDocumentStore {
128 client: Client,
129 parent_collection: Collection<MongoParentDoc>,
130 chunk_collection: Collection<MongoChunkDoc>,
131}
132
133impl MongoChunkedDocumentStore {
134 pub async fn new(config: MongoStoreConfig) -> Result<Self, VectorStoreError> {
136 let client_options = ClientOptions::parse(&config.uri)
137 .await
138 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
139
140 let client = Client::with_options(client_options)
141 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
142
143 let db = client.database(&config.database);
144 let parent_collection = db.collection(&config.parent_collection);
145 let chunk_collection = db.collection(&config.chunk_collection);
146
147 Ok(Self {
148 client,
149 parent_collection,
150 chunk_collection,
151 })
152 }
153
154 pub async fn create_indexes(&self) -> Result<(), VectorStoreError> {
156 self.chunk_collection
157 .create_index(
158 mongodb::IndexModel::builder()
159 .keys(doc! { "parent_id": 1 })
160 .build(),
161 None,
162 )
163 .await
164 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
165
166 Ok(())
167 }
168
169 pub fn client(&self) -> &Client {
171 &self.client
172 }
173}
174
175#[async_trait]
176impl ChunkedDocumentStoreTrait for MongoChunkedDocumentStore {
177 async fn add_parent_document(
178 &self,
179 document: Document,
180 chunk_size: usize,
181 ) -> Result<(String, Vec<String>), VectorStoreError> {
182 let parent_id = document
183 .id
184 .clone()
185 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
186
187 let mongo_parent = MongoParentDoc {
188 id: parent_id.clone(),
189 content: document.content.clone(),
190 metadata: document.metadata.clone(),
191 };
192
193 let upsert = mongodb::options::ReplaceOptions::builder()
198 .upsert(true)
199 .build();
200 self.parent_collection
201 .replace_one(doc! { "_id": parent_id.clone() }, mongo_parent, upsert.clone())
202 .await
203 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
204
205 let splitter = RecursiveCharacterSplitter::new(chunk_size, chunk_size / 10);
206 let chunks = splitter.split_text(&document.content);
207
208 let mut chunk_ids = Vec::new();
209
210 for (segment, chunk_content) in chunks.into_iter().enumerate() {
211 let chunk_id = format!("{}_{}", parent_id, segment);
212
213 let mongo_chunk = MongoChunkDoc {
214 chunk_id: chunk_id.clone(),
215 parent_id: parent_id.clone(),
216 content: chunk_content,
217 segment: segment as i32,
218 metadata: HashMap::new(),
219 };
220
221 self.chunk_collection
222 .replace_one(doc! { "_id": chunk_id.clone() }, mongo_chunk, upsert.clone())
223 .await
224 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
225
226 chunk_ids.push(chunk_id);
227 }
228
229 self.chunk_collection
231 .delete_many(
232 doc! { "parent_id": &parent_id, "chunk_id": doc! { "$nin": &chunk_ids } },
233 None,
234 )
235 .await
236 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
237
238 Ok((parent_id, chunk_ids))
239 }
240
241 async fn add_parent_documents(
242 &self,
243 documents: Vec<Document>,
244 chunk_size: usize,
245 ) -> Result<Vec<(String, Vec<String>)>, VectorStoreError> {
246 let mut results = Vec::new();
247 for doc in documents {
248 let result = self.add_parent_document(doc, chunk_size).await?;
249 results.push(result);
250 }
251 Ok(results)
252 }
253
254 async fn get_parent_document(
255 &self,
256 parent_id: &str,
257 ) -> Result<Option<Document>, VectorStoreError> {
258 let result = self
259 .parent_collection
260 .find_one(doc! { "_id": parent_id }, None)
261 .await
262 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
263
264 Ok(result.map(|m| m.into()))
265 }
266
267 async fn get_chunk(&self, chunk_id: &str) -> Result<Option<ChunkDocument>, VectorStoreError> {
268 let result = self
269 .chunk_collection
270 .find_one(doc! { "_id": chunk_id }, None)
271 .await
272 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
273
274 Ok(result.map(|m| m.into()))
275 }
276
277 async fn get_chunk_document(
278 &self,
279 chunk_id: &str,
280 ) -> Result<Option<Document>, VectorStoreError> {
281 let chunk = self.get_chunk(chunk_id).await?;
282 Ok(chunk.map(|c| c.to_document()))
283 }
284
285 async fn get_chunks_for_parent(
286 &self,
287 parent_id: &str,
288 ) -> Result<Vec<ChunkDocument>, VectorStoreError> {
289 let options = mongodb::options::FindOptions::builder()
290 .sort(doc! { "segment": 1 })
291 .build();
292
293 let mut cursor = self
294 .chunk_collection
295 .find(doc! { "parent_id": parent_id }, options)
296 .await
297 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
298
299 let mut chunks = Vec::new();
300 while cursor
301 .advance()
302 .await
303 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
304 {
305 let doc = cursor
306 .deserialize_current()
307 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
308 chunks.push(doc.into());
309 }
310
311 Ok(chunks)
312 }
313
314 async fn get_chunk_documents_for_parent(
315 &self,
316 parent_id: &str,
317 ) -> Result<Vec<Document>, VectorStoreError> {
318 let chunks = self.get_chunks_for_parent(parent_id).await?;
319 Ok(chunks.into_iter().map(|c| c.to_document()).collect())
320 }
321
322 async fn delete_parent_document(&self, parent_id: &str) -> Result<(), VectorStoreError> {
323 self.chunk_collection
324 .delete_many(doc! { "parent_id": parent_id }, None)
325 .await
326 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
327
328 self.parent_collection
329 .delete_one(doc! { "_id": parent_id }, None)
330 .await
331 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
332
333 Ok(())
334 }
335
336 async fn parent_count(&self) -> usize {
337 self.parent_collection
338 .count_documents(doc! {}, None)
339 .await
340 .unwrap_or(0) as usize
341 }
342
343 async fn chunk_count(&self) -> usize {
344 self.chunk_collection
345 .count_documents(doc! {}, None)
346 .await
347 .unwrap_or(0) as usize
348 }
349
350 async fn get_all_chunks(&self) -> Result<Vec<ChunkDocument>, VectorStoreError> {
351 let mut cursor = self
352 .chunk_collection
353 .find(doc! {}, None)
354 .await
355 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
356
357 let mut chunks = Vec::new();
358 while cursor
359 .advance()
360 .await
361 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
362 {
363 let doc = cursor
364 .deserialize_current()
365 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
366 chunks.push(doc.into());
367 }
368
369 Ok(chunks)
370 }
371
372 async fn clear(&self) -> Result<(), VectorStoreError> {
373 self.parent_collection
374 .delete_many(doc! {}, None)
375 .await
376 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
377
378 self.chunk_collection
379 .delete_many(doc! {}, None)
380 .await
381 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
382
383 Ok(())
384 }
385
386 fn add_parent_document_blocking(
387 &self,
388 document: Document,
389 chunk_size: usize,
390 ) -> Result<(String, Vec<String>), VectorStoreError> {
391 tokio::task::block_in_place(|| {
392 tokio::runtime::Handle::current()
393 .block_on(self.add_parent_document(document, chunk_size))
394 })
395 }
396
397 fn get_parent_document_blocking(
398 &self,
399 parent_id: &str,
400 ) -> Result<Option<Document>, VectorStoreError> {
401 tokio::task::block_in_place(|| {
402 tokio::runtime::Handle::current().block_on(self.get_parent_document(parent_id))
403 })
404 }
405
406 fn get_chunk_blocking(
407 &self,
408 chunk_id: &str,
409 ) -> Result<Option<ChunkDocument>, VectorStoreError> {
410 tokio::task::block_in_place(|| {
411 tokio::runtime::Handle::current().block_on(self.get_chunk(chunk_id))
412 })
413 }
414
415 fn blocking_get_chunks_for_parent(
416 &self,
417 parent_id: &str,
418 ) -> Result<Vec<ChunkDocument>, VectorStoreError> {
419 tokio::task::block_in_place(|| {
420 tokio::runtime::Handle::current().block_on(self.get_chunks_for_parent(parent_id))
421 })
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428
429 #[test]
430 fn test_config_creation() {
431 let config = MongoStoreConfig::new("mongodb://localhost:27017", "test_db");
432 assert_eq!(config.uri, "mongodb://localhost:27017");
433 assert_eq!(config.database, "test_db");
434 }
435
436 #[test]
437 fn test_mongo_parent_doc_conversion() {
438 let doc = Document::new("test content").with_id("test_id");
439 let mongo: MongoParentDoc = doc.clone().into();
440 assert_eq!(mongo.id, "test_id");
441 assert_eq!(mongo.content, "test content");
442
443 let back: Document = mongo.into();
444 assert_eq!(back.content, "test content");
445 }
446
447 #[test]
448 fn test_mongo_chunk_doc_conversion() {
449 let chunk = ChunkDocument::new(
450 "chunk_0".to_string(),
451 "parent_1".to_string(),
452 "content".to_string(),
453 0,
454 );
455 let mongo: MongoChunkDoc = chunk.clone().into();
456 assert_eq!(mongo.chunk_id, "chunk_0");
457 assert_eq!(mongo.parent_id, "parent_1");
458
459 let back: ChunkDocument = mongo.into();
460 assert_eq!(back.chunk_id, "chunk_0");
461 }
462}