rusqlite/transaction.rs
1use crate::{Connection, Result};
2use 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]
8pub 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]
23pub 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)]
62pub 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)]
91pub struct Savepoint<'conn> {
92 conn: &'conn Connection,
93 name: String,
94 depth: u32,
95 drop_behavior: DropBehavior,
96 committed: bool,
97}
98
99impl 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]
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]
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]
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]
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]
174 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]
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]
187 pub fn commit(mut self) -> Result<()> {
188 self.commit_()
189 }
190
191 #[inline]
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]
199 pub fn rollback(mut self) -> Result<()> {
200 self.rollback_()
201 }
202
203 #[inline]
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]
215 pub fn finish(mut self) -> Result<()> {
216 self.finish_()
217 }
218
219 #[inline]
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
233impl Deref for Transaction<'_> {
234 type Target = Connection;
235
236 #[inline]
237 fn deref(&self) -> &Connection {
238 self.conn
239 }
240}
241
242#[allow(unused_must_use)]
243impl Drop for Transaction<'_> {
244 #[inline]
245 fn drop(&mut self) {
246 self.finish_();
247 }
248}
249
250impl Savepoint<'_> {
251 #[inline]
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]
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]
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]
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]
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]
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]
302 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]
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]
315 pub fn commit(mut self) -> Result<()> {
316 self.commit_()
317 }
318
319 #[inline]
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]
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]
344 pub fn finish(mut self) -> Result<()> {
345 self.finish_()
346 }
347
348 #[inline]
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
362impl Deref for Savepoint<'_> {
363 type Target = Connection;
364
365 #[inline]
366 fn deref(&self) -> &Connection {
367 self.conn
368 }
369}
370
371#[allow(unused_must_use)]
372impl Drop for Savepoint<'_> {
373 #[inline]
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")))]
384pub 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
393impl 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]
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]
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.
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]
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]
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")))]
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)]
530mod test {
531 use super::DropBehavior;
532 use crate::{Connection, Error, Result};
533
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]
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!(
556 2i32,
557 tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
558 );
559 }
560 Ok(())
561 }
562 fn assert_nested_tx_error(e: Error) {
563 if let Error::SqliteFailure(e, Some(m)) = &e {
564 assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR);
565 // FIXME: Not ideal...
566 assert_eq!(e.code, crate::ErrorCode::Unknown);
567 assert!(m.contains("transaction"));
568 } else {
569 panic!("Unexpected error type: {:?}", e);
570 }
571 }
572
573 #[test]
574 fn test_unchecked_nesting() -> Result<()> {
575 let db = checked_memory_handle()?;
576
577 {
578 let tx = db.unchecked_transaction()?;
579 let e = tx.unchecked_transaction().unwrap_err();
580 assert_nested_tx_error(e);
581 // default: rollback
582 }
583 {
584 let tx = db.unchecked_transaction()?;
585 tx.execute_batch("INSERT INTO foo VALUES(1)")?;
586 // Ensure this doesn't interfere with ongoing transaction
587 let e = tx.unchecked_transaction().unwrap_err();
588 assert_nested_tx_error(e);
589
590 tx.execute_batch("INSERT INTO foo VALUES(1)")?;
591 tx.commit()?;
592 }
593
594 assert_eq!(
595 2i32,
596 db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
597 );
598 Ok(())
599 }
600
601 #[test]
602 fn test_explicit_rollback_commit() -> Result<()> {
603 let mut db = checked_memory_handle()?;
604 {
605 let mut tx = db.transaction()?;
606 {
607 let mut sp = tx.savepoint()?;
608 sp.execute_batch("INSERT INTO foo VALUES(1)")?;
609 sp.rollback()?;
610 sp.execute_batch("INSERT INTO foo VALUES(2)")?;
611 sp.commit()?;
612 }
613 tx.commit()?;
614 }
615 {
616 let tx = db.transaction()?;
617 tx.execute_batch("INSERT INTO foo VALUES(4)")?;
618 tx.commit()?;
619 }
620 {
621 let tx = db.transaction()?;
622 assert_eq!(
623 6i32,
624 tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
625 );
626 }
627 Ok(())
628 }
629
630 #[test]
631 fn test_savepoint() -> Result<()> {
632 let mut db = checked_memory_handle()?;
633 {
634 let mut tx = db.transaction()?;
635 tx.execute_batch("INSERT INTO foo VALUES(1)")?;
636 assert_current_sum(1, &tx)?;
637 tx.set_drop_behavior(DropBehavior::Commit);
638 {
639 let mut sp1 = tx.savepoint()?;
640 sp1.execute_batch("INSERT INTO foo VALUES(2)")?;
641 assert_current_sum(3, &sp1)?;
642 // will rollback sp1
643 {
644 let mut sp2 = sp1.savepoint()?;
645 sp2.execute_batch("INSERT INTO foo VALUES(4)")?;
646 assert_current_sum(7, &sp2)?;
647 // will rollback sp2
648 {
649 let sp3 = sp2.savepoint()?;
650 sp3.execute_batch("INSERT INTO foo VALUES(8)")?;
651 assert_current_sum(15, &sp3)?;
652 sp3.commit()?;
653 // committed sp3, but will be erased by sp2 rollback
654 }
655 assert_current_sum(15, &sp2)?;
656 }
657 assert_current_sum(3, &sp1)?;
658 }
659 assert_current_sum(1, &tx)?;
660 }
661 assert_current_sum(1, &db)?;
662 Ok(())
663 }
664
665 #[test]
666 fn test_ignore_drop_behavior() -> Result<()> {
667 let mut db = checked_memory_handle()?;
668
669 let mut tx = db.transaction()?;
670 {
671 let mut sp1 = tx.savepoint()?;
672 insert(1, &sp1)?;
673 sp1.rollback()?;
674 insert(2, &sp1)?;
675 {
676 let mut sp2 = sp1.savepoint()?;
677 sp2.set_drop_behavior(DropBehavior::Ignore);
678 insert(4, &sp2)?;
679 }
680 assert_current_sum(6, &sp1)?;
681 sp1.commit()?;
682 }
683 assert_current_sum(6, &tx)?;
684 Ok(())
685 }
686
687 #[test]
688 fn test_savepoint_names() -> Result<()> {
689 let mut db = checked_memory_handle()?;
690
691 {
692 let mut sp1 = db.savepoint_with_name("my_sp")?;
693 insert(1, &sp1)?;
694 assert_current_sum(1, &sp1)?;
695 {
696 let mut sp2 = sp1.savepoint_with_name("my_sp")?;
697 sp2.set_drop_behavior(DropBehavior::Commit);
698 insert(2, &sp2)?;
699 assert_current_sum(3, &sp2)?;
700 sp2.rollback()?;
701 assert_current_sum(1, &sp2)?;
702 insert(4, &sp2)?;
703 }
704 assert_current_sum(5, &sp1)?;
705 sp1.rollback()?;
706 {
707 let mut sp2 = sp1.savepoint_with_name("my_sp")?;
708 sp2.set_drop_behavior(DropBehavior::Ignore);
709 insert(8, &sp2)?;
710 }
711 assert_current_sum(8, &sp1)?;
712 sp1.commit()?;
713 }
714 assert_current_sum(8, &db)?;
715 Ok(())
716 }
717
718 #[test]
719 fn test_rc() -> Result<()> {
720 use std::rc::Rc;
721 let mut conn = Connection::open_in_memory()?;
722 let rc_txn = Rc::new(conn.transaction()?);
723
724 // This will compile only if Transaction is Debug
725 Rc::try_unwrap(rc_txn).unwrap();
726 Ok(())
727 }
728
729 fn insert(x: i32, conn: &Connection) -> Result<usize> {
730 conn.execute("INSERT INTO foo VALUES(?)", [x])
731 }
732
733 fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
734 let i = conn.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
735 assert_eq!(x, i);
736 Ok(())
737 }
738
739 #[test]
740 #[cfg(feature = "modern_sqlite")]
741 fn txn_state() -> Result<()> {
742 use super::TransactionState;
743 use crate::DatabaseName;
744 let db = Connection::open_in_memory()?;
745 assert_eq!(
746 TransactionState::None,
747 db.transaction_state(Some(DatabaseName::Main))?
748 );
749 assert_eq!(TransactionState::None, db.transaction_state(None)?);
750 db.execute_batch("BEGIN")?;
751 assert_eq!(TransactionState::None, db.transaction_state(None)?);
752 let _: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
753 assert_eq!(TransactionState::Read, db.transaction_state(None)?);
754 db.pragma_update(None, "user_version", 1)?;
755 assert_eq!(TransactionState::Write, db.transaction_state(None)?);
756 db.execute_batch("ROLLBACK")?;
757 Ok(())
758 }
759}