rusqlite/backup.rs
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
33use std::marker::PhantomData;
34use std::path::Path;
35use std::ptr;
36
37use std::os::raw::c_int;
38use std::thread;
39use std::time::Duration;
40
41use crate::ffi;
42
43use crate::error::error_from_handle;
44use crate::{Connection, DatabaseName, Result};
45
46impl 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.
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.
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]
138pub 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)]
162pub 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.
170pub struct Backup<'a, 'b> {
171 phantom_from: PhantomData<&'a Connection>,
172 to: &'b Connection,
173 b: *mut ffi::sqlite3_backup,
174}
175
176impl 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]
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`.
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]
235 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]
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`.
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
310impl Drop for Backup<'_, '_> {
311 #[inline]
312 fn drop(&mut self) {
313 unsafe { ffi::sqlite3_backup_finish(self.b) };
314 }
315}
316
317#[cfg(test)]
318mod test {
319 use super::Backup;
320 use crate::{Connection, DatabaseName, Result};
321 use std::time::Duration;
322
323 #[test]
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]
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]
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}