Skip to main content

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    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
552    use wasm_bindgen_test::wasm_bindgen_test as test;
553
554    use super::DropBehavior;
555    use crate::{Connection, Error, Result};
556
557    fn checked_memory_handle() -> Result<Connection> {
558        let db = Connection::open_in_memory()?;
559        db.execute_batch("CREATE TABLE foo (x INTEGER)")?;
560        Ok(db)
561    }
562
563    #[test]
564    fn test_drop() -> Result<()> {
565        let mut db = checked_memory_handle()?;
566        {
567            let tx = db.transaction()?;
568            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
569            // default: rollback
570        }
571        {
572            let mut tx = db.transaction()?;
573            tx.execute_batch("INSERT INTO foo VALUES(2)")?;
574            tx.set_drop_behavior(DropBehavior::Commit)
575        }
576        {
577            let tx = db.transaction()?;
578            assert_eq!(2, tx.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
579        }
580        Ok(())
581    }
582    fn assert_nested_tx_error(e: Error) {
583        if let Error::SqliteFailure(e, Some(m)) = &e {
584            assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR);
585            // FIXME: Not ideal...
586            assert_eq!(e.code, crate::ErrorCode::Unknown);
587            assert!(m.contains("transaction"));
588        } else {
589            panic!("Unexpected error type: {e:?}");
590        }
591    }
592
593    #[test]
594    fn test_unchecked_nesting() -> Result<()> {
595        let db = checked_memory_handle()?;
596
597        {
598            let tx = db.unchecked_transaction()?;
599            let e = tx.unchecked_transaction().unwrap_err();
600            assert_nested_tx_error(e);
601            // default: rollback
602        }
603        {
604            let tx = db.unchecked_transaction()?;
605            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
606            // Ensure this doesn't interfere with ongoing transaction
607            let e = tx.unchecked_transaction().unwrap_err();
608            assert_nested_tx_error(e);
609
610            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
611            tx.commit()?;
612        }
613
614        assert_eq!(2, db.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
615        Ok(())
616    }
617
618    #[test]
619    fn test_explicit_rollback_commit() -> Result<()> {
620        let mut db = checked_memory_handle()?;
621        {
622            let mut tx = db.transaction()?;
623            {
624                let mut sp = tx.savepoint()?;
625                sp.execute_batch("INSERT INTO foo VALUES(1)")?;
626                sp.rollback()?;
627                sp.execute_batch("INSERT INTO foo VALUES(2)")?;
628                sp.commit()?;
629            }
630            tx.commit()?;
631        }
632        {
633            let tx = db.transaction()?;
634            tx.execute_batch("INSERT INTO foo VALUES(4)")?;
635            tx.commit()?;
636        }
637        {
638            let tx = db.transaction()?;
639            assert_eq!(6, tx.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
640        }
641        Ok(())
642    }
643
644    #[test]
645    fn test_savepoint() -> Result<()> {
646        let mut db = checked_memory_handle()?;
647        {
648            let mut tx = db.transaction()?;
649            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
650            assert_current_sum(1, &tx)?;
651            tx.set_drop_behavior(DropBehavior::Commit);
652            {
653                let mut sp1 = tx.savepoint()?;
654                sp1.execute_batch("INSERT INTO foo VALUES(2)")?;
655                assert_current_sum(3, &sp1)?;
656                // will roll back sp1
657                {
658                    let mut sp2 = sp1.savepoint()?;
659                    sp2.execute_batch("INSERT INTO foo VALUES(4)")?;
660                    assert_current_sum(7, &sp2)?;
661                    // will roll back sp2
662                    {
663                        let sp3 = sp2.savepoint()?;
664                        sp3.execute_batch("INSERT INTO foo VALUES(8)")?;
665                        assert_current_sum(15, &sp3)?;
666                        sp3.commit()?;
667                        // committed sp3, but will be erased by sp2 rollback
668                    }
669                    assert_current_sum(15, &sp2)?;
670                }
671                assert_current_sum(3, &sp1)?;
672            }
673            assert_current_sum(1, &tx)?;
674        }
675        assert_current_sum(1, &db)?;
676        Ok(())
677    }
678
679    #[test]
680    fn test_ignore_drop_behavior() -> Result<()> {
681        let mut db = checked_memory_handle()?;
682
683        let mut tx = db.transaction()?;
684        {
685            let mut sp1 = tx.savepoint()?;
686            insert(1, &sp1)?;
687            sp1.rollback()?;
688            insert(2, &sp1)?;
689            {
690                let mut sp2 = sp1.savepoint()?;
691                sp2.set_drop_behavior(DropBehavior::Ignore);
692                insert(4, &sp2)?;
693            }
694            assert_current_sum(6, &sp1)?;
695            sp1.commit()?;
696        }
697        assert_current_sum(6, &tx)?;
698        Ok(())
699    }
700
701    #[test]
702    fn test_savepoint_drop_behavior_releases() -> Result<()> {
703        let mut db = checked_memory_handle()?;
704
705        {
706            let mut sp = db.savepoint()?;
707            sp.set_drop_behavior(DropBehavior::Commit);
708        }
709        assert!(db.is_autocommit());
710        {
711            let mut sp = db.savepoint()?;
712            sp.set_drop_behavior(DropBehavior::Rollback);
713        }
714        assert!(db.is_autocommit());
715
716        Ok(())
717    }
718
719    #[test]
720    fn test_savepoint_release_error() -> Result<()> {
721        let mut db = checked_memory_handle()?;
722
723        db.pragma_update(None, "foreign_keys", true)?;
724        db.execute_batch("CREATE TABLE r(n INTEGER PRIMARY KEY NOT NULL); CREATE TABLE f(n REFERENCES r(n) DEFERRABLE INITIALLY DEFERRED);")?;
725        {
726            let mut sp = db.savepoint()?;
727            sp.execute("INSERT INTO f VALUES (0)", [])?;
728            sp.set_drop_behavior(DropBehavior::Commit);
729        }
730        assert!(db.is_autocommit());
731
732        Ok(())
733    }
734
735    #[test]
736    fn test_savepoint_names() -> Result<()> {
737        let mut db = checked_memory_handle()?;
738
739        {
740            let mut sp1 = db.savepoint_with_name("my_sp")?;
741            insert(1, &sp1)?;
742            assert_current_sum(1, &sp1)?;
743            {
744                let mut sp2 = sp1.savepoint_with_name("my_sp")?;
745                sp2.set_drop_behavior(DropBehavior::Commit);
746                insert(2, &sp2)?;
747                assert_current_sum(3, &sp2)?;
748                sp2.rollback()?;
749                assert_current_sum(1, &sp2)?;
750                insert(4, &sp2)?;
751            }
752            assert_current_sum(5, &sp1)?;
753            sp1.rollback()?;
754            {
755                let mut sp2 = sp1.savepoint_with_name("my_sp")?;
756                sp2.set_drop_behavior(DropBehavior::Ignore);
757                insert(8, &sp2)?;
758            }
759            assert_current_sum(8, &sp1)?;
760            sp1.commit()?;
761        }
762        assert_current_sum(8, &db)?;
763        Ok(())
764    }
765
766    #[test]
767    fn test_rc() -> Result<()> {
768        use std::rc::Rc;
769        let mut conn = Connection::open_in_memory()?;
770        let rc_txn = Rc::new(conn.transaction()?);
771
772        // This will compile only if Transaction is Debug
773        Rc::try_unwrap(rc_txn).unwrap();
774        Ok(())
775    }
776
777    fn insert(x: i32, conn: &Connection) -> Result<usize> {
778        conn.execute("INSERT INTO foo VALUES(?1)", [x])
779    }
780
781    fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
782        assert_eq!(x, conn.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
783        Ok(())
784    }
785
786    #[test]
787    #[cfg(feature = "modern_sqlite")]
788    fn txn_state() -> Result<()> {
789        use super::TransactionState;
790        use crate::MAIN_DB;
791        let db = Connection::open_in_memory()?;
792        assert_eq!(TransactionState::None, db.transaction_state(Some(MAIN_DB))?);
793        assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
794        db.execute_batch("BEGIN")?;
795        assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
796        let _: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
797        assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
798        db.pragma_update(None, "user_version", 1)?;
799        assert_eq!(TransactionState::Write, db.transaction_state::<&str>(None)?);
800        db.execute_batch("ROLLBACK")?;
801        Ok(())
802    }
803
804    #[test]
805    #[cfg(feature = "modern_sqlite")]
806    fn auto_commit() -> Result<()> {
807        use super::TransactionState;
808        let db = Connection::open_in_memory()?;
809        db.execute_batch("CREATE TABLE t(i UNIQUE);")?;
810        assert!(db.is_autocommit());
811        let mut stmt = db.prepare("SELECT name FROM sqlite_master")?;
812        assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
813        {
814            let mut rows = stmt.query([])?;
815            assert!(rows.next()?.is_some()); // start reading
816            assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
817            db.execute("INSERT INTO t VALUES (1)", [])?; // auto-commit
818            assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
819            assert!(rows.next()?.is_some()); // still reading
820            assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
821            assert!(rows.next()?.is_none()); // end
822            assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
823        }
824        Ok(())
825    }
826}