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