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, Name, Result, MAIN_DB};
48
49impl Connection {
50    /// Back up the `name` database to the given
51    /// destination path.
52    ///
53    /// If `progress` is not `None`, 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>>(
65        &self,
66        name: N,
67        dst_path: P,
68        progress: Option<fn(Progress)>,
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(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`, 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/// Possible successful results of calling
138/// [`Backup::step`].
139#[derive(Copy, Clone, Debug, PartialEq, Eq)]
140#[non_exhaustive]
141pub enum StepResult {
142    /// The backup is complete.
143    Done,
144
145    /// The step was successful but there are still more pages that need to be
146    /// backed up.
147    More,
148
149    /// The step failed because appropriate locks could not be acquired. This is
150    /// not a fatal error - the step can be retried.
151    Busy,
152
153    /// The step failed because the source connection was writing to the
154    /// database. This is not a fatal error - the step can be retried.
155    Locked,
156}
157
158/// Struct specifying the progress of a backup.
159///
160/// The percentage completion can be calculated as `(pagecount - remaining) /
161/// pagecount`. The progress of a backup is as of the last call to
162/// [`step`](Backup::step) - if the source database is modified after a call to
163/// [`step`](Backup::step), the progress value will become outdated and
164/// potentially incorrect.
165#[derive(Copy, Clone, Debug)]
166pub struct Progress {
167    /// Number of pages in the source database that still need to be backed up.
168    pub remaining: c_int,
169    /// Total number of pages in the source database.
170    pub pagecount: c_int,
171}
172
173/// A handle to an online backup.
174pub struct Backup<'a, 'b> {
175    phantom_from: PhantomData<&'a Connection>,
176    to: &'b Connection,
177    b: *mut ffi::sqlite3_backup,
178}
179
180impl Backup<'_, '_> {
181    /// Attempt to create a new handle that will allow backups from `from` to
182    /// `to`. Note that `to` is a `&mut` - this is because SQLite forbids any
183    /// API calls on the destination of a backup while the backup is taking
184    /// place.
185    ///
186    /// # Failure
187    ///
188    /// Will return `Err` if the underlying `sqlite3_backup_init` call returns
189    /// `NULL`.
190    #[inline]
191    pub fn new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
192        Backup::new_with_names(from, MAIN_DB, to, MAIN_DB)
193    }
194
195    /// Attempt to create a new handle that will allow backups from the
196    /// `from_name` database of `from` to the `to_name` database of `to`. Note
197    /// that `to` is a `&mut` - this is because SQLite forbids any API calls on
198    /// the destination of a backup while the backup is taking place.
199    ///
200    /// # Failure
201    ///
202    /// Will return `Err` if the underlying `sqlite3_backup_init` call returns
203    /// `NULL`.
204    pub fn new_with_names<'a, 'b, F: Name, T: Name>(
205        from: &'a Connection,
206        from_name: F,
207        to: &'b mut Connection,
208        to_name: T,
209    ) -> Result<Backup<'a, 'b>> {
210        let to_name = to_name.as_cstr()?;
211        let from_name = from_name.as_cstr()?;
212
213        let to_db = to.db.borrow_mut().db;
214
215        let b = unsafe {
216            let b = ffi::sqlite3_backup_init(
217                to_db,
218                to_name.as_ptr(),
219                from.db.borrow_mut().db,
220                from_name.as_ptr(),
221            );
222            if b.is_null() {
223                return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
224            }
225            b
226        };
227
228        Ok(Backup {
229            phantom_from: PhantomData,
230            to,
231            b,
232        })
233    }
234
235    /// Gets the progress of the backup as of the last call to
236    /// [`step`](Backup::step).
237    #[inline]
238    #[must_use]
239    pub fn progress(&self) -> Progress {
240        unsafe {
241            Progress {
242                remaining: ffi::sqlite3_backup_remaining(self.b),
243                pagecount: ffi::sqlite3_backup_pagecount(self.b),
244            }
245        }
246    }
247
248    /// Attempts to back up the given number of pages. If `num_pages` is
249    /// negative, will attempt to back up all remaining pages. This will hold a
250    /// lock on the source database for the duration, so it is probably not
251    /// what you want for databases that are currently active (see
252    /// [`run_to_completion`](Backup::run_to_completion) for a better
253    /// alternative).
254    ///
255    /// # Failure
256    ///
257    /// Will return `Err` if the underlying `sqlite3_backup_step` call returns
258    /// an error code other than `DONE`, `OK`, `BUSY`, or `LOCKED`. `BUSY` and
259    /// `LOCKED` are transient errors and are therefore returned as possible
260    /// `Ok` values.
261    #[inline]
262    pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
263        use self::StepResult::{Busy, Done, Locked, More};
264
265        let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) };
266        match rc {
267            ffi::SQLITE_DONE => Ok(Done),
268            ffi::SQLITE_OK => Ok(More),
269            ffi::SQLITE_BUSY => Ok(Busy),
270            ffi::SQLITE_LOCKED => Ok(Locked),
271            _ => self.to.decode_result(rc).map(|_| More),
272        }
273    }
274
275    /// Attempts to run the entire backup. Will call
276    /// [`step(pages_per_step)`](Backup::step) as many times as necessary,
277    /// sleeping for `pause_between_pages` between each call to give the
278    /// source database time to process any pending queries. This is a
279    /// direct implementation of "Example 2: Online Backup of a Running
280    /// Database" from [SQLite's Online Backup API documentation](https://www.sqlite.org/backup.html).
281    ///
282    /// If `progress` is not `None`, it will be called after each step with the
283    /// current progress of the backup. Note that is possible the progress may
284    /// not change if the step returns `Busy` or `Locked` even though the
285    /// backup is still running.
286    ///
287    /// # Failure
288    ///
289    /// Will return `Err` if any of the calls to [`step`](Backup::step) return
290    /// `Err`.
291    pub fn run_to_completion(
292        &self,
293        pages_per_step: c_int,
294        pause_between_pages: Duration,
295        progress: Option<fn(Progress)>,
296    ) -> Result<()> {
297        use self::StepResult::{Busy, Done, Locked, More};
298
299        assert!(pages_per_step > 0, "pages_per_step must be positive");
300
301        loop {
302            let r = self.step(pages_per_step)?;
303            if let Some(progress) = progress {
304                progress(self.progress());
305            }
306            match r {
307                More | Busy | Locked => thread::sleep(pause_between_pages),
308                Done => return Ok(()),
309            }
310        }
311    }
312}
313
314impl Drop for Backup<'_, '_> {
315    #[inline]
316    fn drop(&mut self) {
317        unsafe { ffi::sqlite3_backup_finish(self.b) };
318    }
319}
320
321#[cfg(test)]
322mod test {
323    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
324    use wasm_bindgen_test::wasm_bindgen_test as test;
325
326    use super::{Backup, Progress};
327    use crate::{Connection, Result, MAIN_DB, TEMP_DB};
328    use std::time::Duration;
329
330    #[cfg_attr(
331        all(target_family = "wasm", target_os = "unknown"),
332        ignore = "no filesystem on this platform"
333    )]
334    #[test]
335    fn backup_to_path() -> Result<()> {
336        let src = Connection::open_in_memory()?;
337        src.execute_batch("CREATE TABLE foo AS SELECT 42 AS x")?;
338
339        let temp_dir = tempfile::tempdir().unwrap();
340        let path = temp_dir.path().join("test.db3");
341
342        fn progress(_: Progress) {}
343
344        src.backup(MAIN_DB, path.as_path(), Some(progress))?;
345
346        let mut dst = Connection::open_in_memory()?;
347        dst.restore(MAIN_DB, path, Some(progress))?;
348
349        Ok(())
350    }
351
352    #[test]
353    fn test_backup() -> Result<()> {
354        let src = Connection::open_in_memory()?;
355        let sql = "BEGIN;
356                   CREATE TABLE foo(x INTEGER);
357                   INSERT INTO foo VALUES(42);
358                   END;";
359        src.execute_batch(sql)?;
360
361        let mut dst = Connection::open_in_memory()?;
362
363        {
364            let backup = Backup::new(&src, &mut dst)?;
365            backup.step(-1)?;
366        }
367
368        assert_eq!(42, dst.one_column::<i64, _>("SELECT x FROM foo", [])?);
369
370        src.execute_batch("INSERT INTO foo VALUES(43)")?;
371
372        {
373            let backup = Backup::new(&src, &mut dst)?;
374            backup.run_to_completion(5, Duration::from_millis(250), None)?;
375        }
376
377        let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo", [])?;
378        assert_eq!(42 + 43, the_answer);
379        Ok(())
380    }
381
382    #[test]
383    fn test_backup_temp() -> Result<()> {
384        let src = Connection::open_in_memory()?;
385        let sql = "BEGIN;
386                   CREATE TEMPORARY TABLE foo(x INTEGER);
387                   INSERT INTO foo VALUES(42);
388                   END;";
389        src.execute_batch(sql)?;
390
391        let mut dst = Connection::open_in_memory()?;
392
393        {
394            let backup = Backup::new_with_names(&src, TEMP_DB, &mut dst, MAIN_DB)?;
395            backup.step(-1)?;
396        }
397
398        assert_eq!(42, dst.one_column::<i64, _>("SELECT x FROM foo", [])?);
399
400        src.execute_batch("INSERT INTO foo VALUES(43)")?;
401
402        {
403            let backup = Backup::new_with_names(&src, TEMP_DB, &mut dst, MAIN_DB)?;
404            backup.run_to_completion(5, Duration::from_millis(250), None)?;
405        }
406
407        let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo", [])?;
408        assert_eq!(42 + 43, the_answer);
409        Ok(())
410    }
411
412    #[test]
413    fn test_backup_attached() -> Result<()> {
414        let src = Connection::open_in_memory()?;
415        let sql = "ATTACH DATABASE ':memory:' AS my_attached;
416                   BEGIN;
417                   CREATE TABLE my_attached.foo(x INTEGER);
418                   INSERT INTO my_attached.foo VALUES(42);
419                   END;";
420        src.execute_batch(sql)?;
421
422        let mut dst = Connection::open_in_memory()?;
423
424        {
425            let backup = Backup::new_with_names(&src, c"my_attached", &mut dst, MAIN_DB)?;
426            backup.step(-1)?;
427        }
428
429        assert_eq!(42, dst.one_column::<i64, _>("SELECT x FROM foo", [])?);
430
431        src.execute_batch("INSERT INTO foo VALUES(43)")?;
432
433        {
434            let backup = Backup::new_with_names(&src, c"my_attached", &mut dst, MAIN_DB)?;
435            backup.run_to_completion(5, Duration::from_millis(250), None)?;
436        }
437
438        let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo", [])?;
439        assert_eq!(42 + 43, the_answer);
440        Ok(())
441    }
442}