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 33 use std::marker::PhantomData; 34 use std::path::Path; 35 use std::ptr; 36 37 use std::os::raw::c_int; 38 use std::thread; 39 use std::time::Duration; 40 41 use crate::ffi; 42 43 use crate::error::error_from_handle; 44 use crate::{Connection, DatabaseName, Result}; 45 46 impl 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. backup<P: AsRef<Path>>( &self, name: DatabaseName<'_>, dst_path: P, progress: Option<fn(Progress)>, ) -> Result<()>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. restore<P: AsRef<Path>, F: Fn(Progress)>( &mut self, name: DatabaseName<'_>, src_path: P, progress: Option<F>, ) -> Result<()>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] 138 pub 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)] 162 pub 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. 170 pub struct Backup<'a, 'b> { 171 phantom_from: PhantomData<&'a Connection>, 172 to: &'b Connection, 173 b: *mut ffi::sqlite3_backup, 174 } 175 176 impl 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] new<'a, 'b>(from: &'a Connection, to: &'b mut Connection) -> Result<Backup<'a, 'b>>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`. new_with_names<'a, 'b>( from: &'a Connection, from_name: DatabaseName<'_>, to: &'b mut Connection, to_name: DatabaseName<'_>, ) -> Result<Backup<'a, 'b>>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] progress(&self) -> Progress235 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] step(&self, num_pages: c_int) -> Result<StepResult>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`. run_to_completion( &self, pages_per_step: c_int, pause_between_pages: Duration, progress: Option<fn(Progress)>, ) -> Result<()>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 310 impl Drop for Backup<'_, '_> { 311 #[inline] drop(&mut self)312 fn drop(&mut self) { 313 unsafe { ffi::sqlite3_backup_finish(self.b) }; 314 } 315 } 316 317 #[cfg(test)] 318 mod test { 319 use super::{Backup, Progress}; 320 use crate::{Connection, DatabaseName, Result}; 321 use std::time::Duration; 322 323 #[test] backup_to_path() -> Result<()>324 fn backup_to_path() -> Result<()> { 325 let src = Connection::open_in_memory()?; 326 src.execute_batch("CREATE TABLE foo AS SELECT 42 AS x")?; 327 328 let temp_dir = tempfile::tempdir().unwrap(); 329 let path = temp_dir.path().join("test.db3"); 330 331 fn progress(_: Progress) {} 332 333 src.backup(DatabaseName::Main, path.as_path(), Some(progress))?; 334 335 let mut dst = Connection::open_in_memory()?; 336 dst.restore(DatabaseName::Main, path, Some(progress))?; 337 338 Ok(()) 339 } 340 341 #[test] test_backup() -> Result<()>342 fn test_backup() -> Result<()> { 343 let src = Connection::open_in_memory()?; 344 let sql = "BEGIN; 345 CREATE TABLE foo(x INTEGER); 346 INSERT INTO foo VALUES(42); 347 END;"; 348 src.execute_batch(sql)?; 349 350 let mut dst = Connection::open_in_memory()?; 351 352 { 353 let backup = Backup::new(&src, &mut dst)?; 354 backup.step(-1)?; 355 } 356 357 let the_answer: i64 = dst.one_column("SELECT x FROM foo")?; 358 assert_eq!(42, the_answer); 359 360 src.execute_batch("INSERT INTO foo VALUES(43)")?; 361 362 { 363 let backup = Backup::new(&src, &mut dst)?; 364 backup.run_to_completion(5, Duration::from_millis(250), None)?; 365 } 366 367 let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?; 368 assert_eq!(42 + 43, the_answer); 369 Ok(()) 370 } 371 372 #[test] test_backup_temp() -> Result<()>373 fn test_backup_temp() -> Result<()> { 374 let src = Connection::open_in_memory()?; 375 let sql = "BEGIN; 376 CREATE TEMPORARY TABLE foo(x INTEGER); 377 INSERT INTO foo VALUES(42); 378 END;"; 379 src.execute_batch(sql)?; 380 381 let mut dst = Connection::open_in_memory()?; 382 383 { 384 let backup = 385 Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)?; 386 backup.step(-1)?; 387 } 388 389 let the_answer: i64 = dst.one_column("SELECT x FROM foo")?; 390 assert_eq!(42, the_answer); 391 392 src.execute_batch("INSERT INTO foo VALUES(43)")?; 393 394 { 395 let backup = 396 Backup::new_with_names(&src, DatabaseName::Temp, &mut dst, DatabaseName::Main)?; 397 backup.run_to_completion(5, Duration::from_millis(250), None)?; 398 } 399 400 let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?; 401 assert_eq!(42 + 43, the_answer); 402 Ok(()) 403 } 404 405 #[test] test_backup_attached() -> Result<()>406 fn test_backup_attached() -> Result<()> { 407 let src = Connection::open_in_memory()?; 408 let sql = "ATTACH DATABASE ':memory:' AS my_attached; 409 BEGIN; 410 CREATE TABLE my_attached.foo(x INTEGER); 411 INSERT INTO my_attached.foo VALUES(42); 412 END;"; 413 src.execute_batch(sql)?; 414 415 let mut dst = Connection::open_in_memory()?; 416 417 { 418 let backup = Backup::new_with_names( 419 &src, 420 DatabaseName::Attached("my_attached"), 421 &mut dst, 422 DatabaseName::Main, 423 )?; 424 backup.step(-1)?; 425 } 426 427 let the_answer: i64 = dst.one_column("SELECT x FROM foo")?; 428 assert_eq!(42, the_answer); 429 430 src.execute_batch("INSERT INTO foo VALUES(43)")?; 431 432 { 433 let backup = Backup::new_with_names( 434 &src, 435 DatabaseName::Attached("my_attached"), 436 &mut dst, 437 DatabaseName::Main, 438 )?; 439 backup.run_to_completion(5, Duration::from_millis(250), None)?; 440 } 441 442 let the_answer: i64 = dst.one_column("SELECT SUM(x) FROM foo")?; 443 assert_eq!(42 + 43, the_answer); 444 Ok(()) 445 } 446 } 447