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
378pub enum TransactionState {
379    /// Equivalent to `SQLITE_TXN_NONE`
380    None,
381    /// Equivalent to `SQLITE_TXN_READ`
382    Read,
383    /// Equivalent to `SQLITE_TXN_WRITE`
384    Write,
385}
386
387impl Connection {
388    /// Begin a new transaction with the default behavior (DEFERRED).
389    ///
390    /// The transaction defaults to rolling back when it is dropped. If you
391    /// want the transaction to commit, you must call
392    /// [`commit`](Transaction::commit) or
393    /// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior).
394    ///
395    /// ## Example
396    ///
397    /// ```rust,no_run
398    /// # use rusqlite::{Connection, Result};
399    /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
400    /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
401    /// fn perform_queries(conn: &mut Connection) -> Result<()> {
402    ///     let tx = conn.transaction()?;
403    ///
404    ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails
405    ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails
406    ///
407    ///     tx.commit()
408    /// }
409    /// ```
410    ///
411    /// # Failure
412    ///
413    /// Will return `Err` if the underlying SQLite call fails.
414    #[inline]
415    pub fn transaction(&mut self) -> Result<Transaction<'_>> {
416        Transaction::new(self, self.transaction_behavior)
417    }
418
419    /// Begin a new transaction with a specified behavior.
420    ///
421    /// See [`transaction`](Connection::transaction).
422    ///
423    /// # Failure
424    ///
425    /// Will return `Err` if the underlying SQLite call fails.
426    #[inline]
427    pub fn transaction_with_behavior(
428        &mut self,
429        behavior: TransactionBehavior,
430    ) -> Result<Transaction<'_>> {
431        Transaction::new(self, behavior)
432    }
433
434    /// Begin a new transaction with the default behavior (DEFERRED).
435    ///
436    /// Attempt to open a nested transaction will result in a SQLite error.
437    /// `Connection::transaction` prevents this at compile time by taking `&mut
438    /// self`, but `Connection::unchecked_transaction()` may be used to defer
439    /// the checking until runtime.
440    ///
441    /// See [`Connection::transaction`] and [`Transaction::new_unchecked`]
442    /// (which can be used if the default transaction behavior is undesirable).
443    ///
444    /// ## Example
445    ///
446    /// ```rust,no_run
447    /// # use rusqlite::{Connection, Result};
448    /// # use std::rc::Rc;
449    /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
450    /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
451    /// fn perform_queries(conn: Rc<Connection>) -> Result<()> {
452    ///     let tx = conn.unchecked_transaction()?;
453    ///
454    ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails
455    ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails
456    ///
457    ///     tx.commit()
458    /// }
459    /// ```
460    ///
461    /// # Failure
462    ///
463    /// Will return `Err` if the underlying SQLite call fails. The specific
464    /// error returned if transactions are nested is currently unspecified.
465    pub fn unchecked_transaction(&self) -> Result<Transaction<'_>> {
466        Transaction::new_unchecked(self, self.transaction_behavior)
467    }
468
469    /// Begin a new savepoint with the default behavior (DEFERRED).
470    ///
471    /// The savepoint defaults to rolling back when it is dropped. If you want
472    /// the savepoint to commit, you must call [`commit`](Savepoint::commit) or
473    /// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::set_drop_behavior).
474    ///
475    /// ## Example
476    ///
477    /// ```rust,no_run
478    /// # use rusqlite::{Connection, Result};
479    /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
480    /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
481    /// fn perform_queries(conn: &mut Connection) -> Result<()> {
482    ///     let sp = conn.savepoint()?;
483    ///
484    ///     do_queries_part_1(&sp)?; // sp causes rollback if this fails
485    ///     do_queries_part_2(&sp)?; // sp causes rollback if this fails
486    ///
487    ///     sp.commit()
488    /// }
489    /// ```
490    ///
491    /// # Failure
492    ///
493    /// Will return `Err` if the underlying SQLite call fails.
494    #[inline]
495    pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
496        Savepoint::new(self)
497    }
498
499    /// Begin a new savepoint with a specified name.
500    ///
501    /// See [`savepoint`](Connection::savepoint).
502    ///
503    /// # Failure
504    ///
505    /// Will return `Err` if the underlying SQLite call fails.
506    #[inline]
507    pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
508        Savepoint::with_name(self, name)
509    }
510
511    /// Determine the transaction state of a database
512    #[cfg(feature = "modern_sqlite")] // 3.37.0
513    pub fn transaction_state<N: crate::Name>(
514        &self,
515        db_name: Option<N>,
516    ) -> Result<TransactionState> {
517        self.db.borrow().txn_state(db_name)
518    }
519
520    /// Set the default transaction behavior for the connection.
521    ///
522    /// ## Note
523    ///
524    /// This will only apply to transactions initiated by [`transaction`](Connection::transaction)
525    /// or [`unchecked_transaction`](Connection::unchecked_transaction).
526    ///
527    /// ## Example
528    ///
529    /// ```rust,no_run
530    /// # use rusqlite::{Connection, Result, TransactionBehavior};
531    /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
532    /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
533    /// fn perform_queries(conn: &mut Connection) -> Result<()> {
534    ///     conn.set_transaction_behavior(TransactionBehavior::Immediate);
535    ///
536    ///     let tx = conn.transaction()?;
537    ///
538    ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails
539    ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails
540    ///
541    ///     tx.commit()
542    /// }
543    /// ```
544    pub fn set_transaction_behavior(&mut self, behavior: TransactionBehavior) {
545        self.transaction_behavior = behavior;
546    }
547}
548
549#[cfg(test)]
550mod test {
551    use super::DropBehavior;
552    use crate::{Connection, Error, Result};
553
554    fn checked_memory_handle() -> Result<Connection> {
555        let db = Connection::open_in_memory()?;
556        db.execute_batch("CREATE TABLE foo (x INTEGER)")?;
557        Ok(db)
558    }
559
560    #[test]
561    fn test_drop() -> Result<()> {
562        let mut db = checked_memory_handle()?;
563        {
564            let tx = db.transaction()?;
565            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
566            // default: rollback
567        }
568        {
569            let mut tx = db.transaction()?;
570            tx.execute_batch("INSERT INTO foo VALUES(2)")?;
571            tx.set_drop_behavior(DropBehavior::Commit)
572        }
573        {
574            let tx = db.transaction()?;
575            assert_eq!(2, tx.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
576        }
577        Ok(())
578    }
579    fn assert_nested_tx_error(e: Error) {
580        if let Error::SqliteFailure(e, Some(m)) = &e {
581            assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR);
582            // FIXME: Not ideal...
583            assert_eq!(e.code, crate::ErrorCode::Unknown);
584            assert!(m.contains("transaction"));
585        } else {
586            panic!("Unexpected error type: {e:?}");
587        }
588    }
589
590    #[test]
591    fn test_unchecked_nesting() -> Result<()> {
592        let db = checked_memory_handle()?;
593
594        {
595            let tx = db.unchecked_transaction()?;
596            let e = tx.unchecked_transaction().unwrap_err();
597            assert_nested_tx_error(e);
598            // default: rollback
599        }
600        {
601            let tx = db.unchecked_transaction()?;
602            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
603            // Ensure this doesn't interfere with ongoing transaction
604            let e = tx.unchecked_transaction().unwrap_err();
605            assert_nested_tx_error(e);
606
607            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
608            tx.commit()?;
609        }
610
611        assert_eq!(2, db.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
612        Ok(())
613    }
614
615    #[test]
616    fn test_explicit_rollback_commit() -> Result<()> {
617        let mut db = checked_memory_handle()?;
618        {
619            let mut tx = db.transaction()?;
620            {
621                let mut sp = tx.savepoint()?;
622                sp.execute_batch("INSERT INTO foo VALUES(1)")?;
623                sp.rollback()?;
624                sp.execute_batch("INSERT INTO foo VALUES(2)")?;
625                sp.commit()?;
626            }
627            tx.commit()?;
628        }
629        {
630            let tx = db.transaction()?;
631            tx.execute_batch("INSERT INTO foo VALUES(4)")?;
632            tx.commit()?;
633        }
634        {
635            let tx = db.transaction()?;
636            assert_eq!(6, tx.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
637        }
638        Ok(())
639    }
640
641    #[test]
642    fn test_savepoint() -> Result<()> {
643        let mut db = checked_memory_handle()?;
644        {
645            let mut tx = db.transaction()?;
646            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
647            assert_current_sum(1, &tx)?;
648            tx.set_drop_behavior(DropBehavior::Commit);
649            {
650                let mut sp1 = tx.savepoint()?;
651                sp1.execute_batch("INSERT INTO foo VALUES(2)")?;
652                assert_current_sum(3, &sp1)?;
653                // will roll back sp1
654                {
655                    let mut sp2 = sp1.savepoint()?;
656                    sp2.execute_batch("INSERT INTO foo VALUES(4)")?;
657                    assert_current_sum(7, &sp2)?;
658                    // will roll back sp2
659                    {
660                        let sp3 = sp2.savepoint()?;
661                        sp3.execute_batch("INSERT INTO foo VALUES(8)")?;
662                        assert_current_sum(15, &sp3)?;
663                        sp3.commit()?;
664                        // committed sp3, but will be erased by sp2 rollback
665                    }
666                    assert_current_sum(15, &sp2)?;
667                }
668                assert_current_sum(3, &sp1)?;
669            }
670            assert_current_sum(1, &tx)?;
671        }
672        assert_current_sum(1, &db)?;
673        Ok(())
674    }
675
676    #[test]
677    fn test_ignore_drop_behavior() -> Result<()> {
678        let mut db = checked_memory_handle()?;
679
680        let mut tx = db.transaction()?;
681        {
682            let mut sp1 = tx.savepoint()?;
683            insert(1, &sp1)?;
684            sp1.rollback()?;
685            insert(2, &sp1)?;
686            {
687                let mut sp2 = sp1.savepoint()?;
688                sp2.set_drop_behavior(DropBehavior::Ignore);
689                insert(4, &sp2)?;
690            }
691            assert_current_sum(6, &sp1)?;
692            sp1.commit()?;
693        }
694        assert_current_sum(6, &tx)?;
695        Ok(())
696    }
697
698    #[test]
699    fn test_savepoint_drop_behavior_releases() -> Result<()> {
700        let mut db = checked_memory_handle()?;
701
702        {
703            let mut sp = db.savepoint()?;
704            sp.set_drop_behavior(DropBehavior::Commit);
705        }
706        assert!(db.is_autocommit());
707        {
708            let mut sp = db.savepoint()?;
709            sp.set_drop_behavior(DropBehavior::Rollback);
710        }
711        assert!(db.is_autocommit());
712
713        Ok(())
714    }
715
716    #[test]
717    fn test_savepoint_release_error() -> Result<()> {
718        let mut db = checked_memory_handle()?;
719
720        db.pragma_update(None, "foreign_keys", true)?;
721        db.execute_batch("CREATE TABLE r(n INTEGER PRIMARY KEY NOT NULL); CREATE TABLE f(n REFERENCES r(n) DEFERRABLE INITIALLY DEFERRED);")?;
722        {
723            let mut sp = db.savepoint()?;
724            sp.execute("INSERT INTO f VALUES (0)", [])?;
725            sp.set_drop_behavior(DropBehavior::Commit);
726        }
727        assert!(db.is_autocommit());
728
729        Ok(())
730    }
731
732    #[test]
733    fn test_savepoint_names() -> Result<()> {
734        let mut db = checked_memory_handle()?;
735
736        {
737            let mut sp1 = db.savepoint_with_name("my_sp")?;
738            insert(1, &sp1)?;
739            assert_current_sum(1, &sp1)?;
740            {
741                let mut sp2 = sp1.savepoint_with_name("my_sp")?;
742                sp2.set_drop_behavior(DropBehavior::Commit);
743                insert(2, &sp2)?;
744                assert_current_sum(3, &sp2)?;
745                sp2.rollback()?;
746                assert_current_sum(1, &sp2)?;
747                insert(4, &sp2)?;
748            }
749            assert_current_sum(5, &sp1)?;
750            sp1.rollback()?;
751            {
752                let mut sp2 = sp1.savepoint_with_name("my_sp")?;
753                sp2.set_drop_behavior(DropBehavior::Ignore);
754                insert(8, &sp2)?;
755            }
756            assert_current_sum(8, &sp1)?;
757            sp1.commit()?;
758        }
759        assert_current_sum(8, &db)?;
760        Ok(())
761    }
762
763    #[test]
764    fn test_rc() -> Result<()> {
765        use std::rc::Rc;
766        let mut conn = Connection::open_in_memory()?;
767        let rc_txn = Rc::new(conn.transaction()?);
768
769        // This will compile only if Transaction is Debug
770        Rc::try_unwrap(rc_txn).unwrap();
771        Ok(())
772    }
773
774    fn insert(x: i32, conn: &Connection) -> Result<usize> {
775        conn.execute("INSERT INTO foo VALUES(?1)", [x])
776    }
777
778    fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
779        assert_eq!(x, conn.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
780        Ok(())
781    }
782
783    #[test]
784    #[cfg(feature = "modern_sqlite")]
785    fn txn_state() -> Result<()> {
786        use super::TransactionState;
787        use crate::MAIN_DB;
788        let db = Connection::open_in_memory()?;
789        assert_eq!(TransactionState::None, db.transaction_state(Some(MAIN_DB))?);
790        assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
791        db.execute_batch("BEGIN")?;
792        assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
793        let _: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
794        assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
795        db.pragma_update(None, "user_version", 1)?;
796        assert_eq!(TransactionState::Write, db.transaction_state::<&str>(None)?);
797        db.execute_batch("ROLLBACK")?;
798        Ok(())
799    }
800
801    #[test]
802    #[cfg(feature = "modern_sqlite")]
803    fn auto_commit() -> Result<()> {
804        use super::TransactionState;
805        let db = Connection::open_in_memory()?;
806        db.execute_batch("CREATE TABLE t(i UNIQUE);")?;
807        assert!(db.is_autocommit());
808        let mut stmt = db.prepare("SELECT name FROM sqlite_master")?;
809        assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
810        {
811            let mut rows = stmt.query([])?;
812            assert!(rows.next()?.is_some()); // start reading
813            assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
814            db.execute("INSERT INTO t VALUES (1)", [])?; // auto-commit
815            assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
816            assert!(rows.next()?.is_some()); // still reading
817            assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
818            assert!(rows.next()?.is_none()); // end
819            assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
820        }
821        Ok(())
822    }
823}