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 self.parent_collection
194 .insert_one(mongo_parent, None)
195 .await
196 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
197
198 let splitter = RecursiveCharacterSplitter::new(chunk_size, chunk_size / 10);
199 let chunks = splitter.split_text(&document.content);
200
201 let mut chunk_ids = Vec::new();
202
203 for (segment, chunk_content) in chunks.into_iter().enumerate() {
204 let chunk_id = format!("{}_{}", parent_id, segment);
205
206 let mongo_chunk = MongoChunkDoc {
207 chunk_id: chunk_id.clone(),
208 parent_id: parent_id.clone(),
209 content: chunk_content,
210 segment: segment as i32,
211 metadata: HashMap::new(),
212 };
213
214 self.chunk_collection
215 .insert_one(mongo_chunk, None)
216 .await
217 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
218
219 chunk_ids.push(chunk_id);
220 }
221
222 Ok((parent_id, chunk_ids))
223 }
224
225 async fn add_parent_documents(
226 &self,
227 documents: Vec<Document>,
228 chunk_size: usize,
229 ) -> Result<Vec<(String, Vec<String>)>, VectorStoreError> {
230 let mut results = Vec::new();
231 for doc in documents {
232 let result = self.add_parent_document(doc, chunk_size).await?;
233 results.push(result);
234 }
235 Ok(results)
236 }
237
238 async fn get_parent_document(
239 &self,
240 parent_id: &str,
241 ) -> Result<Option<Document>, VectorStoreError> {
242 let result = self
243 .parent_collection
244 .find_one(doc! { "_id": parent_id }, None)
245 .await
246 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
247
248 Ok(result.map(|m| m.into()))
249 }
250
251 async fn get_chunk(&self, chunk_id: &str) -> Result<Option<ChunkDocument>, VectorStoreError> {
252 let result = self
253 .chunk_collection
254 .find_one(doc! { "_id": chunk_id }, None)
255 .await
256 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
257
258 Ok(result.map(|m| m.into()))
259 }
260
261 async fn get_chunk_document(
262 &self,
263 chunk_id: &str,
264 ) -> Result<Option<Document>, VectorStoreError> {
265 let chunk = self.get_chunk(chunk_id).await?;
266 Ok(chunk.map(|c| c.to_document()))
267 }
268
269 async fn get_chunks_for_parent(
270 &self,
271 parent_id: &str,
272 ) -> Result<Vec<ChunkDocument>, VectorStoreError> {
273 let options = mongodb::options::FindOptions::builder()
274 .sort(doc! { "segment": 1 })
275 .build();
276
277 let mut cursor = self
278 .chunk_collection
279 .find(doc! { "parent_id": parent_id }, options)
280 .await
281 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
282
283 let mut chunks = Vec::new();
284 while cursor
285 .advance()
286 .await
287 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
288 {
289 let doc = cursor
290 .deserialize_current()
291 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
292 chunks.push(doc.into());
293 }
294
295 Ok(chunks)
296 }
297
298 async fn get_chunk_documents_for_parent(
299 &self,
300 parent_id: &str,
301 ) -> Result<Vec<Document>, VectorStoreError> {
302 let chunks = self.get_chunks_for_parent(parent_id).await?;
303 Ok(chunks.into_iter().map(|c| c.to_document()).collect())
304 }
305
306 async fn delete_parent_document(&self, parent_id: &str) -> Result<(), VectorStoreError> {
307 self.chunk_collection
308 .delete_many(doc! { "parent_id": parent_id }, None)
309 .await
310 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
311
312 self.parent_collection
313 .delete_one(doc! { "_id": parent_id }, None)
314 .await
315 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
316
317 Ok(())
318 }
319
320 async fn parent_count(&self) -> usize {
321 self.parent_collection
322 .count_documents(doc! {}, None)
323 .await
324 .unwrap_or(0) as usize
325 }
326
327 async fn chunk_count(&self) -> usize {
328 self.chunk_collection
329 .count_documents(doc! {}, None)
330 .await
331 .unwrap_or(0) as usize
332 }
333
334 async fn get_all_chunks(&self) -> Result<Vec<ChunkDocument>, VectorStoreError> {
335 let mut cursor = self
336 .chunk_collection
337 .find(doc! {}, None)
338 .await
339 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
340
341 let mut chunks = Vec::new();
342 while cursor
343 .advance()
344 .await
345 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?
346 {
347 let doc = cursor
348 .deserialize_current()
349 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
350 chunks.push(doc.into());
351 }
352
353 Ok(chunks)
354 }
355
356 async fn clear(&self) -> Result<(), VectorStoreError> {
357 self.parent_collection
358 .delete_many(doc! {}, None)
359 .await
360 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
361
362 self.chunk_collection
363 .delete_many(doc! {}, None)
364 .await
365 .map_err(|e| VectorStoreError::StorageError(e.to_string()))?;
366
367 Ok(())
368 }
369
370 fn add_parent_document_blocking(
371 &self,
372 document: Document,
373 chunk_size: usize,
374 ) -> Result<(String, Vec<String>), VectorStoreError> {
375 tokio::task::block_in_place(|| {
376 tokio::runtime::Handle::current()
377 .block_on(self.add_parent_document(document, chunk_size))
378 })
379 }
380
381 fn get_parent_document_blocking(
382 &self,
383 parent_id: &str,
384 ) -> Result<Option<Document>, VectorStoreError> {
385 tokio::task::block_in_place(|| {
386 tokio::runtime::Handle::current().block_on(self.get_parent_document(parent_id))
387 })
388 }
389
390 fn get_chunk_blocking(
391 &self,
392 chunk_id: &str,
393 ) -> Result<Option<ChunkDocument>, VectorStoreError> {
394 tokio::task::block_in_place(|| {
395 tokio::runtime::Handle::current().block_on(self.get_chunk(chunk_id))
396 })
397 }
398
399 fn blocking_get_chunks_for_parent(
400 &self,
401 parent_id: &str,
402 ) -> Result<Vec<ChunkDocument>, VectorStoreError> {
403 tokio::task::block_in_place(|| {
404 tokio::runtime::Handle::current().block_on(self.get_chunks_for_parent(parent_id))
405 })
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 #[test]
414 fn test_config_creation() {
415 let config = MongoStoreConfig::new("mongodb://localhost:27017", "test_db");
416 assert_eq!(config.uri, "mongodb://localhost:27017");
417 assert_eq!(config.database, "test_db");
418 }
419
420 #[test]
421 fn test_mongo_parent_doc_conversion() {
422 let doc = Document::new("test content").with_id("test_id");
423 let mongo: MongoParentDoc = doc.clone().into();
424 assert_eq!(mongo.id, "test_id");
425 assert_eq!(mongo.content, "test content");
426
427 let back: Document = mongo.into();
428 assert_eq!(back.content, "test content");
429 }
430
431 #[test]
432 fn test_mongo_chunk_doc_conversion() {
433 let chunk = ChunkDocument::new(
434 "chunk_0".to_string(),
435 "parent_1".to_string(),
436 "content".to_string(),
437 0,
438 );
439 let mongo: MongoChunkDoc = chunk.clone().into();
440 assert_eq!(mongo.chunk_id, "chunk_0");
441 assert_eq!(mongo.parent_id, "parent_1");
442
443 let back: ChunkDocument = mongo.into();
444 assert_eq!(back.chunk_id, "chunk_0");
445 }
446}