1use std::collections::HashSet;
4use std::sync::Arc;
5
6use futures::TryStreamExt;
7use mongodb::bson::{doc, Document};
8use mongodb::options::IndexOptions;
9use mongodb::{Client, Collection, IndexModel};
10use serde_json::{Map, Value};
11
12use valence_core::{
13 BackendCapabilities, CompiledQuery, Database, DatabaseBackend, DatabaseFromEngine, Error,
14 KnownEngines, RecordId, Result,
15};
16
17use crate::config::MongoConfig;
18
19pub const ENGINE_ID: &str = KnownEngines::MONGODB;
21
22pub const PRIMARY: DatabaseFromEngine = Database::from_engine("primary", ENGINE_ID);
24
25const EDGES_COLLECTION: &str = "valence_edges";
26
27#[derive(Clone)]
65pub struct MongoBackend {
66 client: Client,
67 database: String,
68 unique_fields: Arc<tokio::sync::RwLock<HashSet<(String, String)>>>,
69}
70
71impl std::fmt::Debug for MongoBackend {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 f.debug_struct("MongoBackend")
74 .field("database", &self.database)
75 .finish_non_exhaustive()
76 }
77}
78
79impl MongoBackend {
80 pub fn builder() -> crate::config::MongoBackendBuilder {
82 crate::config::MongoBackendBuilder::new()
83 }
84
85 pub async fn from_env() -> Result<Self> {
87 Self::builder().from_env_defaults().build().await
88 }
89
90 pub async fn connect(uri: &str, database: &str) -> Result<Self> {
92 Self::builder().uri(uri).database(database).build().await
93 }
94
95 pub async fn connect_with_config(config: MongoConfig) -> Result<Self> {
97 let client = Client::with_uri_str(&config.uri)
98 .await
99 .map_err(|e| Error::Database(e.to_string()))?;
100 let backend = Self {
101 client,
102 database: config.database,
103 unique_fields: Arc::new(tokio::sync::RwLock::new(HashSet::new())),
104 };
105 backend.ensure_edges_collection().await?;
106 Ok(backend)
107 }
108
109 fn assert_safe_table(table: &str) -> Result<()> {
110 if table.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
111 Ok(())
112 } else {
113 Err(Error::Validation(format!("unsafe table name: {table}")))
114 }
115 }
116
117 fn collection(&self, table: &str) -> Collection<Document> {
118 self.client.database(&self.database).collection(table)
119 }
120
121 async fn ensure_edges_collection(&self) -> Result<()> {
122 let coll = self.collection(EDGES_COLLECTION);
123 let index = IndexModel::builder()
124 .keys(doc! {
125 "from_table": 1,
126 "from_id": 1,
127 "edge_type": 1,
128 "to_table": 1,
129 "to_id": 1,
130 })
131 .options(IndexOptions::builder().unique(true).build())
132 .build();
133 coll.create_index(index)
134 .await
135 .map_err(|e| Error::Database(e.to_string()))?;
136 Ok(())
137 }
138
139 async fn unique_fields_for(&self, table: &str) -> Vec<String> {
140 self.unique_fields
141 .read()
142 .await
143 .iter()
144 .filter(|(t, _)| t == table)
145 .map(|(_, f)| f.clone())
146 .collect()
147 }
148
149 async fn check_unique_fields(
150 &self,
151 table: &str,
152 record: &Value,
153 exclude_id: Option<&str>,
154 ) -> Result<()> {
155 for field in self.unique_fields_for(table).await {
156 let Some(value) = record.get(&field).and_then(|v| v.as_str()) else {
157 continue;
158 };
159 if let Some(exclude) = exclude_id {
160 if let Ok(Some(row)) = self.get_record(table, exclude).await {
161 if row.get(&field).and_then(|v| v.as_str()) == Some(value) {
162 continue;
163 }
164 }
165 }
166 let coll = self.collection(table);
167 let filter = doc! { field.as_str(): value };
168 if let Some(existing) = coll
169 .find_one(filter)
170 .await
171 .map_err(|e| Error::Database(e.to_string()))?
172 {
173 let existing_id = existing.get_str("_id").unwrap_or("");
174 if exclude_id != Some(existing_id) {
175 return Err(Error::Database(format!(
176 "duplicate unique index value for {table}.{field}"
177 )));
178 }
179 }
180 }
181 Ok(())
182 }
183
184 fn value_to_bson(value: &Value) -> mongodb::bson::Bson {
185 serde_json::from_value(value.clone()).unwrap_or(mongodb::bson::Bson::Null)
186 }
187
188 fn doc_to_row(table: &str, id: &str, doc: Document) -> Value {
189 let mut map = Map::new();
190 for (k, v) in doc {
191 if k == "_id" {
192 continue;
193 }
194 map.insert(k, bson_to_json(v));
195 }
196 row_from_body(table, id, Value::Object(map))
197 }
198
199 async fn rows_for_table(&self, table: &str, limit: Option<usize>) -> Result<Vec<Value>> {
200 Self::assert_safe_table(table)?;
201 let coll = self.collection(table);
202 let mut cursor = coll
203 .find(doc! {})
204 .await
205 .map_err(|e| Error::Database(e.to_string()))?;
206 let mut rows = Vec::new();
207 while let Some(doc) = cursor
208 .try_next()
209 .await
210 .map_err(|e| Error::Database(e.to_string()))?
211 {
212 let id = doc.get_str("_id").unwrap_or("").to_string();
213 rows.push(Self::doc_to_row(table, &id, doc));
214 if limit.is_some_and(|n| rows.len() >= n) {
215 break;
216 }
217 }
218 Ok(rows)
219 }
220}
221
222#[async_trait::async_trait]
223impl DatabaseBackend for MongoBackend {
224 fn engine_id(&self) -> &'static str {
225 ENGINE_ID
226 }
227
228 fn capabilities(&self) -> BackendCapabilities {
229 BackendCapabilities {
230 supports_merge: true,
231 supports_graph_edges: true,
232 telemetry_label: "mongodb",
233 }
234 }
235
236 async fn execute_compiled_query(&self, compiled: &CompiledQuery) -> Result<Vec<Value>> {
237 let q = compiled.query_string.trim();
238 if let Ok(descriptor) = serde_json::from_str::<Value>(q) {
239 if let Some(collection) = descriptor.get("collection").and_then(|v| v.as_str()) {
240 let limit = descriptor
241 .get("limit")
242 .and_then(|v| v.as_u64())
243 .map(|n| n as usize);
244 let mut rows = self.rows_for_table(collection, limit).await?;
245 rows = valence_core::query::apply_equality_where(rows, compiled);
246 rows = valence_core::query::apply_order_limit_offset(rows, &compiled.query_string);
247 return Ok(rows);
248 }
249 }
250
251 let upper = q.to_uppercase();
252 if !upper.starts_with("SELECT ") {
253 return Ok(vec![]);
254 }
255 let from_idx = upper
256 .find(" FROM ")
257 .ok_or_else(|| Error::Internal("missing FROM in select".into()))?;
258 let table = q[from_idx + 6..]
259 .split_whitespace()
260 .next()
261 .unwrap_or("")
262 .trim();
263 if table.is_empty() {
264 return Ok(vec![]);
265 }
266 let mut rows = self.rows_for_table(table, None).await?;
268 rows = valence_core::query::apply_equality_where(rows, compiled);
269 rows = valence_core::query::apply_order_limit_offset(rows, &compiled.query_string);
270 if upper.contains("SELECT ID") && !upper.contains("BODY") {
271 return Ok(rows
273 .iter()
274 .filter_map(|r| {
275 r.get("id")
276 .and_then(|id| id.get("id").and_then(|x| x.as_str()))
277 .or_else(|| r.get("id").and_then(|id| id.as_str()))
278 .map(|id| serde_json::json!({ "id": id }))
279 })
280 .collect());
281 }
282 Ok(rows)
283 }
284
285 async fn ensure_schemaless_table(&self, _table: &str) -> Result<()> {
286 Ok(())
287 }
288
289 async fn get_record(&self, table: &str, id: &str) -> Result<Option<Value>> {
290 Self::assert_safe_table(table)?;
291 let coll = self.collection(table);
292 let doc = coll
293 .find_one(doc! { "_id": id })
294 .await
295 .map_err(|e| Error::Database(e.to_string()))?;
296 Ok(doc.map(|d| Self::doc_to_row(table, id, d)))
297 }
298
299 async fn create_record(&self, table: &str, content: Value) -> Result<Value> {
300 Self::assert_safe_table(table)?;
301 self.check_unique_fields(table, &content, None).await?;
302 let id = storage_id(&content).unwrap_or_else(uuid_simple);
303 let mut record = content;
304 if let Some(obj) = record.as_object_mut() {
305 let has_string_id = obj.get("id").and_then(|v| v.as_str()).is_some();
306 if !has_string_id {
307 obj.insert("id".into(), record_id_json(table, &id));
308 }
309 }
310 let mut doc = body_document(&record);
311 doc.insert("_id", id.clone());
312 let coll = self.collection(table);
313 coll.insert_one(doc).await.map_err(map_duplicate_key)?;
314 Ok(record)
315 }
316
317 async fn update_record(&self, table: &str, id: &str, content: Value) -> Result<Value> {
318 if self.get_record(table, id).await?.is_none() {
319 return Err(Error::NotFound(format!("{table}:{id}")));
320 }
321 self.check_unique_fields(table, &content, Some(id)).await?;
322 let mut record = content;
323 if let Some(obj) = record.as_object_mut() {
324 obj.insert("id".into(), record_id_json(table, id));
325 }
326 let doc = body_document(&record);
327 let coll = self.collection(table);
328 coll.replace_one(doc! { "_id": id }, doc)
329 .await
330 .map_err(map_duplicate_key)?;
331 Ok(record)
332 }
333
334 async fn merge_record(&self, table: &str, id: &str, patch: Value) -> Result<Value> {
335 let existing = self
336 .get_record(table, id)
337 .await?
338 .unwrap_or_else(|| row_from_body(table, id, Value::Object(Map::new())));
339 let mut merged = existing;
340 if let (Some(base), Some(patch_obj)) = (merged.as_object_mut(), patch.as_object()) {
341 for (k, v) in patch_obj {
342 base.insert(k.clone(), v.clone());
343 }
344 }
345 self.check_unique_fields(table, &merged, Some(id)).await?;
346 let doc = body_document(&merged);
347 let coll = self.collection(table);
348 coll.replace_one(doc! { "_id": id }, doc)
349 .await
350 .map_err(|e| {
351 if e.to_string().contains("duplicate key") {
352 Error::Database(format!("duplicate unique index value for {table}"))
353 } else {
354 Error::Database(e.to_string())
355 }
356 })?;
357 Ok(merged)
358 }
359
360 async fn upsert_record(&self, table: &str, id: &str, content: Value) -> Result<Value> {
361 if self.get_record(table, id).await?.is_some() {
362 self.update_record(table, id, content).await
363 } else {
364 let mut record = content;
365 if let Some(obj) = record.as_object_mut() {
366 obj.insert("id".into(), record_id_json(table, id));
367 }
368 self.create_record(table, record).await
369 }
370 }
371
372 async fn delete_record(&self, table: &str, id: &str) -> Result<()> {
373 let coll = self.collection(table);
374 coll.delete_one(doc! { "_id": id })
375 .await
376 .map_err(|e| Error::Database(e.to_string()))?;
377 Ok(())
378 }
379
380 async fn relate_edge(&self, from: &RecordId, edge_table: &str, to: &RecordId) -> Result<()> {
381 let coll = self.collection(EDGES_COLLECTION);
382 coll.insert_one(doc! {
383 "from_table": from.table(),
384 "from_id": from.id(),
385 "edge_type": edge_table,
386 "to_table": to.table(),
387 "to_id": to.id(),
388 })
389 .await
390 .map_err(|e| Error::Database(e.to_string()))?;
391 Ok(())
392 }
393
394 async fn unrelate_edge(&self, from: &RecordId, edge_table: &str, to: &RecordId) -> Result<()> {
395 let coll = self.collection(EDGES_COLLECTION);
396 coll.delete_one(doc! {
397 "from_table": from.table(),
398 "from_id": from.id(),
399 "edge_type": edge_table,
400 "to_table": to.table(),
401 "to_id": to.id(),
402 })
403 .await
404 .map_err(|e| Error::Database(e.to_string()))?;
405 Ok(())
406 }
407
408 async fn get_edge_targets(&self, from: &RecordId, edge_table: &str) -> Result<Vec<RecordId>> {
409 let coll = self.collection(EDGES_COLLECTION);
410 let mut cursor = coll
411 .find(doc! {
412 "from_table": from.table(),
413 "from_id": from.id(),
414 "edge_type": edge_table,
415 })
416 .await
417 .map_err(|e| Error::Database(e.to_string()))?;
418 let mut out = Vec::new();
419 while let Some(edge) = cursor
420 .try_next()
421 .await
422 .map_err(|e| Error::Database(e.to_string()))?
423 {
424 let to_table = edge.get_str("to_table").unwrap_or("").to_string();
425 let to_id = edge.get_str("to_id").unwrap_or("").to_string();
426 out.push(RecordId::new(to_table, to_id));
427 }
428 Ok(out)
429 }
430
431 async fn define_unique_index(&self, table: &str, field: &str) -> Result<()> {
432 Self::assert_safe_table(table)?;
433 self.unique_fields
434 .write()
435 .await
436 .insert((table.to_string(), field.to_string()));
437 let coll = self.collection(table);
438 let index = IndexModel::builder()
439 .keys(doc! { field: 1 })
440 .options(IndexOptions::builder().unique(true).sparse(true).build())
441 .build();
442 coll.create_index(index)
443 .await
444 .map_err(|e| Error::Database(e.to_string()))?;
445 Ok(())
446 }
447}
448
449fn map_duplicate_key(e: mongodb::error::Error) -> Error {
450 if e.to_string().contains("duplicate key") {
451 Error::Database("duplicate unique index value".into())
452 } else {
453 Error::Database(e.to_string())
454 }
455}
456
457fn body_document(record: &Value) -> Document {
458 let mut doc = Document::new();
459 if let Some(obj) = record.as_object() {
460 for (k, v) in obj {
461 if k == "id" {
462 continue;
463 }
464 doc.insert(k.clone(), MongoBackend::value_to_bson(v));
465 }
466 }
467 doc
468}
469
470fn bson_to_json(bson: mongodb::bson::Bson) -> Value {
471 serde_json::to_value(bson).unwrap_or(Value::Null)
472}
473
474fn row_from_body(table: &str, id: &str, body: Value) -> Value {
475 let mut obj = body.as_object().cloned().unwrap_or_default();
476 obj.insert("id".into(), record_id_json(table, id));
477 Value::Object(obj)
478}
479
480fn record_id_json(table: &str, id: &str) -> Value {
481 serde_json::json!({
482 "table": table,
483 "id": id,
484 })
485}
486
487fn storage_id(content: &Value) -> Option<String> {
488 content.get("id").and_then(|v| {
489 v.get("id")
490 .and_then(|x| x.as_str())
491 .map(str::to_string)
492 .or_else(|| v.as_str().map(str::to_string))
493 })
494}
495
496fn uuid_simple() -> String {
497 uuid::Uuid::new_v4().to_string()
498}