1use std::sync::{Arc, Mutex};
2
3use crate::{CovenError, CovenResult, SqlReadContext};
4use coven_database::store::StoreReads;
5use coven_database::{QueryDependencies, StoreRowWrites};
6
7type Query<T> =
8 dyn for<'connection> Fn(SqlReadContext<'connection>) -> CovenResult<T> + Send + Sync;
9type QueryOutcome<Value> = CovenResult<(CovenResult<Value>, QueryDependencies)>;
10type RequestedQuery<Request, Value> = dyn Fn(
11 StoreReads,
12 Request,
13 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = QueryOutcome<Value>> + Send>>
14 + Send
15 + Sync;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
19pub struct LiveQueryRevision(u64);
20
21impl LiveQueryRevision {
22 pub fn get(self) -> u64 {
24 self.0
25 }
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
29#[error("the live query subscription is closed")]
30pub struct LiveQueryClosed;
31
32#[derive(Clone)]
33struct RequestState<Request> {
34 revision: LiveQueryRevision,
35 request: Request,
36}
37
38enum PendingRun {
39 Initial,
40 Triggered {
41 request_changed: bool,
42 commits: Vec<Arc<coven_database::CommittedChanges>>,
43 unknown_commit: bool,
44 previous_dependencies_matched: bool,
45 },
46}
47
48impl PendingRun {
49 fn request_changed(&mut self) {
50 match self {
51 Self::Initial => {
52 *self = Self::Triggered {
53 request_changed: true,
54 commits: Vec::new(),
55 unknown_commit: false,
56 previous_dependencies_matched: false,
57 };
58 }
59 Self::Triggered {
60 request_changed, ..
61 } => *request_changed = true,
62 }
63 }
64
65 fn committed(
66 &mut self,
67 changes: Arc<coven_database::CommittedChanges>,
68 dependencies: &QueryDependencies,
69 ) {
70 if let Self::Triggered {
71 commits,
72 previous_dependencies_matched,
73 ..
74 } = self
75 {
76 *previous_dependencies_matched |= dependencies.is_affected_by(&changes);
77 commits.push(changes);
78 }
79 }
80
81 fn lagged(&mut self) {
82 if let Self::Triggered { unknown_commit, .. } = self {
83 *unknown_commit = true;
84 }
85 }
86
87 fn cause(&self, dependencies: &QueryDependencies) -> ReconfigurableLiveQueryCause {
88 let Self::Triggered {
89 request_changed,
90 commits,
91 unknown_commit,
92 previous_dependencies_matched,
93 } = self
94 else {
95 return ReconfigurableLiveQueryCause::Initial;
96 };
97 let database_changed = *unknown_commit
98 || commits
99 .iter()
100 .any(|changes| dependencies.is_affected_by(changes))
101 || *previous_dependencies_matched;
102 match (*request_changed, database_changed) {
103 (true, true) => ReconfigurableLiveQueryCause::RequestAndDatabaseChanged,
104 (true, false) => ReconfigurableLiveQueryCause::RequestChanged,
105 (false, true) => ReconfigurableLiveQueryCause::DatabaseChanged,
106 (false, false) => {
107 unreachable!("a triggered live query must have a request or database cause")
108 }
109 }
110 }
111}
112
113#[derive(Clone)]
115pub struct LiveQueryRequests<Request> {
116 state: Arc<Mutex<RequestState<Request>>>,
117 sender: tokio::sync::watch::Sender<RequestState<Request>>,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum ReconfigurableLiveQueryCause {
123 Initial,
125 RequestChanged,
127 DatabaseChanged,
129 RequestAndDatabaseChanged,
131}
132
133impl<Request> LiveQueryRequests<Request>
134where
135 Request: Clone + PartialEq,
136{
137 pub fn set(&self, request: Request) -> Result<LiveQueryRevision, LiveQueryClosed> {
142 if self.sender.receiver_count() == 0 {
143 return Err(LiveQueryClosed);
144 }
145 let mut state = self
146 .state
147 .lock()
148 .expect("live query request mutex poisoned");
149 if state.request == request {
150 return Ok(state.revision);
151 }
152 state.revision = LiveQueryRevision(
153 state
154 .revision
155 .0
156 .checked_add(1)
157 .expect("live query request revision overflow"),
158 );
159 state.request = request;
160 self.sender
161 .send(state.clone())
162 .map_err(|_| LiveQueryClosed)?;
163 Ok(state.revision)
164 }
165}
166
167pub struct ReconfigurableLiveQueryEvent<Request, Value> {
169 cause: ReconfigurableLiveQueryCause,
170 state: RequestState<Request>,
171 result: CovenResult<Value>,
172}
173
174impl<Request, Value> ReconfigurableLiveQueryEvent<Request, Value> {
175 pub fn cause(&self) -> ReconfigurableLiveQueryCause {
177 self.cause
178 }
179
180 pub fn revision(&self) -> LiveQueryRevision {
182 self.state.revision
183 }
184
185 pub fn request(&self) -> &Request {
187 &self.state.request
188 }
189
190 pub fn into_result(self) -> CovenResult<Value> {
192 self.result
193 }
194}
195
196pub struct ReconfigurableLiveQuery<Request, Value> {
212 _writer: StoreRowWrites,
213 reader: StoreReads,
214 changes: tokio::sync::broadcast::Receiver<Arc<coven_database::CommittedChanges>>,
215 dependencies: QueryDependencies,
216 pending: Option<PendingRun>,
217 query: Arc<RequestedQuery<Request, Value>>,
218 request_receiver: tokio::sync::watch::Receiver<RequestState<Request>>,
219 requests: LiveQueryRequests<Request>,
220 current: RequestState<Request>,
221 last_delivered: Option<Value>,
225}
226
227impl<Request, Value> ReconfigurableLiveQuery<Request, Value>
228where
229 Request: Clone + PartialEq + Send + Sync + 'static,
230 Value: Send + 'static,
231{
232 pub(crate) fn new<F>(
233 writer: StoreRowWrites,
234 reader: StoreReads,
235 initial_request: Request,
236 query: F,
237 ) -> Self
238 where
239 F: for<'connection> Fn(&Request, SqlReadContext<'connection>) -> CovenResult<Value>
240 + Send
241 + Sync
242 + 'static,
243 {
244 let query = Arc::new(query);
245 let changes = writer.subscribe_committed_changes();
246 let current = RequestState {
247 revision: LiveQueryRevision(0),
248 request: initial_request,
249 };
250 let state = Arc::new(Mutex::new(current.clone()));
251 let (sender, request_receiver) = tokio::sync::watch::channel(current.clone());
252 Self {
253 _writer: writer,
254 reader,
255 changes,
256 dependencies: QueryDependencies::unknown(),
257 pending: Some(PendingRun::Initial),
258 query: Arc::new(move |reader, request| {
259 let query = query.clone();
260 Box::pin(async move {
261 reader
262 .read_tracked(move |sql| query(&request, sql))
263 .await
264 .map_err(CovenError::from)
265 })
266 }),
267 request_receiver,
268 requests: LiveQueryRequests { state, sender },
269 current,
270 last_delivered: None,
271 }
272 }
273
274 pub fn process<P, R>(self, process: P) -> ReconfigurableLiveQuery<Request, R>
282 where
283 P: Fn(&Request, Value) -> CovenResult<R> + Send + Sync + 'static,
284 R: Send + 'static,
285 {
286 let query = self.query;
287 let process = Arc::new(process);
288 ReconfigurableLiveQuery {
289 _writer: self._writer,
290 reader: self.reader,
291 changes: self.changes,
292 dependencies: QueryDependencies::unknown(),
293 pending: Some(PendingRun::Initial),
294 query: Arc::new(move |reader, request| {
295 let query = query.clone();
296 let process = process.clone();
297 Box::pin(async move {
298 let (result, dependencies) = query(reader.clone(), request.clone()).await?;
299 let result = match result {
300 Ok(raw) => reader.process(move || process(&request, raw)).await,
301 Err(error) => Err(error),
302 };
303 Ok((result, dependencies))
304 })
305 }),
306 request_receiver: self.request_receiver,
307 requests: self.requests,
308 current: self.current,
309 last_delivered: None,
310 }
311 }
312
313 pub fn requests(&self) -> LiveQueryRequests<Request> {
315 self.requests.clone()
316 }
317
318 pub async fn next(&mut self) -> ReconfigurableLiveQueryEvent<Request, Value>
326 where
327 Value: Clone + PartialEq,
328 {
329 loop {
330 self.await_pending().await;
331 self.drain_pending();
332 if let Some(event) = self.run().await {
333 return event;
334 }
335 }
336 }
337
338 async fn run(&mut self) -> Option<ReconfigurableLiveQueryEvent<Request, Value>>
341 where
342 Value: Clone + PartialEq,
343 {
344 loop {
345 let state = self.current.clone();
346 let query = self.query.clone();
347 let request = state.request.clone();
348 let outcome = query(self.reader.clone(), request).await;
349
350 if self.request_receiver.has_changed().unwrap_or(false) {
351 self.pending
352 .as_mut()
353 .expect("live query run is pending")
354 .request_changed();
355 self.accept_latest_request();
356 self.drain_pending();
357 continue;
358 }
359
360 let result = match outcome {
361 Ok((result, dependencies)) => {
362 self.dependencies = dependencies;
363 result
364 }
365 Err(error) => {
366 self.dependencies = QueryDependencies::unknown();
367 Err(error)
368 }
369 };
370 let pending = self.pending.take().expect("live query run is pending");
371 let cause = pending.cause(&self.dependencies);
372 if cause == ReconfigurableLiveQueryCause::DatabaseChanged
373 && matches!((&result, &self.last_delivered), (Ok(value), Some(last)) if value == last)
374 {
375 return None;
376 }
377 self.last_delivered = result.as_ref().ok().cloned();
378 return Some(ReconfigurableLiveQueryEvent {
379 cause,
380 state,
381 result,
382 });
383 }
384 }
385
386 async fn await_pending(&mut self) {
387 while self.pending.is_none() {
388 tokio::select! {
389 changed = self.request_receiver.changed() => {
390 changed.expect("the live query retains its request sender");
391 self.accept_latest_request();
392 self.pending = Some(PendingRun::Triggered {
393 request_changed: true,
394 commits: Vec::new(),
395 unknown_commit: false,
396 previous_dependencies_matched: false,
397 });
398 }
399 changes = self.changes.recv() => {
400 match changes {
401 Ok(changes) if self.dependencies.is_affected_by(&changes) => {
402 self.pending = Some(PendingRun::Triggered {
403 request_changed: false,
404 commits: vec![changes],
405 unknown_commit: false,
406 previous_dependencies_matched: true,
407 });
408 }
409 Ok(_) => {}
410 Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {
411 self.pending = Some(PendingRun::Triggered {
412 request_changed: false,
413 commits: Vec::new(),
414 unknown_commit: true,
415 previous_dependencies_matched: false,
416 });
417 }
418 Err(tokio::sync::broadcast::error::RecvError::Closed) => {
419 panic!("the live query retains its committed-change sender")
420 }
421 }
422 }
423 }
424 }
425 }
426
427 fn drain_pending(&mut self) {
428 if self.request_receiver.has_changed().unwrap_or(false) {
429 self.accept_latest_request();
430 self.pending
431 .as_mut()
432 .expect("live query run is pending")
433 .request_changed();
434 }
435 loop {
436 match self.changes.try_recv() {
437 Ok(changes) => self
438 .pending
439 .as_mut()
440 .expect("live query run is pending")
441 .committed(changes, &self.dependencies),
442 Err(tokio::sync::broadcast::error::TryRecvError::Lagged(_)) => self
443 .pending
444 .as_mut()
445 .expect("live query run is pending")
446 .lagged(),
447 Err(tokio::sync::broadcast::error::TryRecvError::Empty) => break,
448 Err(tokio::sync::broadcast::error::TryRecvError::Closed) => {
449 panic!("the live query retains its committed-change sender")
450 }
451 }
452 }
453 }
454
455 fn accept_latest_request(&mut self) {
456 self.current = self.request_receiver.borrow_and_update().clone();
457 }
458}
459
460pub struct LiveQuery<T> {
467 inner: ReconfigurableLiveQuery<(), T>,
468}
469
470impl<T> LiveQuery<T>
471where
472 T: Send + 'static,
473{
474 pub(crate) fn new<F>(writer: StoreRowWrites, reader: StoreReads, query: F) -> Self
475 where
476 F: for<'connection> Fn(SqlReadContext<'connection>) -> CovenResult<T>
477 + Send
478 + Sync
479 + 'static,
480 {
481 let query: Arc<Query<T>> = Arc::new(query);
482 Self {
483 inner: ReconfigurableLiveQuery::new(writer, reader, (), move |(), sql| query(sql)),
484 }
485 }
486
487 pub fn process<P, R>(self, process: P) -> LiveQuery<R>
494 where
495 P: Fn(T) -> CovenResult<R> + Send + Sync + 'static,
496 R: Send + 'static,
497 {
498 LiveQuery {
499 inner: self.inner.process(move |(), raw| process(raw)),
500 }
501 }
502
503 pub async fn next(&mut self) -> CovenResult<T>
507 where
508 T: Clone + PartialEq,
509 {
510 self.inner.next().await.into_result()
511 }
512}