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 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 /// 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::new_(self.conn) 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_name_(self.conn, 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_name_<T: Into<String>>(conn: &Connection, name: T) -> Result<Savepoint<'_>>251 fn with_name_<T: Into<String>>(conn: &Connection, name: T) -> Result<Savepoint<'_>> { 252 let name = name.into(); 253 conn.execute_batch(&format!("SAVEPOINT {name}")) 254 .map(|()| Savepoint { 255 conn, 256 name, 257 drop_behavior: DropBehavior::Rollback, 258 committed: false, 259 }) 260 } 261 262 #[inline] new_(conn: &Connection) -> Result<Savepoint<'_>>263 fn new_(conn: &Connection) -> Result<Savepoint<'_>> { 264 Savepoint::with_name_(conn, "_rusqlite_sp") 265 } 266 267 /// Begin a new savepoint. Can be nested. 268 #[inline] new(conn: &mut Connection) -> Result<Savepoint<'_>>269 pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> { 270 Savepoint::new_(conn) 271 } 272 273 /// Begin a new savepoint with a user-provided savepoint name. 274 #[inline] with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>>275 pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> { 276 Savepoint::with_name_(conn, name) 277 } 278 279 /// Begin a nested savepoint. 280 #[inline] savepoint(&mut self) -> Result<Savepoint<'_>>281 pub fn savepoint(&mut self) -> Result<Savepoint<'_>> { 282 Savepoint::new_(self.conn) 283 } 284 285 /// Begin a nested savepoint with a user-provided savepoint name. 286 #[inline] savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>287 pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> { 288 Savepoint::with_name_(self.conn, name) 289 } 290 291 /// Get the current setting for what happens to the savepoint when it is 292 /// dropped. 293 #[inline] 294 #[must_use] drop_behavior(&self) -> DropBehavior295 pub fn drop_behavior(&self) -> DropBehavior { 296 self.drop_behavior 297 } 298 299 /// Configure the savepoint to perform the specified action when it is 300 /// dropped. 301 #[inline] set_drop_behavior(&mut self, drop_behavior: DropBehavior)302 pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) { 303 self.drop_behavior = drop_behavior; 304 } 305 306 /// A convenience method which consumes and commits a savepoint. 307 #[inline] commit(mut self) -> Result<()>308 pub fn commit(mut self) -> Result<()> { 309 self.commit_() 310 } 311 312 #[inline] commit_(&mut self) -> Result<()>313 fn commit_(&mut self) -> Result<()> { 314 self.conn.execute_batch(&format!("RELEASE {}", self.name))?; 315 self.committed = true; 316 Ok(()) 317 } 318 319 /// A convenience method which rolls back a savepoint. 320 /// 321 /// ## Note 322 /// 323 /// Unlike `Transaction`s, savepoints remain active after they have been 324 /// rolled back, and can be rolled back again or committed. 325 #[inline] rollback(&mut self) -> Result<()>326 pub fn rollback(&mut self) -> Result<()> { 327 self.conn 328 .execute_batch(&format!("ROLLBACK TO {}", self.name)) 329 } 330 331 /// Consumes the savepoint, committing or rolling back according to the 332 /// current setting (see `drop_behavior`). 333 /// 334 /// Functionally equivalent to the `Drop` implementation, but allows 335 /// callers to see any errors that occur. 336 #[inline] finish(mut self) -> Result<()>337 pub fn finish(mut self) -> Result<()> { 338 self.finish_() 339 } 340 341 #[inline] finish_(&mut self) -> Result<()>342 fn finish_(&mut self) -> Result<()> { 343 if self.committed { 344 return Ok(()); 345 } 346 match self.drop_behavior() { 347 DropBehavior::Commit => self 348 .commit_() 349 .or_else(|_| self.rollback().and_then(|()| self.commit_())), 350 DropBehavior::Rollback => self.rollback().and_then(|()| self.commit_()), 351 DropBehavior::Ignore => Ok(()), 352 DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."), 353 } 354 } 355 } 356 357 impl Deref for Savepoint<'_> { 358 type Target = Connection; 359 360 #[inline] deref(&self) -> &Connection361 fn deref(&self) -> &Connection { 362 self.conn 363 } 364 } 365 366 #[allow(unused_must_use)] 367 impl Drop for Savepoint<'_> { 368 #[inline] drop(&mut self)369 fn drop(&mut self) { 370 self.finish_(); 371 } 372 } 373 374 /// Transaction state of a database 375 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 376 #[non_exhaustive] 377 #[cfg(feature = "modern_sqlite")] // 3.37.0 378 #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] 379 pub enum TransactionState { 380 /// Equivalent to SQLITE_TXN_NONE 381 None, 382 /// Equivalent to SQLITE_TXN_READ 383 Read, 384 /// Equivalent to SQLITE_TXN_WRITE 385 Write, 386 } 387 388 impl Connection { 389 /// Begin a new transaction with the default behavior (DEFERRED). 390 /// 391 /// The transaction defaults to rolling back when it is dropped. If you 392 /// want the transaction to commit, you must call 393 /// [`commit`](Transaction::commit) or 394 /// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior). 395 /// 396 /// ## Example 397 /// 398 /// ```rust,no_run 399 /// # use rusqlite::{Connection, Result}; 400 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } 401 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } 402 /// fn perform_queries(conn: &mut Connection) -> Result<()> { 403 /// let tx = conn.transaction()?; 404 /// 405 /// do_queries_part_1(&tx)?; // tx causes rollback if this fails 406 /// do_queries_part_2(&tx)?; // tx causes rollback if this fails 407 /// 408 /// tx.commit() 409 /// } 410 /// ``` 411 /// 412 /// # Failure 413 /// 414 /// Will return `Err` if the underlying SQLite call fails. 415 #[inline] transaction(&mut self) -> Result<Transaction<'_>>416 pub fn transaction(&mut self) -> Result<Transaction<'_>> { 417 Transaction::new(self, self.transaction_behavior) 418 } 419 420 /// Begin a new transaction with a specified behavior. 421 /// 422 /// See [`transaction`](Connection::transaction). 423 /// 424 /// # Failure 425 /// 426 /// Will return `Err` if the underlying SQLite call fails. 427 #[inline] transaction_with_behavior( &mut self, behavior: TransactionBehavior, ) -> Result<Transaction<'_>>428 pub fn transaction_with_behavior( 429 &mut self, 430 behavior: TransactionBehavior, 431 ) -> Result<Transaction<'_>> { 432 Transaction::new(self, behavior) 433 } 434 435 /// Begin a new transaction with the default behavior (DEFERRED). 436 /// 437 /// Attempt to open a nested transaction will result in a SQLite error. 438 /// `Connection::transaction` prevents this at compile time by taking `&mut 439 /// self`, but `Connection::unchecked_transaction()` may be used to defer 440 /// the checking until runtime. 441 /// 442 /// See [`Connection::transaction`] and [`Transaction::new_unchecked`] 443 /// (which can be used if the default transaction behavior is undesirable). 444 /// 445 /// ## Example 446 /// 447 /// ```rust,no_run 448 /// # use rusqlite::{Connection, Result}; 449 /// # use std::rc::Rc; 450 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } 451 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } 452 /// fn perform_queries(conn: Rc<Connection>) -> Result<()> { 453 /// let tx = conn.unchecked_transaction()?; 454 /// 455 /// do_queries_part_1(&tx)?; // tx causes rollback if this fails 456 /// do_queries_part_2(&tx)?; // tx causes rollback if this fails 457 /// 458 /// tx.commit() 459 /// } 460 /// ``` 461 /// 462 /// # Failure 463 /// 464 /// Will return `Err` if the underlying SQLite call fails. The specific 465 /// error returned if transactions are nested is currently unspecified. unchecked_transaction(&self) -> Result<Transaction<'_>>466 pub fn unchecked_transaction(&self) -> Result<Transaction<'_>> { 467 Transaction::new_unchecked(self, self.transaction_behavior) 468 } 469 470 /// Begin a new savepoint with the default behavior (DEFERRED). 471 /// 472 /// The savepoint defaults to rolling back when it is dropped. If you want 473 /// the savepoint to commit, you must call [`commit`](Savepoint::commit) or 474 /// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::set_drop_behavior). 475 /// 476 /// ## Example 477 /// 478 /// ```rust,no_run 479 /// # use rusqlite::{Connection, Result}; 480 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } 481 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } 482 /// fn perform_queries(conn: &mut Connection) -> Result<()> { 483 /// let sp = conn.savepoint()?; 484 /// 485 /// do_queries_part_1(&sp)?; // sp causes rollback if this fails 486 /// do_queries_part_2(&sp)?; // sp causes rollback if this fails 487 /// 488 /// sp.commit() 489 /// } 490 /// ``` 491 /// 492 /// # Failure 493 /// 494 /// Will return `Err` if the underlying SQLite call fails. 495 #[inline] savepoint(&mut self) -> Result<Savepoint<'_>>496 pub fn savepoint(&mut self) -> Result<Savepoint<'_>> { 497 Savepoint::new(self) 498 } 499 500 /// Begin a new savepoint with a specified name. 501 /// 502 /// See [`savepoint`](Connection::savepoint). 503 /// 504 /// # Failure 505 /// 506 /// Will return `Err` if the underlying SQLite call fails. 507 #[inline] savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>508 pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> { 509 Savepoint::with_name(self, name) 510 } 511 512 /// Determine the transaction state of a database 513 #[cfg(feature = "modern_sqlite")] // 3.37.0 514 #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))] transaction_state( &self, db_name: Option<crate::DatabaseName<'_>>, ) -> Result<TransactionState>515 pub fn transaction_state( 516 &self, 517 db_name: Option<crate::DatabaseName<'_>>, 518 ) -> Result<TransactionState> { 519 self.db.borrow().txn_state(db_name) 520 } 521 522 /// Set the default transaction behavior for the connection. 523 /// 524 /// ## Note 525 /// 526 /// This will only apply to transactions initiated by [`transaction`](Connection::transaction) 527 /// or [`unchecked_transaction`](Connection::unchecked_transaction). 528 /// 529 /// ## Example 530 /// 531 /// ```rust,no_run 532 /// # use rusqlite::{Connection, Result, TransactionBehavior}; 533 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) } 534 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) } 535 /// fn perform_queries(conn: &mut Connection) -> Result<()> { 536 /// conn.set_transaction_behavior(TransactionBehavior::Immediate); 537 /// 538 /// let tx = conn.transaction()?; 539 /// 540 /// do_queries_part_1(&tx)?; // tx causes rollback if this fails 541 /// do_queries_part_2(&tx)?; // tx causes rollback if this fails 542 /// 543 /// tx.commit() 544 /// } 545 /// ``` set_transaction_behavior(&mut self, behavior: TransactionBehavior)546 pub fn set_transaction_behavior(&mut self, behavior: TransactionBehavior) { 547 self.transaction_behavior = behavior; 548 } 549 } 550 551 #[cfg(test)] 552 mod test { 553 use super::DropBehavior; 554 use crate::{Connection, Error, Result}; 555 checked_memory_handle() -> Result<Connection>556 fn checked_memory_handle() -> Result<Connection> { 557 let db = Connection::open_in_memory()?; 558 db.execute_batch("CREATE TABLE foo (x INTEGER)")?; 559 Ok(db) 560 } 561 562 #[test] test_drop() -> Result<()>563 fn test_drop() -> Result<()> { 564 let mut db = checked_memory_handle()?; 565 { 566 let tx = db.transaction()?; 567 tx.execute_batch("INSERT INTO foo VALUES(1)")?; 568 // default: rollback 569 } 570 { 571 let mut tx = db.transaction()?; 572 tx.execute_batch("INSERT INTO foo VALUES(2)")?; 573 tx.set_drop_behavior(DropBehavior::Commit) 574 } 575 { 576 let tx = db.transaction()?; 577 assert_eq!(2i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?); 578 } 579 Ok(()) 580 } assert_nested_tx_error(e: Error)581 fn assert_nested_tx_error(e: Error) { 582 if let Error::SqliteFailure(e, Some(m)) = &e { 583 assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR); 584 // FIXME: Not ideal... 585 assert_eq!(e.code, crate::ErrorCode::Unknown); 586 assert!(m.contains("transaction")); 587 } else { 588 panic!("Unexpected error type: {e:?}"); 589 } 590 } 591 592 #[test] test_unchecked_nesting() -> Result<()>593 fn test_unchecked_nesting() -> Result<()> { 594 let db = checked_memory_handle()?; 595 596 { 597 let tx = db.unchecked_transaction()?; 598 let e = tx.unchecked_transaction().unwrap_err(); 599 assert_nested_tx_error(e); 600 // default: rollback 601 } 602 { 603 let tx = db.unchecked_transaction()?; 604 tx.execute_batch("INSERT INTO foo VALUES(1)")?; 605 // Ensure this doesn't interfere with ongoing transaction 606 let e = tx.unchecked_transaction().unwrap_err(); 607 assert_nested_tx_error(e); 608 609 tx.execute_batch("INSERT INTO foo VALUES(1)")?; 610 tx.commit()?; 611 } 612 613 assert_eq!(2i32, db.one_column::<i32>("SELECT SUM(x) FROM foo")?); 614 Ok(()) 615 } 616 617 #[test] test_explicit_rollback_commit() -> Result<()>618 fn test_explicit_rollback_commit() -> Result<()> { 619 let mut db = checked_memory_handle()?; 620 { 621 let mut tx = db.transaction()?; 622 { 623 let mut sp = tx.savepoint()?; 624 sp.execute_batch("INSERT INTO foo VALUES(1)")?; 625 sp.rollback()?; 626 sp.execute_batch("INSERT INTO foo VALUES(2)")?; 627 sp.commit()?; 628 } 629 tx.commit()?; 630 } 631 { 632 let tx = db.transaction()?; 633 tx.execute_batch("INSERT INTO foo VALUES(4)")?; 634 tx.commit()?; 635 } 636 { 637 let tx = db.transaction()?; 638 assert_eq!(6i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?); 639 } 640 Ok(()) 641 } 642 643 #[test] test_savepoint() -> Result<()>644 fn test_savepoint() -> Result<()> { 645 let mut db = checked_memory_handle()?; 646 { 647 let mut tx = db.transaction()?; 648 tx.execute_batch("INSERT INTO foo VALUES(1)")?; 649 assert_current_sum(1, &tx)?; 650 tx.set_drop_behavior(DropBehavior::Commit); 651 { 652 let mut sp1 = tx.savepoint()?; 653 sp1.execute_batch("INSERT INTO foo VALUES(2)")?; 654 assert_current_sum(3, &sp1)?; 655 // will roll back sp1 656 { 657 let mut sp2 = sp1.savepoint()?; 658 sp2.execute_batch("INSERT INTO foo VALUES(4)")?; 659 assert_current_sum(7, &sp2)?; 660 // will roll back sp2 661 { 662 let sp3 = sp2.savepoint()?; 663 sp3.execute_batch("INSERT INTO foo VALUES(8)")?; 664 assert_current_sum(15, &sp3)?; 665 sp3.commit()?; 666 // committed sp3, but will be erased by sp2 rollback 667 } 668 assert_current_sum(15, &sp2)?; 669 } 670 assert_current_sum(3, &sp1)?; 671 } 672 assert_current_sum(1, &tx)?; 673 } 674 assert_current_sum(1, &db)?; 675 Ok(()) 676 } 677 678 #[test] test_ignore_drop_behavior() -> Result<()>679 fn test_ignore_drop_behavior() -> Result<()> { 680 let mut db = checked_memory_handle()?; 681 682 let mut tx = db.transaction()?; 683 { 684 let mut sp1 = tx.savepoint()?; 685 insert(1, &sp1)?; 686 sp1.rollback()?; 687 insert(2, &sp1)?; 688 { 689 let mut sp2 = sp1.savepoint()?; 690 sp2.set_drop_behavior(DropBehavior::Ignore); 691 insert(4, &sp2)?; 692 } 693 assert_current_sum(6, &sp1)?; 694 sp1.commit()?; 695 } 696 assert_current_sum(6, &tx)?; 697 Ok(()) 698 } 699 700 #[test] test_savepoint_drop_behavior_releases() -> Result<()>701 fn test_savepoint_drop_behavior_releases() -> Result<()> { 702 let mut db = checked_memory_handle()?; 703 704 { 705 let mut sp = db.savepoint()?; 706 sp.set_drop_behavior(DropBehavior::Commit); 707 } 708 assert!(db.is_autocommit()); 709 { 710 let mut sp = db.savepoint()?; 711 sp.set_drop_behavior(DropBehavior::Rollback); 712 } 713 assert!(db.is_autocommit()); 714 715 Ok(()) 716 } 717 718 #[test] test_savepoint_release_error() -> Result<()>719 fn test_savepoint_release_error() -> Result<()> { 720 let mut db = checked_memory_handle()?; 721 722 db.pragma_update(None, "foreign_keys", true)?; 723 db.execute_batch("CREATE TABLE r(n INTEGER PRIMARY KEY NOT NULL); CREATE TABLE f(n REFERENCES r(n) DEFERRABLE INITIALLY DEFERRED);")?; 724 { 725 let mut sp = db.savepoint()?; 726 sp.execute("INSERT INTO f VALUES (0)", [])?; 727 sp.set_drop_behavior(DropBehavior::Commit); 728 } 729 assert!(db.is_autocommit()); 730 731 Ok(()) 732 } 733 734 #[test] test_savepoint_names() -> Result<()>735 fn test_savepoint_names() -> Result<()> { 736 let mut db = checked_memory_handle()?; 737 738 { 739 let mut sp1 = db.savepoint_with_name("my_sp")?; 740 insert(1, &sp1)?; 741 assert_current_sum(1, &sp1)?; 742 { 743 let mut sp2 = sp1.savepoint_with_name("my_sp")?; 744 sp2.set_drop_behavior(DropBehavior::Commit); 745 insert(2, &sp2)?; 746 assert_current_sum(3, &sp2)?; 747 sp2.rollback()?; 748 assert_current_sum(1, &sp2)?; 749 insert(4, &sp2)?; 750 } 751 assert_current_sum(5, &sp1)?; 752 sp1.rollback()?; 753 { 754 let mut sp2 = sp1.savepoint_with_name("my_sp")?; 755 sp2.set_drop_behavior(DropBehavior::Ignore); 756 insert(8, &sp2)?; 757 } 758 assert_current_sum(8, &sp1)?; 759 sp1.commit()?; 760 } 761 assert_current_sum(8, &db)?; 762 Ok(()) 763 } 764 765 #[test] test_rc() -> Result<()>766 fn test_rc() -> Result<()> { 767 use std::rc::Rc; 768 let mut conn = Connection::open_in_memory()?; 769 let rc_txn = Rc::new(conn.transaction()?); 770 771 // This will compile only if Transaction is Debug 772 Rc::try_unwrap(rc_txn).unwrap(); 773 Ok(()) 774 } 775 insert(x: i32, conn: &Connection) -> Result<usize>776 fn insert(x: i32, conn: &Connection) -> Result<usize> { 777 conn.execute("INSERT INTO foo VALUES(?1)", [x]) 778 } 779 assert_current_sum(x: i32, conn: &Connection) -> Result<()>780 fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> { 781 let i = conn.one_column::<i32>("SELECT SUM(x) FROM foo")?; 782 assert_eq!(x, i); 783 Ok(()) 784 } 785 786 #[test] 787 #[cfg(feature = "modern_sqlite")] txn_state() -> Result<()>788 fn txn_state() -> Result<()> { 789 use super::TransactionState; 790 use crate::DatabaseName; 791 let db = Connection::open_in_memory()?; 792 assert_eq!( 793 TransactionState::None, 794 db.transaction_state(Some(DatabaseName::Main))? 795 ); 796 assert_eq!(TransactionState::None, db.transaction_state(None)?); 797 db.execute_batch("BEGIN")?; 798 assert_eq!(TransactionState::None, db.transaction_state(None)?); 799 let _: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?; 800 assert_eq!(TransactionState::Read, db.transaction_state(None)?); 801 db.pragma_update(None, "user_version", 1)?; 802 assert_eq!(TransactionState::Write, db.transaction_state(None)?); 803 db.execute_batch("ROLLBACK")?; 804 Ok(()) 805 } 806 807 #[test] 808 #[cfg(feature = "modern_sqlite")] auto_commit() -> Result<()>809 fn auto_commit() -> Result<()> { 810 use super::TransactionState; 811 let db = Connection::open_in_memory()?; 812 db.execute_batch("CREATE TABLE t(i UNIQUE);")?; 813 assert!(db.is_autocommit()); 814 let mut stmt = db.prepare("SELECT name FROM sqlite_master")?; 815 assert_eq!(TransactionState::None, db.transaction_state(None)?); 816 { 817 let mut rows = stmt.query([])?; 818 assert!(rows.next()?.is_some()); // start reading 819 assert_eq!(TransactionState::Read, db.transaction_state(None)?); 820 db.execute("INSERT INTO t VALUES (1)", [])?; // auto-commit 821 assert_eq!(TransactionState::Read, db.transaction_state(None)?); 822 assert!(rows.next()?.is_some()); // still reading 823 assert_eq!(TransactionState::Read, db.transaction_state(None)?); 824 assert!(rows.next()?.is_none()); // end 825 assert_eq!(TransactionState::None, db.transaction_state(None)?); 826 } 827 Ok(()) 828 } 829 } 830