1pub(crate) use crate::database::blob_records::{
12 load_activated_registration_on, remote_audience_to_db,
13};
14pub(crate) use crate::database::circle_snapshot_records::{
15 load_outbound_circle_snapshot_on, load_published_circle_snapshot_on,
16};
17pub(crate) use crate::database::cloud_outbox_records::consume_created_upload_handoff_on;
18use crate::database::connection_io::open_connection;
19use crate::database::connection_io::open_connection_read_only;
20use crate::database::connection_io::scan_max_updated_at;
21use crate::database::connection_io::seed_from;
22use crate::database::local_store_identity::pin_host_device_id_on;
23use crate::database::local_store_identity::validate_host_device_id_on;
24pub(crate) use crate::database::remote_object_records::begin_remote_candidate_nonactivation_on;
25pub(crate) use crate::database::remote_object_records::begin_remote_candidate_nonactivation_with_verified_head_on;
26pub(crate) use crate::database::remote_object_records::candidate_graph_exact_objects;
27pub(crate) use crate::database::remote_object_records::finish_remote_candidate_nonactivation_on;
28pub(crate) use crate::database::remote_object_records::index_retained_replay_owner_on;
29pub(crate) use crate::database::remote_object_records::load_protocol_inert_object_on;
30pub(crate) use crate::database::remote_object_records::load_remote_object_on;
31pub(crate) use crate::database::remote_object_records::mark_remote_object_uploaded_on;
32pub(crate) use crate::database::remote_object_records::mark_reusable_retained_authority_uploaded_on;
33pub(crate) use crate::database::remote_object_records::persist_exact_remote_object_on;
34pub(crate) use crate::database::remote_object_records::persist_prepared_remote_object_on;
35pub(crate) use crate::database::remote_object_records::record_reclaimed_store_package_on;
36pub(crate) use crate::database::remote_object_records::replace_prepared_merge_head_remote_on;
37pub(crate) use crate::database::remote_object_records::update_remote_object_on;
38pub(crate) use crate::database::remote_object_records::{
39 validate_prepared_blob_on, validate_prepared_package_on, validate_remote_object_on,
40 RemoteStoredRepresentationRef,
41};
42use crate::database::snapshot_objects::validate_snapshot_object_owners_on;
43pub(crate) use crate::database::snapshot_objects::{
44 install_snapshot_blob_plan_on, validate_snapshot_blob_plan_on,
45};
46pub(crate) use crate::database::snapshot_records::{
47 load_outbound_store_snapshot_on, load_published_store_snapshot_on,
48};
49pub(crate) use crate::database::store_ack_records::{
50 finish_outbound_circle_acks_on, finish_outbound_store_ack_on, load_outbound_store_ack_on,
51 load_published_store_ack_on, record_activated_circle_acks_on, record_activated_store_ack_on,
52 store_snapshot_first_slot, verify_next_local_store_ack_on,
53};
54pub(crate) use crate::database::store_authority_records::install_store_founder_state_on;
55pub(crate) use crate::database::store_reclaim_records::{
56 insert_store_reclaim_operation_on, load_store_reclaim_operation_on,
57 parse_store_reclaim_operation, record_store_reclaim_activation_on, store_reclaim_journal_error,
58 update_store_reclaim_operation_on,
59};
60pub(crate) use crate::database::stream_activation_records::load_registered_stream_activation_on;
61pub(crate) use crate::database::stream_activation_records::record_verified_stream_activations_on;
62
63use std::collections::{BTreeMap, BTreeSet, HashMap};
64use std::path::{Path, PathBuf};
65use std::sync::Arc;
66
67use rusqlite::{Connection, OptionalExtension};
68use tracing::error;
69
70use crate::blob::decl::BlobDecls;
71use crate::blob::locator::{BlobLocator, RemoteAudience, StoredBlobRef};
72use crate::blob::{BlobRef, RowBlobAuthority, RowBlobRef};
73use crate::db::{
74 apply_coven_schema, expected_coven_schema_manifest, is_reserved_table_name,
75 live_coven_schema_manifest, CovenSchemaManifest, ExternalBlob, OutboxEntry, OutboxOperation,
76 OutboxUploadState,
77};
78use crate::encryption::EncryptionService;
79use crate::migration::{run_migrations_in_transaction, Migration, MigrationError};
80use crate::sync::audience_package::{AudiencePackage, RowBlobLocatorBinding};
81use crate::sync::circle::Audience;
82use crate::sync::gate::{self, Gates};
83use crate::sync::hlc::{Hlc, Timestamp, UpdatedAtStamper, HIGHWATER_STATE_KEY, MAX_FUTURE_SKEW_MS};
84use crate::sync::membership::{AuthorHead, MembershipEntry, MembershipEntryRef, MembershipHeadRef};
85use crate::sync::remote_object::{
86 remote_object_id, CandidateExclusiveObjectDomain, RemoteObjectRecord, RetainedReplayOwner,
87 SharedLiveSetObjectDomain,
88};
89use crate::sync::routing_contract::SyncRoutingContract;
90use crate::sync::session::{quote_ident, SyncedTable};
91use crate::sync::storage::{ExactObjectRef, PreparedExactObject};
92use crate::sync::store::VerifiedStreamActivations;
93use crate::sync::store::{
94 DurableStoreReclaimOperation, ReclaimCommitActivation, ReclaimedStorePackage,
95 StoreReclaimJournalError,
96};
97use crate::sync::store::{
98 RetainedReplayAuthority, RetainedReplayBaseline, RetainedReplayGenesisAuthority,
99 RetainedReplaySnapshotAuthority,
100};
101use crate::sync::store_commit::{
102 ack_slot_prefix, CommitFrontier, ObjectHash, ResolvedStoreDeviceState, SnapshotImageRef,
103 SnapshotMeta, StoreAck, StoreAckRef, StoreBatchCommit, StoreBatchCommitRef, StoreCommitCoord,
104 StoreDeviceHead, StoreDeviceRegistration, StoreDeviceRegistrationRef, StoreProtocolRoot,
105 StoreSnapshotRef, StreamActivationId,
106};
107use crate::write::{PendingWrite, WriteId, WriteStatus};
108
109mod blob_bindings;
110mod blob_records;
111mod circle_operation_records;
112mod cloud_outbox;
113mod cloud_outbox_records;
114mod connection_io;
115pub(crate) use connection_io::{attach_session, capture_changeset, open_database_image};
116mod circle_snapshot_records;
117mod database_open;
118mod database_runtime;
119mod local_state;
120mod local_store_identity;
121mod make_remote;
122mod operation_models;
123mod prepared_audience_objects;
124mod provider_probes;
125mod remote_object_records;
126mod schema_contract;
127mod snapshot_objects;
128mod snapshot_records;
129mod store_ack_records;
130mod store_authority_records;
131mod store_coordinates;
132mod store_reclaim_records;
133mod stream_activation_records;
134mod write_lifecycle;
135mod write_models;
136
137pub(crate) use crate::sync::store::{
138 CandidateCleanupObject, OwnedVerifiedMergeMaterialization, RetainedMergeMaterializationKey,
139 RetainedPackageApplication, VerifiedMergeMaterialization, VerifiedMergeMembershipObjects,
140};
141pub(crate) use blob_records::{load_prepared_audience_objects_on, previous_row_blob_for_write_on};
142pub(crate) use circle_operation_records::{
143 load_circle_operation_on, parse_circle_operation_row, PreparedCircleOperationRow,
144};
145use database_open::{run_connection_thread, ConnectionThread, CovenMetadataOpen, DbJob};
146pub(crate) use local_store_identity::local_merge_stream_id_on;
147pub(crate) use local_store_identity::{
148 local_activated_registration_ref_on, local_store_authority_on,
149};
150pub(crate) use operation_models::{
151 DurableCircleSnapshotPublication, DurableDeviceRegistration, DurableMembershipMutation,
152 DurableSnapshotPublication, LocalDeviceRegistrationJournalRow, LocalDeviceRegistrationState,
153 MembershipMutationActivation, PreparedLocalDeviceRegistrationRow, PreparedSnapshotBlob,
154 PublishedCircleSnapshot, PublishedStoreSnapshot,
155};
156#[cfg(feature = "invariant-tests")]
157pub use prepared_audience_objects::exercise_exact_outbound_blob_graph;
158pub(crate) use prepared_audience_objects::{
159 validate_prepared_audience_blob_graph, BlobActivation, MakeRemoteIntentState,
160 PreparedAudienceBlob, PreparedAudienceObjects, PreparedAudiencePackage, PreparedRemoteObject,
161 StoredBlobReferenceState,
162};
163use schema_contract::validate_host_synced_tables;
164pub(crate) use schema_contract::DurablePreparedProtocolObject;
165pub(crate) use schema_contract::{StoreBatchCompletion, StoreBatchLocalCleanup};
166pub(crate) use store_authority_records::{
167 ensure_founder_replay_baseline_on, founder_graph_identity,
168 install_generation_zero_replay_baseline_on, install_snapshot_replay_baseline_on,
169 install_store_root_authority_on, load_local_store_founder_graph_on,
170 load_store_root_authority_on, validate_founder_graph, DurableFounderMembershipJournal,
171};
172pub(crate) use store_authority_records::{
173 load_generation_zero_replay_baseline_on, required_store_root_authority_on,
174};
175pub(crate) use store_authority_records::{
176 DurableFounderGraph, DurableFounderMembership, FounderMembershipRefs,
177};
178pub(crate) use write_models::{
179 AuthorExclusionActivationLocator, BlockedMergeCandidate, CompletePreparedStoreWriteOutcome,
180 ExactProtocolObject, InitialStoreMembershipAuthority, MergeAbandonmentState,
181 MergeReplayWriteOverlay, OutboundStoreAck, OutboundStoreAckActivation,
182 PreparedMergeAbandonmentCandidates, PreparedProtocolObject, PreparedStoreWrite,
183 PreparedStoreWriteCommit, PreparedStoreWritePartitions, PublishedStoreAck, StoreWriteBase,
184 StoreWriteBlobFact, StoreWriteBlobFacts, StoreWriteBlobMoveDestination, StoreWriteRemoteBlob,
185 StoreWriteRouting, TerminalCandidateAuthority, TerminalCandidateCleanupVerification,
186};
187
188pub const LOCAL_DEVICE_ID_STATE_KEY: &str = "local_device_id";
189const HOST_DEVICE_ID_STATE_KEY: &str = "host_device_id";
190pub(crate) const SYNC_ROUTING_CONTRACT_STATE_KEY: &str = "sync_routing_contract";
191pub const SYNC_ROUTING_HASH_STATE_KEY: &str = "sync_routing_hash";
192pub(crate) const COVEN_SCHEMA_MANIFEST_STATE_KEY: &str = "coven_schema_manifest";
193pub(crate) const COVEN_INITIALIZED_STATE_KEY: &str = "coven_initialized";
194pub(crate) const COVEN_INITIALIZED_STATE_VALUE: &str = "1";
195pub(crate) const STORE_DEVICE_GENESIS_STATE_KEY: &str = "store_device_genesis_state";
196const GATE_BASELINE_SCHEMA: &str = "coven_gate_empty";
197
198pub(crate) fn authorize_host_sql(
199 context: rusqlite::hooks::AuthContext<'_>,
200) -> rusqlite::hooks::Authorization {
201 use rusqlite::hooks::{AuthAction, Authorization};
202
203 if context
204 .database_name
205 .is_some_and(|name| name.eq_ignore_ascii_case(GATE_BASELINE_SCHEMA))
206 || matches!(
207 context.action,
208 AuthAction::Detach { database_name }
209 if database_name.eq_ignore_ascii_case(GATE_BASELINE_SCHEMA)
210 )
211 || matches!(
212 context.action,
213 AuthAction::Pragma { pragma_name, .. }
214 if pragma_name.eq_ignore_ascii_case("database_list")
215 )
216 {
217 Authorization::Deny
218 } else {
219 Authorization::Allow
220 }
221}
222
223#[derive(Debug, thiserror::Error)]
225pub enum DbError {
226 #[error("database error: {0}")]
227 Message(String),
228 #[error("database error: Store protocol root hash is absent")]
229 StoreRootHashMissing,
230 #[error(
235 "device excluded from circle {circle_id} close {close_id} must reset before publishing"
236 )]
237 ExcludedDeviceMustReset {
238 circle_id: crate::sync::circle::CircleId,
239 close_id: crate::sync::circle::CircleEpochCloseId,
240 },
241}
242
243impl DbError {
244 pub(crate) fn into_message(self) -> String {
245 match self {
246 Self::Message(message) => message,
247 Self::StoreRootHashMissing => "Store protocol root hash is absent".to_string(),
248 other @ Self::ExcludedDeviceMustReset { .. } => other.to_string(),
249 }
250 }
251}
252
253impl From<rusqlite::Error> for DbError {
254 fn from(e: rusqlite::Error) -> Self {
255 DbError::Message(e.to_string())
256 }
257}
258
259#[derive(Debug, thiserror::Error)]
265pub enum OpenError {
266 #[error(transparent)]
267 Migration(#[from] MigrationError),
268 #[error(transparent)]
269 Db(#[from] DbError),
270}
271
272#[derive(Clone)]
273struct DatabaseState {
274 hlc: Arc<Hlc>,
275 synced_tables: Arc<Vec<SyncedTable>>,
276 schema_version: u32,
277 sync_routing_hash: ObjectHash,
278 gates: Arc<Gates>,
279 blob_decls: Arc<BlobDecls>,
280 blob_tombstone_grace: chrono::Duration,
285 transfer_limits: crate::blob::TransferLimits,
289 store_runtime: crate::sync::store::StoreDatabaseRuntime,
290 local_blob_cleanup: Arc<tokio::sync::Mutex<()>>,
293 ids: crate::id_provider::IdRef,
294 write_statuses:
295 Arc<std::sync::Mutex<HashMap<WriteId, tokio::sync::watch::Sender<WriteStatus>>>>,
296 #[cfg(any(test, feature = "test-utils"))]
297 test_pause_points: Arc<TestPausePoints<DatabaseTestPoint>>,
298 #[cfg(any(test, feature = "test-utils"))]
299 merge_materialization_failure: Arc<std::sync::Mutex<Option<MergeMaterializationFailurePoint>>>,
300}
301
302#[cfg(any(test, feature = "test-utils"))]
304#[doc(hidden)]
305#[derive(Clone, Debug, PartialEq, Eq)]
306pub enum DatabaseTestPoint {
307 LocalBlobCleanupRequested,
308 LocalBlobCleanupAcquired,
309 LocalBlobCleanupBeforeFilesystem { namespace: String, blob_id: String },
310 LocalBlobCleanupFinished,
311 PullAfterRemoteCommit { device_id: String, seq: u64 },
312 StoreWriteCommitUploaded { write_id: WriteId },
313 StoreWriteHeadReadBack { write_id: WriteId },
314 StoreDeviceExclusionCandidateStaged,
315}
316
317#[cfg(any(test, feature = "test-utils"))]
318#[doc(hidden)]
319#[derive(Clone, Copy, Debug, PartialEq, Eq)]
320pub enum MergeMaterializationFailurePoint {
321 SummaryMaterialization,
322 RetractionDeletion,
323 ProjectionReplacement,
324}
325
326#[cfg(any(test, feature = "test-utils"))]
327#[derive(Clone)]
328pub(crate) struct MergeMaterializationFailureInjection {
329 armed: Arc<std::sync::Mutex<Option<MergeMaterializationFailurePoint>>>,
330}
331
332#[cfg(any(test, feature = "test-utils"))]
333impl MergeMaterializationFailureInjection {
334 pub(crate) fn reach(&self, point: MergeMaterializationFailurePoint) -> Result<bool, DbError> {
335 let mut armed = self.armed.lock().map_err(|_| {
336 DbError::Message("Merge materialization failure lock poisoned".to_string())
337 })?;
338 if armed.as_ref() != Some(&point) {
339 return Ok(false);
340 }
341 armed.take();
342 Ok(true)
343 }
344}
345
346#[cfg(any(test, feature = "test-utils"))]
347struct ArmedTestPause<K> {
348 point: K,
349 reached: Arc<tokio::sync::Notify>,
350 resume: Arc<tokio::sync::Notify>,
351}
352
353#[cfg(any(test, feature = "test-utils"))]
354struct TestPauseState<K> {
355 armed: Option<ArmedTestPause<K>>,
356 observers: Vec<tokio::sync::mpsc::UnboundedSender<K>>,
357}
358
359#[cfg(any(test, feature = "test-utils"))]
360struct TestPausePoints<K> {
361 state: std::sync::Mutex<TestPauseState<K>>,
362}
363
364#[cfg(any(test, feature = "test-utils"))]
365impl<K> Default for TestPausePoints<K> {
366 fn default() -> Self {
367 Self {
368 state: std::sync::Mutex::new(TestPauseState {
369 armed: None,
370 observers: Vec::new(),
371 }),
372 }
373 }
374}
375
376#[cfg(any(test, feature = "test-utils"))]
377impl<K: Clone + PartialEq> TestPausePoints<K> {
378 fn arm(&self, point: K) -> (Arc<tokio::sync::Notify>, Arc<tokio::sync::Notify>) {
379 let reached = Arc::new(tokio::sync::Notify::new());
380 let resume = Arc::new(tokio::sync::Notify::new());
381 let prior = self
382 .state
383 .lock()
384 .expect("database test pause mutex poisoned")
385 .armed
386 .replace(ArmedTestPause {
387 point,
388 reached: reached.clone(),
389 resume: resume.clone(),
390 });
391 assert!(prior.is_none(), "database test pause already armed");
392 (reached, resume)
393 }
394
395 fn observe(&self) -> tokio::sync::mpsc::UnboundedReceiver<K> {
396 let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
397 self.state
398 .lock()
399 .expect("database test pause mutex poisoned")
400 .observers
401 .push(sender);
402 receiver
403 }
404
405 async fn reach(&self, point: K) {
406 let pause = {
407 let mut state = self
408 .state
409 .lock()
410 .expect("database test pause mutex poisoned");
411 state
412 .observers
413 .retain(|observer| observer.send(point.clone()).is_ok());
414 if state
415 .armed
416 .as_ref()
417 .is_some_and(|pause| pause.point == point)
418 {
419 state.armed.take()
420 } else {
421 None
422 }
423 };
424 if let Some(pause) = pause {
425 pause.reached.notify_one();
426 pause.resume.notified().await;
427 }
428 }
429}
430
431struct DatabaseCore {
443 conn: Connection,
444 hlc: Arc<Hlc>,
445 synced_tables: Arc<Vec<SyncedTable>>,
446 schema_version: u32,
447 sync_routing_hash: ObjectHash,
448 gates: Arc<Gates>,
449 blob_decls: Arc<BlobDecls>,
450 blob_tombstone_grace: chrono::Duration,
451 transfer_limits: crate::blob::TransferLimits,
452}
453
454pub(crate) enum StagedCircleDecision {
460 Install {
461 activation_commit: StoreBatchCommitRef,
462 image: crate::sync::store::VerifiedCircleImage,
463 },
464 ClearCoverage(crate::sync::circle::CircleId),
465}
466
467pub(crate) struct VerifiedSnapshotBootstrapInstall {
468 snapshot: PublishedStoreSnapshot,
469 store_root: crate::sync::store_objects::VerifiedObject<StoreProtocolRoot>,
470 founder: crate::sync::store_objects::VerifiedObject<StoreDeviceRegistration>,
471 stability: RetainedReplaySnapshotAuthority,
472 routing_key: Option<crate::sync::circle::RowRoutingKey>,
473 circle_decisions: Vec<StagedCircleDecision>,
474 #[cfg(any(test, feature = "test-utils"))]
478 fail_circle_install: bool,
479}
480
481impl VerifiedSnapshotBootstrapInstall {
482 pub(crate) fn new(
483 snapshot: PublishedStoreSnapshot,
484 store_root: crate::sync::store_objects::VerifiedObject<StoreProtocolRoot>,
485 founder: crate::sync::store_objects::VerifiedObject<StoreDeviceRegistration>,
486 stability: crate::sync::store::VerifiedStoreSnapshotStability,
487 routing_encryption: Option<&EncryptionService>,
488 circle_decisions: Vec<StagedCircleDecision>,
489 ) -> Result<Self, DbError> {
490 if store_root.value.to_bytes() != store_root.bytes
491 || store_root.value.object_hash() != store_root.semantic_hash
492 {
493 return Err(DbError::Message(
494 "bootstrap Store root differs from its verified object".to_string(),
495 ));
496 }
497 let root = crate::sync::store_commit::StoreRootRef {
498 store_root_id: store_root.value.descriptor.store_root_id(),
499 store_root_hash: store_root.semantic_hash,
500 object: store_root.object.clone(),
501 };
502 let founder_reference =
503 StoreDeviceRegistrationRef::from_registration(&founder.value, founder.object.clone());
504 if founder.semantic_hash != founder_reference.registration_hash {
505 return Err(DbError::Message(
506 "bootstrap founder semantic hash differs from its exact registration".to_string(),
507 ));
508 }
509 let stability = stability.into_authority();
510 stability.validate()?;
511 if stability.store_root != root
512 || stability.founder_registration != founder_reference
513 || stability.snapshot != snapshot.reference
514 || stability.metadata != snapshot.meta
515 || snapshot.meta.successor.next_slot != snapshot.successor_slot
516 {
517 return Err(DbError::Message(
518 "bootstrap snapshot differs from its verified stability authority".to_string(),
519 ));
520 }
521 let routing_key = routing_encryption
522 .map(|encryption| {
523 crate::sync::circle::derive_row_routing_key(encryption, root.store_root_hash)
524 .map_err(|error| {
525 DbError::Message(format!("derive bootstrap row-routing key: {error}"))
526 })
527 })
528 .transpose()?;
529 Ok(Self {
530 snapshot,
531 store_root,
532 founder,
533 stability,
534 routing_key,
535 circle_decisions,
536 #[cfg(any(test, feature = "test-utils"))]
537 fail_circle_install: false,
538 })
539 }
540
541 pub(crate) fn with_circle_decisions(
546 mut self,
547 circle_decisions: Vec<StagedCircleDecision>,
548 ) -> Self {
549 self.circle_decisions = circle_decisions;
550 self
551 }
552
553 #[cfg(any(test, feature = "test-utils"))]
557 pub(crate) fn fail_circle_install_for_test(mut self) -> Self {
558 self.fail_circle_install = true;
559 self
560 }
561
562 fn install_on(
563 &self,
564 conn: &Connection,
565 schema_version: u32,
566 routing_hash: ObjectHash,
567 synced_tables: &[SyncedTable],
568 ) -> Result<(), DbError> {
569 let root = crate::sync::store_commit::StoreRootRef {
570 store_root_id: self.store_root.value.descriptor.store_root_id(),
571 store_root_hash: self.store_root.semantic_hash,
572 object: self.store_root.object.clone(),
573 };
574 let founder_reference = StoreDeviceRegistrationRef::from_registration(
575 &self.founder.value,
576 self.founder.object.clone(),
577 );
578 let genesis = ResolvedStoreDeviceState::founder(
579 &root,
580 founder_reference.clone(),
581 &self.store_root.value.descriptor.founder_pubkey,
582 self.store_root.value.descriptor.founder_grant.clone(),
583 &self.store_root.value.descriptor.founder_recovery,
584 )
585 .map_err(|error| DbError::Message(error.to_string()))?;
586 validate_snapshot_object_owners_on(conn, &root, &self.snapshot.meta)?;
587 install_store_root_authority_on(conn, &root, &self.store_root.bytes)?;
588 install_store_founder_state_on(
589 conn,
590 &root,
591 &founder_reference,
592 &self.founder.value,
593 &self.founder.bytes,
594 &genesis,
595 )?;
596 conn.execute("DELETE FROM snapshot_coverage", [])
597 .map_err(DbError::from)?;
598 for (stream_id, reference) in self.snapshot.meta.coverage.clone().into_refs() {
599 let encoded = serde_json::to_string(&reference).map_err(|error| {
600 DbError::Message(format!("serialize snapshot exact commit ref: {error}"))
601 })?;
602 conn.execute(
603 "INSERT INTO snapshot_coverage
604 (device_id, seq, commit_ref, snapshot_hash) VALUES (?1, ?2, ?3, ?4)",
605 (
606 &stream_id,
607 Database::sequence_to_sqlite(&stream_id, reference.coord.sequence())?,
608 encoded,
609 self.snapshot.reference.snapshot_hash.to_string(),
610 ),
611 )
612 .map_err(DbError::from)?;
613 }
614 install_snapshot_replay_baseline_on(
615 conn,
616 schema_version,
617 routing_hash,
618 self.stability.clone(),
619 )?;
620 self.install_circle_decisions_on(conn, synced_tables)
621 }
622
623 fn install_circle_decisions_on(
630 &self,
631 conn: &Connection,
632 synced_tables: &[SyncedTable],
633 ) -> Result<(), DbError> {
634 use crate::sync::store::StoreDatabase;
635 #[cfg(any(test, feature = "test-utils"))]
636 if self.fail_circle_install {
637 return Err(DbError::Message(
638 "injected Circle install failure after Store install".to_string(),
639 ));
640 }
641 for decision in &self.circle_decisions {
642 match decision {
643 StagedCircleDecision::Install {
644 activation_commit,
645 image,
646 } => {
647 let activation = StoreDatabase::verified_circle_activation_on(
648 conn,
649 image.circle_id(),
650 image.control(),
651 )?
652 .ok_or_else(|| {
653 DbError::Message(format!(
654 "restored Circle {} image names a control absent from the installed \
655 control indexes",
656 image.circle_id()
657 ))
658 })?;
659 crate::sync::store::install_circle_bootstrap_image_on(
660 conn,
661 synced_tables,
662 activation_commit,
663 image,
664 )?;
665 StoreDatabase::record_one_circle_bootstrap_coverage_on(
666 conn,
667 activation_commit,
668 image,
669 &activation.control,
670 )?;
671 }
672 StagedCircleDecision::ClearCoverage(circle_id) => {
673 StoreDatabase::clear_circle_bootstrap_coverage_on(conn, *circle_id)?;
674 }
675 }
676 }
677 Ok(())
678 }
679}
680
681#[derive(Clone)]
685pub struct Database {
686 thread: Arc<ConnectionThread>,
687 state: DatabaseState,
688}
689
690#[cfg(test)]
691#[path = "database/tests.rs"]
692mod tests;