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. new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>>105 pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>> { 106 Self::new_unchecked(conn, behavior) 107 } 108 109 /// Begin a new transaction, failing if a transaction is open. 110 /// 111 /// If a transaction is already open, this will return an error. Where 112 /// possible, [`Transaction::new`] should be preferred, as it provides a 113 /// compile-time guarantee that transactions are not nested. new_unchecked( conn: &Connection, behavior: TransactionBehavior, ) -> Result<Transaction<'_>>114 pub fn new_unchecked( 115 conn: &Connection, 116 behavior: TransactionBehavior, 117 ) -> Result<Transaction<'_>> { 118 let query = match behavior { 119 TransactionBehavior::Deferred => "BEGIN DEFERRED", 120 TransactionBehavior::Immediate => "BEGIN IMMEDIATE", 121 TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE", 122 }; 123 conn.execute_batch(query).map(move |_| Transaction { 124 conn, 125 drop_behavior: DropBehavior::Rollback, 126 }) 127 } 128 129 /// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested 130 /// transactions. 131 /// 132 /// ## Note 133 /// 134 /// Just like outer level transactions, savepoint transactions rollback by 135 /// default. 136 /// 137 /// ## Example 138 /// 139 /// ```rust,no_run 140 /// # use rusqlite::{Connection, Result}; 141 /// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true } 142 /// fn perform_queries(conn: &mut Connection) -> Result<()> { 143 /// let mut tx = conn.transaction()?; 144 /// 145 /// { 146 /// let sp = tx.savepoint()?; 147 /// if perform_queries_part_1_succeeds(&sp) { 148 /// sp.commit()?; 149 /// } 150 /// // otherwise, sp will rollback 151 /// } 152 /// 153 /// tx.commit() 154 /// } 155 /// ``` savepoint(&mut self) -> Result<Savepoint<'_>>156 pub fn savepoint(&mut self) -> Result<Savepoint<'_>> { 157 Savepoint::with_depth(self.conn, 1) 158 } 159 160 /// Create a new savepoint with a custom savepoint name. See `savepoint()`. savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>161 pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> { 162 Savepoint::with_depth_and_name(self.conn, 1, name) 163 } 164 165 /// Get the current setting for what happens to the transaction when it is 166 /// dropped. drop_behavior(&self) -> DropBehavior167 pub fn drop_behavior(&self) -> DropBehavior { 168 self.drop_behavior 169 } 170 171 /// Configure the transaction to perform the specified action when it is 172 /// dropped. set_drop_behavior(&mut self, drop_behavior: DropBehavior)173 pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) { 174 self.drop_behavior = drop_behavior 175 } 176 177 /// A convenience method which consumes and commits a transaction. commit(mut self) -> Result<()>178 pub fn commit(mut self) -> Result<()> { 179 self.commit_() 180 } 181 commit_(&mut self) -> Result<()>182 fn commit_(&mut self) -> Result<()> { 183 self.conn.execute_batch("COMMIT")?; 184 Ok(()) 185 } 186 187 /// A convenience method which consumes and rolls back a transaction. rollback(mut self) -> Result<()>188 pub fn rollback(mut self) -> Result<()> { 189 self.rollback_() 190 } 191 rollback_(&mut self) -> Result<()>192 fn rollback_(&mut self) -> Result<()> { 193 self.conn.execute_batch("ROLLBACK")?; 194 Ok(()) 195 } 196 197 /// Consumes the transaction, committing or rolling back according to the 198 /// current setting (see `drop_behavior`). 199 /// 200 /// Functionally equivalent to the `Drop` implementation, but allows 201 /// callers to see any errors that occur. finish(mut self) -> Result<()>202 pub fn finish(mut self) -> Result<()> { 203 self.finish_() 204 } 205 finish_(&mut self) -> Result<()>206 fn finish_(&mut self) -> Result<()> { 207 if self.conn.is_autocommit() { 208 return Ok(()); 209 } 210 match self.drop_behavior() { 211 DropBehavior::Commit => self.commit_().or_else(|_| self.rollback_()), 212 DropBehavior::Rollback => self.rollback_(), 213 DropBehavior::Ignore => Ok(()), 214 DropBehavior::Panic => panic!("Transaction dropped unexpectedly."), 215 } 216 } 217 } 218 219 impl Deref for Transaction<'_> { 220 type Target = Connection; 221 deref(&self) -> &Connection222 fn deref(&self) -> &Connection { 223 self.conn 224 } 225 } 226 227 #[allow(unused_must_use)] 228 impl Drop for Transaction<'_> { drop(&mut self)229 fn drop(&mut self) { 230 self.finish_(); 231 } 232 } 233 234 impl Savepoint<'_> { with_depth_and_name<T: Into<String>>( conn: &Connection, depth: u32, name: T, ) -> Result<Savepoint<'_>>235 fn with_depth_and_name<T: Into<String>>( 236 conn: &Connection, 237 depth: u32, 238 name: T, 239 ) -> Result<Savepoint<'_>> { 240 let name = name.into(); 241 conn.execute_batch(&format!("SAVEPOINT {}", name)) 242 .map(|_| Savepoint { 243 conn, 244 name, 245 depth, 246 drop_behavior: DropBehavior::Rollback, 247 committed: false, 248 }) 249 } 250 with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>>251 fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>> { 252 let name = format!("_rusqlite_sp_{}", depth); 253 Savepoint::with_depth_and_name(conn, depth, name) 254 } 255 256 /// Begin a new savepoint. Can be nested. new(conn: &mut Connection) -> Result<Savepoint<'_>>257 pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> { 258 Savepoint::with_depth(conn, 0) 259 } 260 261 /// Begin a new savepoint with a user-provided savepoint name. with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>>262 pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> { 263 Savepoint::with_depth_and_name(conn, 0, name) 264 } 265 266 /// Begin a nested savepoint. savepoint(&mut self) -> Result<Savepoint<'_>>267 pub fn savepoint(&mut self) -> Result<Savepoint<'_>> { 268 Savepoint::with_depth(self.conn, self.depth + 1) 269 } 270 271 /// Begin a nested savepoint with a user-provided savepoint name. savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>272 pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> { 273 Savepoint::with_depth_and_name(self.conn, self.depth + 1, name) 274 } 275 276 /// Get the current setting for what happens to the savepoint when it is 277 /// dropped. drop_behavior(&self) -> DropBehavior278 pub fn drop_behavior(&self) -> DropBehavior { 279 self.drop_behavior 280 } 281 282 /// Configure the savepoint to perform the specified action when it is 283 /// dropped. set_drop_behavior(&mut self, drop_behavior: DropBehavior)284 pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) { 285 self.drop_behavior = drop_behavior 286 } 287 288 /// A convenience method which consumes and commits a savepoint. commit(mut self) -> Result<()>289 pub fn commit(mut self) -> Result<()> { 290 self.commit_() 291 } 292 commit_(&mut self) -> Result<()>293 fn commit_(&mut self) -> Result<()> { 294 self.conn.execute_batch(&format!("RELEASE {}", self.name))?; 295 self.committed = true; 296 Ok(()) 297 } 298 299 /// A convenience method which rolls back a savepoint. 300 /// 301 /// ## Note 302 /// 303 /// Unlike `Transaction`s, savepoints remain active after they have been 304 /// rolled back, and can be rolled back again or committed. rollback(&mut self) -> Result<()>305 pub fn rollback(&mut self) -> Result<()> { 306 self.conn 307 .execute_batch(&format!("ROLLBACK TO {}", self.name)) 308 } 309 310 /// Consumes the savepoint, committing or rolling back according to the 311 /// current setting (see `drop_behavior`). 312 /// 313 /// Functionally equivalent to the `Drop` implementation, but allows 314 /// callers to see any errors that occur. finish(mut self) -> Result<()>315 pub fn finish(mut self) -> Result<()> { 316 self.finish_() 317 } 318 finish_(&mut self) -> Result<()>319 fn finish_(&mut self) -> Result<()> { 320 if self.committed { 321 return Ok(()); 322 } 323 match self.drop_behavior() { 324 DropBehavior::Commit => self.commit_().or_else(|_| self.rollback()), 325 DropBehavior::Rollback => self.rollback(), 326 DropBehavior::Ignore => Ok(()), 327 DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."), 328 } 329 } 330 } 331 332 impl Deref for Savepoint<'_> { 333 type Target = Connection; 334 deref(&self) -> &Connection335 fn deref(&self) -> &Connection { 336 self.conn 337 } 338 } 339 340 #[allow(unused_must_use)] 341 impl Drop for Savepoint<'_> { drop(&mut self)342 fn drop(&mut self) { 343 self.finish_(); 344 } 345 } 346 347 impl Connection { 348 /// Begin a new transaction with the default behavior (DEFERRED). 349 /// 350 /// The transaction defaults to rolling back when it is dropped. If you 351 /// want the transaction to commit, you must call `commit` or 352 /// `set_drop_behavior(DropBehavior::Commit)`. 353 /// 354 /// ## Example 355 /// 356 /// ```rust,no_run 357 /// # use rusqlite::{Connection, Result}; 358 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } 359 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } 360 /// fn perform_queries(conn: &mut Connection) -> Result<()> { 361 /// let tx = conn.transaction()?; 362 /// 363 /// do_queries_part_1(&tx)?; // tx causes rollback if this fails 364 /// do_queries_part_2(&tx)?; // tx causes rollback if this fails 365 /// 366 /// tx.commit() 367 /// } 368 /// ``` 369 /// 370 /// # Failure 371 /// 372 /// Will return `Err` if the underlying SQLite call fails. transaction(&mut self) -> Result<Transaction<'_>>373 pub fn transaction(&mut self) -> Result<Transaction<'_>> { 374 Transaction::new(self, TransactionBehavior::Deferred) 375 } 376 377 /// Begin a new transaction with a specified behavior. 378 /// 379 /// See `transaction`. 380 /// 381 /// # Failure 382 /// 383 /// Will return `Err` if the underlying SQLite call fails. transaction_with_behavior( &mut self, behavior: TransactionBehavior, ) -> Result<Transaction<'_>>384 pub fn transaction_with_behavior( 385 &mut self, 386 behavior: TransactionBehavior, 387 ) -> Result<Transaction<'_>> { 388 Transaction::new(self, behavior) 389 } 390 391 /// Begin a new transaction with the default behavior (DEFERRED). 392 /// 393 /// Attempt to open a nested transaction will result in a SQLite error. 394 /// `Connection::transaction` prevents this at compile time by taking `&mut 395 /// self`, but `Connection::unchecked_transaction()` may be used to defer 396 /// the checking until runtime. 397 /// 398 /// See [`Connection::transaction`] and [`Transaction::new_unchecked`] 399 /// (which can be used if the default transaction behavior is undesirable). 400 /// 401 /// ## Example 402 /// 403 /// ```rust,no_run 404 /// # use rusqlite::{Connection, Result}; 405 /// # use std::rc::Rc; 406 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } 407 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } 408 /// fn perform_queries(conn: Rc<Connection>) -> Result<()> { 409 /// let tx = conn.unchecked_transaction()?; 410 /// 411 /// do_queries_part_1(&tx)?; // tx causes rollback if this fails 412 /// do_queries_part_2(&tx)?; // tx causes rollback if this fails 413 /// 414 /// tx.commit() 415 /// } 416 /// ``` 417 /// 418 /// # Failure 419 /// 420 /// Will return `Err` if the underlying SQLite call fails. The specific 421 /// error returned if transactions are nested is currently unspecified. unchecked_transaction(&self) -> Result<Transaction<'_>>422 pub fn unchecked_transaction(&self) -> Result<Transaction<'_>> { 423 Transaction::new_unchecked(self, TransactionBehavior::Deferred) 424 } 425 426 /// Begin a new savepoint with the default behavior (DEFERRED). 427 /// 428 /// The savepoint defaults to rolling back when it is dropped. If you want 429 /// the savepoint to commit, you must call `commit` or 430 /// `set_drop_behavior(DropBehavior::Commit)`. 431 /// 432 /// ## Example 433 /// 434 /// ```rust,no_run 435 /// # use rusqlite::{Connection, Result}; 436 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } 437 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } 438 /// fn perform_queries(conn: &mut Connection) -> Result<()> { 439 /// let sp = conn.savepoint()?; 440 /// 441 /// do_queries_part_1(&sp)?; // sp causes rollback if this fails 442 /// do_queries_part_2(&sp)?; // sp causes rollback if this fails 443 /// 444 /// sp.commit() 445 /// } 446 /// ``` 447 /// 448 /// # Failure 449 /// 450 /// Will return `Err` if the underlying SQLite call fails. savepoint(&mut self) -> Result<Savepoint<'_>>451 pub fn savepoint(&mut self) -> Result<Savepoint<'_>> { 452 Savepoint::new(self) 453 } 454 455 /// Begin a new savepoint with a specified name. 456 /// 457 /// See `savepoint`. 458 /// 459 /// # Failure 460 /// 461 /// Will return `Err` if the underlying SQLite call fails. savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>462 pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> { 463 Savepoint::with_name(self, name) 464 } 465 } 466 467 #[cfg(test)] 468 mod test { 469 use super::DropBehavior; 470 use crate::{Connection, Error, NO_PARAMS}; 471 checked_memory_handle() -> Connection472 fn checked_memory_handle() -> Connection { 473 let db = Connection::open_in_memory().unwrap(); 474 db.execute_batch("CREATE TABLE foo (x INTEGER)").unwrap(); 475 db 476 } 477 478 #[test] test_drop()479 fn test_drop() { 480 let mut db = checked_memory_handle(); 481 { 482 let tx = db.transaction().unwrap(); 483 tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap(); 484 // default: rollback 485 } 486 { 487 let mut tx = db.transaction().unwrap(); 488 tx.execute_batch("INSERT INTO foo VALUES(2)").unwrap(); 489 tx.set_drop_behavior(DropBehavior::Commit) 490 } 491 { 492 let tx = db.transaction().unwrap(); 493 assert_eq!( 494 2i32, 495 tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0)) 496 .unwrap() 497 ); 498 } 499 } assert_nested_tx_error(e: crate::Error)500 fn assert_nested_tx_error(e: crate::Error) { 501 if let Error::SqliteFailure(e, Some(m)) = &e { 502 assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR); 503 // FIXME: Not ideal... 504 assert_eq!(e.code, crate::ErrorCode::Unknown); 505 assert!(m.contains("transaction")); 506 } else { 507 panic!("Unexpected error type: {:?}", e); 508 } 509 } 510 511 #[test] test_unchecked_nesting()512 fn test_unchecked_nesting() { 513 let db = checked_memory_handle(); 514 515 { 516 let tx = db.unchecked_transaction().unwrap(); 517 let e = tx.unchecked_transaction().unwrap_err(); 518 assert_nested_tx_error(e); 519 // default: rollback 520 } 521 { 522 let tx = db.unchecked_transaction().unwrap(); 523 tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap(); 524 // Ensure this doesn't interfere with ongoing transaction 525 let e = tx.unchecked_transaction().unwrap_err(); 526 assert_nested_tx_error(e); 527 528 tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap(); 529 tx.commit().unwrap(); 530 } 531 532 assert_eq!( 533 2i32, 534 db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0)) 535 .unwrap() 536 ); 537 } 538 539 #[test] test_explicit_rollback_commit()540 fn test_explicit_rollback_commit() { 541 let mut db = checked_memory_handle(); 542 { 543 let mut tx = db.transaction().unwrap(); 544 { 545 let mut sp = tx.savepoint().unwrap(); 546 sp.execute_batch("INSERT INTO foo VALUES(1)").unwrap(); 547 sp.rollback().unwrap(); 548 sp.execute_batch("INSERT INTO foo VALUES(2)").unwrap(); 549 sp.commit().unwrap(); 550 } 551 tx.commit().unwrap(); 552 } 553 { 554 let tx = db.transaction().unwrap(); 555 tx.execute_batch("INSERT INTO foo VALUES(4)").unwrap(); 556 tx.commit().unwrap(); 557 } 558 { 559 let tx = db.transaction().unwrap(); 560 assert_eq!( 561 6i32, 562 tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0)) 563 .unwrap() 564 ); 565 } 566 } 567 568 #[test] test_savepoint()569 fn test_savepoint() { 570 let mut db = checked_memory_handle(); 571 { 572 let mut tx = db.transaction().unwrap(); 573 tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap(); 574 assert_current_sum(1, &tx); 575 tx.set_drop_behavior(DropBehavior::Commit); 576 { 577 let mut sp1 = tx.savepoint().unwrap(); 578 sp1.execute_batch("INSERT INTO foo VALUES(2)").unwrap(); 579 assert_current_sum(3, &sp1); 580 // will rollback sp1 581 { 582 let mut sp2 = sp1.savepoint().unwrap(); 583 sp2.execute_batch("INSERT INTO foo VALUES(4)").unwrap(); 584 assert_current_sum(7, &sp2); 585 // will rollback sp2 586 { 587 let sp3 = sp2.savepoint().unwrap(); 588 sp3.execute_batch("INSERT INTO foo VALUES(8)").unwrap(); 589 assert_current_sum(15, &sp3); 590 sp3.commit().unwrap(); 591 // committed sp3, but will be erased by sp2 rollback 592 } 593 assert_current_sum(15, &sp2); 594 } 595 assert_current_sum(3, &sp1); 596 } 597 assert_current_sum(1, &tx); 598 } 599 assert_current_sum(1, &db); 600 } 601 602 #[test] test_ignore_drop_behavior()603 fn test_ignore_drop_behavior() { 604 let mut db = checked_memory_handle(); 605 606 let mut tx = db.transaction().unwrap(); 607 { 608 let mut sp1 = tx.savepoint().unwrap(); 609 insert(1, &sp1); 610 sp1.rollback().unwrap(); 611 insert(2, &sp1); 612 { 613 let mut sp2 = sp1.savepoint().unwrap(); 614 sp2.set_drop_behavior(DropBehavior::Ignore); 615 insert(4, &sp2); 616 } 617 assert_current_sum(6, &sp1); 618 sp1.commit().unwrap(); 619 } 620 assert_current_sum(6, &tx); 621 } 622 623 #[test] test_savepoint_names()624 fn test_savepoint_names() { 625 let mut db = checked_memory_handle(); 626 627 { 628 let mut sp1 = db.savepoint_with_name("my_sp").unwrap(); 629 insert(1, &sp1); 630 assert_current_sum(1, &sp1); 631 { 632 let mut sp2 = sp1.savepoint_with_name("my_sp").unwrap(); 633 sp2.set_drop_behavior(DropBehavior::Commit); 634 insert(2, &sp2); 635 assert_current_sum(3, &sp2); 636 sp2.rollback().unwrap(); 637 assert_current_sum(1, &sp2); 638 insert(4, &sp2); 639 } 640 assert_current_sum(5, &sp1); 641 sp1.rollback().unwrap(); 642 { 643 let mut sp2 = sp1.savepoint_with_name("my_sp").unwrap(); 644 sp2.set_drop_behavior(DropBehavior::Ignore); 645 insert(8, &sp2); 646 } 647 assert_current_sum(8, &sp1); 648 sp1.commit().unwrap(); 649 } 650 assert_current_sum(8, &db); 651 } 652 653 #[test] test_rc()654 fn test_rc() { 655 use std::rc::Rc; 656 let mut conn = Connection::open_in_memory().unwrap(); 657 let rc_txn = Rc::new(conn.transaction().unwrap()); 658 659 // This will compile only if Transaction is Debug 660 Rc::try_unwrap(rc_txn).unwrap(); 661 } 662 insert(x: i32, conn: &Connection)663 fn insert(x: i32, conn: &Connection) { 664 conn.execute("INSERT INTO foo VALUES(?)", &[x]).unwrap(); 665 } 666 assert_current_sum(x: i32, conn: &Connection)667 fn assert_current_sum(x: i32, conn: &Connection) { 668 let i = conn 669 .query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0)) 670 .unwrap(); 671 assert_eq!(x, i); 672 } 673 } 674