Skip to main content

coven_database/
read.rs

1//! Borrowed reads and their connection-scoped SQL capability.
2
3use crate::{database, sqlite::DatabaseConnection, Database, DbError, LostValue, Mode, Writer};
4use coven_foundation::task::CarriesBug;
5use rusqlite::{Params, Row};
6use std::{
7    any::Any,
8    future::Future,
9    pin::Pin,
10    task::{Context, Poll},
11};
12
13/// SQL access to one read-only snapshot. The connection and internal tables
14/// remain private; even SQL with RETURNING cannot write through this context.
15pub struct SqlReadContext<'connection> {
16    database: &'connection DatabaseConnection,
17}
18
19impl<'connection> SqlReadContext<'connection> {
20    pub(crate) fn new(database: &'connection DatabaseConnection) -> Self {
21        Self { database }
22    }
23
24    /// Map the first result row of an ordinary SQL read.
25    pub fn query_row<T, P, F>(&self, sql: &str, params: P, map: F) -> rusqlite::Result<T>
26    where
27        P: Params,
28        F: FnOnce(&Row<'_>) -> rusqlite::Result<T>,
29    {
30        self.database.app_query_row(sql, params, map)
31    }
32
33    /// Map all result rows of an ordinary SQL read.
34    pub fn query<T, P, F>(&self, sql: &str, params: P, map: F) -> rusqlite::Result<Vec<T>>
35    where
36        P: Params,
37        F: FnMut(&Row<'_>) -> rusqlite::Result<T>,
38    {
39        self.database.app_query(sql, params, map)
40    }
41
42    /// Where this device keeps the bytes of `file`, while its record says
43    /// it is on this device only.
44    pub(crate) fn device_only_source(
45        &self,
46        device: coven_foundation::id_source::DeviceId,
47        file: coven_foundation::id_source::FileId,
48    ) -> Result<Option<crate::file_ref::LocalSource>, crate::DbError> {
49        crate::file_ref::device_only_source(self.database, device, file)
50    }
51
52    pub(crate) fn file_ref(
53        &self,
54        file: coven_foundation::id_source::FileId,
55    ) -> Result<crate::FileRef, crate::DbError> {
56        crate::file_ref::read(self.database, file)
57    }
58
59    pub(crate) fn user_file(
60        &self,
61        file: coven_foundation::id_source::FileId,
62    ) -> Result<Option<crate::UserFile>, crate::DbError> {
63        crate::user_file::read(self.database, file)
64    }
65
66    pub(crate) fn lost_values(&self) -> Result<Vec<LostValue>, DbError> {
67        self.database.lost_values()
68    }
69
70    pub(crate) fn pending<P: crate::PendingValue>(&self) -> Result<Vec<P>, DbError> {
71        crate::pending::read(self.database)
72    }
73
74    pub(crate) fn pin_progress(
75        &self,
76        files: &[crate::FileRef],
77        device: coven_foundation::id_source::DeviceId,
78    ) -> Result<crate::PinProgress, DbError> {
79        crate::cache_progress::pin_progress(self.database, files, device)
80    }
81
82    pub(crate) fn cache_budgets(&self) -> Result<Vec<crate::NamespaceBytes>, DbError> {
83        crate::database::local_bytes::budgets(self.database)
84    }
85
86    /// Every namespace's counters and the partials' sum, in one snapshot.
87    pub(crate) fn cache_bytes(&self) -> Result<(Vec<crate::NamespaceBytes>, u64), DbError> {
88        Ok((
89            crate::database::local_bytes::budgets(self.database)?,
90            crate::database::local_bytes::partials(self.database)?,
91        ))
92    }
93
94    pub(crate) fn files_pinned(
95        &self,
96        files: &[coven_foundation::id_source::FileId],
97    ) -> Result<Vec<Option<bool>>, DbError> {
98        crate::cache_progress::files_pinned(self.database, files)
99    }
100
101    pub(crate) fn unsent(&self) -> Result<crate::UnsentWork, DbError> {
102        crate::file_queue::unsent(self.database)
103    }
104
105    pub(crate) fn sync_facts<P: crate::PendingValue + Clone>(
106        &self,
107    ) -> Result<crate::SyncFacts<P>, DbError> {
108        crate::sync_facts::read(self.database)
109    }
110
111    /// `coven_sync_status`'s row, as coven reads it.
112    #[cfg(test)]
113    pub(crate) fn sync_status_row(&self) -> Result<crate::SyncStatusRow, DbError> {
114        crate::internal_schema::sync_status::read(self.database)
115    }
116
117    pub(crate) fn upload_queue(&self) -> Result<crate::UploadQueueState, DbError> {
118        crate::file_queue::state(self.database)
119    }
120
121    pub(crate) fn device_reset(
122        &self,
123    ) -> Result<Option<coven_foundation::files::DeviceReset>, DbError> {
124        crate::device_reset::read(self.database)
125    }
126
127    pub(crate) fn store_name(&self) -> Result<String, DbError> {
128        crate::store_state_tables::name(self.database)
129    }
130
131    pub(crate) fn named_store(&self) -> Result<(String, coven_merge::Slot), DbError> {
132        crate::store_state_tables::named(self.database)
133    }
134
135    pub(crate) fn joined_invitee(&self) -> Result<Option<String>, DbError> {
136        crate::joined_invitee::read(self.database)
137    }
138
139    pub(crate) fn members(
140        &self,
141        device: coven_foundation::id_source::DeviceId,
142    ) -> Result<Vec<crate::MemberInfo>, DbError> {
143        crate::members::members(self.database, device)
144    }
145
146    pub(crate) fn circles(
147        &self,
148        device: coven_foundation::id_source::DeviceId,
149    ) -> Result<Vec<crate::Circle>, DbError> {
150        crate::members::circles(self.database, device)
151    }
152
153    pub(crate) fn circle_members(
154        &self,
155        device: coven_foundation::id_source::DeviceId,
156        circle: coven_foundation::id_source::CircleId,
157    ) -> Result<Vec<crate::CircleMemberInfo>, DbError> {
158        crate::members::circle_members(self.database, device, circle)
159    }
160
161    #[cfg(any(test, feature = "test-utils"))]
162    pub(crate) fn store_state(&self) -> Result<crate::StoreState, DbError> {
163        crate::store_state_tables::read(self.database)
164    }
165
166    #[cfg(any(test, feature = "test-utils"))]
167    pub(crate) fn retry_wait(
168        &self,
169        subject: crate::RetrySubject,
170    ) -> Result<Option<crate::RetryWait>, DbError> {
171        crate::retry::find(self.database, subject)
172    }
173}
174
175/// A read that starts when polled and retains its store borrow until it
176/// ends, on the store's writer or a read-only handle.
177#[must_use = "reads run when awaited"]
178pub struct Read<'a, F, M: Mode = Writer> {
179    database: &'a Database<M>,
180    state: ReadState<F>,
181}
182
183// The public type names the closure, rather than its result. Only poll creates
184// the running slot, as RunningRead<R, E> for that closure.
185enum ReadState<F> {
186    Pending(F),
187    Running(Box<dyn Any + Send>),
188    Finished,
189}
190
191impl<'a, F, R, E, M: Mode> Read<'a, F, M>
192where
193    F: FnOnce(SqlReadContext<'_>) -> Result<R, E> + Send + 'static,
194    R: Send + 'static,
195    E: From<DbError> + CarriesBug + Send + 'static,
196{
197    pub(crate) fn new(database: &'a Database<M>, read: F) -> Self {
198        Self { database, state: ReadState::Pending(read) }
199    }
200
201    /// Process the value on another worker after releasing the read
202    /// connection. It fails with the read's own error type.
203    pub async fn process<P, T>(self, process: P) -> Result<T, E>
204    where
205        P: FnOnce(R) -> Result<T, E> + Send + 'static,
206        T: Send + 'static,
207    {
208        let bugs = self.database.bugs().clone();
209        let result = self.await?;
210        database::process(&bugs, move || process(result)).await
211    }
212}
213
214// No field relies on a pinned address. The task itself is a movable handle.
215impl<F, M: Mode> Unpin for Read<'_, F, M> {}
216
217impl<F, R, E, M: Mode> Future for Read<'_, F, M>
218where
219    F: FnOnce(SqlReadContext<'_>) -> Result<R, E> + Send + 'static,
220    R: Send + 'static,
221    E: From<DbError> + CarriesBug + Send + 'static,
222{
223    type Output = Result<R, E>;
224
225    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
226        let this = self.get_mut();
227        let mut task: Box<dyn Any + Send> =
228            match std::mem::replace(&mut this.state, ReadState::Finished) {
229                ReadState::Pending(read) => Box::new(this.database.start_read(read)),
230                ReadState::Running(task) => task,
231                ReadState::Finished => panic!("read polled after completion"),
232            };
233        let running =
234            task.downcast_mut::<RunningRead<R, E>>().expect("read task matches its closure result");
235        match Pin::new(running).poll(cx) {
236            Poll::Pending => {
237                this.state = ReadState::Running(task);
238                Poll::Pending
239            }
240            Poll::Ready(result) => Poll::Ready(result),
241        }
242    }
243}
244
245/// One read's call on the blocking pool, which its database started for the
246/// `Read` that awaits it.
247pub(crate) struct RunningRead<R, E> {
248    call: coven_foundation::task::CompletingTask<Result<R, E>>,
249}
250
251impl<R, E> RunningRead<R, E> {
252    pub(crate) fn new(call: coven_foundation::task::CompletingTask<Result<R, E>>) -> Self {
253        Self { call }
254    }
255}
256
257impl<R, E: From<DbError>> Future for RunningRead<R, E> {
258    type Output = Result<R, E>;
259
260    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
261        Pin::new(&mut self.get_mut().call).poll(cx).map(database::finish_blocking)
262    }
263}
264
265#[cfg(test)]
266#[path = "read_tests.rs"]
267pub(crate) mod tests;