mirror of
				https://github.com/isar/rusqlite.git
				synced 2025-11-04 08:08:55 +08:00 
			
		
		
		
	Merge pull request #93 from jgallagher/online-backup
Initial implementation of the online backup API.
This commit is contained in:
		@@ -8,7 +8,11 @@ env:
 | 
			
		||||
script:
 | 
			
		||||
    - cargo build
 | 
			
		||||
    - cargo test
 | 
			
		||||
    - cargo doc --no-deps
 | 
			
		||||
    - cargo test --features backup
 | 
			
		||||
    - cargo test --features load_extension
 | 
			
		||||
    - cargo test --features trace
 | 
			
		||||
    - cargo test --features "backup load_extension trace"
 | 
			
		||||
    - cargo doc --no-deps --features "backup load_extension trace"
 | 
			
		||||
 | 
			
		||||
after_success: |
 | 
			
		||||
    [ $TRAVIS_BRANCH = master ] &&
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ name = "rusqlite"
 | 
			
		||||
 | 
			
		||||
[features]
 | 
			
		||||
load_extension = ["libsqlite3-sys/load_extension"]
 | 
			
		||||
backup = []
 | 
			
		||||
trace = []
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,7 @@
 | 
			
		||||
# Version UPCOMING (TBD)
 | 
			
		||||
 | 
			
		||||
* Adds `backup` feature that exposes SQLite's online backup API.
 | 
			
		||||
 | 
			
		||||
# Version 0.5.0 (2015-12-08)
 | 
			
		||||
 | 
			
		||||
* Adds `trace` feature that allows the use of SQLite's logging, tracing, and profiling hooks.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										414
									
								
								src/backup.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										414
									
								
								src/backup.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,414 @@
 | 
			
		||||
//! Online SQLite backup API.
 | 
			
		||||
//!
 | 
			
		||||
//! To create a `Backup`, you must have two distinct `SqliteConnection`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` will attempt to back up a specified number of pages, `progress` gets
 | 
			
		||||
//! the current progress of the backup as of the last call to `step`, and
 | 
			
		||||
//! `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, SqliteConnection, SqliteResult};
 | 
			
		||||
//! # use std::path::Path;
 | 
			
		||||
//! # use std::time;
 | 
			
		||||
//!
 | 
			
		||||
//! fn backupDb<P: AsRef<Path>>(src: &SqliteConnection, dst: P, progress: fn(backup::Progress))
 | 
			
		||||
//!     -> SqliteResult<()> {
 | 
			
		||||
//!     let mut dst = try!(SqliteConnection::open(dst));
 | 
			
		||||
//!     let backup = try!(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 libc::c_int;
 | 
			
		||||
use std::thread;
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
use ffi;
 | 
			
		||||
 | 
			
		||||
use {DatabaseName, SqliteConnection, SqliteError, SqliteResult};
 | 
			
		||||
 | 
			
		||||
impl SqliteConnection {
 | 
			
		||||
    /// 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)>)
 | 
			
		||||
                                  -> SqliteResult<()> {
 | 
			
		||||
        use self::StepResult::{More, Done, Busy, Locked};
 | 
			
		||||
        let mut dst = try!(SqliteConnection::open(dst_path));
 | 
			
		||||
        let backup = try!(Backup::new_with_names(self, name, &mut dst, DatabaseName::Main));
 | 
			
		||||
 | 
			
		||||
        let mut r = More;
 | 
			
		||||
        while r == More {
 | 
			
		||||
            r = try!(backup.step(100));
 | 
			
		||||
            if let Some(f) = progress {
 | 
			
		||||
                f(backup.progress());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        match r {
 | 
			
		||||
            Done => Ok(()),
 | 
			
		||||
            Busy => Err(SqliteError::from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)),
 | 
			
		||||
            Locked => Err(SqliteError::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>>(&mut self,
 | 
			
		||||
                                   name: DatabaseName,
 | 
			
		||||
                                   src_path: P,
 | 
			
		||||
                                   progress: Option<fn(Progress)>)
 | 
			
		||||
                                   -> SqliteResult<()> {
 | 
			
		||||
        use self::StepResult::{More, Done, Busy, Locked};
 | 
			
		||||
        let src = try!(SqliteConnection::open(src_path));
 | 
			
		||||
        let restore = try!(Backup::new_with_names(&src, DatabaseName::Main, self, name));
 | 
			
		||||
 | 
			
		||||
        let mut r = More;
 | 
			
		||||
        let mut busy_count = 0i32;
 | 
			
		||||
        'restore_loop: while r == More || r == Busy {
 | 
			
		||||
            r = try!(restore.step(100));
 | 
			
		||||
            if let Some(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(SqliteError::from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)),
 | 
			
		||||
            Locked => Err(SqliteError::from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)),
 | 
			
		||||
            More => unreachable!(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Possible successful results of calling `Backup::step`.
 | 
			
		||||
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
 | 
			
		||||
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 aquired. 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` - if the source database is
 | 
			
		||||
/// modified after a call to `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 ()>,
 | 
			
		||||
    phantom_to: PhantomData<&'b ()>,
 | 
			
		||||
    b: *mut ffi::sqlite3_backup,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a, 'b> Backup<'a, 'b> {
 | 
			
		||||
    /// 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`.
 | 
			
		||||
    pub fn new(from: &'a SqliteConnection,
 | 
			
		||||
               to: &'b mut SqliteConnection)
 | 
			
		||||
               -> SqliteResult<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(from: &'a SqliteConnection,
 | 
			
		||||
                          from_name: DatabaseName,
 | 
			
		||||
                          to: &'b mut SqliteConnection,
 | 
			
		||||
                          to_name: DatabaseName)
 | 
			
		||||
                          -> SqliteResult<Backup<'a, 'b>> {
 | 
			
		||||
        let to_name = try!(to_name.to_cstring());
 | 
			
		||||
        let from_name = try!(from_name.to_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(SqliteError::from_handle(to_db, ffi::sqlite3_errcode(to_db)));
 | 
			
		||||
            }
 | 
			
		||||
            b
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Ok(Backup {
 | 
			
		||||
            phantom_from: PhantomData,
 | 
			
		||||
            phantom_to: PhantomData,
 | 
			
		||||
            b: b,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Gets the progress of the backup as of the last call to `step`.
 | 
			
		||||
    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` 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.
 | 
			
		||||
    pub fn step(&self, num_pages: c_int) -> SqliteResult<StepResult> {
 | 
			
		||||
        use self::StepResult::{Done, More, Busy, Locked};
 | 
			
		||||
 | 
			
		||||
        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),
 | 
			
		||||
            rc => {
 | 
			
		||||
                Err(SqliteError {
 | 
			
		||||
                    code: rc,
 | 
			
		||||
                    message: ffi::code_to_str(rc).into(),
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Attempts to run the entire backup. Will call `step(pages_per_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` return `Err`.
 | 
			
		||||
    pub fn run_to_completion(&self,
 | 
			
		||||
                             pages_per_step: c_int,
 | 
			
		||||
                             pause_between_pages: Duration,
 | 
			
		||||
                             progress: Option<fn(Progress)>)
 | 
			
		||||
                             -> SqliteResult<()> {
 | 
			
		||||
        use self::StepResult::{Done, More, Busy, Locked};
 | 
			
		||||
 | 
			
		||||
        assert!(pages_per_step > 0, "pages_per_step must be positive");
 | 
			
		||||
 | 
			
		||||
        loop {
 | 
			
		||||
            let r = try!(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<'a, 'b> Drop for Backup<'a, 'b> {
 | 
			
		||||
    fn drop(&mut self) {
 | 
			
		||||
        unsafe { ffi::sqlite3_backup_finish(self.b) };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use {SqliteConnection, DatabaseName};
 | 
			
		||||
    use std::time::Duration;
 | 
			
		||||
    use super::Backup;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    #[cfg_attr(rustfmt, rustfmt_skip)]
 | 
			
		||||
    fn test_backup() {
 | 
			
		||||
        let src = SqliteConnection::open_in_memory().unwrap();
 | 
			
		||||
        let sql = "BEGIN;
 | 
			
		||||
                   CREATE TABLE foo(x INTEGER);
 | 
			
		||||
                   INSERT INTO foo VALUES(42);
 | 
			
		||||
                   END;";
 | 
			
		||||
        src.execute_batch(sql).unwrap();
 | 
			
		||||
 | 
			
		||||
        let mut dst = SqliteConnection::open_in_memory().unwrap();
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            let backup = Backup::new(&src, &mut dst).unwrap();
 | 
			
		||||
            backup.step(-1).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let the_answer = dst.query_row("SELECT x FROM foo", &[], |r| r.get::<i64>(0)).unwrap();
 | 
			
		||||
        assert_eq!(42, the_answer);
 | 
			
		||||
 | 
			
		||||
        src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            let backup = Backup::new(&src, &mut dst).unwrap();
 | 
			
		||||
            backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let the_answer = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get::<i64>(0)).unwrap();
 | 
			
		||||
        assert_eq!(42 + 43, the_answer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    #[cfg_attr(rustfmt, rustfmt_skip)]
 | 
			
		||||
    fn test_backup_temp() {
 | 
			
		||||
        let src = SqliteConnection::open_in_memory().unwrap();
 | 
			
		||||
        let sql = "BEGIN;
 | 
			
		||||
                   CREATE TEMPORARY TABLE foo(x INTEGER);
 | 
			
		||||
                   INSERT INTO foo VALUES(42);
 | 
			
		||||
                   END;";
 | 
			
		||||
        src.execute_batch(sql).unwrap();
 | 
			
		||||
 | 
			
		||||
        let mut dst = SqliteConnection::open_in_memory().unwrap();
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            let backup = Backup::new_with_names(&src,
 | 
			
		||||
                                                DatabaseName::Temp,
 | 
			
		||||
                                                &mut dst,
 | 
			
		||||
                                                DatabaseName::Main)
 | 
			
		||||
                             .unwrap();
 | 
			
		||||
            backup.step(-1).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let the_answer = dst.query_row("SELECT x FROM foo", &[], |r| r.get::<i64>(0)).unwrap();
 | 
			
		||||
        assert_eq!(42, the_answer);
 | 
			
		||||
 | 
			
		||||
        src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            let backup = Backup::new_with_names(&src,
 | 
			
		||||
                                                DatabaseName::Temp,
 | 
			
		||||
                                                &mut dst,
 | 
			
		||||
                                                DatabaseName::Main)
 | 
			
		||||
                             .unwrap();
 | 
			
		||||
            backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let the_answer = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get::<i64>(0)).unwrap();
 | 
			
		||||
        assert_eq!(42 + 43, the_answer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    #[cfg_attr(rustfmt, rustfmt_skip)]
 | 
			
		||||
    fn test_backup_attached() {
 | 
			
		||||
        let src = SqliteConnection::open_in_memory().unwrap();
 | 
			
		||||
        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).unwrap();
 | 
			
		||||
 | 
			
		||||
        let mut dst = SqliteConnection::open_in_memory().unwrap();
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            let backup = Backup::new_with_names(&src,
 | 
			
		||||
                                                DatabaseName::Attached("my_attached"),
 | 
			
		||||
                                                &mut dst,
 | 
			
		||||
                                                DatabaseName::Main)
 | 
			
		||||
                             .unwrap();
 | 
			
		||||
            backup.step(-1).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let the_answer = dst.query_row("SELECT x FROM foo", &[], |r| r.get::<i64>(0)).unwrap();
 | 
			
		||||
        assert_eq!(42, the_answer);
 | 
			
		||||
 | 
			
		||||
        src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            let backup = Backup::new_with_names(&src,
 | 
			
		||||
                                                DatabaseName::Attached("my_attached"),
 | 
			
		||||
                                                &mut dst,
 | 
			
		||||
                                                DatabaseName::Main)
 | 
			
		||||
                             .unwrap();
 | 
			
		||||
            backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let the_answer = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get::<i64>(0)).unwrap();
 | 
			
		||||
        assert_eq!(42 + 43, the_answer);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								src/lib.rs
									
									
									
									
									
								
							@@ -82,6 +82,7 @@ pub mod types;
 | 
			
		||||
mod transaction;
 | 
			
		||||
#[cfg(feature = "load_extension")] mod load_extension_guard;
 | 
			
		||||
#[cfg(feature = "trace")] pub mod trace;
 | 
			
		||||
#[cfg(feature = "backup")] pub mod backup;
 | 
			
		||||
 | 
			
		||||
/// A typedef of the result returned by many methods.
 | 
			
		||||
pub type SqliteResult<T> = Result<T, SqliteError>;
 | 
			
		||||
@@ -142,6 +143,32 @@ fn path_to_cstring(p: &Path) -> SqliteResult<CString> {
 | 
			
		||||
    str_to_cstring(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Name for a database within a SQLite connection.
 | 
			
		||||
pub enum DatabaseName<'a> {
 | 
			
		||||
    /// The main database.
 | 
			
		||||
    Main,
 | 
			
		||||
 | 
			
		||||
    /// The temporary database (e.g., any "CREATE TEMPORARY TABLE" tables).
 | 
			
		||||
    Temp,
 | 
			
		||||
 | 
			
		||||
    /// A database that has been attached via "ATTACH DATABASE ...".
 | 
			
		||||
    Attached(&'a str),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Currently DatabaseName is only used by the backup mod, so hide this (private)
 | 
			
		||||
// impl to avoid dead code warnings.
 | 
			
		||||
#[cfg(feature = "backup")]
 | 
			
		||||
impl<'a> DatabaseName<'a> {
 | 
			
		||||
    fn to_cstring(self) -> SqliteResult<CString> {
 | 
			
		||||
        use self::DatabaseName::{Main, Temp, Attached};
 | 
			
		||||
        match self {
 | 
			
		||||
            Main        => str_to_cstring("main"),
 | 
			
		||||
            Temp        => str_to_cstring("temp"),
 | 
			
		||||
            Attached(s) => str_to_cstring(s),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A connection to a SQLite database.
 | 
			
		||||
pub struct SqliteConnection {
 | 
			
		||||
    db: RefCell<InnerSqliteConnection>,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user