db.rs (6839B)
1 use core::num::NonZeroU32; 2 3 use harvestcircle_domain::{SafeError, SafeErrorCode, SafeMessage}; 4 use radroots_runtime_paths::RuntimeContext; 5 use radroots_service_sqlite::{ 6 ExistingServiceDatabaseIntent, MigrationAppliedAtUnixSeconds, MigrationBuildIdentity, 7 ServiceDatabaseMetadata, ServiceSqliteConnectionOptions, ServiceSqliteErrorKind, 8 ServiceSqliteHost, ServiceSqliteInitializer, ServiceSqliteInitializerFuture, 9 ServiceSqliteTransactionError, ServiceSqliteTransactionErrorKind, 10 }; 11 use radroots_storage::event::SourceGeneration; 12 13 use crate::contract::{ 14 HARVESTCIRCLE_INITIAL_STATE_SCHEMA_VERSION, harvestcircle_initial_schema_sql, 15 }; 16 use crate::{HARVESTCIRCLE_STATE_SCHEMA_VERSION, HarvestCircleStorageContract}; 17 18 pub const CURRENT_SCHEMA_VERSION: u32 = HARVESTCIRCLE_STATE_SCHEMA_VERSION; 19 20 /// The sole HarvestCircle SQLite host. 21 /// 22 /// The underlying pool and connections remain sealed by `radroots_service_sqlite`. 23 pub struct Database { 24 host: ServiceSqliteHost, 25 metadata: ServiceDatabaseMetadata, 26 } 27 28 impl Database { 29 /// Opens or initializes the canonical HarvestCircle service-instance state. 30 /// 31 /// The caller injects creation and migration evidence. Fresh state receives 32 /// a new opaque source generation from host entropy; existing state is 33 /// admitted through its persisted identity without guessing that generation. 34 pub async fn open( 35 context: &RuntimeContext, 36 created_at_unix_ms: u64, 37 applied_at_unix_s: u64, 38 build: &MigrationBuildIdentity, 39 ) -> Result<Self, SafeError> { 40 let contract = HarvestCircleStorageContract::from_runtime_context(context) 41 .map_err(|_| invalid_storage_contract())?; 42 let applied_at = MigrationAppliedAtUnixSeconds::new(applied_at_unix_s) 43 .map_err(|_| invalid_storage_contract())?; 44 let options = ServiceSqliteConnectionOptions::reviewed(); 45 46 let mut generation = [0_u8; 32]; 47 getrandom::getrandom(&mut generation).map_err(|_| storage_unavailable())?; 48 let generation = SourceGeneration::new(generation).map_err(|_| storage_unavailable())?; 49 let metadata = ServiceDatabaseMetadata::new( 50 contract.paths(), 51 generation, 52 NonZeroU32::new(HARVESTCIRCLE_INITIAL_STATE_SCHEMA_VERSION) 53 .expect("initial schema version is nonzero"), 54 created_at_unix_ms, 55 contract.application_id(), 56 ) 57 .map_err(|_| invalid_storage_contract())?; 58 context 59 .state_directory_plan() 60 .map_err(|_| invalid_storage_contract())? 61 .provision() 62 .map_err(|_| storage_unavailable())?; 63 let (opened, _) = ServiceSqliteHost::open_or_initialize( 64 contract.paths(), 65 &metadata, 66 contract.migrations(), 67 contract.schema(), 68 options, 69 applied_at, 70 build, 71 &[], 72 initialize_application_schema, 73 ) 74 .await 75 .map_err(map_service_error)?; 76 let (host, metadata) = opened.into_parts(); 77 Ok(Self { host, metadata }) 78 } 79 80 /// Explicitly drains SQLite work, checkpoints WAL state, and releases authority. 81 pub async fn close(&self) -> Result<(), SafeError> { 82 self.host.close().await.map_err(map_service_error) 83 } 84 85 #[must_use] 86 pub const fn metadata(&self) -> &ServiceDatabaseMetadata { 87 &self.metadata 88 } 89 90 pub(crate) const fn host(&self) -> &ServiceSqliteHost { 91 &self.host 92 } 93 94 pub(crate) async fn open_existing( 95 contract: &HarvestCircleStorageContract, 96 applied_at: MigrationAppliedAtUnixSeconds, 97 build: &MigrationBuildIdentity, 98 ) -> Result<Self, SafeError> { 99 let intent = ExistingServiceDatabaseIntent::new( 100 contract.paths(), 101 contract.state_schema_version(), 102 contract.application_id(), 103 ); 104 let (opened, _) = ServiceSqliteHost::open_read_write_existing_with_intent( 105 contract.paths(), 106 &intent, 107 contract.migrations(), 108 contract.schema(), 109 ServiceSqliteConnectionOptions::reviewed(), 110 applied_at, 111 build, 112 &[], 113 ) 114 .await 115 .map_err(map_service_error)?; 116 let (host, metadata) = opened.into_parts(); 117 Ok(Self { host, metadata }) 118 } 119 } 120 121 fn initialize_application_schema<'a>( 122 initializer: &'a mut ServiceSqliteInitializer<'_>, 123 ) -> ServiceSqliteInitializerFuture<'a, sqlx::Error> { 124 Box::pin(async move { 125 for statement in harvestcircle_initial_schema_sql() { 126 sqlx::query(*statement).execute(&mut *initializer).await?; 127 } 128 sqlx::query("INSERT INTO runtime_state (singleton) VALUES (1)") 129 .execute(&mut *initializer) 130 .await?; 131 Ok(()) 132 }) 133 } 134 135 pub(crate) fn map_transaction_error(error: ServiceSqliteTransactionError<SafeError>) -> SafeError { 136 if let Some(operation) = error.operation_error() { 137 return *operation; 138 } 139 match error.kind() { 140 ServiceSqliteTransactionErrorKind::CommitOutcomeUnknown => commit_outcome_unknown(), 141 ServiceSqliteTransactionErrorKind::NotCommitted 142 | ServiceSqliteTransactionErrorKind::OperationRolledBack 143 | ServiceSqliteTransactionErrorKind::RollbackFailed => storage_unavailable(), 144 } 145 } 146 147 pub(crate) fn map_service_error(error: radroots_service_sqlite::ServiceSqliteError) -> SafeError { 148 match error.kind() { 149 ServiceSqliteErrorKind::Metadata 150 | ServiceSqliteErrorKind::Migration 151 | ServiceSqliteErrorKind::Integrity 152 | ServiceSqliteErrorKind::Recovery => corrupt_storage(), 153 ServiceSqliteErrorKind::Authority 154 | ServiceSqliteErrorKind::Open 155 | ServiceSqliteErrorKind::Create 156 | ServiceSqliteErrorKind::Pragma 157 | ServiceSqliteErrorKind::Backup 158 | ServiceSqliteErrorKind::Restore => storage_unavailable(), 159 } 160 } 161 162 pub(crate) const fn storage_unavailable() -> SafeError { 163 SafeError::new( 164 SafeErrorCode::StorageUnavailable, 165 SafeMessage::new("The application state is unavailable."), 166 ) 167 } 168 169 pub(crate) const fn corrupt_storage() -> SafeError { 170 SafeError::new( 171 SafeErrorCode::StorageCorrupt, 172 SafeMessage::new("The application state could not be verified."), 173 ) 174 } 175 176 pub(crate) const fn invalid_storage_contract() -> SafeError { 177 SafeError::new( 178 SafeErrorCode::InvalidApplicationState, 179 SafeMessage::new("The application storage contract is invalid."), 180 ) 181 } 182 183 const fn commit_outcome_unknown() -> SafeError { 184 SafeError::new( 185 SafeErrorCode::PendingOperationRecoveryRequired, 186 SafeMessage::new("The storage commit outcome must be reconciled."), 187 ) 188 }