1use super::*;
4use coven_foundation::files::StoreDirAccess;
5
6use crate::{migration::MigrationOperation, AutoVacuum, NewOperation, OpenError, OpenStep};
7use coven_foundation::progress::{ProgressChannel, ProgressReport, ProgressWatch};
8
9pub struct DatabaseBuilder {
11 directory: StoreDir,
12 tables: Option<Vec<SyncedTable>>,
13 namespaces: Vec<crate::Namespace>,
14 migrations: Option<Vec<Migration>>,
15 migration_operation: Option<Box<MigrationOperation>>,
16 installation: crate::migration_run::Installation,
17 clock: Option<ClockRef>,
18 ids: Option<IdSourceRef>,
19 sqlite: crate::SqliteSettings,
20 functions: crate::SqlFunctions,
21 progress: ProgressChannel<OpenStep>,
22 #[cfg(test)]
23 upkeep_threads: Option<usize>,
24 #[cfg(any(test, feature = "test-utils"))]
25 refuse_close: bool,
26}
27
28impl DatabaseBuilder {
29 pub fn new(directory: StoreDir) -> Self {
31 Self {
32 directory,
33 tables: None,
34 namespaces: Vec::new(),
35 migrations: None,
36 migration_operation: None,
37 installation: crate::migration_run::Installation::Own,
38 clock: None,
39 ids: None,
40 sqlite: crate::SqliteSettings::default(),
41 functions: crate::SqlFunctions::default(),
42 progress: ProgressChannel::new(),
43 #[cfg(test)]
44 upkeep_threads: None,
45 #[cfg(any(test, feature = "test-utils"))]
46 refuse_close: false,
47 }
48 }
49
50 #[cfg(any(test, feature = "test-utils"))]
52 pub fn refuse_close(mut self) -> Self {
53 self.refuse_close = true;
54 self
55 }
56
57 #[cfg(test)]
59 pub(crate) fn refuse_upkeep_thread_after(mut self, started: usize) -> Self {
60 self.upkeep_threads = Some(started);
61 self
62 }
63
64 pub fn subscribe_progress(&self) -> ProgressWatch<OpenStep> {
67 self.progress.watch()
68 }
69
70 pub fn synced_tables(mut self, tables: Vec<SyncedTable>) -> Self {
72 self.tables = Some(tables);
73 self
74 }
75
76 pub fn namespaces(mut self, namespaces: Vec<crate::Namespace>) -> Self {
78 self.namespaces = namespaces;
79 self
80 }
81
82 pub fn migrations(mut self, migrations: Vec<Migration>) -> Self {
84 self.migrations = Some(migrations);
85 self
86 }
87
88 pub fn loading_store(mut self) -> Self {
93 self.installation = crate::migration_run::Installation::Loading;
94 self
95 }
96
97 pub fn migration_operation<F>(mut self, operation: F) -> Self
102 where
103 F: Fn(u32) -> Result<NewOperation, DbError> + Send + Sync + 'static,
104 {
105 self.migration_operation = Some(Box::new(operation));
106 self
107 }
108
109 pub fn clock(mut self, clock: ClockRef) -> Self {
111 self.clock = Some(clock);
112 self
113 }
114
115 pub fn id_source(mut self, ids: IdSourceRef) -> Self {
117 self.ids = Some(ids);
118 self
119 }
120
121 pub fn sqlite_settings(mut self, settings: crate::SqliteSettings) -> Self {
125 self.sqlite = settings;
126 self
127 }
128
129 pub fn sql_functions(mut self, functions: crate::SqlFunctions) -> Self {
132 self.functions = functions;
133 self
134 }
135
136 pub async fn open(self) -> Result<Database, OpenError> {
141 let lock = self.directory.lock_for_open().await?;
142 self.open_locked(lock).await
143 }
144
145 pub async fn open_locked(self, lock: StoreLock) -> Result<Database, OpenError> {
148 let closing = lock.closing_guard();
149 let bugs = self.directory.bugs().clone();
150 let result = finish_blocking(
151 blocking_completing("open the store", bugs, move || self.open_graph(lock)).await,
152 );
153 closing.disarm();
156 result
157 }
158
159 pub async fn open_read_only(self) -> Result<Database<ReadOnly>, OpenError> {
163 let bugs = self.directory.bugs().clone();
164 finish_blocking(
165 blocking_completing("open the store read-only", bugs, move || self.open_read_graph())
166 .await,
167 )
168 }
169
170 fn open_graph(self, lock: StoreLock) -> Result<Database, OpenError> {
171 let tables =
172 self.tables.ok_or(OpenError::MissingConfiguration { field: "synced_tables" })?;
173 let migrations =
174 self.migrations.ok_or(OpenError::MissingConfiguration { field: "migrations" })?;
175 coven_foundation::files::verify_lock(&self.directory, &lock)?;
177 let settings = lock.settings()?;
178 let clock = match self.clock {
179 Some(clock) => clock,
180 None => Arc::new(SystemClock),
181 };
182 let ids = match self.ids {
183 Some(ids) => ids,
184 None => Arc::new(UuidIds),
185 };
186 let started = clock.elapsed();
187 let path = self.directory.database_path();
188 let observer = CommitObserver::new(self.directory.bugs().clone());
191 #[cfg(test)]
192 observer.refuse_upkeep_thread_after(self.upkeep_threads);
193 let transfers = crate::transfers_in_flight::TransfersInFlight::new(observer.clone());
194 let network = crate::network_facts::NetworkFacts::new(observer.clone());
195 let mut writer = DatabaseConnection::open(
196 &path,
197 false,
198 SqlAuthorization::new(&tables),
199 &self.sqlite,
200 &self.functions,
201 )?;
202 writer.serve_views(settings.device_id, &transfers, &network)?;
204 let (outcomes, write_schema, inputs, mut checkpoints, migrating, migrated, optimized) =
205 writer.with_profile(|writer| -> Result<_, OpenError> {
206 writer.enable_wal()?;
207 let mut progress = ProgressReport::of(&self.progress, clock.clone());
208 let migrating = clock.elapsed();
209 let outcomes = writer.prepare_schema(
210 &tables,
211 &migrations,
212 &self.functions.versions(),
213 Some(crate::migration_run::MigrationOrigin(
214 settings.device_id,
215 clock.now(),
216 self.installation,
217 )),
218 self.migration_operation.as_deref(),
219 &mut progress,
220 )?;
221 crate::database::file_database::declare_budgets(writer, &self.namespaces)?;
222 crate::file_removals::finish_at_open(writer, &self.directory)?;
223 crate::pending::forget_causes(writer)?;
224 crate::internal_schema::retry_effect::save(
226 writer,
227 crate::internal_schema::retry_effect::RetryState::OPENED,
228 )?;
229 let mut write_schema =
230 crate::write_schema::WriteSchema::read(writer, tables.clone())?;
231 write_schema.conversions = crate::migration_receive::read(writer, &migrations)?;
232 write_schema.namespaces = self.namespaces;
233 crate::write_schema::prepare_evaluators(writer, &write_schema)?;
234 let migrated = clock.elapsed();
235 DatabaseConnection::open(
240 &path,
241 false,
242 SqlAuthorization::new(&tables),
243 &self.sqlite,
244 &self.functions,
245 )?
246 .gather_statistics()?;
247 writer.batch("ANALYZE sqlite_schema")?;
249 let optimized = clock.elapsed();
250 let input_path = self.directory.input_database_path();
253 let mut input_writer = coven_only(&input_path, false)?;
254 input_writer.enable_wal()?;
255 let version: u32 =
256 input_writer.query_row("PRAGMA application_id", [], |r| r.get(0))?;
257 match version {
258 0 => input_writer.transaction(|db| {
259 db.batch(&crate::internal_schema::initial_input_schema())
260 })?,
261 1 => (),
262 _ => return Err(DbError::DamagedDatabase.into()),
263 }
264 let mut input_reader = coven_only(&input_path, true)?;
265 input_writer.serve(observer.clone());
266 input_reader.serve(observer.clone());
267 let input_writer = Arc::new(Mutex::new(input_writer));
268 let checkpoints = vec![crate::wal_checkpoints::WalCheckpointer::start(
272 coven_only(&input_path, false)?,
273 &input_writer,
274 crate::sqlite_settings::CHECKPOINT_PAGES,
275 clock.clone(),
276 observer.clone(),
277 )?];
278 let inputs = input_database::InputDatabase::new(input_reader, input_writer)?;
279 Ok((outcomes, write_schema, inputs, checkpoints, migrating, migrated, optimized))
280 })?;
281 writer.observe_commits(observer.clone(), settings.device_id, &tables);
282 #[cfg(any(test, feature = "test-utils"))]
283 if self.refuse_close {
284 writer.leave_statement_open();
285 }
286 let writer = Arc::new(Mutex::new(writer));
287 if let Some(pages) = self.sqlite.checkpoint_pages() {
288 let checkpointing = DatabaseConnection::open(
289 &path,
290 false,
291 SqlAuthorization::new(&[]),
292 &self.sqlite,
293 &crate::SqlFunctions::default(),
294 )?;
295 checkpoints.push(crate::wal_checkpoints::WalCheckpointer::start(
296 checkpointing,
297 &writer,
298 pages,
299 clock.clone(),
300 observer.clone(),
301 )?);
302 }
303 let statistics =
304 crate::upkeep::UpkeepThread::<crate::planner_statistics::PlannerStatistics>::start(
305 DatabaseConnection::open(
306 &path,
307 false,
308 SqlAuthorization::new(&tables),
309 &self.sqlite,
310 &self.functions,
311 )?,
312 &writer,
313 observer.clone(),
314 )?;
315 tracing::info!(
316 migrations = migrations.len(),
317 migrating = ?(migrated - migrating),
318 optimizing = ?(optimized - migrated),
319 total = ?(clock.elapsed() - started),
320 "opened the database"
321 );
322 let mut readers = Vec::new();
323 for _ in 0..4 {
324 let mut reader = DatabaseConnection::open(
325 &path,
326 true,
327 SqlAuthorization::new(&tables),
328 &self.sqlite,
329 &self.functions,
330 )?;
331 reader.serve_views(settings.device_id, &transfers, &network)?;
332 reader.serve(observer.clone());
333 readers.push(Mutex::new(reader));
334 }
335 Ok(Database {
336 observer: observer.clone(),
337 closing: Arc::new(lock.closing_guard()),
338 writer: WriterParts {
339 transfers,
340 network,
341 file_tasks: Arc::new(tokio::sync::RwLock::new(())),
342 local_bytes_changes: Arc::new(tokio::sync::watch::channel(0).0),
343 #[cfg(test)]
344 close_waits: Arc::new(Mutex::new(None)),
345 #[cfg(test)]
346 abandoned_stagings: Arc::new(Mutex::new(Vec::new())),
347 },
348 bugs: self.directory.bugs().clone(),
349 inner: Arc::new(RwLock::new(Some(DatabaseInner {
350 inputs,
351 staging: Arc::new(crate::reserved_names::ReservedNames::new(observer.clone())),
352 access: DatabaseAccess {
353 writer,
354 readers: ReadPool::new(readers),
355 observer,
356 write_schema,
357 directory: self.directory,
358 device: settings.device_id,
359 lock,
360 #[cfg(test)]
361 local_open_gate: Mutex::new(None),
362 },
363 checkpoints,
364 statistics,
365 migrations: outcomes,
366 raise_operation: self.migration_operation,
367 clock,
368 ids,
369 copying: Arc::new(Mutex::new(BTreeSet::new())),
370 #[cfg(test)]
371 removal_gate: Mutex::new(None),
372 #[cfg(test)]
373 flush_hook: Mutex::new(None),
374 #[cfg(any(test, feature = "test-utils"))]
375 retry_end_failures: std::sync::atomic::AtomicUsize::new(0),
376 #[cfg(any(test, feature = "test-utils"))]
377 applied_slot_failures: std::sync::atomic::AtomicUsize::new(0),
378 }))),
379 })
380 }
381 fn open_read_graph(self) -> Result<Database<ReadOnly>, OpenError> {
382 let tables =
383 self.tables.ok_or(OpenError::MissingConfiguration { field: "synced_tables" })?;
384 let migrations =
385 self.migrations.ok_or(OpenError::MissingConfiguration { field: "migrations" })?;
386 let lock = self.directory.lock_read_only()?;
387 let settings = self.directory.settings()?;
388 let path = self.directory.database_path();
389 let observer = CommitObserver::new(self.directory.bugs().clone());
391 let transfers = crate::transfers_in_flight::TransfersInFlight::new(observer.clone());
393 let network = crate::network_facts::NetworkFacts::new(observer.clone());
395 let mut first = DatabaseConnection::open(
396 &path,
397 true,
398 SqlAuthorization::read_only(&tables),
399 &self.sqlite,
400 &self.functions,
401 )?;
402 first.serve_views(settings.device_id, &transfers, &network)?;
403 let clock = match self.clock {
405 Some(clock) => clock,
406 None => Arc::new(SystemClock),
407 };
408 first.prepare_schema(
409 &tables,
410 &migrations,
411 &self.functions.versions(),
412 None,
413 None,
414 &mut ProgressReport::of(&self.progress, clock),
415 )?;
416 let mut schema = crate::write_schema::WriteSchema::read(&first, tables.clone())?;
417 schema.namespaces = self.namespaces;
418 first.serve(observer.clone());
419 let mut readers = vec![Mutex::new(first)];
420 for _ in 1..4 {
421 let mut reader = DatabaseConnection::open(
422 &path,
423 true,
424 SqlAuthorization::read_only(&tables),
425 &self.sqlite,
426 &self.functions,
427 )?;
428 reader.serve_views(settings.device_id, &transfers, &network)?;
429 reader.serve(observer.clone());
430 readers.push(Mutex::new(reader));
431 }
432 let mut writer = DatabaseConnection::open(
433 &path,
434 false,
435 SqlAuthorization::read_only(&tables),
436 &self.sqlite,
437 &self.functions,
438 )?;
439 writer.serve_views(settings.device_id, &transfers, &network)?;
440 writer.serve(observer.clone());
441 Ok(Database {
442 observer: observer.clone(),
443 closing: Arc::new(lock.closing_guard()),
444 writer: (),
445 bugs: self.directory.bugs().clone(),
446 inner: Arc::new(RwLock::new(Some(DatabaseAccess {
447 writer: Arc::new(Mutex::new(writer)),
448 readers: ReadPool::new(readers),
449 observer,
450 lock,
451 write_schema: schema,
452 directory: self.directory,
453 device: settings.device_id,
454 #[cfg(test)]
455 local_open_gate: Mutex::new(None),
456 }))),
457 })
458 }
459}
460
461pub async fn discarded_operations(
468 path: std::path::PathBuf,
469 bugs: &coven_foundation::task::BugSinkRef,
470) -> Result<Vec<crate::OperationRecord>, DbError> {
471 finish_blocking(
472 blocking_completing("read the discarded operations", bugs.clone(), move || {
473 let db = coven_only(&path, true)?;
474 crate::operation::read(&db)
475 })
476 .await,
477 )
478}
479
480pub async fn read_store_name(directory: StoreDir) -> Result<String, OpenError> {
486 let bugs = directory.bugs().clone();
487 finish_blocking(
488 blocking_completing("read the store name", bugs, move || {
489 let _lock = directory.lock_read_only()?;
490 let database = coven_only(&directory.database_path(), true)?;
491 Ok(crate::store_state_tables::name(&database)?)
492 })
493 .await,
494 )
495}
496
497pub async fn closed_sync_facts<P: crate::PendingValue + Clone + Send + 'static>(
507 directory: StoreDir,
508 lock: coven_foundation::files::ClosedStoreLock,
509) -> Result<(crate::SyncFacts<P>, crate::UnsentWork), OpenError> {
510 let bugs = directory.bugs().clone();
511 finish_blocking(
512 blocking_completing("read the closed store's sync status", bugs, move || {
513 let _lock = lock;
514 let settings = directory.settings()?;
515 let mut database = coven_only(&directory.database_path(), true)?;
516 database.prepare_internal_schema()?;
517 let observer = CommitObserver::new(directory.bugs().clone());
518 let transfers = crate::transfers_in_flight::TransfersInFlight::new(observer.clone());
519 let network = crate::network_facts::NetworkFacts::new(observer);
520 database.serve_views(settings.device_id, &transfers, &network)?;
521 database.read_snapshot(|sql| Ok::<_, OpenError>((sql.sync_facts()?, sql.unsent()?)))
522 })
523 .await,
524 )
525}
526
527pub async fn discarded_store_name(
533 path: std::path::PathBuf,
534 bugs: &coven_foundation::task::BugSinkRef,
535) -> Result<String, DbError> {
536 finish_blocking(
537 blocking_completing("read the discarded store name", bugs.clone(), move || {
538 let database = coven_only(&path, true)?;
539 crate::store_state_tables::name(&database)
540 })
541 .await,
542 )
543}
544
545fn coven_only(path: &std::path::Path, read_only: bool) -> Result<DatabaseConnection, DbError> {
549 DatabaseConnection::open(
550 path,
551 read_only,
552 SqlAuthorization::new(&[]),
553 &crate::SqliteSettings { auto_vacuum: Some(AutoVacuum::Off), ..Default::default() },
557 &crate::SqlFunctions::default(),
558 )
559}
560
561#[cfg(test)]
562#[path = "database_builder_tests.rs"]
563mod tests;