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