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