coven/handle_writes.rs
1//! Writes: the app's SQL and coven's steps as one write, and the app bytes
2//! staged before one (§4, §17.2, E3).
3use crate::coven_write::CovenWrite;
4use crate::*;
5
6impl CovenHandle {
7 /// Runs one write: the app's SQL and coven's steps, in one transaction.
8 /// It fails with the closure's own error type, which keeps a failed
9 /// rollback or byte removal beside the write's failure
10 /// (`WriteFailure`).
11 pub async fn write<F, R, E>(&self, write: F) -> Result<R, E>
12 where
13 F: FnOnce(CovenWrite<'_>) -> Result<R, E> + Send + 'static,
14 R: Send + 'static,
15 E: WriteFailure + Send + 'static,
16 {
17 let (steps, files) = (self.steps.clone(), self.files.clone());
18 self.database.write_as(move |sql| write(CovenWrite::new(sql, &steps, &files))).await
19 }
20
21 /// Runs one bulk write: SQL that changes device-local tables alone, run
22 /// as plain SQLite with no record of each row change, so emptying a table
23 /// takes one step and filling one runs at SQLite's own speed (§4). A
24 /// statement that would write a synced table, itself or through a
25 /// trigger or foreign-key action, fails the write with
26 /// `SyncedTableInBulkWrite`. Triggers still run. After commit, every live
27 /// query that read a table the write's statements may change runs again,
28 /// whatever rows it read; a small change that should rerun only the live
29 /// queries reading its rows belongs in `write`.
30 pub async fn write_bulk<F, R, E>(&self, sql: F) -> Result<R, E>
31 where
32 F: FnOnce(BulkSqlContext<'_>) -> Result<R, E> + Send + 'static,
33 R: Send + 'static,
34 E: WriteFailure + Send + 'static,
35 {
36 self.database.write_bulk(sql).await
37 }
38
39 /// Stages app bytes for a write that puts them in rows (§17.2): `stage`
40 /// stages each file and returns what that write needs, such as the ids
41 /// staging gave them, and the write takes it with
42 /// `CovenWrite::put_staged`. The bytes are streamed to coven's own copies,
43 /// synced and flushed before this returns; close waits for staging and
44 /// its cleanup, including cancellation. The staged files hold nothing
45 /// close waits for: dropped uncommitted, or after the store closes, they
46 /// are removed by the next write or open. An undeclared namespace fails
47 /// with `UndeclaredNamespace`, a user one with `NamespaceKind`.
48 pub async fn stage_files<F, T, E>(&self, stage: F) -> Result<StagedFiles<T>, E>
49 where
50 F: FnOnce(&mut WriteBatch<'_>) -> Result<T, E> + Send + 'static,
51 T: Send + 'static,
52 E: WriteFailure + Send + 'static,
53 {
54 self.database.stage_files(stage).await
55 }
56}
57
58#[cfg(test)]
59impl CovenHandle {
60 /// Stage app bytes, then run a write that puts them in its rows, as an
61 /// app does with `stage_files` and `put_staged`, for tests.
62 pub(crate) async fn test_staged_write<F, S, T, R, E>(&self, stage: F, write: S) -> Result<R, E>
63 where
64 F: FnOnce(&mut WriteBatch<'_>) -> Result<T, E> + Send + 'static,
65 S: FnOnce(CovenWrite<'_>, T) -> Result<R, E> + Send + 'static,
66 T: Send + 'static,
67 R: Send + 'static,
68 E: WriteFailure + Send + 'static,
69 {
70 let staged = self.stage_files(stage).await?;
71 self.write(move |sql| {
72 let value = sql.put_staged(staged)?;
73 write(sql, value)
74 })
75 .await
76 }
77}
78
79#[cfg(test)]
80#[path = "handle_writes_tests.rs"]
81mod tests;