1 use crate::{Connection, Result}; 2 use std::ops::Deref; 3 4 /// Options for transaction behavior. See [BEGIN 5 /// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details. 6 #[derive(Copy, Clone)] 7 #[non_exhaustive] 8 pub enum TransactionBehavior { 9 /// DEFERRED means that the transaction does not actually start until the 10 /// database is first accessed. 11 Deferred, 12 /// IMMEDIATE cause the database connection to start a new write 13 /// immediately, without waiting for a writes statement. 14 Immediate, 15 /// EXCLUSIVE prevents other database connections from reading the database 16 /// while the transaction is underway. 17 Exclusive, 18 } 19 20 /// Options for how a Transaction or Savepoint should behave when it is dropped. 21 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 22 #[non_exhaustive] 23 pub enum DropBehavior { 24 /// Roll back the changes. This is the default. 25 Rollback, 26 27 /// Commit the changes. 28 Commit, 29 30 /// Do not commit or roll back changes - this will leave the transaction or 31 /// savepoint open, so should be used with care. 32 Ignore, 33 34 /// Panic. Used to enforce intentional behavior during development. 35 Panic, 36 } 37 38 /// Represents a transaction on a database connection. 39 /// 40 /// ## Note 41 /// 42 /// Transactions will roll back by default. Use `commit` method to explicitly 43 /// commit the transaction, or use `set_drop_behavior` to change what happens 44 /// when the transaction is dropped. 45 /// 46 /// ## Example 47 /// 48 /// ```rust,no_run 49 /// # use rusqlite::{Connection, Result}; 50 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } 51 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } 52 /// fn perform_queries(conn: &mut Connection) -> Result<()> { 53 /// let tx = conn.transaction()?; 54 /// 55 /// do_queries_part_1(&tx)?; // tx causes rollback if this fails 56 /// do_queries_part_2(&tx)?; // tx causes rollback if this fails 57 /// 58 /// tx.commit() 59 /// } 60 /// ``` 61 #[derive(Debug)] 62 pub struct Transaction<'conn> { 63 conn: &'conn Connection, 64 drop_behavior: DropBehavior, 65 } 66 67 /// Represents a savepoint on a database connection. 68 /// 69 /// ## Note 70 /// 71 /// Savepoints will roll back by default. Use `commit` method to explicitly 72 /// commit the savepoint, or use `set_drop_behavior` to change what happens 73 /// when the savepoint is dropped. 74 /// 75 /// ## Example 76 /// 77 /// ```rust,no_run 78 /// # use rusqlite::{Connection, Result}; 79 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } 80 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } 81 /// fn perform_queries(conn: &mut Connection) -> Result<()> { 82 /// let sp = conn.savepoint()?; 83 /// 84 /// do_queries_part_1(&sp)?; // sp causes rollback if this fails 85 /// do_queries_part_2(&sp)?; // sp causes rollback if this fails 86 /// 87 /// sp.commit() 88 /// } 89 /// ``` 90 pub struct Savepoint<'conn> { 91 conn: &'conn Connection, 92 name: String, 93 depth: u32, 94 drop_behavior: DropBehavior, 95 committed: bool, 96 } 97 98 impl Transaction<'_> { 99 /// Begin a new transaction. Cannot be nested; see `savepoint` for nested 100 /// transactions. 101 /// 102 /// Even though we don't mutate the connection, we take a `&mut Connection` 103 /// so as to prevent nested transactions on the same connection. For cases 104 /// where this is unacceptable, [`Transaction::new_unchecked`] is available. 105 #[inline] new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>>106 pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>> { 107 Self::new_unchecked(conn, behavior) 108 } 109 110 /// Begin a new transaction, failing if a transaction is open. 111 /// 112 /// If a transaction is already open, this will return an error. Where 113 /// possible, [`Transaction::new`] should be preferred, as it provides a 114 /// compile-time guarantee that transactions are not nested. 115 #[inline] new_unchecked( conn: &Connection, behavior: TransactionBehavior, ) -> Result<Transaction<'_>>116 pub fn new_unchecked( 117 conn: &Connection, 118 behavior: TransactionBehavior, 119 ) -> Result<Transaction<'_>> { 120 let query = match behavior { 121 TransactionBehavior::Deferred => "BEGIN DEFERRED", 122 TransactionBehavior::Immediate => "BEGIN IMMEDIATE", 123 TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE", 124 }; 125 conn.execute_batch(query).map(move |_| Transaction { 126 conn, 127 drop_behavior: DropBehavior::Rollback, 128 }) 129 } 130 131 /// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested 132 /// transactions. 133 /// 134 /// ## Note 135 /// 136 /// Just like outer level transactions, savepoint transactions rollback by 137 /// default. 138 /// 139 /// ## Example 140 /// 141 /// ```rust,no_run 142 /// # use rusqlite::{Connection, Result}; 143 /// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true } 144 /// fn perform_queries(conn: &mut Connection) -> Result<()> { 145 /// let mut tx = conn.transaction()?; 146 /// 147 /// { 148 /// let sp = tx.savepoint()?; 149 /// if perform_queries_part_1_succeeds(&sp) { 150 /// sp.commit()?; 151 /// } 152 /// // otherwise, sp will rollback 153 /// } 154 /// 155 /// tx.commit() 156 /// } 157 /// ``` 158 #[inline] savepoint(&mut self) -> Result<Savepoint<'_>>159 pub fn savepoint(&mut self) -> Result<Savepoint<'_>> { 160 Savepoint::with_depth(self.conn, 1) 161 } 162 163 /// Create a new savepoint with a custom savepoint name. See `savepoint()`. 164 #[inline] savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>165 pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> { 166 Savepoint::with_depth_and_name(self.conn, 1, name) 167 } 168 169 /// Get the current setting for what happens to the transaction when it is 170 /// dropped. 171 #[inline] 172 #[must_use] drop_behavior(&self) -> DropBehavior173 pub fn drop_behavior(&self) -> DropBehavior { 174 self.drop_behavior 175 } 176 177 /// Configure the transaction to perform the specified action when it is 178 /// dropped. 179 #[inline] set_drop_behavior(&mut self, drop_behavior: DropBehavior)180 pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) { 181 self.drop_behavior = drop_behavior; 182 } 183 184 /// A convenience method which consumes and commits a transaction. 185 #[inline] commit(mut self) -> Result<()>186 pub fn commit(mut self) -> Result<()> { 187 self.commit_() 188 } 189 190 #[inline] commit_(&mut self) -> Result<()>191 fn commit_(&mut self) -> Result<()> { 192 self.conn.execute_batch("COMMIT")?; 193 Ok(()) 194 } 195 196 /// A convenience method which consumes and rolls back a transaction. 197 #[inline] rollback(mut self) -> Result<()>198 pub fn rollback(mut self) -> Result<()> { 199 self.rollback_() 200 } 201 202 #[inline] rollback_(&mut self) -> Result<()>203 fn rollback_(&mut self) -> Result<()> { 204 self.conn.execute_batch("ROLLBACK")?; 205 Ok(()) 206 } 207 208 /// Consumes the transaction, committing or rolling back according to the 209 /// current setting (see `drop_behavior`). 210 /// 211 /// Functionally equivalent to the `Drop` implementation, but allows 212 /// callers to see any errors that occur. 213 #[inline] finish(mut self) -> Result<()>214 pub fn finish(mut self) -> Result<()> { 215 self.finish_() 216 } 217 218 #[inline] finish_(&mut self) -> Result<()>219 fn finish_(&mut self) -> Result<()> { 220 if self.conn.is_autocommit() { 221 return Ok(()); 222 } 223 match self.drop_behavior() { 224 DropBehavior::Commit => self.commit_().or_else(|_| self.rollback_()), 225 DropBehavior::Rollback => self.rollback_(), 226 DropBehavior::Ignore => Ok(()), 227 DropBehavior::Panic => panic!("Transaction dropped unexpectedly."), 228 } 229 } 230 } 231 232 impl Deref for Transaction<'_> { 233 type Target = Connection; 234 235 #[inline] deref(&self) -> &Connection236 fn deref(&self) -> &Connection { 237 self.conn 238 } 239 } 240 241 #[allow(unused_must_use)] 242 impl Drop for Transaction<'_> { 243 #[inline] drop(&mut self)244 fn drop(&mut self) { 245 self.finish_(); 246 } 247 } 248 249 impl Savepoint<'_> { 250 #[inline] with_depth_and_name<T: Into<String>>( conn: &Connection, depth: u32, name: T, ) -> Result<Savepoint<'_>>251 fn with_depth_and_name<T: Into<String>>( 252 conn: &Connection, 253 depth: u32, 254 name: T, 255 ) -> Result<Savepoint<'_>> { 256 let name = name.into(); 257 conn.execute_batch(&format!("SAVEPOINT {}", name)) 258 .map(|_| Savepoint { 259 conn, 260 name, 261 depth, 262 drop_behavior: DropBehavior::Rollback, 263 committed: false, 264 }) 265 } 266 267 #[inline] with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>>268 fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>> { 269 let name = format!("_rusqlite_sp_{}", depth); 270 Savepoint::with_depth_and_name(conn, depth, name) 271 } 272 273 /// Begin a new savepoint. Can be nested. 274 #[inline] new(conn: &mut Connection) -> Result<Savepoint<'_>>275 pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> { 276 Savepoint::with_depth(conn, 0) 277 } 278 279 /// Begin a new savepoint with a user-provided savepoint name. 280 #[inline] with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>>281 pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> { 282 Savepoint::with_depth_and_name(conn, 0, name) 283 } 284 285 /// Begin a nested savepoint. 286 #[inline] savepoint(&mut self) -> Result<Savepoint<'_>>287 pub fn savepoint(&mut self) -> Result<Savepoint<'_>> { 288 Savepoint::with_depth(self.conn, self.depth + 1) 289 } 290 291 /// Begin a nested savepoint with a user-provided savepoint name. 292 #[inline] savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>293 pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> { 294 Savepoint::with_depth_and_name(self.conn, self.depth + 1, name) 295 } 296 297 /// Get the current setting for what happens to the savepoint when it is 298 /// dropped. 299 #[inline] 300 #[must_use] drop_behavior(&self) -> DropBehavior301 pub fn drop_behavior(&self) -> DropBehavior { 302 self.drop_behavior 303 } 304 305 /// Configure the savepoint to perform the specified action when it is 306 /// dropped. 307 #[inline] set_drop_behavior(&mut self, drop_behavior: DropBehavior)308 pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) { 309 self.drop_behavior = drop_behavior; 310 } 311 312 /// A convenience method which consumes and commits a savepoint. 313 #[inline] commit(mut self) -> Result<()>314 pub fn commit(mut self) -> Result<()> { 315 self.commit_() 316 } 317 318 #[inline] commit_(&mut self) -> Result<()>319 fn commit_(&mut self) -> Result<()> { 320 self.conn.execute_batch(&format!("RELEASE {}", self.name))?; 321 self.committed = true; 322 Ok(()) 323 } 324 325 /// A convenience method which rolls back a savepoint. 326 /// 327 /// ## Note 328 /// 329 /// Unlike `Transaction`s, savepoints remain active after they have been 330 /// rolled back, and can be rolled back again or committed. 331 #[inline] rollback(&mut self) -> Result<()>332 pub fn rollback(&mut self) -> Result<()> { 333 self.conn 334 .execute_batch(&format!("ROLLBACK TO {}", self.name)) 335 } 336 337 /// Consumes the savepoint, committing or rolling back according to the 338 /// current setting (see `drop_behavior`). 339 /// 340 /// Functionally equivalent to the `Drop` implementation, but allows 341 /// callers to see any errors that occur. 342 #[inline] finish(mut self) -> Result<()>343 pub fn finish(mut self) -> Result<()> { 344 self.finish_() 345 } 346 347 #[inline] finish_(&mut self) -> Result<()>348 fn finish_(&mut self) -> Result<()> { 349 if self.committed { 350 return Ok(()); 351 } 352 match self.drop_behavior() { 353 DropBehavior::Commit => self.commit_().or_else(|_| self.rollback()), 354 DropBehavior::Rollback => self.rollback(), 355 DropBehavior::Ignore => Ok(()), 356 DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."), 357 } 358 } 359 } 360 361 impl Deref for Savepoint<'_> { 362 type Target = Connection; 363 364 #[inline] deref(&self) -> &Connection365 fn deref(&self) -> &Connection { 366 self.conn 367 } 368 } 369 370 #[allow(unused_must_use)] 371 impl Drop for Savepoint<'_> { 372 #[inline] drop(&mut self)373 fn drop(&mut self) { 374 self.finish_(); 375 } 376 } 377 378 impl Connection { 379 /// Begin a new transaction with the default behavior (DEFERRED). 380 /// 381 /// The transaction defaults to rolling back when it is dropped. If you 382 /// want the transaction to commit, you must call 383 /// [`commit`](Transaction::commit) or 384 /// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior). 385 /// 386 /// ## Example 387 /// 388 /// ```rust,no_run 389 /// # use rusqlite::{Connection, Result}; 390 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } 391 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } 392 /// fn perform_queries(conn: &mut Connection) -> Result<()> { 393 /// let tx = conn.transaction()?; 394 /// 395 /// do_queries_part_1(&tx)?; // tx causes rollback if this fails 396 /// do_queries_part_2(&tx)?; // tx causes rollback if this fails 397 /// 398 /// tx.commit() 399 /// } 400 /// ``` 401 /// 402 /// # Failure 403 /// 404 /// Will return `Err` if the underlying SQLite call fails. 405 #[inline] transaction(&mut self) -> Result<Transaction<'_>>406 pub fn transaction(&mut self) -> Result<Transaction<'_>> { 407 Transaction::new(self, TransactionBehavior::Deferred) 408 } 409 410 /// Begin a new transaction with a specified behavior. 411 /// 412 /// See [`transaction`](Connection::transaction). 413 /// 414 /// # Failure 415 /// 416 /// Will return `Err` if the underlying SQLite call fails. 417 #[inline] transaction_with_behavior( &mut self, behavior: TransactionBehavior, ) -> Result<Transaction<'_>>418 pub fn transaction_with_behavior( 419 &mut self, 420 behavior: TransactionBehavior, 421 ) -> Result<Transaction<'_>> { 422 Transaction::new(self, behavior) 423 } 424 425 /// Begin a new transaction with the default behavior (DEFERRED). 426 /// 427 /// Attempt to open a nested transaction will result in a SQLite error. 428 /// `Connection::transaction` prevents this at compile time by taking `&mut 429 /// self`, but `Connection::unchecked_transaction()` may be used to defer 430 /// the checking until runtime. 431 /// 432 /// See [`Connection::transaction`] and [`Transaction::new_unchecked`] 433 /// (which can be used if the default transaction behavior is undesirable). 434 /// 435 /// ## Example 436 /// 437 /// ```rust,no_run 438 /// # use rusqlite::{Connection, Result}; 439 /// # use std::rc::Rc; 440 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } 441 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } 442 /// fn perform_queries(conn: Rc<Connection>) -> Result<()> { 443 /// let tx = conn.unchecked_transaction()?; 444 /// 445 /// do_queries_part_1(&tx)?; // tx causes rollback if this fails 446 /// do_queries_part_2(&tx)?; // tx causes rollback if this fails 447 /// 448 /// tx.commit() 449 /// } 450 /// ``` 451 /// 452 /// # Failure 453 /// 454 /// Will return `Err` if the underlying SQLite call fails. The specific 455 /// error returned if transactions are nested is currently unspecified. unchecked_transaction(&self) -> Result<Transaction<'_>>456 pub fn unchecked_transaction(&self) -> Result<Transaction<'_>> { 457 Transaction::new_unchecked(self, TransactionBehavior::Deferred) 458 } 459 460 /// Begin a new savepoint with the default behavior (DEFERRED). 461 /// 462 /// The savepoint defaults to rolling back when it is dropped. If you want 463 /// the savepoint to commit, you must call [`commit`](Savepoint::commit) or 464 /// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint:: 465 /// set_drop_behavior). 466 /// 467 /// ## Example 468 /// 469 /// ```rust,no_run 470 /// # use rusqlite::{Connection, Result}; 471 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } 472 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } 473 /// fn perform_queries(conn: &mut Connection) -> Result<()> { 474 /// let sp = conn.savepoint()?; 475 /// 476 /// do_queries_part_1(&sp)?; // sp causes rollback if this fails 477 /// do_queries_part_2(&sp)?; // sp causes rollback if this fails 478 /// 479 /// sp.commit() 480 /// } 481 /// ``` 482 /// 483 /// # Failure 484 /// 485 /// Will return `Err` if the underlying SQLite call fails. 486 #[inline] savepoint(&mut self) -> Result<Savepoint<'_>>487 pub fn savepoint(&mut self) -> Result<Savepoint<'_>> { 488 Savepoint::new(self) 489 } 490 491 /// Begin a new savepoint with a specified name. 492 /// 493 /// See [`savepoint`](Connection::savepoint). 494 /// 495 /// # Failure 496 /// 497 /// Will return `Err` if the underlying SQLite call fails. 498 #[inline] savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>499 pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> { 500 Savepoint::with_name(self, name) 501 } 502 } 503 504 #[cfg(test)] 505 mod test { 506 use super::DropBehavior; 507 use crate::{Connection, Error, Result}; 508 checked_memory_handle() -> Result<Connection>509 fn checked_memory_handle() -> Result<Connection> { 510 let db = Connection::open_in_memory()?; 511 db.execute_batch("CREATE TABLE foo (x INTEGER)")?; 512 Ok(db) 513 } 514 515 #[test] test_drop() -> Result<()>516 fn test_drop() -> Result<()> { 517 let mut db = checked_memory_handle()?; 518 { 519 let tx = db.transaction()?; 520 tx.execute_batch("INSERT INTO foo VALUES(1)")?; 521 // default: rollback 522 } 523 { 524 let mut tx = db.transaction()?; 525 tx.execute_batch("INSERT INTO foo VALUES(2)")?; 526 tx.set_drop_behavior(DropBehavior::Commit) 527 } 528 { 529 let tx = db.transaction()?; 530 assert_eq!( 531 2i32, 532 tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))? 533 ); 534 } 535 Ok(()) 536 } assert_nested_tx_error(e: crate::Error)537 fn assert_nested_tx_error(e: crate::Error) { 538 if let Error::SqliteFailure(e, Some(m)) = &e { 539 assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR); 540 // FIXME: Not ideal... 541 assert_eq!(e.code, crate::ErrorCode::Unknown); 542 assert!(m.contains("transaction")); 543 } else { 544 panic!("Unexpected error type: {:?}", e); 545 } 546 } 547 548 #[test] test_unchecked_nesting() -> Result<()>549 fn test_unchecked_nesting() -> Result<()> { 550 let db = checked_memory_handle()?; 551 552 { 553 let tx = db.unchecked_transaction()?; 554 let e = tx.unchecked_transaction().unwrap_err(); 555 assert_nested_tx_error(e); 556 // default: rollback 557 } 558 { 559 let tx = db.unchecked_transaction()?; 560 tx.execute_batch("INSERT INTO foo VALUES(1)")?; 561 // Ensure this doesn't interfere with ongoing transaction 562 let e = tx.unchecked_transaction().unwrap_err(); 563 assert_nested_tx_error(e); 564 565 tx.execute_batch("INSERT INTO foo VALUES(1)")?; 566 tx.commit()?; 567 } 568 569 assert_eq!( 570 2i32, 571 db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))? 572 ); 573 Ok(()) 574 } 575 576 #[test] test_explicit_rollback_commit() -> Result<()>577 fn test_explicit_rollback_commit() -> Result<()> { 578 let mut db = checked_memory_handle()?; 579 { 580 let mut tx = db.transaction()?; 581 { 582 let mut sp = tx.savepoint()?; 583 sp.execute_batch("INSERT INTO foo VALUES(1)")?; 584 sp.rollback()?; 585 sp.execute_batch("INSERT INTO foo VALUES(2)")?; 586 sp.commit()?; 587 } 588 tx.commit()?; 589 } 590 { 591 let tx = db.transaction()?; 592 tx.execute_batch("INSERT INTO foo VALUES(4)")?; 593 tx.commit()?; 594 } 595 { 596 let tx = db.transaction()?; 597 assert_eq!( 598 6i32, 599 tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))? 600 ); 601 } 602 Ok(()) 603 } 604 605 #[test] test_savepoint() -> Result<()>606 fn test_savepoint() -> Result<()> { 607 let mut db = checked_memory_handle()?; 608 { 609 let mut tx = db.transaction()?; 610 tx.execute_batch("INSERT INTO foo VALUES(1)")?; 611 assert_current_sum(1, &tx)?; 612 tx.set_drop_behavior(DropBehavior::Commit); 613 { 614 let mut sp1 = tx.savepoint()?; 615 sp1.execute_batch("INSERT INTO foo VALUES(2)")?; 616 assert_current_sum(3, &sp1)?; 617 // will rollback sp1 618 { 619 let mut sp2 = sp1.savepoint()?; 620 sp2.execute_batch("INSERT INTO foo VALUES(4)")?; 621 assert_current_sum(7, &sp2)?; 622 // will rollback sp2 623 { 624 let sp3 = sp2.savepoint()?; 625 sp3.execute_batch("INSERT INTO foo VALUES(8)")?; 626 assert_current_sum(15, &sp3)?; 627 sp3.commit()?; 628 // committed sp3, but will be erased by sp2 rollback 629 } 630 assert_current_sum(15, &sp2)?; 631 } 632 assert_current_sum(3, &sp1)?; 633 } 634 assert_current_sum(1, &tx)?; 635 } 636 assert_current_sum(1, &db)?; 637 Ok(()) 638 } 639 640 #[test] test_ignore_drop_behavior() -> Result<()>641 fn test_ignore_drop_behavior() -> Result<()> { 642 let mut db = checked_memory_handle()?; 643 644 let mut tx = db.transaction()?; 645 { 646 let mut sp1 = tx.savepoint()?; 647 insert(1, &sp1)?; 648 sp1.rollback()?; 649 insert(2, &sp1)?; 650 { 651 let mut sp2 = sp1.savepoint()?; 652 sp2.set_drop_behavior(DropBehavior::Ignore); 653 insert(4, &sp2)?; 654 } 655 assert_current_sum(6, &sp1)?; 656 sp1.commit()?; 657 } 658 assert_current_sum(6, &tx)?; 659 Ok(()) 660 } 661 662 #[test] test_savepoint_names() -> Result<()>663 fn test_savepoint_names() -> Result<()> { 664 let mut db = checked_memory_handle()?; 665 666 { 667 let mut sp1 = db.savepoint_with_name("my_sp")?; 668 insert(1, &sp1)?; 669 assert_current_sum(1, &sp1)?; 670 { 671 let mut sp2 = sp1.savepoint_with_name("my_sp")?; 672 sp2.set_drop_behavior(DropBehavior::Commit); 673 insert(2, &sp2)?; 674 assert_current_sum(3, &sp2)?; 675 sp2.rollback()?; 676 assert_current_sum(1, &sp2)?; 677 insert(4, &sp2)?; 678 } 679 assert_current_sum(5, &sp1)?; 680 sp1.rollback()?; 681 { 682 let mut sp2 = sp1.savepoint_with_name("my_sp")?; 683 sp2.set_drop_behavior(DropBehavior::Ignore); 684 insert(8, &sp2)?; 685 } 686 assert_current_sum(8, &sp1)?; 687 sp1.commit()?; 688 } 689 assert_current_sum(8, &db)?; 690 Ok(()) 691 } 692 693 #[test] test_rc() -> Result<()>694 fn test_rc() -> Result<()> { 695 use std::rc::Rc; 696 let mut conn = Connection::open_in_memory()?; 697 let rc_txn = Rc::new(conn.transaction()?); 698 699 // This will compile only if Transaction is Debug 700 Rc::try_unwrap(rc_txn).unwrap(); 701 Ok(()) 702 } 703 insert(x: i32, conn: &Connection) -> Result<usize>704 fn insert(x: i32, conn: &Connection) -> Result<usize> { 705 conn.execute("INSERT INTO foo VALUES(?)", [x]) 706 } 707 assert_current_sum(x: i32, conn: &Connection) -> Result<()>708 fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> { 709 let i = conn.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?; 710 assert_eq!(x, i); 711 Ok(()) 712 } 713 } 714