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