1use super::{Database, Mode};
4use crate::internal_schema::retry_effect::{self, RetryState};
5use crate::DbError;
6use coven_foundation::task::CarriesBug;
7
8impl Database {
9 pub async fn claim_upload(&self) -> Result<crate::ClaimUpload, DbError> {
11 self.call(|inner| inner.access.with_committed(crate::claim::upload)).await
12 }
13 pub async fn claim_range(&self, offset: u64, length: usize) -> Result<Vec<u8>, DbError> {
15 self.call(move |inner| {
16 inner.access.with_committed(|db| crate::claim::range(db, offset, length))
17 })
18 .await
19 }
20 pub async fn save_claim_upload(&self, session: Vec<u8>) -> Result<(), DbError> {
22 self.call(move |inner| {
23 inner.access.with_writer(|writer| {
24 writer.stream_transaction(|db| crate::claim::save_upload(db, session))
25 })
26 })
27 .await
28 }
29 pub async fn prepare_uploads<E, F>(
32 &self,
33 through: u64,
34 select: F,
35 ) -> Result<crate::PreparedUploads, E>
36 where
37 E: From<DbError> + From<coven_format::Error> + CarriesBug + Send + 'static,
38 F: FnMut(
39 &coven_format::write_stream::WriteHeaderFrame,
40 ) -> Result<coven_format::sealed_write::WriteObjectPrefix, E>
41 + Send
42 + 'static,
43 {
44 self.call(move |inner| {
45 inner.access.with_writer(|writer| {
46 crate::upload::prepare_batch(writer, inner.access.device, through, select)
47 })
48 })
49 .await
50 }
51
52 pub async fn read_uploads<E, F>(
55 &self,
56 prepared: crate::PreparedUploads,
57 consume: F,
58 ) -> Result<(), E>
59 where
60 E: From<DbError> + CarriesBug + Send + 'static,
61 F: FnMut(crate::WaitingUpload<'_>) -> Result<(), E> + Send + 'static,
62 {
63 self.call(move |inner| {
64 inner
65 .access
66 .with_committed(|reader| crate::upload::read_batch(reader, prepared, consume))
67 })
68 .await
69 }
70
71 #[cfg(test)]
74 pub async fn retain_write_sources<R, F>(
75 &self,
76 write: crate::DownloadedWriteStream<R>,
77 authenticate: F,
78 ) -> Result<(), DbError>
79 where
80 R: std::io::Read + Send + 'static,
81 F: FnOnce() -> Result<(), DbError> + Send + 'static,
82 {
83 self.call(move |inner| {
84 inner.inputs.prepare(|inputs| {
85 let write = write.prepare(inputs)?;
86 authenticate()?;
87 inner.access.with_writer(|writer| {
88 writer.stream_transaction(|db| {
89 crate::download_stream::retain_sources(
90 db,
91 &inner.access.write_schema,
92 write,
93 )
94 })
95 })
96 })
97 })
98 .await
99 }
100
101 pub async fn apply_slot<P, R, F>(
106 &self,
107 incoming: crate::IncomingSlot<crate::DownloadedWriteStream<R>>,
108 writers: crate::Writers,
109 authenticate: F,
110 ) -> Result<crate::ApplyOutcome, DbError>
111 where
112 P: crate::PendingValue,
113 R: std::io::Read + Send + 'static,
114 F: FnOnce() -> Result<(), DbError> + Send + 'static,
115 {
116 self.call(move |inner| {
117 if let crate::SlotContent::Admin { effect: Some(effect), .. } = &incoming.content {
118 if let Some(store) = &effect.state.store {
119 crate::store_state::check_creation(store.id, inner.access.directory.id())?;
120 }
121 }
122 let started = inner.clock.elapsed();
123 inner.inputs.prepare(|inputs| {
124 let slot = incoming.slot;
125 let incoming = incoming.prepare(inputs)?;
126 let prepared = inner.clock.elapsed();
127 let reader = inner.access.readers.acquire_reader();
128 let applied = inner.with_files("apply a slot", |writer, files| {
129 reader.with_reader(|reader| {
130 reader.read_transaction(|| {
131 crate::write_schema::prepare_evaluators(
132 reader,
133 &inner.access.write_schema,
134 )?;
135 reader.schema_version()?;
136 crate::sequence_apply::apply::<P>(
137 writer,
138 reader,
139 &inner.access.write_schema,
140 incoming,
141 &writers,
142 files,
143 inner.raise_operation.as_deref(),
144 authenticate,
145 )
146 })
147 })
148 });
149 tracing::debug!(
150 slot = slot.get(),
151 preparing = ?(prepared - started),
152 applying = ?(inner.clock.elapsed() - prepared),
153 "applied slot"
154 );
155 applied
156 })
157 })
158 .await
159 }
160 #[cfg(test)]
162 pub async fn prepare_upload<F>(
163 &self,
164 select: F,
165 ) -> Result<Option<coven_merge::WriteId>, DbError>
166 where
167 F: FnOnce(
168 &crate::StoreState,
169 u32,
170 &coven_format::write_stream::WriteHeaderFrame,
171 ) -> Result<coven_format::sealed_write::WriteObjectPrefix, DbError>
172 + Send
173 + 'static,
174 {
175 self.call(move |inner| {
176 inner.access.with_writer(|db| {
177 let state = crate::store_state_tables::read(db)?;
178 crate::upload::prepare(db, &state, select)
179 })
180 })
181 .await
182 }
183 pub async fn sendable_through<P: crate::PendingValue>(&self) -> Result<Option<u64>, DbError> {
189 self.call(|inner| {
190 let device = inner.access.device;
191 let ahead =
192 inner.access.with_committed(|db| crate::raise_capture::first_ahead(db, device))?;
193 if ahead.is_none() {
194 return inner
195 .access
196 .with_committed(|db| crate::upload::sendable_through(db, device));
197 }
198 inner.access.with_writer(|writer| {
199 writer.state_transaction(|db| {
200 crate::raise_capture::record_ahead::<P>(db, device)?;
201 crate::upload::sendable_through(db, device)
202 })
203 })
204 })
205 .await
206 }
207
208 #[cfg(test)]
211 pub async fn checked_write_header(
212 &self,
213 id: coven_merge::WriteId,
214 ) -> Result<Option<coven_format::write_stream::WriteHeaderFrame>, DbError> {
215 self.call(move |inner| {
216 inner
217 .access
218 .readers
219 .acquire_reader()
220 .with_reader(|db| crate::write_headers::read(db, id))
221 })
222 .await
223 }
224
225 pub async fn reloading_audiences(
228 &self,
229 ) -> Result<std::collections::BTreeSet<coven_merge::Audience>, DbError> {
230 self.call(move |inner| {
231 inner
232 .access
233 .readers
234 .acquire_reader()
235 .with_reader(crate::reload_gate::reloading_audiences)
236 })
237 .await
238 }
239
240 pub async fn pending<P: crate::PendingValue>(&self) -> Result<Vec<P>, DbError> {
242 self.read(|sql| sql.pending()).await
243 }
244
245 pub async fn unsent(&self) -> Result<crate::UnsentWork, DbError> {
248 self.read(|sql| sql.unsent()).await
249 }
250
251 pub fn subscribe_unsent<E>(&self) -> crate::LiveQuery<crate::UnsentWork, E>
253 where
254 E: From<DbError> + coven_foundation::task::CarriesBug + Send + 'static,
255 {
256 self.subscribe(|sql| sql.unsent().map_err(E::from))
257 }
258
259 pub async fn sync_facts<P, T, E>(
264 &self,
265 compute: impl FnOnce(crate::SyncFacts<P>) -> Result<T, E> + Send + 'static,
266 ) -> Result<T, E>
267 where
268 P: crate::PendingValue + Clone,
269 T: Send + 'static,
270 E: From<DbError> + coven_foundation::task::CarriesBug + Send + 'static,
271 {
272 self.read(move |sql| compute(sql.sync_facts()?)).await
273 }
274
275 pub fn subscribe_sync_facts<P, T, E>(
278 &self,
279 compute: impl Fn(crate::SyncFacts<P>) -> Result<T, E> + Send + Sync + 'static,
280 ) -> crate::LiveQuery<T, E>
281 where
282 P: crate::PendingValue + Clone,
283 T: Send + 'static,
284 E: From<DbError> + coven_foundation::task::CarriesBug + Send + 'static,
285 {
286 self.subscribe(move |sql| compute(sql.sync_facts()?))
287 }
288
289 pub async fn upload_queue(&self) -> Result<crate::UploadQueueState, DbError> {
292 self.read(|sql| sql.upload_queue()).await
293 }
294
295 pub async fn record_opened(
302 &self,
303 location: crate::RecordedLocation<String>,
304 finished: Option<std::time::SystemTime>,
305 ) -> Result<(), DbError> {
306 self.save_sync_status(move |db| {
307 crate::internal_schema::sync_status::save_location(db, &location, finished, true)
308 })
309 .await
310 }
311
312 pub async fn record_location(
317 &self,
318 location: crate::RecordedLocation<String>,
319 finished: Option<std::time::SystemTime>,
320 ) -> Result<(), DbError> {
321 self.save_sync_status(move |db| {
322 crate::internal_schema::sync_status::save_location(db, &location, finished, false)
323 })
324 .await
325 }
326
327 pub async fn record_moving(&self) -> Result<(), DbError> {
330 self.save_sync_status(crate::internal_schema::sync_status::save_moving).await
331 }
332
333 pub async fn record_last_pass(&self, last_pass: Vec<u8>) -> Result<(), DbError> {
338 self.save_sync_status(move |db| {
339 crate::internal_schema::sync_status::save_last_pass(db, &last_pass)
340 })
341 .await
342 }
343
344 pub async fn record_halt(&self, last_pass: Vec<u8>) -> Result<(), DbError> {
349 self.save_sync_status(move |db| {
350 crate::internal_schema::sync_status::save_halt(db, &last_pass)
351 })
352 .await
353 }
354
355 pub async fn record_pass_end(
360 &self,
361 last_pass: Vec<u8>,
362 finished: Option<std::time::SystemTime>,
363 ) -> Result<(), DbError> {
364 self.save_sync_status(move |db| {
365 crate::internal_schema::sync_status::save_pass_end(db, &last_pass, finished)
366 })
367 .await
368 }
369
370 async fn save_sync_status(
372 &self,
373 save: impl FnOnce(&crate::sqlite::DatabaseConnection) -> Result<(), DbError> + Send + 'static,
374 ) -> Result<(), DbError> {
375 self.call(move |inner| inner.access.with_writer(|writer| writer.state_transaction(save)))
376 .await
377 }
378
379 pub async fn change_retry_state<T: Send + 'static>(
387 &self,
388 change: impl FnOnce(RetryState) -> RetryState + Send + 'static,
389 then: impl FnOnce(RetryState) -> T + Send + 'static,
390 ) -> Result<T, DbError> {
391 self.retry_step(change, then, |_| Ok(())).await
392 }
393
394 pub async fn start_retry_state<T: Send + 'static>(
399 &self,
400 change: impl FnOnce(RetryState) -> RetryState + Send + 'static,
401 then: impl FnOnce(RetryState) -> T + Send + 'static,
402 ) -> Result<T, DbError> {
403 self.retry_step(change, then, crate::internal_schema::sync_status::save_start).await
404 }
405
406 async fn retry_step<T: Send + 'static>(
407 &self,
408 change: impl FnOnce(RetryState) -> RetryState + Send + 'static,
409 then: impl FnOnce(RetryState) -> T + Send + 'static,
410 also: fn(&crate::sqlite::DatabaseConnection) -> Result<(), DbError>,
411 ) -> Result<T, DbError> {
412 self.call(move |inner| {
413 inner.access.with_writer(|writer| {
414 let state = writer.state_transaction(|db| {
415 let state = retry_effect::stored(change(retry_effect::read(db)?))?;
416 retry_effect::save(db, state)?;
417 also(db)?;
418 Ok(state)
419 })?;
420 Ok(then(state))
421 })
422 })
423 .await
424 }
425
426 pub async fn record_cooldown(
430 &self,
431 wait: crate::RetryWait,
432 change: impl FnOnce(RetryState) -> RetryState + Send + 'static,
433 ) -> Result<(), DbError> {
434 self.call(move |inner| {
435 inner.access.with_writer(|writer| {
436 writer.state_transaction(|db| {
437 crate::retry::save(db, crate::RetrySubject::Provider, &wait)?;
438 let state = retry_effect::stored(change(retry_effect::read(db)?))?;
439 retry_effect::save(db, state)
440 })
441 })
442 })
443 .await
444 }
445
446 pub async fn record_pending<P: crate::PendingValue>(&self, record: P) -> Result<(), DbError> {
448 self.call(move |inner| {
449 inner.access.with_writer(|writer| {
450 if let Some(id) = record.operation() {
451 if crate::operation::find(writer, id)?.is_none() {
452 return Err(DbError::OperationChanged(id));
453 }
454 }
455 writer.state_transaction(|db| crate::pending::save(db, &record))
456 })
457 })
458 .await
459 }
460
461 pub async fn clear_pending<S: serde::Serialize + Send + 'static>(
464 &self,
465 subject: S,
466 ) -> Result<(), DbError> {
467 self.clear_pending_checked(subject, crate::pending::recorded, |_| Ok(())).await.map(drop)
468 }
469
470 pub(crate) async fn clear_checked<P: crate::PendingValue>(
473 &self,
474 subject: P::Subject,
475 checked: impl Fn(&P) -> bool + Send + 'static,
476 ) -> Result<(), DbError>
477 where
478 P::Subject: Send + 'static,
479 {
480 self.clear_pending_checked(
481 subject,
482 move |db, subject| {
483 Ok(crate::pending::find::<P>(db, subject)?.is_some_and(|record| checked(&record)))
484 },
485 |_| Ok(()),
486 )
487 .await
488 .map(drop)
489 }
490
491 pub(crate) async fn clear_pending_if<P: crate::PendingValue>(
495 &self,
496 object: (coven_foundation::id_source::DeviceId, coven_foundation::id_source::ObjectId),
497 subject: P::Subject,
498 checked: impl Fn(&P) -> bool + Send + 'static,
499 ) -> Result<Option<crate::RetrySubject>, DbError>
500 where
501 P::Subject: Send + 'static,
502 {
503 self.clear_pending_checked(
504 subject,
505 move |db, subject| {
506 Ok(crate::pending::find::<P>(db, subject)?.is_some_and(|record| checked(&record)))
507 },
508 move |db| {
509 let retry = crate::file_queue::upload_id(db, object.0, object.1)?
510 .map(crate::RetrySubject::Upload);
511 if let Some(retry) = retry {
512 crate::retry::clear(db, retry)?;
513 }
514 Ok(retry)
515 },
516 )
517 .await
518 .map(Option::flatten)
519 }
520
521 async fn clear_pending_checked<S, F, T>(
522 &self,
523 subject: S,
524 checked: F,
525 settled: impl FnOnce(&crate::sqlite::DatabaseConnection) -> Result<T, DbError> + Send + 'static,
526 ) -> Result<Option<T>, DbError>
527 where
528 S: serde::Serialize + Send + 'static,
529 T: Send + 'static,
530 F: Fn(&crate::sqlite::DatabaseConnection, &S) -> Result<bool, DbError> + Send + 'static,
531 {
532 self.call(move |inner| {
533 let should_clear =
534 inner.access.readers.acquire_reader().with_reader(|db| checked(db, &subject))?;
535 if !should_clear {
536 return Ok(None);
537 }
538 inner.access.with_writer(|writer| {
539 writer.state_transaction(|db| {
540 if checked(db, &subject)? {
543 crate::pending::clear(db, &subject)?;
544 return settled(db).map(Some);
545 }
546 Ok(None)
547 })
548 })
549 })
550 .await
551 }
552
553 #[cfg(test)]
558 pub async fn replace_local_pending<P: crate::PendingValue>(
559 &self,
560 scanned: impl Fn(&P) -> bool + Send + 'static,
561 records: Vec<P>,
562 ) -> Result<(), DbError> {
563 use std::collections::BTreeSet;
564 self.call(move |inner| {
565 let mut identities = BTreeSet::new();
566 for record in &records {
567 if record.operation().is_some()
568 || !scanned(record)
569 || !identities.insert(record.subject())
570 {
571 return Err(DbError::Invariant {
572 what: "a scan replaces distinct records it covers",
573 });
574 }
575 }
576 let encode = |r: &P| serde_json::to_vec(r).map_err(DbError::StateEncoding);
577 let next = records.iter().map(encode).collect::<Result<BTreeSet<_>, _>>()?;
578 let unchanged = inner.access.readers.acquire_reader().with_reader(|reader| {
580 let current = crate::pending::read::<P>(reader)?
581 .into_iter()
582 .filter(|r| scanned(r))
583 .map(|r| encode(&r))
584 .collect::<Result<BTreeSet<_>, _>>()?;
585 Ok::<_, DbError>(current == next)
586 })?;
587 if unchanged {
588 return Ok(());
589 }
590 inner.access.with_writer(|writer| {
591 let previous: Vec<_> =
592 crate::pending::read::<P>(writer)?.into_iter().filter(|r| scanned(r)).collect();
593 if previous.iter().map(encode).collect::<Result<BTreeSet<_>, _>>()? == next {
594 return Ok(());
595 }
596 writer.state_transaction(|db| {
597 for record in &previous {
598 if !identities.contains(record.subject()) {
599 crate::pending::clear(db, record.subject())?;
600 }
601 }
602 for record in &records {
603 crate::pending::save(db, record)?;
604 }
605 Ok(())
606 })
607 })
608 })
609 .await
610 }
611
612 pub async fn pending_can_retry<P: crate::PendingValue>(
614 &self,
615 subject: P::Subject,
616 ) -> Result<bool, DbError>
617 where
618 P::Subject: Send + 'static,
619 {
620 self.call(move |inner| {
621 inner.access.with_writer(|writer| crate::pending::retry::<P>(writer, &subject))
622 })
623 .await
624 }
625
626 #[cfg(any(test, feature = "test-utils"))]
629 pub async fn set_pending_build(&self, build: u128) -> Result<(), DbError> {
630 self.call(move |inner| {
631 inner.access.with_writer(|writer| {
632 writer.state_transaction(|db| crate::pending::set_build(db, build))
633 })
634 })
635 .await
636 }
637
638 #[cfg(any(test, feature = "test-utils"))]
642 pub async fn read_oldest_upload<F, R, E>(
643 &self,
644 consume: F,
645 ) -> Result<Option<R>, crate::UploadReadError<E>>
646 where
647 F: FnOnce(crate::UploadPlaintext<'_>) -> Result<R, E> + Send + 'static,
648 R: Send + 'static,
649 E: CarriesBug + Send + 'static,
650 {
651 self.call(move |inner| {
652 let reader = inner.access.readers.acquire_reader();
653 reader.with_reader(|reader| {
654 reader.read_transaction(|| crate::upload::read(reader, consume))
655 })
656 })
657 .await
658 }
659
660 #[cfg(test)]
663 pub(crate) async fn oldest_upload_keys(
664 &self,
665 ) -> Result<
666 Option<(coven_merge::WriteId, Option<coven_format::sealed_write::WriteObjectPrefix>)>,
667 DbError,
668 > {
669 self.call(move |inner| {
670 let reader = inner.access.readers.acquire_reader();
671 reader.with_reader(|reader| {
672 reader.read_transaction(|| crate::upload::oldest_keys(reader))
673 })
674 })
675 .await
676 }
677
678 #[cfg(any(test, feature = "test-utils"))]
681 pub fn subscribe_store_state(&self) -> crate::LiveQuery<crate::StoreState, DbError> {
682 self.subscribe(|sql| sql.store_state())
683 }
684
685 pub async fn applied_slot(&self) -> Result<u64, DbError> {
687 self.call(|inner| {
688 #[cfg(any(test, feature = "test-utils"))]
689 if inner
690 .applied_slot_failures
691 .fetch_update(
692 std::sync::atomic::Ordering::SeqCst,
693 std::sync::atomic::Ordering::SeqCst,
694 |left| left.checked_sub(1),
695 )
696 .is_ok()
697 {
698 return Err(DbError::Sqlite(rusqlite::Error::SqliteFailure(
699 rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_IOERR),
700 Some("injected applied slot read failure".into()),
701 )));
702 }
703 inner.access.with_committed(crate::sequence::applied)
704 })
705 .await
706 }
707
708 #[cfg(any(test, feature = "test-utils"))]
710 pub async fn fail_applied_slots(&self, count: usize) {
711 self.call(move |inner| {
712 inner.applied_slot_failures.store(count, std::sync::atomic::Ordering::SeqCst);
713 Ok::<_, DbError>(())
714 })
715 .await
716 .expect("an open store arms its applied slot failures");
717 }
718
719 #[cfg(any(test, feature = "test-utils"))]
721 pub async fn last_claims(&self) -> Result<Vec<coven_format::snapshot::LastClaim>, DbError> {
722 self.call(|inner| inner.access.with_committed(crate::sequence::claims)).await
723 }
724
725 pub async fn original_slots(&self) -> Result<Vec<coven_format::floor::OriginalSlot>, DbError> {
727 self.call(|inner| {
728 inner.access.with_committed(|db| {
729 db.query("SELECT slot,record FROM _coven_slot_records ORDER BY slot", [], |row| {
730 Ok(coven_format::floor::OriginalSlot {
731 slot: crate::write_encoding::slot_counter(row.get(0)?)?,
732 object: row.get(1)?,
733 })
734 })
735 })
736 })
737 .await
738 }
739
740 #[cfg(any(test, feature = "test-utils"))]
742 pub async fn claim_counter(&self) -> Result<u64, DbError> {
743 self.call(|inner| {
744 inner.access.with_committed(|db| crate::claim::reserved(db, inner.access.device))
745 })
746 .await
747 }
748
749 pub async fn attempted_slot(
751 &self,
752 number: u64,
753 path: coven_format::path::ObjectPath,
754 digest: [u8; 32],
755 ) -> Result<bool, DbError> {
756 self.call(move |inner| {
757 inner
758 .access
759 .with_committed(|db| crate::claim::attempted_digest(db, number, &path, &digest))
760 })
761 .await
762 }
763
764 pub async fn current_claim(&self) -> Result<Option<crate::Claim>, DbError> {
767 self.call(|inner| inner.access.with_committed(crate::claim::current)).await
768 }
769
770 pub async fn begin_claim<F, R, E>(
774 &self,
775 work: crate::ClaimWork,
776 seal: F,
777 ) -> Result<crate::Claim, E>
778 where
779 F: FnOnce(u64) -> Result<crate::ClaimAttempt<R>, E> + Send + 'static,
780 R: std::io::Read + Send + 'static,
781 E: From<DbError> + CarriesBug + Send + 'static,
782 {
783 self.call(move |inner| {
784 let number = inner
785 .access
786 .with_committed(|db| crate::claim::reserved(db, inner.access.device))?
787 .checked_add(1)
788 .ok_or(DbError::ClaimNumberExhausted)?;
789 let attempt = seal(number)?;
790 Ok(inner.inputs.prepare_claim(attempt, |prepared| {
791 inner.access.with_writer(|writer| {
792 writer.stream_transaction(|db| {
793 crate::claim::begin(db, inner.access.device, work, number, prepared)
794 })
795 })
796 })?)
797 })
798 .await
799 }
800
801 #[cfg(any(test, feature = "test-utils"))]
803 pub async fn read_current_attempt<T, E, F>(&self, consume: F) -> Result<T, E>
804 where
805 T: Send + 'static,
806 E: From<DbError> + CarriesBug + Send + 'static,
807 F: FnOnce(crate::ClaimBytes<'_>) -> Result<T, E> + Send + 'static,
808 {
809 self.call(move |inner| {
810 inner
811 .access
812 .readers
813 .acquire_reader()
814 .with_reader(|db| db.read_transaction(|| crate::claim::read_attempt(db, consume)))
815 })
816 .await
817 }
818
819 pub async fn retry_claim<R: std::io::Read + Send + 'static>(
821 &self,
822 attempt: crate::ClaimAttempt<R>,
823 ) -> Result<(), DbError> {
824 self.call(move |inner| {
825 inner.inputs.prepare_claim(attempt, |prepared| {
826 inner.access.with_writer(|writer| {
827 writer.stream_transaction(|db| crate::claim::retry(db, prepared))
828 })
829 })
830 })
831 .await
832 }
833
834 pub async fn settle_claim(
836 &self,
837 read: crate::ClaimRead,
838 ) -> Result<crate::ClaimSettlement, DbError> {
839 self.call(move |inner| {
840 inner.access.with_writer(|writer| {
841 writer.stream_transaction(|db| crate::claim::settle(db, read))
842 })
843 })
844 .await
845 }
846
847 pub async fn finish_claim(
849 &self,
850 operation: Option<crate::OperationUpdate>,
851 ) -> Result<(), DbError> {
852 self.call(move |inner| {
853 inner.access.with_writer(|writer| {
854 writer.stream_transaction(|db| {
855 crate::claim::finish(db)?;
856 if let Some(update) = operation {
857 crate::operation::advance(db, &update)?;
858 }
859 Ok(())
860 })
861 })
862 })
863 .await
864 }
865
866 #[cfg(any(test, feature = "test-utils"))]
868 pub async fn attempted_claim(
869 &self,
870 path: coven_format::path::ObjectPath,
871 ) -> Result<bool, DbError> {
872 self.call(move |inner| inner.access.with_committed(|db| crate::claim::attempted(db, &path)))
873 .await
874 }
875}
876
877#[path = "database_device.rs"]
878mod device;
879
880impl<M: Mode> Database<M> {
881 pub async fn store_state(&self) -> Result<crate::StoreState, DbError> {
883 self.call(|open| M::access(open).with_committed(crate::store_state_tables::read)).await
884 }
885}