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