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 
33 use std::marker::PhantomData;
34 use std::path::Path;
35 use std::ptr;
36 
37 use std::os::raw::c_int;
38 use std::thread;
39 use std::time::Duration;
40 
41 use crate::ffi;
42 
43 use crate::error::error_from_handle;
44 use crate::{Connection, DatabaseName, Result};
45 
46 impl 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.
backup<P: AsRef<Path>>( &self, name: DatabaseName<'_>, dst_path: P, progress: Option<fn(Progress)>, ) -> Result<()>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.
restore<P: AsRef<Path>, F: Fn(Progress)>( &mut self, name: DatabaseName<'_>, src_path: P, progress: Option<F>, ) -> Result<()>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]
138 pub 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)]
162 pub 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.
170 pub struct Backup<'a, 'b> {
171     phantom_from: PhantomData<&'a Connection>,
172     to: &'b Connection,
173     b: *mut ffi::sqlite3_backup,
174 }
175 
176 impl 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]
new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>>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`.
new_with_names<'a, 'b>( from: &'a Connection, from_name: DatabaseName<'_>, to: &'b mut Connection, to_name: DatabaseName<'_>, ) -> Result<Backup<'a, 'b>>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]
progress(&self) -> Progress235     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]
step(&self, num_pages: c_int) -> Result<StepResult>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`.
run_to_completion( &self, pages_per_step: c_int, pause_between_pages: Duration, progress: Option<fn(Progress)>, ) -> Result<()>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 
310 impl Drop for Backup<'_, '_> {
311     #[inline]
drop(&mut self)312     fn drop(&mut self) {
313         unsafe { ffi::sqlite3_backup_finish(self.b) };
314     }
315 }
316 
317 #[cfg(test)]
318 mod test {
319     use super::Backup;
320     use crate::{Connection, DatabaseName, Result};
321     use std::time::Duration;
322 
323     #[test]
test_backup() -> Result<()>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]
test_backup_temp() -> Result<()>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]
test_backup_attached() -> Result<()>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 }
429