couchbase_lite/
transaction.rs1use 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 #[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 #[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}