Skip to main content

rusqlite/
backup.rs

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