rusqlite/
backup.rs

1//! Online SQLite backup API.
2//!
3//! To create a [`Backup`], you must have two distinct [`Connection`]s - one
4//! for the source (which can be used while the backup is running) and one for
5//! the destination (which cannot).  A [`Backup`] handle exposes three methods:
6//! [`step`](Backup::step) will attempt to back up a specified number of pages,
7//! [`progress`](Backup::progress) gets the current progress of the backup as of
8//! the last call to [`step`](Backup::step), and
9//! [`run_to_completion`](Backup::run_to_completion) will attempt to back up the
10//! entire source database, allowing you to specify how many pages are backed up
11//! at a time and how long the thread should sleep between chunks of pages.
12//!
13//! The following example is equivalent to "Example 2: Online Backup of a
14//! Running Database" from [SQLite's Online Backup API
15//! documentation](https://www.sqlite.org/backup.html).
16//!
17//! ```rust,no_run
18//! # use rusqlite::{backup, Connection, Result};
19//! # use std::path::Path;
20//! # use std::time;
21//!
22//! fn backup_db<P: AsRef<Path>>(
23//!     src: &Connection,
24//!     dst: P,
25//!     progress: fn(backup::Progress),
26//! ) -> Result<()> {
27//!     let mut dst = Connection::open(dst)?;
28//!     let backup = backup::Backup::new(src, &mut dst)?;
29//!     backup.run_to_completion(5, time::Duration::from_millis(250), Some(progress))
30//! }
31//! ```
32
33use std::marker::PhantomData;
34use std::path::Path;
35use std::ptr;
36
37use std::os::raw::c_int;
38use std::thread;
39use std::time::Duration;
40
41use crate::ffi;
42
43use crate::error::error_from_handle;
44use crate::{Connection, DatabaseName, Result};
45
46impl Connection {
47    /// Back up the `name` database to the given
48    /// destination path.
49    ///
50    /// If `progress` is not `None`, it will be called periodically
51    /// until the backup completes.
52    ///
53    /// For more fine-grained control over the backup process (e.g.,
54    /// to sleep periodically during the backup or to back up to an
55    /// already-open database connection), see the `backup` module.
56    ///
57    /// # Failure
58    ///
59    /// Will return `Err` if the destination path cannot be opened
60    /// or if the backup fails.
61    pub fn backup<P: AsRef<Path>>(
62        &self,
63        name: DatabaseName<'_>,
64        dst_path: P,
65        progress: Option<fn(Progress)>,
66    ) -> Result<()> {
67        use self::StepResult::{Busy, Done, Locked, More};
68        let mut dst = Connection::open(dst_path)?;
69        let backup = Backup::new_with_names(self, name, &mut dst, DatabaseName::Main)?;
70
71        let mut r = More;
72        while r == More {
73            r = backup.step(100)?;
74            if let Some(f) = progress {
75                f(backup.progress());
76            }
77        }
78
79        match r {
80            Done => Ok(()),
81            Busy => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY) }),
82            Locked => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED) }),
83            More => unreachable!(),
84        }
85    }
86
87    /// Restore the given source path into the
88    /// `name` database. If `progress` is not `None`, it will be
89    /// called periodically until the restore completes.
90    ///
91    /// For more fine-grained control over the restore process (e.g.,
92    /// to sleep periodically during the restore or to restore from an
93    /// already-open database connection), see the `backup` module.
94    ///
95    /// # Failure
96    ///
97    /// Will return `Err` if the destination path cannot be opened
98    /// or if the restore fails.
99    pub fn restore<P: AsRef<Path>, F: Fn(Progress)>(
100        &mut self,
101        name: DatabaseName<'_>,
102        src_path: P,
103        progress: Option<F>,
104    ) -> Result<()> {
105        use self::StepResult::{Busy, Done, Locked, More};
106        let src = Connection::open(src_path)?;
107        let restore = Backup::new_with_names(&src, DatabaseName::Main, self, name)?;
108
109        let mut r = More;
110        let mut busy_count = 0_i32;
111        'restore_loop: while r == More || r == Busy {
112            r = restore.step(100)?;
113            if let Some(ref f) = progress {
114                f(restore.progress());
115            }
116            if r == Busy {
117                busy_count += 1;
118                if busy_count >= 3 {
119                    break 'restore_loop;
120                }
121                thread::sleep(Duration::from_millis(100));
122            }
123        }
124
125        match r {
126            Done => Ok(()),
127            Busy => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_BUSY) }),
128            Locked => Err(unsafe { error_from_handle(ptr::null_mut(), ffi::SQLITE_LOCKED) }),
129            More => unreachable!(),
130        }
131    }
132}
133
134/// Possible successful results of calling
135/// [`Backup::step`].
136#[derive(Copy, Clone, Debug, PartialEq, Eq)]
137#[non_exhaustive]
138pub enum StepResult {
139    /// The backup is complete.
140    Done,
141
142    /// The step was successful but there are still more pages that need to be
143    /// backed up.
144    More,
145
146    /// The step failed because appropriate locks could not be acquired. This is
147    /// not a fatal error - the step can be retried.
148    Busy,
149
150    /// The step failed because the source connection was writing to the
151    /// database. This is not a fatal error - the step can be retried.
152    Locked,
153}
154
155/// Struct specifying the progress of a backup. The
156/// percentage completion can be calculated as `(pagecount - remaining) /
157/// pagecount`. The progress of a backup is as of the last call to
158/// [`step`](Backup::step) - if the source database is modified after a call to
159/// [`step`](Backup::step), the progress value will become outdated and
160/// potentially incorrect.
161#[derive(Copy, Clone, Debug)]
162pub struct Progress {
163    /// Number of pages in the source database that still need to be backed up.
164    pub remaining: c_int,
165    /// Total number of pages in the source database.
166    pub pagecount: c_int,
167}
168
169/// A handle to an online backup.
170pub struct Backup<'a, 'b> {
171    phantom_from: PhantomData<&'a Connection>,
172    to: &'b Connection,
173    b: *mut ffi::sqlite3_backup,
174}
175
176impl Backup<'_, '_> {
177    /// Attempt to create a new handle that will allow backups from `from` to
178    /// `to`. Note that `to` is a `&mut` - this is because SQLite forbids any
179    /// API calls on the destination of a backup while the backup is taking
180    /// place.
181    ///
182    /// # Failure
183    ///
184    /// Will return `Err` if the underlying `sqlite3_backup_init` call returns
185    /// `NULL`.
186    #[inline]
187    pub fn new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
188        Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main)
189    }
190
191    /// Attempt to create a new handle that will allow backups from the
192    /// `from_name` database of `from` to the `to_name` database of `to`. Note
193    /// that `to` is a `&mut` - this is because SQLite forbids any API calls on
194    /// the destination of a backup while the backup is taking place.
195    ///
196    /// # Failure
197    ///
198    /// Will return `Err` if the underlying `sqlite3_backup_init` call returns
199    /// `NULL`.
200    pub fn new_with_names<'a, 'b>(
201        from: &'a Connection,
202        from_name: DatabaseName<'_>,
203        to: &'b mut Connection,
204        to_name: DatabaseName<'_>,
205    ) -> Result<Backup<'a, 'b>> {
206        let to_name = to_name.as_cstring()?;
207        let from_name = from_name.as_cstring()?;
208
209        let to_db = to.db.borrow_mut().db;
210
211        let b = unsafe {
212            let b = ffi::sqlite3_backup_init(
213                to_db,
214                to_name.as_ptr(),
215                from.db.borrow_mut().db,
216                from_name.as_ptr(),
217            );
218            if b.is_null() {
219                return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
220            }
221            b
222        };
223
224        Ok(Backup {
225            phantom_from: PhantomData,
226            to,
227            b,
228        })
229    }
230
231    /// Gets the progress of the backup as of the last call to
232    /// [`step`](Backup::step).
233    #[inline]
234    #[must_use]
235    pub fn progress(&self) -> Progress {
236        unsafe {
237            Progress {
238                remaining: ffi::sqlite3_backup_remaining(self.b),
239                pagecount: ffi::sqlite3_backup_pagecount(self.b),
240            }
241        }
242    }
243
244    /// Attempts to back up the given number of pages. If `num_pages` is
245    /// negative, will attempt to back up all remaining pages. This will hold a
246    /// lock on the source database for the duration, so it is probably not
247    /// what you want for databases that are currently active (see
248    /// [`run_to_completion`](Backup::run_to_completion) for a better
249    /// alternative).
250    ///
251    /// # Failure
252    ///
253    /// Will return `Err` if the underlying `sqlite3_backup_step` call returns
254    /// an error code other than `DONE`, `OK`, `BUSY`, or `LOCKED`. `BUSY` and
255    /// `LOCKED` are transient errors and are therefore returned as possible
256    /// `Ok` values.
257    #[inline]
258    pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
259        use self::StepResult::{Busy, Done, Locked, More};
260
261        let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) };
262        match rc {
263            ffi::SQLITE_DONE => Ok(Done),
264            ffi::SQLITE_OK => Ok(More),
265            ffi::SQLITE_BUSY => Ok(Busy),
266            ffi::SQLITE_LOCKED => Ok(Locked),
267            _ => self.to.decode_result(rc).map(|_| More),
268        }
269    }
270
271    /// Attempts to run the entire backup. Will call
272    /// [`step(pages_per_step)`](Backup::step) as many times as necessary,
273    /// sleeping for `pause_between_pages` between each call to give the
274    /// source database time to process any pending queries. This is a
275    /// direct implementation of "Example 2: Online Backup of a Running
276    /// Database" from [SQLite's Online Backup API documentation](https://www.sqlite.org/backup.html).
277    ///
278    /// If `progress` is not `None`, it will be called after each step with the
279    /// current progress of the backup. Note that is possible the progress may
280    /// not change if the step returns `Busy` or `Locked` even though the
281    /// backup is still running.
282    ///
283    /// # Failure
284    ///
285    /// Will return `Err` if any of the calls to [`step`](Backup::step) return
286    /// `Err`.
287    pub fn run_to_completion(
288        &self,
289        pages_per_step: c_int,
290        pause_between_pages: Duration,
291        progress: Option<fn(Progress)>,
292    ) -> Result<()> {
293        use self::StepResult::{Busy, Done, Locked, More};
294
295        assert!(pages_per_step > 0, "pages_per_step must be positive");
296
297        loop {
298            let r = self.step(pages_per_step)?;
299            if let Some(progress) = progress {
300                progress(self.progress());
301            }
302            match r {
303                More | Busy | Locked => thread::sleep(pause_between_pages),
304                Done => return Ok(()),
305            }
306        }
307    }
308}
309
310impl Drop for Backup<'_, '_> {
311    #[inline]
312    fn drop(&mut self) {
313        unsafe { ffi::sqlite3_backup_finish(self.b) };
314    }
315}
316
317#[cfg(test)]
318mod test {
319    use super::Backup;
320    use crate::{Connection, DatabaseName, Result};
321    use std::time::Duration;
322
323    #[test]
324    fn test_backup() -> Result<()> {
325        let src = Connection::open_in_memory()?;
326        let sql = "BEGIN;
327                   CREATE TABLE foo(x INTEGER);
328                   INSERT INTO foo VALUES(42);
329                   END;";
330        src.execute_batch(sql)?;
331
332        let mut dst = Connection::open_in_memory()?;
333
334        {
335            let backup = Backup::new(&src, &mut dst)?;
336            backup.step(-1)?;
337        }
338
339        let the_answer: i64 = dst.one_column("SELECT x FROM foo")?;
340        assert_eq!(42, the_answer);
341
342        src.execute_batch("INSERT INTO foo VALUES(43)")?;
343
344        {
345            let backup = Backup::new(&src, &mut dst)?;
346            backup.run_to_completion(5, Duration::from_millis(250), None)?;
347        }
348
349        let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?;
350        assert_eq!(42 + 43, the_answer);
351        Ok(())
352    }
353
354    #[test]
355    fn test_backup_temp() -> Result<()> {
356        let src = Connection::open_in_memory()?;
357        let sql = "BEGIN;
358                   CREATE TEMPORARY TABLE foo(x INTEGER);
359                   INSERT INTO foo VALUES(42);
360                   END;";
361        src.execute_batch(sql)?;
362
363        let mut dst = Connection::open_in_memory()?;
364
365        {
366            let backup =
367                Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)?;
368            backup.step(-1)?;
369        }
370
371        let the_answer: i64 = dst.one_column("SELECT x FROM foo")?;
372        assert_eq!(42, the_answer);
373
374        src.execute_batch("INSERT INTO foo VALUES(43)")?;
375
376        {
377            let backup =
378                Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)?;
379            backup.run_to_completion(5, Duration::from_millis(250), None)?;
380        }
381
382        let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?;
383        assert_eq!(42 + 43, the_answer);
384        Ok(())
385    }
386
387    #[test]
388    fn test_backup_attached() -> Result<()> {
389        let src = Connection::open_in_memory()?;
390        let sql = "ATTACH DATABASE ':memory:' AS my_attached;
391                   BEGIN;
392                   CREATE TABLE my_attached.foo(x INTEGER);
393                   INSERT INTO my_attached.foo VALUES(42);
394                   END;";
395        src.execute_batch(sql)?;
396
397        let mut dst = Connection::open_in_memory()?;
398
399        {
400            let backup = Backup::new_with_names(
401                &src,
402                DatabaseName::Attached("my_attached"),
403                &mut dst,
404                DatabaseName::Main,
405            )?;
406            backup.step(-1)?;
407        }
408
409        let the_answer: i64 = dst.one_column("SELECT x FROM foo")?;
410        assert_eq!(42, the_answer);
411
412        src.execute_batch("INSERT INTO foo VALUES(43)")?;
413
414        {
415            let backup = Backup::new_with_names(
416                &src,
417                DatabaseName::Attached("my_attached"),
418                &mut dst,
419                DatabaseName::Main,
420            )?;
421            backup.run_to_completion(5, Duration::from_millis(250), None)?;
422        }
423
424        let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?;
425        assert_eq!(42 + 43, the_answer);
426        Ok(())
427    }
428}