Skip to main content

rusqlite/
backup.rs

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