summaryrefslogtreecommitdiffstats
path: root/third_party/rust/rusqlite/src/backup.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/rusqlite/src/backup.rs
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/rusqlite/src/backup.rs')
-rw-r--r--third_party/rust/rusqlite/src/backup.rs428
1 files changed, 428 insertions, 0 deletions
diff --git a/third_party/rust/rusqlite/src/backup.rs b/third_party/rust/rusqlite/src/backup.rs
new file mode 100644
index 0000000000..6da01fd052
--- /dev/null
+++ b/third_party/rust/rusqlite/src/backup.rs
@@ -0,0 +1,428 @@
+//! Online SQLite backup API.
+//!
+//! To create a [`Backup`], you must have two distinct [`Connection`]s - one
+//! for the source (which can be used while the backup is running) and one for
+//! the destination (which cannot). A [`Backup`] handle exposes three methods:
+//! [`step`](Backup::step) will attempt to back up a specified number of pages,
+//! [`progress`](Backup::progress) gets the current progress of the backup as of
+//! the last call to [`step`](Backup::step), and
+//! [`run_to_completion`](Backup::run_to_completion) will attempt to back up the
+//! entire source database, allowing you to specify how many pages are backed up
+//! at a time and how long the thread should sleep between chunks of pages.
+//!
+//! The following example is equivalent to "Example 2: Online Backup of a
+//! Running Database" from [SQLite's Online Backup API
+//! documentation](https://www.sqlite.org/backup.html).
+//!
+//! ```rust,no_run
+//! # use rusqlite::{backup, Connection, Result};
+//! # use std::path::Path;
+//! # use std::time;
+//!
+//! fn backup_db<P: AsRef<Path>>(
+//! src: &Connection,
+//! dst: P,
+//! progress: fn(backup::Progress),
+//! ) -> Result<()> {
+//! let mut dst = Connection::open(dst)?;
+//! let backup = backup::Backup::new(src, &mut dst)?;
+//! backup.run_to_completion(5, time::Duration::from_millis(250), Some(progress))
+//! }
+//! ```
+
+use std::marker::PhantomData;
+use std::path::Path;
+use std::ptr;
+
+use std::os::raw::c_int;
+use std::thread;
+use std::time::Duration;
+
+use crate::ffi;
+
+use crate::error::error_from_handle;
+use crate::{Connection, DatabaseName, Result};
+
+impl Connection {
+ /// Back up the `name` database to the given
+ /// destination path.
+ ///
+ /// If `progress` is not `None`, it will be called periodically
+ /// until the backup completes.
+ ///
+ /// For more fine-grained control over the backup process (e.g.,
+ /// to sleep periodically during the backup or to back up to an
+ /// already-open database connection), see the `backup` module.
+ ///
+ /// # Failure
+ ///
+ /// Will return `Err` if the destination path cannot be opened
+ /// or if the backup fails.
+ pub fn backup<P: AsRef<Path>>(
+ &self,
+ name: DatabaseName<'_>,
+ dst_path: P,
+ progress: Option<fn(Progress)>,
+ ) -> Result<()> {
+ use self::StepResult::{Busy, Done, Locked, More};
+ let mut dst = Connection::open(dst_path)?;
+ let backup = Backup::new_with_names(self, name, &mut dst, DatabaseName::Main)?;
+
+ let mut r = More;
+ while r == More {
+ r = backup.step(100)?;
+ if let Some(f) = progress {
+ f(backup.progress());
+ }
+ }
+
+ match r {
+ Done => Ok(()),
+ Busy => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY) }),
+ Locked => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED) }),
+ More => unreachable!(),
+ }
+ }
+
+ /// Restore the given source path into the
+ /// `name` database. If `progress` is not `None`, it will be
+ /// called periodically until the restore completes.
+ ///
+ /// For more fine-grained control over the restore process (e.g.,
+ /// to sleep periodically during the restore or to restore from an
+ /// already-open database connection), see the `backup` module.
+ ///
+ /// # Failure
+ ///
+ /// Will return `Err` if the destination path cannot be opened
+ /// or if the restore fails.
+ pub fn restore<P: AsRef<Path>, F: Fn(Progress)>(
+ &mut self,
+ name: DatabaseName<'_>,
+ src_path: P,
+ progress: Option<F>,
+ ) -> Result<()> {
+ use self::StepResult::{Busy, Done, Locked, More};
+ let src = Connection::open(src_path)?;
+ let restore = Backup::new_with_names(&src, DatabaseName::Main, self, name)?;
+
+ let mut r = More;
+ let mut busy_count = 0_i32;
+ 'restore_loop: while r == More || r == Busy {
+ r = restore.step(100)?;
+ if let Some(ref f) = progress {
+ f(restore.progress());
+ }
+ if r == Busy {
+ busy_count += 1;
+ if busy_count >= 3 {
+ break 'restore_loop;
+ }
+ thread::sleep(Duration::from_millis(100));
+ }
+ }
+
+ match r {
+ Done => Ok(()),
+ Busy => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY) }),
+ Locked => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED) }),
+ More => unreachable!(),
+ }
+ }
+}
+
+/// Possible successful results of calling
+/// [`Backup::step`].
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum StepResult {
+ /// The backup is complete.
+ Done,
+
+ /// The step was successful but there are still more pages that need to be
+ /// backed up.
+ More,
+
+ /// The step failed because appropriate locks could not be acquired. This is
+ /// not a fatal error - the step can be retried.
+ Busy,
+
+ /// The step failed because the source connection was writing to the
+ /// database. This is not a fatal error - the step can be retried.
+ Locked,
+}
+
+/// Struct specifying the progress of a backup. The
+/// percentage completion can be calculated as `(pagecount - remaining) /
+/// pagecount`. The progress of a backup is as of the last call to
+/// [`step`](Backup::step) - if the source database is modified after a call to
+/// [`step`](Backup::step), the progress value will become outdated and
+/// potentially incorrect.
+#[derive(Copy, Clone, Debug)]
+pub struct Progress {
+ /// Number of pages in the source database that still need to be backed up.
+ pub remaining: c_int,
+ /// Total number of pages in the source database.
+ pub pagecount: c_int,
+}
+
+/// A handle to an online backup.
+pub struct Backup<'a, 'b> {
+ phantom_from: PhantomData<&'a Connection>,
+ to: &'b Connection,
+ b: *mut ffi::sqlite3_backup,
+}
+
+impl Backup<'_, '_> {
+ /// Attempt to create a new handle that will allow backups from `from` to
+ /// `to`. Note that `to` is a `&mut` - this is because SQLite forbids any
+ /// API calls on the destination of a backup while the backup is taking
+ /// place.
+ ///
+ /// # Failure
+ ///
+ /// Will return `Err` if the underlying `sqlite3_backup_init` call returns
+ /// `NULL`.
+ #[inline]
+ pub fn new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
+ Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main)
+ }
+
+ /// Attempt to create a new handle that will allow backups from the
+ /// `from_name` database of `from` to the `to_name` database of `to`. Note
+ /// that `to` is a `&mut` - this is because SQLite forbids any API calls on
+ /// the destination of a backup while the backup is taking place.
+ ///
+ /// # Failure
+ ///
+ /// Will return `Err` if the underlying `sqlite3_backup_init` call returns
+ /// `NULL`.
+ pub fn new_with_names<'a, 'b>(
+ from: &'a Connection,
+ from_name: DatabaseName<'_>,
+ to: &'b mut Connection,
+ to_name: DatabaseName<'_>,
+ ) -> Result<Backup<'a, 'b>> {
+ let to_name = to_name.as_cstring()?;
+ let from_name = from_name.as_cstring()?;
+
+ let to_db = to.db.borrow_mut().db;
+
+ let b = unsafe {
+ let b = ffi::sqlite3_backup_init(
+ to_db,
+ to_name.as_ptr(),
+ from.db.borrow_mut().db,
+ from_name.as_ptr(),
+ );
+ if b.is_null() {
+ return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
+ }
+ b
+ };
+
+ Ok(Backup {
+ phantom_from: PhantomData,
+ to,
+ b,
+ })
+ }
+
+ /// Gets the progress of the backup as of the last call to
+ /// [`step`](Backup::step).
+ #[inline]
+ #[must_use]
+ pub fn progress(&self) -> Progress {
+ unsafe {
+ Progress {
+ remaining: ffi::sqlite3_backup_remaining(self.b),
+ pagecount: ffi::sqlite3_backup_pagecount(self.b),
+ }
+ }
+ }
+
+ /// Attempts to back up the given number of pages. If `num_pages` is
+ /// negative, will attempt to back up all remaining pages. This will hold a
+ /// lock on the source database for the duration, so it is probably not
+ /// what you want for databases that are currently active (see
+ /// [`run_to_completion`](Backup::run_to_completion) for a better
+ /// alternative).
+ ///
+ /// # Failure
+ ///
+ /// Will return `Err` if the underlying `sqlite3_backup_step` call returns
+ /// an error code other than `DONE`, `OK`, `BUSY`, or `LOCKED`. `BUSY` and
+ /// `LOCKED` are transient errors and are therefore returned as possible
+ /// `Ok` values.
+ #[inline]
+ pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
+ use self::StepResult::{Busy, Done, Locked, More};
+
+ let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) };
+ match rc {
+ ffi::SQLITE_DONE => Ok(Done),
+ ffi::SQLITE_OK => Ok(More),
+ ffi::SQLITE_BUSY => Ok(Busy),
+ ffi::SQLITE_LOCKED => Ok(Locked),
+ _ => self.to.decode_result(rc).map(|_| More),
+ }
+ }
+
+ /// Attempts to run the entire backup. Will call
+ /// [`step(pages_per_step)`](Backup::step) as many times as necessary,
+ /// sleeping for `pause_between_pages` between each call to give the
+ /// source database time to process any pending queries. This is a
+ /// direct implementation of "Example 2: Online Backup of a Running
+ /// Database" from [SQLite's Online Backup API documentation](https://www.sqlite.org/backup.html).
+ ///
+ /// If `progress` is not `None`, it will be called after each step with the
+ /// current progress of the backup. Note that is possible the progress may
+ /// not change if the step returns `Busy` or `Locked` even though the
+ /// backup is still running.
+ ///
+ /// # Failure
+ ///
+ /// Will return `Err` if any of the calls to [`step`](Backup::step) return
+ /// `Err`.
+ pub fn run_to_completion(
+ &self,
+ pages_per_step: c_int,
+ pause_between_pages: Duration,
+ progress: Option<fn(Progress)>,
+ ) -> Result<()> {
+ use self::StepResult::{Busy, Done, Locked, More};
+
+ assert!(pages_per_step > 0, "pages_per_step must be positive");
+
+ loop {
+ let r = self.step(pages_per_step)?;
+ if let Some(progress) = progress {
+ progress(self.progress());
+ }
+ match r {
+ More | Busy | Locked => thread::sleep(pause_between_pages),
+ Done => return Ok(()),
+ }
+ }
+ }
+}
+
+impl Drop for Backup<'_, '_> {
+ #[inline]
+ fn drop(&mut self) {
+ unsafe { ffi::sqlite3_backup_finish(self.b) };
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::Backup;
+ use crate::{Connection, DatabaseName, Result};
+ use std::time::Duration;
+
+ #[test]
+ fn test_backup() -> Result<()> {
+ let src = Connection::open_in_memory()?;
+ let sql = "BEGIN;
+ CREATE TABLE foo(x INTEGER);
+ INSERT INTO foo VALUES(42);
+ END;";
+ src.execute_batch(sql)?;
+
+ let mut dst = Connection::open_in_memory()?;
+
+ {
+ let backup = Backup::new(&src, &mut dst)?;
+ backup.step(-1)?;
+ }
+
+ let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
+ assert_eq!(42, the_answer);
+
+ src.execute_batch("INSERT INTO foo VALUES(43)")?;
+
+ {
+ let backup = Backup::new(&src, &mut dst)?;
+ backup.run_to_completion(5, Duration::from_millis(250), None)?;
+ }
+
+ let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
+ assert_eq!(42 + 43, the_answer);
+ Ok(())
+ }
+
+ #[test]
+ fn test_backup_temp() -> Result<()> {
+ let src = Connection::open_in_memory()?;
+ let sql = "BEGIN;
+ CREATE TEMPORARY TABLE foo(x INTEGER);
+ INSERT INTO foo VALUES(42);
+ END;";
+ src.execute_batch(sql)?;
+
+ let mut dst = Connection::open_in_memory()?;
+
+ {
+ let backup =
+ Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)?;
+ backup.step(-1)?;
+ }
+
+ let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
+ assert_eq!(42, the_answer);
+
+ src.execute_batch("INSERT INTO foo VALUES(43)")?;
+
+ {
+ let backup =
+ Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)?;
+ backup.run_to_completion(5, Duration::from_millis(250), None)?;
+ }
+
+ let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
+ assert_eq!(42 + 43, the_answer);
+ Ok(())
+ }
+
+ #[test]
+ fn test_backup_attached() -> Result<()> {
+ let src = Connection::open_in_memory()?;
+ let sql = "ATTACH DATABASE ':memory:' AS my_attached;
+ BEGIN;
+ CREATE TABLE my_attached.foo(x INTEGER);
+ INSERT INTO my_attached.foo VALUES(42);
+ END;";
+ src.execute_batch(sql)?;
+
+ let mut dst = Connection::open_in_memory()?;
+
+ {
+ let backup = Backup::new_with_names(
+ &src,
+ DatabaseName::Attached("my_attached"),
+ &mut dst,
+ DatabaseName::Main,
+ )?;
+ backup.step(-1)?;
+ }
+
+ let the_answer: i64 = dst.query_row("SELECT x FROM foo", [], |r| r.get(0))?;
+ assert_eq!(42, the_answer);
+
+ src.execute_batch("INSERT INTO foo VALUES(43)")?;
+
+ {
+ let backup = Backup::new_with_names(
+ &src,
+ DatabaseName::Attached("my_attached"),
+ &mut dst,
+ DatabaseName::Main,
+ )?;
+ backup.run_to_completion(5, Duration::from_millis(250), None)?;
+ }
+
+ let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
+ assert_eq!(42 + 43, the_answer);
+ Ok(())
+ }
+}