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` will attempt to back up a specified number of pages, `progress` gets
7 //! the current progress of the backup as of the last call to `step`, and
8 //! `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 
32 use std::marker::PhantomData;
33 use std::path::Path;
34 use std::ptr;
35 
36 use std::os::raw::c_int;
37 use std::thread;
38 use std::time::Duration;
39 
40 use crate::ffi;
41 
42 use crate::error::{error_from_handle, error_from_sqlite_code};
43 use crate::{Connection, DatabaseName, Result};
44 
45 impl 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.
backup<P: AsRef<Path>>( &self, name: DatabaseName<'_>, dst_path: P, progress: Option<fn(Progress)>, ) -> Result<()>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.
restore<P: AsRef<Path>, F: Fn(Progress)>( &mut self, name: DatabaseName<'_>, src_path: P, progress: Option<F>, ) -> Result<()>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]
136 pub 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` - if
156 /// the source database is modified after a call to `step`, the progress value
157 /// will become outdated and potentially incorrect.
158 #[derive(Copy, Clone, Debug)]
159 pub 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.
167 pub struct Backup<'a, 'b> {
168     phantom_from: PhantomData<&'a ()>,
169     phantom_to: PhantomData<&'b ()>,
170     b: *mut ffi::sqlite3_backup,
171 }
172 
173 impl 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`.
new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>>183     pub fn new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>> {
184         Backup::new_with_names(from, DatabaseName::Main, to, DatabaseName::Main)
185     }
186 
187     /// Attempt to create a new handle that will allow backups from the
188     /// `from_name` database of `from` to the `to_name` database of `to`. Note
189     /// that `to` is a `&mut` - this is because SQLite forbids any API calls on
190     /// the destination of a backup while the backup is taking place.
191     ///
192     /// # Failure
193     ///
194     /// Will return `Err` if the underlying `sqlite3_backup_init` call returns
195     /// `NULL`.
new_with_names<'a, 'b>( from: &'a Connection, from_name: DatabaseName<'_>, to: &'b mut Connection, to_name: DatabaseName<'_>, ) -> Result<Backup<'a, 'b>>196     pub fn new_with_names<'a, 'b>(
197         from: &'a Connection,
198         from_name: DatabaseName<'_>,
199         to: &'b mut Connection,
200         to_name: DatabaseName<'_>,
201     ) -> Result<Backup<'a, 'b>> {
202         let to_name = to_name.to_cstring()?;
203         let from_name = from_name.to_cstring()?;
204 
205         let to_db = to.db.borrow_mut().db;
206 
207         let b = unsafe {
208             let b = ffi::sqlite3_backup_init(
209                 to_db,
210                 to_name.as_ptr(),
211                 from.db.borrow_mut().db,
212                 from_name.as_ptr(),
213             );
214             if b.is_null() {
215                 return Err(error_from_handle(to_db, ffi::sqlite3_errcode(to_db)));
216             }
217             b
218         };
219 
220         Ok(Backup {
221             phantom_from: PhantomData,
222             phantom_to: PhantomData,
223             b,
224         })
225     }
226 
227     /// Gets the progress of the backup as of the last call to `step`.
progress(&self) -> Progress228     pub fn progress(&self) -> Progress {
229         unsafe {
230             Progress {
231                 remaining: ffi::sqlite3_backup_remaining(self.b),
232                 pagecount: ffi::sqlite3_backup_pagecount(self.b),
233             }
234         }
235     }
236 
237     /// Attempts to back up the given number of pages. If `num_pages` is
238     /// negative, will attempt to back up all remaining pages. This will hold a
239     /// lock on the source database for the duration, so it is probably not
240     /// what you want for databases that are currently active (see
241     /// `run_to_completion` for a better alternative).
242     ///
243     /// # Failure
244     ///
245     /// Will return `Err` if the underlying `sqlite3_backup_step` call returns
246     /// an error code other than `DONE`, `OK`, `BUSY`, or `LOCKED`. `BUSY` and
247     /// `LOCKED` are transient errors and are therefore returned as possible
248     /// `Ok` values.
step(&self, num_pages: c_int) -> Result<StepResult>249     pub fn step(&self, num_pages: c_int) -> Result<StepResult> {
250         use self::StepResult::{Busy, Done, Locked, More};
251 
252         let rc = unsafe { ffi::sqlite3_backup_step(self.b, num_pages) };
253         match rc {
254             ffi::SQLITE_DONE => Ok(Done),
255             ffi::SQLITE_OK => Ok(More),
256             ffi::SQLITE_BUSY => Ok(Busy),
257             ffi::SQLITE_LOCKED => Ok(Locked),
258             _ => Err(error_from_sqlite_code(rc, None)),
259         }
260     }
261 
262     /// Attempts to run the entire backup. Will call `step(pages_per_step)` as
263     /// many times as necessary, sleeping for `pause_between_pages` between
264     /// each call to give the source database time to process any pending
265     /// queries. This is a direct implementation of "Example 2: Online Backup
266     /// of a Running Database" from [SQLite's Online Backup API
267     /// documentation](https://www.sqlite.org/backup.html).
268     ///
269     /// If `progress` is not `None`, it will be called after each step with the
270     /// current progress of the backup. Note that is possible the progress may
271     /// not change if the step returns `Busy` or `Locked` even though the
272     /// backup is still running.
273     ///
274     /// # Failure
275     ///
276     /// Will return `Err` if any of the calls to `step` return `Err`.
run_to_completion( &self, pages_per_step: c_int, pause_between_pages: Duration, progress: Option<fn(Progress)>, ) -> Result<()>277     pub fn run_to_completion(
278         &self,
279         pages_per_step: c_int,
280         pause_between_pages: Duration,
281         progress: Option<fn(Progress)>,
282     ) -> Result<()> {
283         use self::StepResult::{Busy, Done, Locked, More};
284 
285         assert!(pages_per_step > 0, "pages_per_step must be positive");
286 
287         loop {
288             let r = self.step(pages_per_step)?;
289             if let Some(progress) = progress {
290                 progress(self.progress())
291             }
292             match r {
293                 More | Busy | Locked => thread::sleep(pause_between_pages),
294                 Done => return Ok(()),
295             }
296         }
297     }
298 }
299 
300 impl Drop for Backup<'_, '_> {
drop(&mut self)301     fn drop(&mut self) {
302         unsafe { ffi::sqlite3_backup_finish(self.b) };
303     }
304 }
305 
306 #[cfg(test)]
307 mod test {
308     use super::Backup;
309     use crate::{Connection, DatabaseName, NO_PARAMS};
310     use std::time::Duration;
311 
312     #[test]
test_backup()313     fn test_backup() {
314         let src = Connection::open_in_memory().unwrap();
315         let sql = "BEGIN;
316                    CREATE TABLE foo(x INTEGER);
317                    INSERT INTO foo VALUES(42);
318                    END;";
319         src.execute_batch(sql).unwrap();
320 
321         let mut dst = Connection::open_in_memory().unwrap();
322 
323         {
324             let backup = Backup::new(&src, &mut dst).unwrap();
325             backup.step(-1).unwrap();
326         }
327 
328         let the_answer: i64 = dst
329             .query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0))
330             .unwrap();
331         assert_eq!(42, the_answer);
332 
333         src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
334 
335         {
336             let backup = Backup::new(&src, &mut dst).unwrap();
337             backup
338                 .run_to_completion(5, Duration::from_millis(250), None)
339                 .unwrap();
340         }
341 
342         let the_answer: i64 = dst
343             .query_row("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
344             .unwrap();
345         assert_eq!(42 + 43, the_answer);
346     }
347 
348     #[test]
test_backup_temp()349     fn test_backup_temp() {
350         let src = Connection::open_in_memory().unwrap();
351         let sql = "BEGIN;
352                    CREATE TEMPORARY TABLE foo(x INTEGER);
353                    INSERT INTO foo VALUES(42);
354                    END;";
355         src.execute_batch(sql).unwrap();
356 
357         let mut dst = Connection::open_in_memory().unwrap();
358 
359         {
360             let backup =
361                 Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
362                     .unwrap();
363             backup.step(-1).unwrap();
364         }
365 
366         let the_answer: i64 = dst
367             .query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0))
368             .unwrap();
369         assert_eq!(42, the_answer);
370 
371         src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
372 
373         {
374             let backup =
375                 Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)
376                     .unwrap();
377             backup
378                 .run_to_completion(5, Duration::from_millis(250), None)
379                 .unwrap();
380         }
381 
382         let the_answer: i64 = dst
383             .query_row("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
384             .unwrap();
385         assert_eq!(42 + 43, the_answer);
386     }
387 
388     #[test]
test_backup_attached()389     fn test_backup_attached() {
390         let src = Connection::open_in_memory().unwrap();
391         let sql = "ATTACH DATABASE ':memory:' AS my_attached;
392                    BEGIN;
393                    CREATE TABLE my_attached.foo(x INTEGER);
394                    INSERT INTO my_attached.foo VALUES(42);
395                    END;";
396         src.execute_batch(sql).unwrap();
397 
398         let mut dst = Connection::open_in_memory().unwrap();
399 
400         {
401             let backup = Backup::new_with_names(
402                 &src,
403                 DatabaseName::Attached("my_attached"),
404                 &mut dst,
405                 DatabaseName::Main,
406             )
407             .unwrap();
408             backup.step(-1).unwrap();
409         }
410 
411         let the_answer: i64 = dst
412             .query_row("SELECT x FROM foo", NO_PARAMS, |r| r.get(0))
413             .unwrap();
414         assert_eq!(42, the_answer);
415 
416         src.execute_batch("INSERT INTO foo VALUES(43)").unwrap();
417 
418         {
419             let backup = Backup::new_with_names(
420                 &src,
421                 DatabaseName::Attached("my_attached"),
422                 &mut dst,
423                 DatabaseName::Main,
424             )
425             .unwrap();
426             backup
427                 .run_to_completion(5, Duration::from_millis(250), None)
428                 .unwrap();
429         }
430 
431         let the_answer: i64 = dst
432             .query_row("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
433             .unwrap();
434         assert_eq!(42 + 43, the_answer);
435     }
436 }
437