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!(2i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?);
556        }
557        Ok(())
558    }
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]
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]
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]
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]
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]
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]
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
720    fn insert(x: i32, conn: &Connection) -> Result<usize> {
721        conn.execute("INSERT INTO foo VALUES(?1)", [x])
722    }
723
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")]
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}