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    drop_behavior: DropBehavior,
95    committed: bool,
96}
97
98impl 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]
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]
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]
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]
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]
173    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]
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]
186    pub fn commit(mut self) -> Result<()> {
187        self.commit_()
188    }
189
190    #[inline]
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]
198    pub fn rollback(mut self) -> Result<()> {
199        self.rollback_()
200    }
201
202    #[inline]
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]
214    pub fn finish(mut self) -> Result<()> {
215        self.finish_()
216    }
217
218    #[inline]
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
232impl Deref for Transaction<'_> {
233    type Target = Connection;
234
235    #[inline]
236    fn deref(&self) -> &Connection {
237        self.conn
238    }
239}
240
241#[expect(unused_must_use)]
242impl Drop for Transaction<'_> {
243    #[inline]
244    fn drop(&mut self) {
245        self.finish_();
246    }
247}
248
249impl Savepoint<'_> {
250    #[inline]
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]
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]
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]
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]
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]
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]
295    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]
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]
308    pub fn commit(mut self) -> Result<()> {
309        self.commit_()
310    }
311
312    #[inline]
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]
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]
337    pub fn finish(mut self) -> Result<()> {
338        self.finish_()
339    }
340
341    #[inline]
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
357impl Deref for Savepoint<'_> {
358    type Target = Connection;
359
360    #[inline]
361    fn deref(&self) -> &Connection {
362        self.conn
363    }
364}
365
366#[expect(unused_must_use)]
367impl Drop for Savepoint<'_> {
368    #[inline]
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")))]
379pub 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
388impl 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]
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]
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.
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]
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]
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")))]
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    /// ```
546    pub fn set_transaction_behavior(&mut self, behavior: TransactionBehavior) {
547        self.transaction_behavior = behavior;
548    }
549}
550
551#[cfg(test)]
552mod test {
553    use super::DropBehavior;
554    use crate::{Connection, Error, Result};
555
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]
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    }
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]
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]
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]
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]
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]
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]
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]
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]
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
776    fn insert(x: i32, conn: &Connection) -> Result<usize> {
777        conn.execute("INSERT INTO foo VALUES(?1)", [x])
778    }
779
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")]
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")]
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}