use rusqlite::{ Connection, backup::{self}, }; use sqlx::sqlite::{LockedSqliteHandle, SqlitePool}; pub struct Builder<'p> { from: &'p SqlitePool, } impl<'p> Builder<'p> { pub fn to(self, to: &'p SqlitePool) -> Backup<'p> { Backup { from: self.from, to, } } } pub struct Backup<'p> { from: &'p SqlitePool, to: &'p SqlitePool, } impl<'p> Backup<'p> { pub fn from(from: &'p SqlitePool) -> Builder<'p> { Builder { from } } } impl Backup<'_> { pub async fn backup(&mut self) -> Result<(), Error> { let mut to = self.to.acquire().await?; let mut to = Self::connection(&mut to.lock_handle().await?)?; let mut from = self.from.acquire().await?; let from = Self::connection(&mut from.lock_handle().await?)?; let backup = backup::Backup::new(&from, &mut to)?; loop { use rusqlite::backup::StepResult::{Busy, Done, Locked, More}; match backup.step(-1)? { Done => break Ok(()), // In the system as actually written, yielding does nothing: this is the // only task actually in flight at the time. However, that may change; if // it does, we _definitely_ want to give other tasks an opportunity to // make progress before we retry. More => tokio::task::consume_budget().await, // If another task is actually using the DB, then it _definitely_ needs // an opportunity to make progress before we retry. Busy | Locked => tokio::task::yield_now().await, // As of this writing, this arm is formally unreachable: all of the enum // constants that actually exist in rusqlite are matched above. However, // they've reserved the prerogative to add additional arms without a // breaking change by making the type as non-exhaustive; if we get one, // stop immediately as there's no way to guess what the correct handling // will be, a priori. other => panic!("unexpected backup step result: {other:?}"), } } } fn connection(handle: &mut LockedSqliteHandle) -> Result { let handle = handle.as_raw_handle(); let connection = unsafe { Connection::from_handle(handle.as_ptr()) }?; Ok(connection) } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] Sqlx(#[from] sqlx::Error), #[error(transparent)] Rusqlite(#[from] rusqlite::Error), }