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, String>,
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, String>,
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,
87 pub database: String,
88 pub parent_collection: String,
89 pub chunk_collection: String,
90}
91
92impl Default for MongoStoreConfig {
93 fn default() -> Self {
94 Self {
95 uri: "mongodb://localhost:27017".to_string(),
96 database: "langchainrust".to_string(),
97 parent_collection: "parent_docs".to_string(),
98 chunk_collection: "chunks".to_string(),
99 }
100 }
101}
102
103impl MongoStoreConfig {
104 pub fn new(uri: impl Into<String>, database: impl Into<String>) -> Self {
105 Self {
106 uri: uri.into(),
107 database: database.into(),
108 parent_collection: "parent_docs".to_string(),
109 chunk_collection: "chunks".to_string(),
110 }
111 }
112
113 pub fn with_collections(mut self, parent: impl Into<String>, chunk: impl Into<String>) -> Self {
114 self.parent_collection = parent.into();
115 self.chunk_collection = chunk.into();
116 self
117 }
118}
119
120pub struct MongoChunkedDocumentStore {
122 client: Client,
123 parent_collection: Collection<MongoParentDoc>,
124 chunk_collection: Collection<MongoChunkDoc>,
125}
126
127impl MongoChunkedDocumentStore {
128 pub async fn new(config: MongoStoreConfig) -> Result<Self, VectorStoreError> {
129 let client_options = ClientOptions::parse(&config.uri)
130 .await
131 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
132
133 let client = Client::with_options(client_options)
134 .map_err(|e| VectorStoreError::ConnectionError(e.to_string()))?;
135
136 let db = client.database(&config.database);
137 let parent_collection = db.collection(&config.parent_collection);
138 let chunk_collection = db.collection(&config.chunk_collection);
139
140 Ok(Self {
141 client,
142 parent_collection,
143 chunk_collection,
144 })
145 }
146
147 pub async fn create_indexes(&self) -> Result<(), VectorStoreError> {
148 self.chunk_collection
149 .create_index(
150 mongodb::IndexModel::builder()
151 .keys(doc! { "parent_id": 1 })
152 .build(),
153 None,
154 )
155 .await
156 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
157
158 Ok(())
159 }
160
161 pub fn client(&self) -> &Client {
162 &self.client
163 }
164}
165
166#[async_trait]
167impl ChunkedDocumentStoreTrait for MongoChunkedDocumentStore {
168 async fn add_parent_document(
169 &self,
170 document: Document,
171 chunk_size: usize,
172 ) -> Result<(String, Vec<String>), VectorStoreError> {
173 let parent_id = document
174 .id
175 .clone()
176 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
177
178 let mongo_parent = MongoParentDoc {
179 id: parent_id.clone(),
180 content: document.content.clone(),
181 metadata: document.metadata.clone(),
182 };
183
184 self.parent_collection
185 .insert_one(mongo_parent, None)
186 .await
187 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
188
189 let splitter = RecursiveCharacterSplitter::new(chunk_size, chunk_size / 10);
190 let chunks = splitter.split_text(&document.content);
191
192 let mut chunk_ids = Vec::new();
193
194 for (segment, chunk_content) in chunks.into_iter().enumerate() {
195 let chunk_id = format!("{}_{}", parent_id, segment);
196
197 let mongo_chunk = MongoChunkDoc {
198 chunk_id: chunk_id.clone(),
199 parent_id: parent_id.clone(),
200 content: chunk_content,
201 segment: segment as i32,
202 metadata: HashMap::new(),
203 };
204
205 self.chunk_collection
206 .insert_one(mongo_chunk, None)
207 .await
208 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
209
210 chunk_ids.push(chunk_id);
211 }
212
213 Ok((parent_id, chunk_ids))
214 }
215
216 async fn add_parent_documents(
217 &self,
218 documents: Vec<Document>,
219 chunk_size: usize,
220 ) -> Result<Vec<(String, Vec<String>)>, VectorStoreError> {
221 let mut results = Vec::new();
222 for doc in documents {
223 let result = self.add_parent_document(doc, chunk_size).await?;
224 results.push(result);
225 }
226 Ok(results)
227 }
228
229 async fn get_parent_document(
230 &self,
231 parent_id: &str,
232 ) -> Result<Option<Document>, VectorStoreError> {
233 let result = self
234 .parent_collection
235 .find_one(doc! { "_id": parent_id }, None)
236 .await
237 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
238
239 Ok(result.map(|m| m.into()))
240 }
241
242 async fn get_chunk(&self, chunk_id: &str) -> Result<Option<ChunkDocument>, VectorStoreError> {
243 let result = self
244 .chunk_collection
245 .find_one(doc! { "_id": chunk_id }, None)
246 .await
247 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
248
249 Ok(result.map(|m| m.into()))
250 }
251
252 async fn get_chunk_document(
253 &self,
254 chunk_id: &str,
255 ) -> Result<Option<Document>, VectorStoreError> {
256 let chunk = self.get_chunk(chunk_id).await?;
257 Ok(chunk.map(|c| c.to_document()))
258 }
259
260 async fn get_chunks_for_parent(
261 &self,
262 parent_id: &str,
263 ) -> Result<Vec<ChunkDocument>, VectorStoreError> {
264 let options = mongodb::options::FindOptions::builder()
265 .sort(doc! { "segment": 1 })
266 .build();
267
268 let mut cursor = self
269 .chunk_collection
270 .find(doc! { "parent_id": parent_id }, options)
271 .await
272 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
273
274 let mut chunks = Vec::new();
275 while cursor
276 .advance()
277 .await
278 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
279 {
280 let doc = cursor
281 .deserialize_current()
282 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
283 chunks.push(doc.into());
284 }
285
286 Ok(chunks)
287 }
288
289 async fn get_chunk_documents_for_parent(
290 &self,
291 parent_id: &str,
292 ) -> Result<Vec<Document>, VectorStoreError> {
293 let chunks = self.get_chunks_for_parent(parent_id).await?;
294 Ok(chunks.into_iter().map(|c| c.to_document()).collect())
295 }
296
297 async fn delete_parent_document(&self, parent_id: &str) -> Result<(), VectorStoreError> {
298 self.chunk_collection
299 .delete_many(doc! { "parent_id": parent_id }, None)
300 .await
301 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
302
303 self.parent_collection
304 .delete_one(doc! { "_id": parent_id }, None)
305 .await
306 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
307
308 Ok(())
309 }
310
311 async fn parent_count(&self) -> usize {
312 self.parent_collection
313 .count_documents(doc! {}, None)
314 .await
315 .unwrap_or(0) as usize
316 }
317
318 async fn chunk_count(&self) -> usize {
319 self.chunk_collection
320 .count_documents(doc! {}, None)
321 .await
322 .unwrap_or(0) as usize
323 }
324
325 async fn get_all_chunks(&self) -> Result<Vec<ChunkDocument>, VectorStoreError> {
326 let mut cursor = self
327 .chunk_collection
328 .find(doc! {}, None)
329 .await
330 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
331
332 let mut chunks = Vec::new();
333 while cursor
334 .advance()
335 .await
336 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
337 {
338 let doc = cursor
339 .deserialize_current()
340 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
341 chunks.push(doc.into());
342 }
343
344 Ok(chunks)
345 }
346
347 async fn clear(&self) -> Result<(), VectorStoreError> {
348 self.parent_collection
349 .delete_many(doc! {}, None)
350 .await
351 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
352
353 self.chunk_collection
354 .delete_many(doc! {}, None)
355 .await
356 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
357
358 Ok(())
359 }
360
361 fn add_parent_document_blocking(
362 &self,
363 document: Document,
364 chunk_size: usize,
365 ) -> Result<(String, Vec<String>), VectorStoreError> {
366 tokio::task::block_in_place(|| {
367 tokio::runtime::Handle::current()
368 .block_on(self.add_parent_document(document, chunk_size))
369 })
370 }
371
372 fn get_parent_document_blocking(
373 &self,
374 parent_id: &str,
375 ) -> Result<Option<Document>, VectorStoreError> {
376 tokio::task::block_in_place(|| {
377 tokio::runtime::Handle::current().block_on(self.get_parent_document(parent_id))
378 })
379 }
380
381 fn get_chunk_blocking(
382 &self,
383 chunk_id: &str,
384 ) -> Result<Option<ChunkDocument>, VectorStoreError> {
385 tokio::task::block_in_place(|| {
386 tokio::runtime::Handle::current().block_on(self.get_chunk(chunk_id))
387 })
388 }
389
390 fn blocking_get_chunks_for_parent(
391 &self,
392 parent_id: &str,
393 ) -> Result<Vec<ChunkDocument>, VectorStoreError> {
394 tokio::task::block_in_place(|| {
395 tokio::runtime::Handle::current().block_on(self.get_chunks_for_parent(parent_id))
396 })
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403
404 #[test]
405 fn test_config_creation() {
406 let config = MongoStoreConfig::new("mongodb://localhost:27017", "test_db");
407 assert_eq!(config.uri, "mongodb://localhost:27017");
408 assert_eq!(config.database, "test_db");
409 }
410
411 #[test]
412 fn test_mongo_parent_doc_conversion() {
413 let doc = Document::new("test content").with_id("test_id");
414 let mongo: MongoParentDoc = doc.clone().into();
415 assert_eq!(mongo.id, "test_id");
416 assert_eq!(mongo.content, "test content");
417
418 let back: Document = mongo.into();
419 assert_eq!(back.content, "test content");
420 }
421
422 #[test]
423 fn test_mongo_chunk_doc_conversion() {
424 let chunk = ChunkDocument::new(
425 "chunk_0".to_string(),
426 "parent_1".to_string(),
427 "content".to_string(),
428 0,
429 );
430 let mongo: MongoChunkDoc = chunk.clone().into();
431 assert_eq!(mongo.chunk_id, "chunk_0");
432 assert_eq!(mongo.parent_id, "parent_1");
433
434 let back: ChunkDocument = mongo.into();
435 assert_eq!(back.chunk_id, "chunk_0");
436 }
437}