couchbase_lite/
transaction.rs

1use crate::{
2    document::{C4DocumentOwner, Document},
3    error::{c4error_init, Error, Result},
4    ffi::{
5        c4db_beginTransaction, c4db_endTransaction, c4db_getSharedFleeceEncoder, c4db_purgeDoc,
6        c4doc_put, c4doc_update, C4DocPutRequest, C4ErrorCode, C4ErrorDomain, C4RevisionFlags,
7        FLSlice, FLSliceResult,
8    },
9    Database,
10};
11use log::error;
12use serde_fleece::FlEncoderSession;
13use std::{
14    ops::Deref,
15    ptr::{self, NonNull},
16};
17
18pub struct Transaction<'db> {
19    db: &'db Database,
20    finished: bool,
21}
22
23impl Transaction<'_> {
24    pub(crate) fn new<'a>(db: &'a mut Database) -> Result<Transaction<'a>> {
25        let mut c4err = c4error_init();
26        if unsafe { c4db_beginTransaction(db.inner.0.as_ptr(), &mut c4err) } {
27            Ok(Transaction {
28                db,
29                finished: false,
30            })
31        } else {
32            Err(c4err.into())
33        }
34    }
35
36    #[inline]
37    pub fn commit(mut self) -> Result<()> {
38        self.end_transaction(true)
39    }
40
41    fn end_transaction(&mut self, commit: bool) -> Result<()> {
42        self.finished = true;
43        let mut c4err = c4error_init();
44        if unsafe { c4db_endTransaction(self.db.inner.0.as_ptr(), commit, &mut c4err) } {
45            Ok(())
46        } else {
47            Err(c4err.into())
48        }
49    }
50
51    #[inline]
52    pub fn save(&mut self, doc: &mut Document) -> Result<()> {
53        self.main_save(doc, false)
54    }
55
56    #[inline]
57    pub fn delete(&mut self, doc: &mut Document) -> Result<()> {
58        self.main_save(doc, true)
59    }
60
61    /// Removes all trace of a document and its revisions from the database.
62    #[inline]
63    pub fn purge_by_id(&mut self, doc_id: &str) -> Result<()> {
64        let mut c4err = c4error_init();
65        if unsafe { c4db_purgeDoc(self.db.inner.0.as_ptr(), doc_id.into(), &mut c4err) } {
66            Ok(())
67        } else {
68            Err(c4err.into())
69        }
70    }
71
72    /// Get shared "fleece" encoder, `&mut self` to make possible
73    /// exists only one session
74    #[inline]
75    pub fn shared_encoder_session(&mut self) -> Result<FlEncoderSession> {
76        let enc = unsafe { c4db_getSharedFleeceEncoder(self.db.inner.0.as_ptr()) };
77        NonNull::new(enc)
78            .ok_or_else(|| {
79                Error::LogicError("c4db_getSharedFleeceEncoder return null.into()".into())
80            })
81            .map(FlEncoderSession::new)
82    }
83
84    fn main_save(&mut self, doc: &mut Document, deletion: bool) -> Result<()> {
85        let mut retrying = false;
86        let mut saving_doc = None;
87        loop {
88            if !retrying {
89                if deletion && !doc.exists() {
90                    return Err(Error::LogicError(
91                        format!(
92                            "Cannot delete a document that has not yet been saved, doc_id {}",
93                            doc.id()
94                        )
95                        .into(),
96                    ));
97                }
98                saving_doc = doc.inner.take();
99            }
100            let (rev_flags, body) = if !deletion {
101                (
102                    C4RevisionFlags(0),
103                    doc.unsaved_body
104                        .as_ref()
105                        .map(FLSliceResult::as_fl_slice)
106                        .unwrap_or_default(),
107                )
108            } else {
109                (C4RevisionFlags::kRevDeleted, FLSlice::default())
110            };
111            retrying = false;
112            let mut c4err = c4error_init();
113            let new_doc = if let Some(doc) = saving_doc.as_mut() {
114                unsafe { c4doc_update(doc.0.as_ptr(), body, rev_flags, &mut c4err) }
115            } else {
116                let rq = C4DocPutRequest {
117                    body,
118                    docID: doc.id().into(),
119                    revFlags: rev_flags,
120                    existingRevision: false,
121                    allowConflict: false,
122                    history: ptr::null(),
123                    historyCount: 0,
124                    save: true,
125                    maxRevTreeDepth: 0,
126                    remoteDBID: 0,
127                    allocedBody: FLSliceResult::default(),
128                    deltaCB: None,
129                    deltaCBContext: ptr::null_mut(),
130                    deltaSourceRevID: FLSlice::default(),
131                };
132                unsafe { c4doc_put(self.db.inner.0.as_ptr(), &rq, ptr::null_mut(), &mut c4err) }
133            };
134            if new_doc.is_null()
135                && !(c4err.domain == C4ErrorDomain::LiteCoreDomain
136                    && c4err.code == C4ErrorCode::kC4ErrorConflict.0)
137            {
138                return Err(c4err.into());
139            }
140            if let Some(new_doc) = NonNull::new(new_doc) {
141                doc.replace_c4doc(Some(C4DocumentOwner(new_doc)));
142            } else {
143                saving_doc = match self.db.internal_get(doc.id(), true) {
144                    Ok(x) => Some(x),
145                    Err(Error::C4Error(c4err))
146                        if deletion
147                            && c4err.domain == C4ErrorDomain::LiteCoreDomain
148                            && c4err.code == C4ErrorCode::kC4ErrorNotFound.0 =>
149                    {
150                        return Ok(());
151                    }
152                    Err(err) => return Err(err),
153                };
154                retrying = true;
155            }
156
157            if !retrying {
158                break;
159            }
160        }
161        Ok(())
162    }
163}
164
165impl Deref for Transaction<'_> {
166    type Target = Database;
167    #[inline]
168    fn deref(&self) -> &Database {
169        self.db
170    }
171}
172
173impl Drop for Transaction<'_> {
174    #[inline]
175    fn drop(&mut self) {
176        if !self.finished {
177            if let Err(err) = self.end_transaction(false) {
178                error!("end_transaction failed: {err}");
179            }
180        }
181    }
182}