diff options
Diffstat (limited to 'src/db/backup.rs')
| -rw-r--r-- | src/db/backup.rs | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/src/db/backup.rs b/src/db/backup.rs new file mode 100644 index 0000000..bb36aea --- /dev/null +++ b/src/db/backup.rs @@ -0,0 +1,76 @@ +use rusqlite::{ + backup::{self}, + Connection, +}; +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<'p> Backup<'p> { + 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<Connection, rusqlite::Error> { + 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), +} |
