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; 320 use crate::{Connection, DatabaseName, Result}; 321 use std::time::Duration; 322 323 #[test] test_backup() -> Result<()>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] test_backup_temp() -> Result<()>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] test_backup_attached() -> Result<()>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 } 429