• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::{Connection, Result};
2 use 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]
8 pub 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]
23 pub 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)]
62 pub 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)]
91 pub struct Savepoint<'conn> {
92     conn: &'conn Connection,
93     name: String,
94     drop_behavior: DropBehavior,
95     committed: bool,
96 }
97 
98 impl 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]
new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>>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]
new_unchecked( conn: &Connection, behavior: TransactionBehavior, ) -> Result<Transaction<'_>>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]
savepoint(&mut self) -> Result<Savepoint<'_>>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]
savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>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]
drop_behavior(&self) -> DropBehavior173     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]
set_drop_behavior(&mut self, drop_behavior: DropBehavior)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]
commit(mut self) -> Result<()>186     pub fn commit(mut self) -> Result<()> {
187         self.commit_()
188     }
189 
190     #[inline]
commit_(&mut self) -> Result<()>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]
rollback(mut self) -> Result<()>198     pub fn rollback(mut self) -> Result<()> {
199         self.rollback_()
200     }
201 
202     #[inline]
rollback_(&mut self) -> Result<()>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]
finish(mut self) -> Result<()>214     pub fn finish(mut self) -> Result<()> {
215         self.finish_()
216     }
217 
218     #[inline]
finish_(&mut self) -> Result<()>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 
232 impl Deref for Transaction<'_> {
233     type Target = Connection;
234 
235     #[inline]
deref(&self) -> &Connection236     fn deref(&self) -> &Connection {
237         self.conn
238     }
239 }
240 
241 #[allow(unused_must_use)]
242 impl Drop for Transaction<'_> {
243     #[inline]
drop(&mut self)244     fn drop(&mut self) {
245         self.finish_();
246     }
247 }
248 
249 impl Savepoint<'_> {
250     #[inline]
with_name_<T: Into<String>>(conn: &Connection, name: T) -> Result<Savepoint<'_>>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]
new_(conn: &Connection) -> Result<Savepoint<'_>>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]
new(conn: &mut Connection) -> Result<Savepoint<'_>>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]
with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>>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]
savepoint(&mut self) -> Result<Savepoint<'_>>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]
savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>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]
drop_behavior(&self) -> DropBehavior295     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]
set_drop_behavior(&mut self, drop_behavior: DropBehavior)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]
commit(mut self) -> Result<()>308     pub fn commit(mut self) -> Result<()> {
309         self.commit_()
310     }
311 
312     #[inline]
commit_(&mut self) -> Result<()>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]
rollback(&mut self) -> Result<()>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]
finish(mut self) -> Result<()>337     pub fn finish(mut self) -> Result<()> {
338         self.finish_()
339     }
340 
341     #[inline]
finish_(&mut self) -> Result<()>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 
357 impl Deref for Savepoint<'_> {
358     type Target = Connection;
359 
360     #[inline]
deref(&self) -> &Connection361     fn deref(&self) -> &Connection {
362         self.conn
363     }
364 }
365 
366 #[allow(unused_must_use)]
367 impl Drop for Savepoint<'_> {
368     #[inline]
drop(&mut self)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")))]
379 pub 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 
388 impl 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]
transaction(&mut self) -> Result<Transaction<'_>>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]
transaction_with_behavior( &mut self, behavior: TransactionBehavior, ) -> Result<Transaction<'_>>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.
unchecked_transaction(&self) -> Result<Transaction<'_>>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]
savepoint(&mut self) -> Result<Savepoint<'_>>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]
savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>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")))]
transaction_state( &self, db_name: Option<crate::DatabaseName<'_>>, ) -> Result<TransactionState>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     /// ```
set_transaction_behavior(&mut self, behavior: TransactionBehavior)546     pub fn set_transaction_behavior(&mut self, behavior: TransactionBehavior) {
547         self.transaction_behavior = behavior;
548     }
549 }
550 
551 #[cfg(test)]
552 mod test {
553     use super::DropBehavior;
554     use crate::{Connection, Error, Result};
555 
checked_memory_handle() -> Result<Connection>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]
test_drop() -> Result<()>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     }
assert_nested_tx_error(e: Error)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]
test_unchecked_nesting() -> Result<()>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]
test_explicit_rollback_commit() -> Result<()>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]
test_savepoint() -> Result<()>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]
test_ignore_drop_behavior() -> Result<()>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]
test_savepoint_drop_behavior_releases() -> Result<()>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]
test_savepoint_release_error() -> Result<()>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]
test_savepoint_names() -> Result<()>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]
test_rc() -> Result<()>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 
insert(x: i32, conn: &Connection) -> Result<usize>776     fn insert(x: i32, conn: &Connection) -> Result<usize> {
777         conn.execute("INSERT INTO foo VALUES(?1)", [x])
778     }
779 
assert_current_sum(x: i32, conn: &Connection) -> Result<()>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")]
txn_state() -> Result<()>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")]
auto_commit() -> Result<()>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 }
830