Skip to main content

siderite_backends/mongodb/
exec.rs

1//! The MongoDB executor: [`MongoBackend`] and its transactions.
2
3use super::compile::{
4    CompiledDelete, CompiledInsert, CompiledQuery, CompiledUpdate, CompiledWrite, Keys, UpdateSpec,
5    compile_query, compile_write,
6};
7use super::value::from_bson;
8use ::mongodb::action::Action;
9use ::mongodb::bson::{Bson, Document, doc};
10use ::mongodb::error::{Error as DriverError, ErrorKind};
11use ::mongodb::options::ReturnDocument;
12use ::mongodb::{Client, ClientSession, Collection, Cursor, Database};
13use async_trait::async_trait;
14use siderite_orm::{
15    Backend, BackendCapabilities, BackendCapabilityError, BackendError, BackendKind, ExecResult,
16    Executor, Feature, IsolationLevel, OrmError, QueryError, QueryPlan, QueryResult, Row,
17    Transaction, Value, WritePlan,
18};
19use std::sync::Arc;
20use tokio::sync::Mutex;
21
22/// Collection holding one `{_id: <collection>, seq: <last key>}` document per
23/// collection with generated integer keys.
24const COUNTERS: &str = "siderite_counters";
25
26/// MongoDB adapter: compiles plans to filters and pipelines and runs them.
27///
28/// Collections are named after tables. The primary-key column of a
29/// collection is stored as `_id` (see [`Keys`]); rows come back with `_id`
30/// renamed to that column. Integer keys that a plan does not supply are
31/// generated from a counter collection, `siderite_counters`.
32#[derive(Debug, Clone)]
33pub struct MongoBackend {
34    client: Client,
35    db: Database,
36    keys: Arc<Keys>,
37}
38
39impl MongoBackend {
40    /// Connect to `url` and use database `database`.
41    ///
42    /// # Errors
43    /// [`BackendError::Connection`] if the URL is invalid or no server can be
44    /// reached.
45    pub async fn connect(url: &str, database: &str) -> Result<Self, BackendError> {
46        let client = Client::with_uri_str(url)
47            .await
48            .map_err(|e| BackendError::Connection(e.to_string()))?;
49        client
50            .database("admin")
51            .run_command(doc! { "ping": 1_i32 })
52            .await
53            .map_err(|e| BackendError::Connection(e.to_string()))?;
54        Ok(Self::from_client(client, database))
55    }
56
57    /// Use an existing client.
58    pub fn from_client(client: Client, database: &str) -> Self {
59        Self {
60            db: client.database(database),
61            client,
62            keys: Arc::new(Keys::default()),
63        }
64    }
65
66    /// Set which column of each collection is stored as `_id` (default: a
67    /// column named `id`).
68    #[must_use]
69    pub fn with_keys(mut self, keys: Keys) -> Self {
70        self.keys = Arc::new(keys);
71        self
72    }
73
74    /// The database this adapter works in.
75    pub fn database(&self) -> &Database {
76        &self.db
77    }
78
79    /// Run a database command (`db.runCommand`), the escape hatch for what
80    /// plans cannot express.
81    ///
82    /// # Errors
83    /// [`BackendError`] when the server rejects the command.
84    pub async fn raw_command(&self, command: Document) -> Result<Document, OrmError> {
85        self.db
86            .run_command(command)
87            .await
88            .map_err(|e| map_error(&e).into())
89    }
90
91    /// Create a unique index on `column` of `collection`, so duplicate
92    /// values fail with [`BackendError::Constraint`] like a SQL unique
93    /// constraint. MongoDB enforces uniqueness only for `_id` otherwise.
94    ///
95    /// # Errors
96    /// [`BackendError`] when the index cannot be created.
97    pub async fn create_unique_index(
98        &self,
99        collection: &str,
100        column: &str,
101    ) -> Result<(), OrmError> {
102        let field = if column == self.keys.pk(collection) {
103            "_id"
104        } else {
105            column
106        };
107        let index = ::mongodb::IndexModel::builder()
108            .keys(doc! { field: 1_i32 })
109            .options(
110                ::mongodb::options::IndexOptions::builder()
111                    .unique(true)
112                    .build(),
113            )
114            .build();
115        self.db
116            .collection::<Document>(collection)
117            .create_index(index)
118            .await
119            .map_err(|e| OrmError::from(map_error(&e)))?;
120        Ok(())
121    }
122
123    async fn run_fetch(
124        &self,
125        plan: &QueryPlan,
126        session: Option<&mut ClientSession>,
127    ) -> Result<QueryResult, OrmError> {
128        let compiled = compile_query(plan, &self.keys)?;
129        Io {
130            db: &self.db,
131            session,
132        }
133        .query(&compiled)
134        .await
135    }
136
137    async fn run_execute(
138        &self,
139        plan: &WritePlan,
140        session: Option<&mut ClientSession>,
141    ) -> Result<ExecResult, OrmError> {
142        let compiled = compile_write(plan, &self.keys)?;
143        Io {
144            db: &self.db,
145            session,
146        }
147        .write(compiled)
148        .await
149    }
150}
151
152fn raw_unsupported() -> OrmError {
153    BackendCapabilityError::from_feature(BackendKind::MongoDb, Feature::RawSql).into()
154}
155
156#[async_trait]
157impl Executor for MongoBackend {
158    fn capabilities(&self) -> BackendCapabilities {
159        BackendCapabilities::mongodb()
160    }
161
162    async fn fetch(&self, plan: &QueryPlan) -> Result<QueryResult, OrmError> {
163        self.run_fetch(plan, None).await
164    }
165
166    async fn execute(&self, plan: &WritePlan) -> Result<ExecResult, OrmError> {
167        self.run_execute(plan, None).await
168    }
169
170    async fn fetch_raw(&self, _sql: &str, _params: Vec<Value>) -> Result<QueryResult, OrmError> {
171        Err(raw_unsupported())
172    }
173
174    async fn execute_raw(&self, _sql: &str, _params: Vec<Value>) -> Result<u64, OrmError> {
175        Err(raw_unsupported())
176    }
177
178    async fn execute_script(&self, _sql: &str) -> Result<(), OrmError> {
179        Err(raw_unsupported())
180    }
181}
182
183#[async_trait]
184impl Backend for MongoBackend {
185    async fn begin(
186        &self,
187        isolation: Option<IsolationLevel>,
188    ) -> Result<Box<dyn Transaction>, OrmError> {
189        if let Some(level) = isolation {
190            self.capabilities().require(Feature::Isolation(level))?;
191        }
192        let mut session = self
193            .client
194            .start_session()
195            .await
196            .map_err(|e| OrmError::from(map_error(&e)))?;
197        session
198            .start_transaction()
199            .await
200            .map_err(|e| OrmError::from(map_error(&e)))?;
201        Ok(Box::new(MongoTransaction {
202            backend: self.clone(),
203            session: Mutex::new(Some(session)),
204        }))
205    }
206}
207
208/// An open MongoDB transaction. Dropped without commit, the driver aborts it.
209struct MongoTransaction {
210    backend: MongoBackend,
211    session: Mutex<Option<ClientSession>>,
212}
213
214impl MongoTransaction {
215    async fn take(&self) -> Result<ClientSession, QueryError> {
216        self.session
217            .lock()
218            .await
219            .take()
220            .ok_or(QueryError::TransactionClosed)
221    }
222}
223
224#[async_trait]
225impl Executor for MongoTransaction {
226    fn capabilities(&self) -> BackendCapabilities {
227        BackendCapabilities::mongodb()
228    }
229
230    async fn fetch(&self, plan: &QueryPlan) -> Result<QueryResult, OrmError> {
231        // Compile before locking: unsupported plans fail without I/O.
232        let compiled = compile_query(plan, &self.backend.keys)?;
233        let mut guard = self.session.lock().await;
234        let session = guard.as_mut().ok_or(QueryError::TransactionClosed)?;
235        Io {
236            db: &self.backend.db,
237            session: Some(session),
238        }
239        .query(&compiled)
240        .await
241    }
242
243    async fn execute(&self, plan: &WritePlan) -> Result<ExecResult, OrmError> {
244        let compiled = compile_write(plan, &self.backend.keys)?;
245        let mut guard = self.session.lock().await;
246        let session = guard.as_mut().ok_or(QueryError::TransactionClosed)?;
247        Io {
248            db: &self.backend.db,
249            session: Some(session),
250        }
251        .write(compiled)
252        .await
253    }
254
255    async fn fetch_raw(&self, _sql: &str, _params: Vec<Value>) -> Result<QueryResult, OrmError> {
256        Err(raw_unsupported())
257    }
258
259    async fn execute_raw(&self, _sql: &str, _params: Vec<Value>) -> Result<u64, OrmError> {
260        Err(raw_unsupported())
261    }
262
263    async fn execute_script(&self, _sql: &str) -> Result<(), OrmError> {
264        Err(raw_unsupported())
265    }
266}
267
268#[async_trait]
269impl Transaction for MongoTransaction {
270    async fn commit(&self) -> Result<(), OrmError> {
271        let mut session = self.take().await?;
272        session
273            .commit_transaction()
274            .await
275            .map_err(|e| map_error(&e).into())
276    }
277
278    async fn rollback(&self) -> Result<(), OrmError> {
279        let mut session = self.take().await?;
280        session
281            .abort_transaction()
282            .await
283            .map_err(|e| map_error(&e).into())
284    }
285}
286
287/// Classify a driver error. Duplicate-key and validation failures are
288/// constraint violations; connection-level failures are connection errors.
289fn map_error(e: &DriverError) -> BackendError {
290    let message = e.to_string();
291    if message.contains("E11000")
292        || message.contains("code: 11000")
293        || message.contains("code: 121")
294    {
295        return BackendError::Constraint(message);
296    }
297    match e.kind.as_ref() {
298        ErrorKind::ServerSelection { .. }
299        | ErrorKind::Io(_)
300        | ErrorKind::DnsResolve { .. }
301        | ErrorKind::Authentication { .. } => BackendError::Connection(message),
302        _ => BackendError::Database(message),
303    }
304}
305
306fn db_err(e: &DriverError) -> OrmError {
307    map_error(e).into()
308}
309
310/// Rows of `doc`: the requested `columns`, or every field when empty
311/// (`_id` renamed to `pk`).
312fn doc_to_row(doc: Document, columns: &[String], pk: &str) -> Result<Row, QueryError> {
313    if columns.is_empty() {
314        let has_pk_field = doc.contains_key(pk);
315        let mut out = Vec::with_capacity(doc.len());
316        for (key, value) in doc {
317            let name = if key == "_id" {
318                if has_pk_field {
319                    continue;
320                }
321                pk.to_owned()
322            } else {
323                key
324            };
325            let value = from_bson(value, &name)?;
326            out.push((name, value));
327        }
328        return Ok(Row::new(out));
329    }
330    let mut out = Vec::with_capacity(columns.len());
331    for column in columns {
332        let key = if column == pk { "_id" } else { column.as_str() };
333        let value = match doc.get(key) {
334            Some(bson) => from_bson(bson.clone(), column)?,
335            None => Value::Null,
336        };
337        out.push((column.clone(), value));
338    }
339    Ok(Row::new(out))
340}
341
342/// Database access for one statement, on a session or the implicit one.
343struct Io<'a> {
344    db: &'a Database,
345    session: Option<&'a mut ClientSession>,
346}
347
348impl Io<'_> {
349    fn coll(&self, name: &str) -> Collection<Document> {
350        self.db.collection(name)
351    }
352
353    fn session(&mut self) -> Option<&mut ClientSession> {
354        self.session.as_deref_mut()
355    }
356
357    async fn aggregate(
358        &mut self,
359        collection: &str,
360        pipeline: Vec<Document>,
361    ) -> Result<Vec<Document>, OrmError> {
362        let coll = self.coll(collection);
363        let mut out = Vec::new();
364        match self.session() {
365            Some(session) => {
366                let mut cursor = coll
367                    .aggregate(pipeline)
368                    .session(&mut *session)
369                    .await
370                    .map_err(|e| db_err(&e))?;
371                while cursor.advance(session).await.map_err(|e| db_err(&e))? {
372                    out.push(cursor.deserialize_current().map_err(|e| db_err(&e))?);
373                }
374            }
375            None => {
376                let cursor = coll.aggregate(pipeline).await.map_err(|e| db_err(&e))?;
377                out = drain(cursor).await?;
378            }
379        }
380        Ok(out)
381    }
382
383    async fn find(
384        &mut self,
385        collection: &str,
386        filter: Document,
387        projection: Option<Document>,
388    ) -> Result<Vec<Document>, OrmError> {
389        let coll = self.coll(collection);
390        let mut out = Vec::new();
391        match self.session() {
392            Some(session) => {
393                let mut cursor = coll
394                    .find(filter)
395                    .optional(projection, |a, p| a.projection(p))
396                    .session(&mut *session)
397                    .await
398                    .map_err(|e| db_err(&e))?;
399                while cursor.advance(session).await.map_err(|e| db_err(&e))? {
400                    out.push(cursor.deserialize_current().map_err(|e| db_err(&e))?);
401                }
402            }
403            None => {
404                let cursor = coll
405                    .find(filter)
406                    .optional(projection, |a, p| a.projection(p))
407                    .await
408                    .map_err(|e| db_err(&e))?;
409                out = drain(cursor).await?;
410            }
411        }
412        Ok(out)
413    }
414
415    async fn insert_many(&mut self, collection: &str, docs: Vec<Document>) -> Result<(), OrmError> {
416        let coll = self.coll(collection);
417        match self.session() {
418            Some(session) => coll.insert_many(docs).session(session).await,
419            None => coll.insert_many(docs).await,
420        }
421        .map(|_| ())
422        .map_err(|e| db_err(&e))
423    }
424
425    async fn update_many(
426        &mut self,
427        collection: &str,
428        filter: Document,
429        update: UpdateSpec,
430    ) -> Result<u64, OrmError> {
431        let coll = self.coll(collection);
432        let result = match (update, self.session()) {
433            (UpdateSpec::Set(doc), Some(s)) => coll.update_many(filter, doc).session(s).await,
434            (UpdateSpec::Set(doc), None) => coll.update_many(filter, doc).await,
435            (UpdateSpec::Pipeline(p), Some(s)) => coll.update_many(filter, p).session(s).await,
436            (UpdateSpec::Pipeline(p), None) => coll.update_many(filter, p).await,
437        };
438        result.map(|r| r.matched_count).map_err(|e| db_err(&e))
439    }
440
441    async fn delete_many(&mut self, collection: &str, filter: Document) -> Result<u64, OrmError> {
442        let coll = self.coll(collection);
443        match self.session() {
444            Some(session) => coll.delete_many(filter).session(session).await,
445            None => coll.delete_many(filter).await,
446        }
447        .map(|r| r.deleted_count)
448        .map_err(|e| db_err(&e))
449    }
450
451    /// Reserve `n` consecutive integer keys for `collection`; returns the
452    /// first. Runs outside the transaction (like a sequence) so concurrent
453    /// transactions never conflict on the counter; a rolled-back
454    /// transaction leaves a gap.
455    async fn reserve_keys(&self, collection: &str, n: usize) -> Result<i64, OrmError> {
456        let n = i64::try_from(n).map_err(|_| QueryError::InvalidPlan("too many rows".into()))?;
457        let counters: Collection<Document> = self.db.collection(COUNTERS);
458        let after = counters
459            .find_one_and_update(doc! { "_id": collection }, doc! { "$inc": { "seq": n } })
460            .upsert(true)
461            .return_document(ReturnDocument::After)
462            .await
463            .map_err(|e| db_err(&e))?
464            .and_then(|d| d.get("seq").and_then(as_i64))
465            .ok_or_else(|| BackendError::Database("key counter has no `seq`".into()))?;
466        Ok(after - n + 1)
467    }
468
469    async fn query(&mut self, compiled: &CompiledQuery) -> Result<QueryResult, OrmError> {
470        let docs = self
471            .aggregate(&compiled.collection, compiled.pipeline.clone())
472            .await?;
473        if docs.is_empty()
474            && let Some(values) = &compiled.empty_row
475        {
476            let row = compiled
477                .columns
478                .iter()
479                .cloned()
480                .zip(values.iter().cloned())
481                .collect();
482            return Ok(QueryResult {
483                rows: vec![Row::new(row)],
484            });
485        }
486        let rows = docs
487            .into_iter()
488            .map(|d| doc_to_row(d, &compiled.columns, &compiled.pk))
489            .collect::<Result<_, _>>()?;
490        Ok(QueryResult { rows })
491    }
492
493    async fn write(&mut self, compiled: CompiledWrite) -> Result<ExecResult, OrmError> {
494        match compiled {
495            CompiledWrite::Insert(p) => self.insert(p).await,
496            CompiledWrite::Update(p) => self.update(p).await,
497            CompiledWrite::Delete(p) => self.delete(p).await,
498        }
499    }
500
501    async fn insert(&mut self, plan: CompiledInsert) -> Result<ExecResult, OrmError> {
502        let CompiledInsert {
503            collection,
504            pk,
505            docs,
506            returning,
507        } = plan;
508        if docs.is_empty() {
509            return Ok(ExecResult::default());
510        }
511        let missing = docs.iter().filter(|d| !d.contains_key("_id")).count();
512        let mut next = if missing > 0 {
513            self.reserve_keys(&collection, missing).await?
514        } else {
515            0
516        };
517        let docs: Vec<Document> = docs
518            .into_iter()
519            .map(|doc| {
520                if doc.contains_key("_id") {
521                    return doc;
522                }
523                // `_id` first, as MongoDB stores it.
524                let mut with_id = doc! { "_id": next };
525                next += 1;
526                with_id.extend(doc);
527                with_id
528            })
529            .collect();
530        self.insert_many(&collection, docs.clone()).await?;
531        let mut result = ExecResult {
532            rows_affected: docs.len() as u64,
533            returning: Vec::new(),
534        };
535        if !returning.is_empty() {
536            // Echo what was stored (values as BSON holds them).
537            result.returning = docs
538                .into_iter()
539                .map(|d| doc_to_row(d, &returning, &pk))
540                .collect::<Result<_, _>>()?;
541        }
542        Ok(result)
543    }
544
545    async fn update(&mut self, plan: CompiledUpdate) -> Result<ExecResult, OrmError> {
546        let CompiledUpdate {
547            collection,
548            pk,
549            filter,
550            update,
551            returning,
552        } = plan;
553        if returning.is_empty() {
554            let matched = self.update_many(&collection, filter, update).await?;
555            return Ok(ExecResult {
556                rows_affected: matched,
557                returning: Vec::new(),
558            });
559        }
560        // Pin the rows first, so the read-back is not re-matched against
561        // the updated values.
562        let ids = self.matching_ids(&collection, filter).await?;
563        if ids.is_empty() {
564            return Ok(ExecResult::default());
565        }
566        let by_id = doc! { "_id": { "$in": ids } };
567        let matched = self.update_many(&collection, by_id.clone(), update).await?;
568        let rows = self
569            .find(&collection, by_id, None)
570            .await?
571            .into_iter()
572            .map(|d| doc_to_row(d, &returning, &pk))
573            .collect::<Result<_, _>>()?;
574        Ok(ExecResult {
575            rows_affected: matched,
576            returning: rows,
577        })
578    }
579
580    async fn delete(&mut self, plan: CompiledDelete) -> Result<ExecResult, OrmError> {
581        let CompiledDelete {
582            collection,
583            pk,
584            filter,
585            returning,
586        } = plan;
587        if returning.is_empty() {
588            let deleted = self.delete_many(&collection, filter).await?;
589            return Ok(ExecResult {
590                rows_affected: deleted,
591                returning: Vec::new(),
592            });
593        }
594        let docs = self.find(&collection, filter, None).await?;
595        let ids: Vec<Bson> = docs.iter().filter_map(|d| d.get("_id").cloned()).collect();
596        if ids.is_empty() {
597            return Ok(ExecResult::default());
598        }
599        let deleted = self
600            .delete_many(&collection, doc! { "_id": { "$in": ids } })
601            .await?;
602        let rows = docs
603            .into_iter()
604            .map(|d| doc_to_row(d, &returning, &pk))
605            .collect::<Result<_, _>>()?;
606        Ok(ExecResult {
607            rows_affected: deleted,
608            returning: rows,
609        })
610    }
611
612    async fn matching_ids(
613        &mut self,
614        collection: &str,
615        filter: Document,
616    ) -> Result<Vec<Bson>, OrmError> {
617        Ok(self
618            .find(collection, filter, Some(doc! { "_id": 1_i32 }))
619            .await?
620            .into_iter()
621            .filter_map(|d| d.get("_id").cloned())
622            .collect())
623    }
624}
625
626fn as_i64(value: &Bson) -> Option<i64> {
627    match value {
628        Bson::Int32(i) => Some(i64::from(*i)),
629        Bson::Int64(i) => Some(*i),
630        _ => None,
631    }
632}
633
634async fn drain(mut cursor: Cursor<Document>) -> Result<Vec<Document>, OrmError> {
635    let mut out = Vec::new();
636    while cursor.advance().await.map_err(|e| db_err(&e))? {
637        out.push(cursor.deserialize_current().map_err(|e| db_err(&e))?);
638    }
639    Ok(out)
640}