Skip to main content

coven_database/
write.rs

1//! Transaction-scoped app SQL. The connection remains inside its owner.
2
3use rusqlite::{Params, Row};
4
5/// App SQL inside one local write, and coven's steps beside it (§4).
6/// Returning an error rolls back every statement and step. This borrowed
7/// context cannot change the transaction or access coven's tables.
8pub struct SqlContext<'connection, 'write> {
9    files: &'write crate::file_write::FileWrite<'connection>,
10}
11
12impl<'connection, 'write> SqlContext<'connection, 'write> {
13    pub(crate) fn new(files: &'write crate::file_write::FileWrite<'connection>) -> Self {
14        Self { files }
15    }
16
17    /// Record a move of these files of this device, on it only, to the
18    /// cloud (§17.3). It survives restarts; once every file is stored, one
19    /// write switches them all to the cloud, changing no app row.
20    pub fn move_to_cloud(
21        &self,
22        files: Vec<crate::MoveFile>,
23    ) -> Result<crate::MoveId, crate::DbError> {
24        self.files.step(|| self.files.move_to_cloud(files))
25    }
26
27    /// Cancel a recorded move: every file stays on this device only, and
28    /// the move's record ends (§17.3).
29    pub fn cancel_move(&self, id: crate::MoveId) -> Result<(), crate::DbError> {
30        self.files.step(|| self.files.cancel_move(id))
31    }
32
33    /// Remove these files' copies from this device in this write: every
34    /// cache entry of their stored bytes, pinned or not, the partial
35    /// download a pin left, and the pin requests coven keeps for them
36    /// (§17.8). Nothing else is evicted. A file this device has no record
37    /// of, or one on one device only, has nothing to remove. The bytes go
38    /// once the write has committed.
39    pub fn unpin(
40        &self,
41        files: &[coven_foundation::id_source::FileId],
42    ) -> Result<(), crate::DbError> {
43        self.files.step(|| self.files.unpin(files))
44    }
45
46    /// Remove a stored file's cached copies, pinned or not, in this write,
47    /// with no storage operation (§17.8). A file this device has no record
48    /// of, or one on one device only, has none. The bytes go once the write
49    /// has committed.
50    pub fn evict_file(
51        &self,
52        file: coven_foundation::id_source::FileId,
53    ) -> Result<(), crate::DbError> {
54        self.files.step(|| self.files.evict(file))
55    }
56
57    /// Clear a declared namespace's cache in this write (§17.8): every
58    /// unpinned file and chunk goes. Pinned copies, the partial downloads
59    /// pins left and other namespaces stay. An undeclared namespace fails
60    /// with `UndeclaredNamespace`. The bytes go once the write has
61    /// committed.
62    pub fn clear_cache(&self, namespace: &str) -> Result<(), crate::DbError> {
63        self.files.step(|| self.files.clear_cache(namespace))
64    }
65
66    /// Set a declared namespace's cache budget and evict its unpinned
67    /// entries until they are within it, in this write (§17.8). Pinned
68    /// bytes don't count toward the budget. An undeclared namespace
69    /// fails with `UndeclaredNamespace`. The evicted bytes go once the write
70    /// has committed.
71    pub fn set_cache_budget(&self, namespace: &str, bytes: u64) -> Result<(), crate::DbError> {
72        self.files.step(|| self.files.set_cache_budget(namespace, bytes))
73    }
74
75    /// Dismiss lost values the app has dealt with, in this write (§8.4): its
76    /// record carries the acknowledgements and the removed rows' deletes,
77    /// with their cascades, after the app's own changes, so every device
78    /// drops them. A removed row is deleted for good. A value no longer lost
79    /// as it was, such as a row restored since, is left alone. The write
80    /// fails with `DismissedRowChanged` if it also changes a row its
81    /// dismissals delete.
82    pub fn dismiss_lost_values(&self, values: &[crate::LostValue]) -> Result<(), crate::DbError> {
83        self.files.step(|| {
84            self.files.dismiss(values);
85            Ok(())
86        })
87    }
88
89    /// Pause trailing uploads, or resume them, in this write (§17.9). A
90    /// paused upload keeps its place and its provider session.
91    pub fn set_uploads_paused(&self, paused: bool) -> Result<(), crate::DbError> {
92        self.files.step(|| self.files.set_uploads_paused(paused))
93    }
94
95    /// Move files only on unmetered networks, or on any, in this write
96    /// (§17.9).
97    pub fn set_unmetered_only(&self, unmetered_only: bool) -> Result<(), crate::DbError> {
98        self.files.step(|| self.files.set_unmetered_only(unmetered_only))
99    }
100
101    /// Run `effect` once this write has committed, as the in-memory side of
102    /// one of coven's steps: nothing runs for a write that rolls back. The
103    /// write returns only once every effect has run.
104    pub fn after_commit(&self, effect: impl FnOnce() + 'static) {
105        self.files.after_commit(Box::new(effect));
106    }
107
108    /// Put files `stage_files` staged into this write, and return what its
109    /// batch returned, such as their ids, for this write's rows to name
110    /// (§17.2). Files staged by another open store, or by this one before it
111    /// closed, fail with `PreparedElsewhere`. A file no row names fails the
112    /// write with `FileUnreferenced`.
113    pub fn put_staged<T>(&self, staged: crate::StagedFiles<T>) -> Result<T, crate::DbError> {
114        self.files.step(|| self.files.put_staged(staged))
115    }
116
117    /// Stage a user's original, read whole once already, as a new file in a
118    /// user namespace, and return its id for a row to reference (§17.1,
119    /// §17.3). Coven records the original's path, size, modification time and
120    /// the hashes its read found; it never reads it here. An undeclared
121    /// namespace fails with `UndeclaredNamespace`, an app one with
122    /// `NamespaceKind`.
123    pub fn put_user_file(
124        &self,
125        namespace: &str,
126        original: crate::PreparedUserFile,
127        place: crate::Place,
128    ) -> Result<coven_foundation::id_source::FileId, crate::DbError> {
129        self.files.step(|| self.files.put_user_file(namespace, original, place))
130    }
131
132    /// Stage a new file over an existing file's bytes, for a row in any
133    /// audience, and return its id (§17.2). The write fails with
134    /// `FileAbsent` if this device has no record of `from`, and with
135    /// `NotThisDevice` if `from` is on another device only.
136    pub fn existing_file(
137        &self,
138        from: coven_foundation::id_source::FileId,
139    ) -> coven_foundation::id_source::FileId {
140        self.files.existing_file(from)
141    }
142
143    /// Pin a prepared pin's files in this write (§17.8): each whole copy
144    /// becomes the file's pinned cache entry, and each copy already kept
145    /// stays. Files prepared in another open store, or in this one before
146    /// it closed, fail with `PreparedElsewhere`; a file whose record left
147    /// its stored bytes since with `NoLongerStored`; and a kept copy evicted
148    /// or unpinned since with `PinChanged`.
149    pub fn pin(&self, prepared: crate::PreparedPin) -> Result<(), crate::DbError> {
150        self.files.step(|| self.files.pin(prepared))
151    }
152
153    /// End the pin request `id`, in this write, returning whether it still
154    /// waited (§17.8).
155    pub(crate) fn end_pin_request(&self, id: i64) -> Result<bool, crate::DbError> {
156        self.files.step(|| self.files.end_pin_request(id))
157    }
158
159    /// Switch a prepared call's files to device-only on this device, each to
160    /// its copy, which becomes the original this device records, in this
161    /// write (§17.3). Each must still be one this device may make
162    /// device-only, and still stored where its copy came from
163    /// (`NotInCloud`); copies prepared in another open store, or in this
164    /// one before it closed, fail with `PreparedElsewhere`. A write that
165    /// also removes such a file's last reference fails with
166    /// `DeviceOnlyUnreferenced`.
167    pub fn make_device_only(
168        &self,
169        prepared: crate::PreparedDeviceOnly,
170    ) -> Result<(), crate::DbError> {
171        self.files.step(|| self.files.make_prepared_device_only(prepared))
172    }
173
174    /// Acknowledge a finished operation the app's writes journaled, in this
175    /// write: it leaves `coven_operations` (§11.4). One still running fails
176    /// with `OperationRunning`; one not listed there with
177    /// `OperationUnknown`.
178    pub fn acknowledge_operation(
179        &self,
180        operation: crate::OperationId,
181    ) -> Result<(), crate::DbError> {
182        self.journal_step(|journal| journal.acknowledge(operation))
183    }
184
185    /// End the new person's name the store a join published keeps, in this
186    /// write, which records the app's profile of that person, or its request
187    /// for one (§13.2). With no name kept, it changes nothing.
188    pub fn acknowledge_joined_invitee(&self) -> Result<(), crate::DbError> {
189        self.files.step(|| self.files.acknowledge_joined_invitee())
190    }
191
192    /// Run one of sync's steps on the journal in this write (§4, §11). A
193    /// failed step fails the write, even if the closure goes on and returns
194    /// success, since it may have made only part of its change.
195    pub fn journal_step<T, E>(
196        &self,
197        step: impl FnOnce(&crate::Journal<'_>) -> Result<T, E>,
198    ) -> Result<T, E> {
199        self.files.step(|| step(&crate::Journal::new(self.files)))
200    }
201
202    /// Execute one app statement with parameters.
203    pub fn execute<P: Params>(&self, sql: &str, params: P) -> rusqlite::Result<usize> {
204        self.files.execute(sql, params)
205    }
206
207    /// Execute app statements within this write's transaction.
208    pub fn execute_batch(&self, sql: &str) -> rusqlite::Result<()> {
209        self.files.execute_batch(sql)
210    }
211
212    /// Map one app row, including changes already made by this write.
213    pub fn query_row<T, P, F>(&self, sql: &str, params: P, map: F) -> rusqlite::Result<T>
214    where
215        P: Params,
216        F: FnOnce(&Row<'_>) -> rusqlite::Result<T>,
217    {
218        self.files.query_row(sql, params, map)
219    }
220
221    /// Map all app rows produced by a query within this write.
222    pub fn query<T, P, F>(&self, sql: &str, params: P, map: F) -> rusqlite::Result<Vec<T>>
223    where
224        P: Params,
225        F: FnMut(&Row<'_>) -> rusqlite::Result<T>,
226    {
227        self.files.query(sql, params, map)
228    }
229}
230
231/// App SQL inside one bulk write (§4): it changes device-local tables
232/// alone, and may read any table app SQL reads. Returning an error rolls back
233/// every statement. This borrowed context cannot change the transaction,
234/// access coven's tables or write a synced one.
235pub struct BulkSqlContext<'connection> {
236    database: &'connection crate::sqlite::DatabaseConnection,
237}
238
239impl<'connection> BulkSqlContext<'connection> {
240    pub(crate) fn new(database: &'connection crate::sqlite::DatabaseConnection) -> Self {
241        Self { database }
242    }
243
244    /// Execute one app statement with parameters.
245    pub fn execute<P: Params>(&self, sql: &str, params: P) -> rusqlite::Result<usize> {
246        self.database.app_execute(sql, params)
247    }
248
249    /// Execute app statements within this write's transaction.
250    pub fn execute_batch(&self, sql: &str) -> rusqlite::Result<()> {
251        self.database.app_batch(sql)
252    }
253
254    /// Map one app row, including changes already made by this write.
255    pub fn query_row<T, P, F>(&self, sql: &str, params: P, map: F) -> rusqlite::Result<T>
256    where
257        P: Params,
258        F: FnOnce(&Row<'_>) -> rusqlite::Result<T>,
259    {
260        self.database.app_query_row(sql, params, map)
261    }
262
263    /// Map all app rows produced by a query within this write.
264    pub fn query<T, P, F>(&self, sql: &str, params: P, map: F) -> rusqlite::Result<Vec<T>>
265    where
266        P: Params,
267        F: FnMut(&Row<'_>) -> rusqlite::Result<T>,
268    {
269        self.database.app_query(sql, params, map)
270    }
271}
272
273#[cfg(test)]
274#[path = "write_tests.rs"]
275pub(crate) mod tests;