Skip to main content

rusqlite/
transaction.rs

1use crate::pragma::Sql;
2use crate::{Connection, Result};
3use std::ops::Deref;
4
5/// Options for transaction behavior. See [BEGIN
6/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.
7#[derive(Copy, Clone)]
8#[non_exhaustive]
9pub enum TransactionBehavior {
10    /// DEFERRED means that the transaction does not actually start until the
11    /// database is first accessed.
12    Deferred,
13    /// IMMEDIATE cause the database connection to start a new write
14    /// immediately, without waiting for a writes statement.
15    Immediate,
16    /// EXCLUSIVE prevents other database connections from reading the database
17    /// while the transaction is underway.
18    Exclusive,
19}
20
21/// Options for how a Transaction or Savepoint should behave when it is dropped.
22#[derive(Copy, Clone, Debug, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum DropBehavior {
25    /// Roll back the changes. This is the default.
26    Rollback,
27
28    /// Commit the changes.
29    Commit,
30
31    /// Do not commit or roll back changes - this will leave the transaction or
32    /// savepoint open, so should be used with care.
33    Ignore,
34
35    /// Panic. Used to enforce intentional behavior during development.
36    Panic,
37}
38
39/// Represents a transaction on a database connection.
40///
41/// ## Note
42///
43/// Transactions will roll back by default. Use `commit` method to explicitly
44/// commit the transaction, or use `set_drop_behavior` to change what happens
45/// when the transaction is dropped.
46///
47/// ## Example
48///
49/// ```rust,no_run
50/// # use rusqlite::{Connection, Result};
51/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
52/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
53/// fn perform_queries(conn: &mut Connection) -> Result<()> {
54///     let tx = conn.transaction()?;
55///
56///     do_queries_part_1(&tx)?; // tx causes rollback if this fails
57///     do_queries_part_2(&tx)?; // tx causes rollback if this fails
58///
59///     tx.commit()
60/// }
61/// ```
62#[derive(Debug)]
63pub struct Transaction<'conn> {
64    conn: &'conn Connection,
65    drop_behavior: DropBehavior,
66}
67
68/// Represents a savepoint on a database connection.
69///
70/// ## Note
71///
72/// Savepoints will roll back by default. Use `commit` method to explicitly
73/// commit the savepoint, or use `set_drop_behavior` to change what happens
74/// when the savepoint is dropped.
75///
76/// ## Example
77///
78/// ```rust,no_run
79/// # use rusqlite::{Connection, Result};
80/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
81/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
82/// fn perform_queries(conn: &mut Connection) -> Result<()> {
83///     let sp = conn.savepoint()?;
84///
85///     do_queries_part_1(&sp)?; // sp causes rollback if this fails
86///     do_queries_part_2(&sp)?; // sp causes rollback if this fails
87///
88///     sp.commit()
89/// }
90/// ```
91#[derive(Debug)]
92pub struct Savepoint<'conn> {
93    conn: &'conn Connection,
94    name: String,
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    /// 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::new_(self.conn)
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_name_(self.conn, 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#[expect(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_name_<T: Into<String>>(conn: &Connection, name: T) -> Result<Savepoint<'_>> {
253        let name = name.into();
254        let sql = cmd("SAVEPOINT", false, name.as_str())?;
255        conn.execute_batch(sql.as_str()).map(|()| Savepoint {
256            conn,
257            name,
258            drop_behavior: DropBehavior::Rollback,
259            committed: false,
260        })
261    }
262
263    #[inline]
264    fn new_(conn: &Connection) -> Result<Savepoint<'_>> {
265        Savepoint::with_name_(conn, "_rusqlite_sp")
266    }
267
268    /// Begin a new savepoint. Can be nested.
269    #[inline]
270    pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
271        Savepoint::new_(conn)
272    }
273
274    /// Begin a new savepoint with a user-provided savepoint name.
275    #[inline]
276    pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> {
277        Savepoint::with_name_(conn, name)
278    }
279
280    /// Begin a nested savepoint.
281    #[inline]
282    pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
283        Savepoint::new_(self.conn)
284    }
285
286    /// Begin a nested savepoint with a user-provided savepoint name.
287    #[inline]
288    pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
289        Savepoint::with_name_(self.conn, name)
290    }
291
292    /// Get the current setting for what happens to the savepoint when it is
293    /// dropped.
294    #[inline]
295    #[must_use]
296    pub fn drop_behavior(&self) -> DropBehavior {
297        self.drop_behavior
298    }
299
300    /// Configure the savepoint to perform the specified action when it is
301    /// dropped.
302    #[inline]
303    pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
304        self.drop_behavior = drop_behavior;
305    }
306
307    /// A convenience method which consumes and commits a savepoint.
308    #[inline]
309    pub fn commit(mut self) -> Result<()> {
310        self.commit_()
311    }
312
313    #[inline]
314    fn commit_(&mut self) -> Result<()> {
315        let sql = cmd("RELEASE", false, self.name.as_str())?;
316        self.conn.execute_batch(sql.as_str())?;
317        self.committed = true;
318        Ok(())
319    }
320
321    /// A convenience method which rolls back a savepoint.
322    ///
323    /// ## Note
324    ///
325    /// Unlike `Transaction`s, savepoints remain active after they have been
326    /// rolled back, and can be rolled back again or committed.
327    #[inline]
328    pub fn rollback(&mut self) -> Result<()> {
329        let sql = cmd("ROLLBACK", true, self.name.as_str())?;
330        self.conn.execute_batch(sql.as_str())
331    }
332
333    /// Consumes the savepoint, committing or rolling back according to the
334    /// current setting (see `drop_behavior`).
335    ///
336    /// Functionally equivalent to the `Drop` implementation, but allows
337    /// callers to see any errors that occur.
338    #[inline]
339    pub fn finish(mut self) -> Result<()> {
340        self.finish_()
341    }
342
343    #[inline]
344    fn finish_(&mut self) -> Result<()> {
345        if self.committed {
346            return Ok(());
347        }
348        match self.drop_behavior() {
349            DropBehavior::Commit => self
350                .commit_()
351                .or_else(|_| self.rollback().and_then(|()| self.commit_())),
352            DropBehavior::Rollback => self.rollback().and_then(|()| self.commit_()),
353            DropBehavior::Ignore => Ok(()),
354            DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
355        }
356    }
357}
358
359fn cmd(cmd: &'static str, to: bool, name: &str) -> Result<Sql> {
360    let mut sql = Sql::new();
361    sql.push_keyword(cmd)?;
362    sql.push_space();
363    if to {
364        sql.push_keyword("TO")?;
365        sql.push_space();
366    }
367    sql.push_identifier(name);
368    Ok(sql)
369}
370
371impl Deref for Savepoint<'_> {
372    type Target = Connection;
373
374    #[inline]
375    fn deref(&self) -> &Connection {
376        self.conn
377    }
378}
379
380#[expect(unused_must_use)]
381impl Drop for Savepoint<'_> {
382    #[inline]
383    fn drop(&mut self) {
384        self.finish_();
385    }
386}
387
388/// Transaction state of a database
389#[derive(Clone, Copy, Debug, PartialEq, Eq)]
390#[non_exhaustive]
391pub enum TransactionState {
392    /// Equivalent to `SQLITE_TXN_NONE`
393    None,
394    /// Equivalent to `SQLITE_TXN_READ`
395    Read,
396    /// Equivalent to `SQLITE_TXN_WRITE`
397    Write,
398}
399
400impl Connection {
401    /// Begin a new transaction with the default behavior (DEFERRED).
402    ///
403    /// The transaction defaults to rolling back when it is dropped. If you
404    /// want the transaction to commit, you must call
405    /// [`commit`](Transaction::commit) or
406    /// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior).
407    ///
408    /// ## Example
409    ///
410    /// ```rust,no_run
411    /// # use rusqlite::{Connection, Result};
412    /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
413    /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
414    /// fn perform_queries(conn: &mut Connection) -> Result<()> {
415    ///     let tx = conn.transaction()?;
416    ///
417    ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails
418    ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails
419    ///
420    ///     tx.commit()
421    /// }
422    /// ```
423    ///
424    /// # Failure
425    ///
426    /// Will return `Err` if the underlying SQLite call fails.
427    #[inline]
428    pub fn transaction(&mut self) -> Result<Transaction<'_>> {
429        Transaction::new(self, self.transaction_behavior)
430    }
431
432    /// Begin a new transaction with a specified behavior.
433    ///
434    /// See [`transaction`](Connection::transaction).
435    ///
436    /// # Failure
437    ///
438    /// Will return `Err` if the underlying SQLite call fails.
439    #[inline]
440    pub fn transaction_with_behavior(
441        &mut self,
442        behavior: TransactionBehavior,
443    ) -> Result<Transaction<'_>> {
444        Transaction::new(self, behavior)
445    }
446
447    /// Begin a new transaction with the default behavior (DEFERRED).
448    ///
449    /// Attempt to open a nested transaction will result in a SQLite error.
450    /// `Connection::transaction` prevents this at compile time by taking `&mut
451    /// self`, but `Connection::unchecked_transaction()` may be used to defer
452    /// the checking until runtime.
453    ///
454    /// See [`Connection::transaction`] and [`Transaction::new_unchecked`]
455    /// (which can be used if the default transaction behavior is undesirable).
456    ///
457    /// ## Example
458    ///
459    /// ```rust,no_run
460    /// # use rusqlite::{Connection, Result};
461    /// # use std::rc::Rc;
462    /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
463    /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
464    /// fn perform_queries(conn: Rc<Connection>) -> Result<()> {
465    ///     let tx = conn.unchecked_transaction()?;
466    ///
467    ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails
468    ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails
469    ///
470    ///     tx.commit()
471    /// }
472    /// ```
473    ///
474    /// # Failure
475    ///
476    /// Will return `Err` if the underlying SQLite call fails. The specific
477    /// error returned if transactions are nested is currently unspecified.
478    pub fn unchecked_transaction(&self) -> Result<Transaction<'_>> {
479        Transaction::new_unchecked(self, self.transaction_behavior)
480    }
481
482    /// Begin a new savepoint with the default behavior (DEFERRED).
483    ///
484    /// The savepoint defaults to rolling back when it is dropped. If you want
485    /// the savepoint to commit, you must call [`commit`](Savepoint::commit) or
486    /// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::set_drop_behavior).
487    ///
488    /// ## Example
489    ///
490    /// ```rust,no_run
491    /// # use rusqlite::{Connection, Result};
492    /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
493    /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
494    /// fn perform_queries(conn: &mut Connection) -> Result<()> {
495    ///     let sp = conn.savepoint()?;
496    ///
497    ///     do_queries_part_1(&sp)?; // sp causes rollback if this fails
498    ///     do_queries_part_2(&sp)?; // sp causes rollback if this fails
499    ///
500    ///     sp.commit()
501    /// }
502    /// ```
503    ///
504    /// # Failure
505    ///
506    /// Will return `Err` if the underlying SQLite call fails.
507    #[inline]
508    pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
509        Savepoint::new(self)
510    }
511
512    /// Begin a new savepoint with a specified name.
513    ///
514    /// See [`savepoint`](Connection::savepoint).
515    ///
516    /// # Failure
517    ///
518    /// Will return `Err` if the underlying SQLite call fails.
519    #[inline]
520    pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
521        Savepoint::with_name(self, name)
522    }
523
524    /// Determine the transaction state of a database
525    pub fn transaction_state<N: crate::Name>(
526        &self,
527        db_name: Option<N>,
528    ) -> Result<TransactionState> {
529        self.db.borrow().txn_state(db_name)
530    }
531
532    /// Set the default transaction behavior for the connection.
533    ///
534    /// ## Note
535    ///
536    /// This will only apply to transactions initiated by [`transaction`](Connection::transaction)
537    /// or [`unchecked_transaction`](Connection::unchecked_transaction).
538    ///
539    /// ## Example
540    ///
541    /// ```rust,no_run
542    /// # use rusqlite::{Connection, Result, TransactionBehavior};
543    /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
544    /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
545    /// fn perform_queries(conn: &mut Connection) -> Result<()> {
546    ///     conn.set_transaction_behavior(TransactionBehavior::Immediate);
547    ///
548    ///     let tx = conn.transaction()?;
549    ///
550    ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails
551    ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails
552    ///
553    ///     tx.commit()
554    /// }
555    /// ```
556    pub fn set_transaction_behavior(&mut self, behavior: TransactionBehavior) {
557        self.transaction_behavior = behavior;
558    }
559}
560
561#[cfg(all(test, not(miri)))]
562mod test {
563    use std::assert_matches;
564    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
565    use wasm_bindgen_test::wasm_bindgen_test as test;
566
567    use super::DropBehavior;
568    use crate::{Connection, Error, Result};
569
570    fn checked_memory_handle() -> Result<Connection> {
571        let db = Connection::open_in_memory()?;
572        db.execute_batch("CREATE TABLE foo (x INTEGER)")?;
573        Ok(db)
574    }
575
576    #[test]
577    fn test_drop() -> Result<()> {
578        let mut db = checked_memory_handle()?;
579        {
580            let tx = db.transaction()?;
581            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
582            // default: rollback
583        }
584        {
585            let mut tx = db.transaction()?;
586            tx.execute_batch("INSERT INTO foo VALUES(2)")?;
587            tx.set_drop_behavior(DropBehavior::Commit);
588        }
589        {
590            let tx = db.transaction()?;
591            assert_eq!(2, tx.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
592        }
593        Ok(())
594    }
595    fn assert_nested_tx_error(e: Error) {
596        assert_matches!(
597            e,
598            Error::SqliteFailure(
599                crate::ffi::Error {
600                    code: crate::ErrorCode::Unknown,
601                    extended_code: crate::ffi::SQLITE_ERROR,
602                },
603                Some(msg),
604            ) if msg.contains("transaction")
605        );
606    }
607
608    #[test]
609    fn test_unchecked_nesting() -> Result<()> {
610        let db = checked_memory_handle()?;
611
612        {
613            let tx = db.unchecked_transaction()?;
614            let e = tx.unchecked_transaction().unwrap_err();
615            assert_nested_tx_error(e);
616            // default: rollback
617        }
618        {
619            let tx = db.unchecked_transaction()?;
620            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
621            // Ensure this doesn't interfere with ongoing transaction
622            let e = tx.unchecked_transaction().unwrap_err();
623            assert_nested_tx_error(e);
624
625            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
626            tx.commit()?;
627        }
628
629        assert_eq!(2, db.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
630        Ok(())
631    }
632
633    #[test]
634    fn test_explicit_rollback_commit() -> Result<()> {
635        let mut db = checked_memory_handle()?;
636        {
637            let mut tx = db.transaction()?;
638            {
639                let mut sp = tx.savepoint()?;
640                sp.execute_batch("INSERT INTO foo VALUES(1)")?;
641                sp.rollback()?;
642                sp.execute_batch("INSERT INTO foo VALUES(2)")?;
643                sp.commit()?;
644            }
645            tx.commit()?;
646        }
647        {
648            let tx = db.transaction()?;
649            tx.execute_batch("INSERT INTO foo VALUES(4)")?;
650            tx.commit()?;
651        }
652        {
653            let tx = db.transaction()?;
654            assert_eq!(6, tx.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
655        }
656        Ok(())
657    }
658
659    #[test]
660    fn test_savepoint() -> Result<()> {
661        let mut db = checked_memory_handle()?;
662        {
663            let mut tx = db.transaction()?;
664            tx.execute_batch("INSERT INTO foo VALUES(1)")?;
665            assert_current_sum(1, &tx)?;
666            tx.set_drop_behavior(DropBehavior::Commit);
667            {
668                let mut sp1 = tx.savepoint()?;
669                sp1.execute_batch("INSERT INTO foo VALUES(2)")?;
670                assert_current_sum(3, &sp1)?;
671                // will roll back sp1
672                {
673                    let mut sp2 = sp1.savepoint()?;
674                    sp2.execute_batch("INSERT INTO foo VALUES(4)")?;
675                    assert_current_sum(7, &sp2)?;
676                    // will roll back sp2
677                    {
678                        let sp3 = sp2.savepoint()?;
679                        sp3.execute_batch("INSERT INTO foo VALUES(8)")?;
680                        assert_current_sum(15, &sp3)?;
681                        sp3.commit()?;
682                        // committed sp3, but will be erased by sp2 rollback
683                    }
684                    assert_current_sum(15, &sp2)?;
685                }
686                assert_current_sum(3, &sp1)?;
687            }
688            assert_current_sum(1, &tx)?;
689        }
690        assert_current_sum(1, &db)?;
691        Ok(())
692    }
693
694    #[test]
695    fn test_ignore_drop_behavior() -> Result<()> {
696        let mut db = checked_memory_handle()?;
697
698        let mut tx = db.transaction()?;
699        {
700            let mut sp1 = tx.savepoint()?;
701            insert(1, &sp1)?;
702            sp1.rollback()?;
703            insert(2, &sp1)?;
704            {
705                let mut sp2 = sp1.savepoint()?;
706                sp2.set_drop_behavior(DropBehavior::Ignore);
707                insert(4, &sp2)?;
708            }
709            assert_current_sum(6, &sp1)?;
710            sp1.commit()?;
711        }
712        assert_current_sum(6, &tx)?;
713        Ok(())
714    }
715
716    #[test]
717    fn test_savepoint_drop_behavior_releases() -> Result<()> {
718        let mut db = checked_memory_handle()?;
719
720        {
721            let mut sp = db.savepoint()?;
722            sp.set_drop_behavior(DropBehavior::Commit);
723        }
724        assert!(db.is_autocommit());
725        {
726            let mut sp = db.savepoint()?;
727            sp.set_drop_behavior(DropBehavior::Rollback);
728        }
729        assert!(db.is_autocommit());
730
731        Ok(())
732    }
733
734    #[test]
735    fn test_savepoint_release_error() -> Result<()> {
736        let mut db = checked_memory_handle()?;
737
738        db.pragma_update(None, "foreign_keys", true)?;
739        db.execute_batch("CREATE TABLE r(n INTEGER PRIMARY KEY NOT NULL); CREATE TABLE f(n REFERENCES r(n) DEFERRABLE INITIALLY DEFERRED);")?;
740        {
741            let mut sp = db.savepoint()?;
742            sp.execute("INSERT INTO f VALUES (0)", [])?;
743            sp.set_drop_behavior(DropBehavior::Commit);
744        }
745        assert!(db.is_autocommit());
746
747        Ok(())
748    }
749
750    #[test]
751    fn test_savepoint_names() -> Result<()> {
752        let mut db = checked_memory_handle()?;
753
754        {
755            let mut sp1 = db.savepoint_with_name("my_sp")?;
756            insert(1, &sp1)?;
757            assert_current_sum(1, &sp1)?;
758            {
759                let mut sp2 = sp1.savepoint_with_name("my_sp")?;
760                sp2.set_drop_behavior(DropBehavior::Commit);
761                insert(2, &sp2)?;
762                assert_current_sum(3, &sp2)?;
763                sp2.rollback()?;
764                assert_current_sum(1, &sp2)?;
765                insert(4, &sp2)?;
766            }
767            assert_current_sum(5, &sp1)?;
768            sp1.rollback()?;
769            {
770                let mut sp2 = sp1.savepoint_with_name("my_sp")?;
771                sp2.set_drop_behavior(DropBehavior::Ignore);
772                insert(8, &sp2)?;
773            }
774            assert_current_sum(8, &sp1)?;
775            sp1.commit()?;
776        }
777        assert_current_sum(8, &db)?;
778        Ok(())
779    }
780
781    #[test]
782    fn test_rc() -> Result<()> {
783        use std::rc::Rc;
784        let mut conn = Connection::open_in_memory()?;
785        let rc_txn = Rc::new(conn.transaction()?);
786
787        // This will compile only if Transaction is Debug
788        Rc::try_unwrap(rc_txn).unwrap();
789        Ok(())
790    }
791
792    fn insert(x: i32, conn: &Connection) -> Result<usize> {
793        conn.execute("INSERT INTO foo VALUES(?1)", [x])
794    }
795
796    fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
797        assert_eq!(x, conn.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
798        Ok(())
799    }
800
801    #[test]
802    fn txn_state() -> Result<()> {
803        use super::TransactionState;
804        use crate::{DEFAULT_NAME, MAIN_DB};
805        let db = Connection::open_in_memory()?;
806        assert_eq!(TransactionState::None, db.transaction_state(Some(MAIN_DB))?);
807        assert_eq!(TransactionState::None, db.transaction_state(DEFAULT_NAME)?);
808        db.execute_batch("BEGIN")?;
809        assert_eq!(TransactionState::None, db.transaction_state(DEFAULT_NAME)?);
810        let _: i32 = db.pragma_query_one(None, "user_version")?;
811        assert_eq!(TransactionState::Read, db.transaction_state(DEFAULT_NAME)?);
812        db.pragma_update(None, "user_version", 1)?;
813        assert_eq!(TransactionState::Write, db.transaction_state(DEFAULT_NAME)?);
814        db.execute_batch("ROLLBACK")?;
815        Ok(())
816    }
817
818    #[test]
819    fn auto_commit() -> Result<()> {
820        use super::TransactionState;
821        use crate::DEFAULT_NAME;
822        let db = Connection::open_in_memory()?;
823        db.execute_batch("CREATE TABLE t(i UNIQUE);")?;
824        assert!(db.is_autocommit());
825        let mut stmt = db.prepare("SELECT name FROM sqlite_master")?;
826        assert_eq!(TransactionState::None, db.transaction_state(DEFAULT_NAME)?);
827        {
828            let mut rows = stmt.query([])?;
829            assert!(rows.next()?.is_some()); // start reading
830            assert_eq!(TransactionState::Read, db.transaction_state(DEFAULT_NAME)?);
831            db.execute("INSERT INTO t VALUES (1)", [])?; // auto-commit
832            assert_eq!(TransactionState::Read, db.transaction_state(DEFAULT_NAME)?);
833            assert!(rows.next()?.is_some()); // still reading
834            assert_eq!(TransactionState::Read, db.transaction_state(DEFAULT_NAME)?);
835            assert!(rows.next()?.is_none()); // end
836            assert_eq!(TransactionState::None, db.transaction_state(DEFAULT_NAME)?);
837        }
838        Ok(())
839    }
840}