2015-12-09 13:19:59 +08:00
|
|
|
//! Online SQLite backup API.
|
|
|
|
//!
|
2015-12-13 02:50:12 +08:00
|
|
|
//! To create a `Backup`, you must have two distinct `Connection`s - one
|
2015-12-09 13:19:59 +08:00
|
|
|
//! 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
|
2015-12-13 03:06:03 +08:00
|
|
|
//! # use rusqlite::{backup, Connection, Result};
|
2015-12-09 13:19:59 +08:00
|
|
|
//! # use std::path::Path;
|
|
|
|
//! # use std::time;
|
|
|
|
//!
|
2015-12-13 02:50:12 +08:00
|
|
|
//! fn backupDb<P: AsRef<Path>>(src: &Connection, dst: P, progress: fn(backup::Progress))
|
2015-12-13 03:06:03 +08:00
|
|
|
//! -> Result<()> {
|
2015-12-13 02:50:12 +08:00
|
|
|
//! let mut dst = try!(Connection::open(dst));
|
2015-12-09 13:19:59 +08:00
|
|
|
//! 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;
|
2015-12-11 05:06:16 +08:00
|
|
|
use std::path::Path;
|
|
|
|
use std::ptr;
|
2015-12-09 13:19:59 +08:00
|
|
|
|
|
|
|
use libc::c_int;
|
|
|
|
use std::thread;
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
use ffi;
|
|
|
|
|
2015-12-13 14:04:09 +08:00
|
|
|
use {DatabaseName, Connection, Result};
|
|
|
|
use error::{error_from_sqlite_code, error_from_handle};
|
2015-12-09 13:19:59 +08:00
|
|
|
|
2015-12-13 02:50:12 +08:00
|
|
|
impl Connection {
|
2015-12-11 05:06:16 +08:00
|
|
|
/// 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)>)
|
2015-12-13 03:06:03 +08:00
|
|
|
-> Result<()> {
|
2015-12-11 05:06:16 +08:00
|
|
|
use self::StepResult::{More, Done, Busy, Locked};
|
2015-12-13 02:50:12 +08:00
|
|
|
let mut dst = try!(Connection::open(dst_path));
|
2015-12-11 05:06:16 +08:00
|
|
|
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(()),
|
2015-12-13 14:04:09 +08:00
|
|
|
Busy => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)),
|
|
|
|
Locked => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)),
|
2015-12-11 05:06:16 +08:00
|
|
|
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)>)
|
2015-12-13 03:06:03 +08:00
|
|
|
-> Result<()> {
|
2015-12-11 05:06:16 +08:00
|
|
|
use self::StepResult::{More, Done, Busy, Locked};
|
2015-12-13 02:50:12 +08:00
|
|
|
let src = try!(Connection::open(src_path));
|
2015-12-11 05:06:16 +08:00
|
|
|
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(()),
|
2015-12-13 14:04:09 +08:00
|
|
|
Busy => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY)),
|
|
|
|
Locked => Err(error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED)),
|
2015-12-11 05:06:16 +08:00
|
|
|
More => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-09 13:19:59 +08:00
|
|
|
/// Possible successful results of calling `Backup::step`.
|
2015-12-11 05:06:16 +08:00
|
|
|
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
|
2015-12-09 13:19:59 +08:00
|
|
|
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`.
|
2016-02-03 02:12:00 +08:00
|
|
|
pub fn new(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
|
2015-12-10 05:27:18 +08:00
|
|
|
Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main)
|
2015-12-09 13:19:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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`.
|
2015-12-13 02:50:12 +08:00
|
|
|
pub fn new_with_names(from: &'a Connection,
|
2015-12-11 02:13:15 +08:00
|
|
|
from_name: DatabaseName,
|
2015-12-13 02:50:12 +08:00
|
|
|
to: &'b mut Connection,
|
2015-12-11 02:13:15 +08:00
|
|
|
to_name: DatabaseName)
|
2015-12-13 03:06:03 +08:00
|
|
|
-> Result<Backup<'a, 'b>> {
|
2015-12-09 13:19:59 +08:00
|
|
|
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 {
|
2015-12-11 02:13:15 +08:00
|
|
|
let b = ffi::sqlite3_backup_init(to_db,
|
|
|
|
to_name.as_ptr(),
|
|
|
|
from.db.borrow_mut().db,
|
|
|
|
from_name.as_ptr());
|
2015-12-09 13:19:59 +08:00
|
|
|
if b.is_null() {
|
2015-12-13 14:04:09 +08:00
|
|
|
return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
|
2015-12-09 13:19:59 +08:00
|
|
|
}
|
|
|
|
b
|
|
|
|
};
|
|
|
|
|
2015-12-11 02:13:15 +08:00
|
|
|
Ok(Backup {
|
2015-12-09 13:19:59 +08:00
|
|
|
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 {
|
2015-12-11 02:13:15 +08:00
|
|
|
Progress {
|
2015-12-09 13:19:59 +08:00
|
|
|
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.
|
2015-12-13 03:06:03 +08:00
|
|
|
pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
|
2015-12-09 13:19:59 +08:00
|
|
|
use self::StepResult::{Done, More, Busy, Locked};
|
|
|
|
|
2015-12-11 02:13:15 +08:00
|
|
|
let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) };
|
2015-12-09 13:19:59 +08:00
|
|
|
match rc {
|
2015-12-11 02:13:15 +08:00
|
|
|
ffi::SQLITE_DONE => Ok(Done),
|
|
|
|
ffi::SQLITE_OK => Ok(More),
|
|
|
|
ffi::SQLITE_BUSY => Ok(Busy),
|
2015-12-09 13:19:59 +08:00
|
|
|
ffi::SQLITE_LOCKED => Ok(Locked),
|
2015-12-13 14:04:09 +08:00
|
|
|
_ => Err(error_from_sqlite_code(rc, None)),
|
2015-12-09 13:19:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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`.
|
2015-12-11 02:13:15 +08:00
|
|
|
pub fn run_to_completion(&self,
|
|
|
|
pages_per_step: c_int,
|
|
|
|
pause_between_pages: Duration,
|
|
|
|
progress: Option<fn(Progress)>)
|
2015-12-13 03:06:03 +08:00
|
|
|
-> Result<()> {
|
2015-12-09 13:19:59 +08:00
|
|
|
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 {
|
2015-12-13 02:50:12 +08:00
|
|
|
use {Connection, DatabaseName};
|
2015-12-09 13:19:59 +08:00
|
|
|
use std::time::Duration;
|
2015-12-10 05:27:18 +08:00
|
|
|
use super::Backup;
|
2015-12-09 13:19:59 +08:00
|
|
|
|
|
|
|
#[test]
|
2015-12-11 02:13:15 +08:00
|
|
|
#[cfg_attr(rustfmt, rustfmt_skip)]
|
2015-12-09 13:19:59 +08:00
|
|
|
fn test_backup() {
|
2015-12-13 02:50:12 +08:00
|
|
|
let src = Connection::open_in_memory().unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
let sql = "BEGIN;
|
|
|
|
CREATE TABLE foo(x INTEGER);
|
|
|
|
INSERT INTO foo VALUES(42);
|
|
|
|
END;";
|
|
|
|
src.execute_batch(sql).unwrap();
|
|
|
|
|
2015-12-13 02:50:12 +08:00
|
|
|
let mut dst = Connection::open_in_memory().unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
|
|
|
|
{
|
|
|
|
let backup = Backup::new(&src, &mut dst).unwrap();
|
|
|
|
backup.step(-1).unwrap();
|
|
|
|
}
|
|
|
|
|
2016-01-02 19:13:37 +08:00
|
|
|
let the_answer: i64 = dst.query_row("SELECT x FROM foo", &[], |r| r.get(0)).unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2016-01-02 19:13:37 +08:00
|
|
|
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
assert_eq!(42 + 43, the_answer);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2015-12-11 02:13:15 +08:00
|
|
|
#[cfg_attr(rustfmt, rustfmt_skip)]
|
2015-12-09 13:19:59 +08:00
|
|
|
fn test_backup_temp() {
|
2015-12-13 02:50:12 +08:00
|
|
|
let src = Connection::open_in_memory().unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
let sql = "BEGIN;
|
|
|
|
CREATE TEMPORARY TABLE foo(x INTEGER);
|
|
|
|
INSERT INTO foo VALUES(42);
|
|
|
|
END;";
|
|
|
|
src.execute_batch(sql).unwrap();
|
|
|
|
|
2015-12-13 02:50:12 +08:00
|
|
|
let mut dst = Connection::open_in_memory().unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
|
|
|
|
{
|
2015-12-11 02:13:15 +08:00
|
|
|
let backup = Backup::new_with_names(&src,
|
|
|
|
DatabaseName::Temp,
|
|
|
|
&mut dst,
|
|
|
|
DatabaseName::Main)
|
|
|
|
.unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
backup.step(-1).unwrap();
|
|
|
|
}
|
|
|
|
|
2016-01-02 19:13:37 +08:00
|
|
|
let the_answer: i64 = dst.query_row("SELECT x FROM foo", &[], |r| r.get(0)).unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
assert_eq!(42, the_answer);
|
|
|
|
|
|
|
|
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
|
|
|
|
|
|
|
|
{
|
2015-12-11 02:13:15 +08:00
|
|
|
let backup = Backup::new_with_names(&src,
|
|
|
|
DatabaseName::Temp,
|
|
|
|
&mut dst,
|
|
|
|
DatabaseName::Main)
|
|
|
|
.unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
|
|
|
|
}
|
|
|
|
|
2016-01-02 19:13:37 +08:00
|
|
|
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
assert_eq!(42 + 43, the_answer);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2015-12-11 02:13:15 +08:00
|
|
|
#[cfg_attr(rustfmt, rustfmt_skip)]
|
2015-12-09 13:19:59 +08:00
|
|
|
fn test_backup_attached() {
|
2015-12-13 02:50:12 +08:00
|
|
|
let src = Connection::open_in_memory().unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
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();
|
|
|
|
|
2015-12-13 02:50:12 +08:00
|
|
|
let mut dst = Connection::open_in_memory().unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
|
|
|
|
{
|
2015-12-11 02:13:15 +08:00
|
|
|
let backup = Backup::new_with_names(&src,
|
|
|
|
DatabaseName::Attached("my_attached"),
|
|
|
|
&mut dst,
|
|
|
|
DatabaseName::Main)
|
|
|
|
.unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
backup.step(-1).unwrap();
|
|
|
|
}
|
|
|
|
|
2016-01-02 19:13:37 +08:00
|
|
|
let the_answer: i64 = dst.query_row("SELECT x FROM foo", &[], |r| r.get(0)).unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
assert_eq!(42, the_answer);
|
|
|
|
|
|
|
|
src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
|
|
|
|
|
|
|
|
{
|
2015-12-11 02:13:15 +08:00
|
|
|
let backup = Backup::new_with_names(&src,
|
|
|
|
DatabaseName::Attached("my_attached"),
|
|
|
|
&mut dst,
|
|
|
|
DatabaseName::Main)
|
|
|
|
.unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
backup.run_to_completion(5, Duration::from_millis(250), None).unwrap();
|
|
|
|
}
|
|
|
|
|
2016-01-02 19:13:37 +08:00
|
|
|
let the_answer: i64 = dst.query_row("SELECT SUM(x) FROM foo", &[], |r| r.get(0)).unwrap();
|
2015-12-09 13:19:59 +08:00
|
|
|
assert_eq!(42 + 43, the_answer);
|
|
|
|
}
|
|
|
|
}
|