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     depth: u32,
95     drop_behavior: DropBehavior,
96     committed: bool,
97 }
98 
99 impl 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     /// so as to prevent nested transactions on the same connection. For cases
105     /// where this is unacceptable, [`Transaction::new_unchecked`] is available.
106     #[inline]
new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>>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]
new_unchecked( conn: &Connection, behavior: TransactionBehavior, ) -> Result<Transaction<'_>>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]
savepoint(&mut self) -> Result<Savepoint<'_>>160     pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
161         Savepoint::with_depth(self.conn, 1)
162     }
163 
164     /// Create a new savepoint with a custom savepoint name. See `savepoint()`.
165     #[inline]
savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>166     pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
167         Savepoint::with_depth_and_name(self.conn, 1, name)
168     }
169 
170     /// Get the current setting for what happens to the transaction when it is
171     /// dropped.
172     #[inline]
173     #[must_use]
drop_behavior(&self) -> DropBehavior174     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]
set_drop_behavior(&mut self, drop_behavior: DropBehavior)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]
commit(mut self) -> Result<()>187     pub fn commit(mut self) -> Result<()> {
188         self.commit_()
189     }
190 
191     #[inline]
commit_(&mut self) -> Result<()>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]
rollback(mut self) -> Result<()>199     pub fn rollback(mut self) -> Result<()> {
200         self.rollback_()
201     }
202 
203     #[inline]
rollback_(&mut self) -> Result<()>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]
finish(mut self) -> Result<()>215     pub fn finish(mut self) -> Result<()> {
216         self.finish_()
217     }
218 
219     #[inline]
finish_(&mut self) -> Result<()>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 
233 impl Deref for Transaction<'_> {
234     type Target = Connection;
235 
236     #[inline]
deref(&self) -> &Connection237     fn deref(&self) -> &Connection {
238         self.conn
239     }
240 }
241 
242 #[allow(unused_must_use)]
243 impl Drop for Transaction<'_> {
244     #[inline]
drop(&mut self)245     fn drop(&mut self) {
246         self.finish_();
247     }
248 }
249 
250 impl Savepoint<'_> {
251     #[inline]
with_depth_and_name<T: Into<String>>( conn: &Connection, depth: u32, name: T, ) -> Result<Savepoint<'_>>252     fn with_depth_and_name<T: Into<String>>(
253         conn: &Connection,
254         depth: u32,
255         name: T,
256     ) -> Result<Savepoint<'_>> {
257         let name = name.into();
258         conn.execute_batch(&format!("SAVEPOINT {name}"))
259             .map(|_| Savepoint {
260                 conn,
261                 name,
262                 depth,
263                 drop_behavior: DropBehavior::Rollback,
264                 committed: false,
265             })
266     }
267 
268     #[inline]
with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>>269     fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>> {
270         let name = format!("_rusqlite_sp_{depth}");
271         Savepoint::with_depth_and_name(conn, depth, name)
272     }
273 
274     /// Begin a new savepoint. Can be nested.
275     #[inline]
new(conn: &mut Connection) -> Result<Savepoint<'_>>276     pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
277         Savepoint::with_depth(conn, 0)
278     }
279 
280     /// Begin a new savepoint with a user-provided savepoint name.
281     #[inline]
with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>>282     pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> {
283         Savepoint::with_depth_and_name(conn, 0, name)
284     }
285 
286     /// Begin a nested savepoint.
287     #[inline]
savepoint(&mut self) -> Result<Savepoint<'_>>288     pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
289         Savepoint::with_depth(self.conn, self.depth + 1)
290     }
291 
292     /// Begin a nested savepoint with a user-provided savepoint name.
293     #[inline]
savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>294     pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
295         Savepoint::with_depth_and_name(self.conn, self.depth + 1, name)
296     }
297 
298     /// Get the current setting for what happens to the savepoint when it is
299     /// dropped.
300     #[inline]
301     #[must_use]
drop_behavior(&self) -> DropBehavior302     pub fn drop_behavior(&self) -> DropBehavior {
303         self.drop_behavior
304     }
305 
306     /// Configure the savepoint to perform the specified action when it is
307     /// dropped.
308     #[inline]
set_drop_behavior(&mut self, drop_behavior: DropBehavior)309     pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
310         self.drop_behavior = drop_behavior;
311     }
312 
313     /// A convenience method which consumes and commits a savepoint.
314     #[inline]
commit(mut self) -> Result<()>315     pub fn commit(mut self) -> Result<()> {
316         self.commit_()
317     }
318 
319     #[inline]
commit_(&mut self) -> Result<()>320     fn commit_(&mut self) -> Result<()> {
321         self.conn.execute_batch(&format!("RELEASE {}", self.name))?;
322         self.committed = true;
323         Ok(())
324     }
325 
326     /// A convenience method which rolls back a savepoint.
327     ///
328     /// ## Note
329     ///
330     /// Unlike `Transaction`s, savepoints remain active after they have been
331     /// rolled back, and can be rolled back again or committed.
332     #[inline]
rollback(&mut self) -> Result<()>333     pub fn rollback(&mut self) -> Result<()> {
334         self.conn
335             .execute_batch(&format!("ROLLBACK TO {}", self.name))
336     }
337 
338     /// Consumes the savepoint, committing or rolling back according to the
339     /// current setting (see `drop_behavior`).
340     ///
341     /// Functionally equivalent to the `Drop` implementation, but allows
342     /// callers to see any errors that occur.
343     #[inline]
finish(mut self) -> Result<()>344     pub fn finish(mut self) -> Result<()> {
345         self.finish_()
346     }
347 
348     #[inline]
finish_(&mut self) -> Result<()>349     fn finish_(&mut self) -> Result<()> {
350         if self.committed {
351             return Ok(());
352         }
353         match self.drop_behavior() {
354             DropBehavior::Commit => self.commit_().or_else(|_| self.rollback()),
355             DropBehavior::Rollback => self.rollback(),
356             DropBehavior::Ignore => Ok(()),
357             DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
358         }
359     }
360 }
361 
362 impl Deref for Savepoint<'_> {
363     type Target = Connection;
364 
365     #[inline]
deref(&self) -> &Connection366     fn deref(&self) -> &Connection {
367         self.conn
368     }
369 }
370 
371 #[allow(unused_must_use)]
372 impl Drop for Savepoint<'_> {
373     #[inline]
drop(&mut self)374     fn drop(&mut self) {
375         self.finish_();
376     }
377 }
378 
379 /// Transaction state of a database
380 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
381 #[non_exhaustive]
382 #[cfg(feature = "modern_sqlite")] // 3.37.0
383 #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
384 pub enum TransactionState {
385     /// Equivalent to SQLITE_TXN_NONE
386     None,
387     /// Equivalent to SQLITE_TXN_READ
388     Read,
389     /// Equivalent to SQLITE_TXN_WRITE
390     Write,
391 }
392 
393 impl Connection {
394     /// Begin a new transaction with the default behavior (DEFERRED).
395     ///
396     /// The transaction defaults to rolling back when it is dropped. If you
397     /// want the transaction to commit, you must call
398     /// [`commit`](Transaction::commit) or
399     /// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior).
400     ///
401     /// ## Example
402     ///
403     /// ```rust,no_run
404     /// # use rusqlite::{Connection, Result};
405     /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
406     /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
407     /// fn perform_queries(conn: &mut Connection) -> Result<()> {
408     ///     let tx = conn.transaction()?;
409     ///
410     ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails
411     ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails
412     ///
413     ///     tx.commit()
414     /// }
415     /// ```
416     ///
417     /// # Failure
418     ///
419     /// Will return `Err` if the underlying SQLite call fails.
420     #[inline]
transaction(&mut self) -> Result<Transaction<'_>>421     pub fn transaction(&mut self) -> Result<Transaction<'_>> {
422         Transaction::new(self, TransactionBehavior::Deferred)
423     }
424 
425     /// Begin a new transaction with a specified behavior.
426     ///
427     /// See [`transaction`](Connection::transaction).
428     ///
429     /// # Failure
430     ///
431     /// Will return `Err` if the underlying SQLite call fails.
432     #[inline]
transaction_with_behavior( &mut self, behavior: TransactionBehavior, ) -> Result<Transaction<'_>>433     pub fn transaction_with_behavior(
434         &mut self,
435         behavior: TransactionBehavior,
436     ) -> Result<Transaction<'_>> {
437         Transaction::new(self, behavior)
438     }
439 
440     /// Begin a new transaction with the default behavior (DEFERRED).
441     ///
442     /// Attempt to open a nested transaction will result in a SQLite error.
443     /// `Connection::transaction` prevents this at compile time by taking `&mut
444     /// self`, but `Connection::unchecked_transaction()` may be used to defer
445     /// the checking until runtime.
446     ///
447     /// See [`Connection::transaction`] and [`Transaction::new_unchecked`]
448     /// (which can be used if the default transaction behavior is undesirable).
449     ///
450     /// ## Example
451     ///
452     /// ```rust,no_run
453     /// # use rusqlite::{Connection, Result};
454     /// # use std::rc::Rc;
455     /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
456     /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
457     /// fn perform_queries(conn: Rc<Connection>) -> Result<()> {
458     ///     let tx = conn.unchecked_transaction()?;
459     ///
460     ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails
461     ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails
462     ///
463     ///     tx.commit()
464     /// }
465     /// ```
466     ///
467     /// # Failure
468     ///
469     /// Will return `Err` if the underlying SQLite call fails. The specific
470     /// error returned if transactions are nested is currently unspecified.
unchecked_transaction(&self) -> Result<Transaction<'_>>471     pub fn unchecked_transaction(&self) -> Result<Transaction<'_>> {
472         Transaction::new_unchecked(self, TransactionBehavior::Deferred)
473     }
474 
475     /// Begin a new savepoint with the default behavior (DEFERRED).
476     ///
477     /// The savepoint defaults to rolling back when it is dropped. If you want
478     /// the savepoint to commit, you must call [`commit`](Savepoint::commit) or
479     /// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::
480     /// set_drop_behavior).
481     ///
482     /// ## Example
483     ///
484     /// ```rust,no_run
485     /// # use rusqlite::{Connection, Result};
486     /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
487     /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
488     /// fn perform_queries(conn: &mut Connection) -> Result<()> {
489     ///     let sp = conn.savepoint()?;
490     ///
491     ///     do_queries_part_1(&sp)?; // sp causes rollback if this fails
492     ///     do_queries_part_2(&sp)?; // sp causes rollback if this fails
493     ///
494     ///     sp.commit()
495     /// }
496     /// ```
497     ///
498     /// # Failure
499     ///
500     /// Will return `Err` if the underlying SQLite call fails.
501     #[inline]
savepoint(&mut self) -> Result<Savepoint<'_>>502     pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
503         Savepoint::new(self)
504     }
505 
506     /// Begin a new savepoint with a specified name.
507     ///
508     /// See [`savepoint`](Connection::savepoint).
509     ///
510     /// # Failure
511     ///
512     /// Will return `Err` if the underlying SQLite call fails.
513     #[inline]
savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>514     pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
515         Savepoint::with_name(self, name)
516     }
517 
518     /// Determine the transaction state of a database
519     #[cfg(feature = "modern_sqlite")] // 3.37.0
520     #[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
transaction_state( &self, db_name: Option<crate::DatabaseName<'_>>, ) -> Result<TransactionState>521     pub fn transaction_state(
522         &self,
523         db_name: Option<crate::DatabaseName<'_>>,
524     ) -> Result<TransactionState> {
525         self.db.borrow().txn_state(db_name)
526     }
527 }
528 
529 #[cfg(test)]
530 mod test {
531     use super::DropBehavior;
532     use crate::{Connection, Error, Result};
533 
checked_memory_handle() -> Result<Connection>534     fn checked_memory_handle() -> Result<Connection> {
535         let db = Connection::open_in_memory()?;
536         db.execute_batch("CREATE TABLE foo (x INTEGER)")?;
537         Ok(db)
538     }
539 
540     #[test]
test_drop() -> Result<()>541     fn test_drop() -> Result<()> {
542         let mut db = checked_memory_handle()?;
543         {
544             let tx = db.transaction()?;
545             tx.execute_batch("INSERT INTO foo VALUES(1)")?;
546             // default: rollback
547         }
548         {
549             let mut tx = db.transaction()?;
550             tx.execute_batch("INSERT INTO foo VALUES(2)")?;
551             tx.set_drop_behavior(DropBehavior::Commit)
552         }
553         {
554             let tx = db.transaction()?;
555             assert_eq!(2i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?);
556         }
557         Ok(())
558     }
assert_nested_tx_error(e: Error)559     fn assert_nested_tx_error(e: Error) {
560         if let Error::SqliteFailure(e, Some(m)) = &e {
561             assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR);
562             // FIXME: Not ideal...
563             assert_eq!(e.code, crate::ErrorCode::Unknown);
564             assert!(m.contains("transaction"));
565         } else {
566             panic!("Unexpected error type: {:?}", e);
567         }
568     }
569 
570     #[test]
test_unchecked_nesting() -> Result<()>571     fn test_unchecked_nesting() -> Result<()> {
572         let db = checked_memory_handle()?;
573 
574         {
575             let tx = db.unchecked_transaction()?;
576             let e = tx.unchecked_transaction().unwrap_err();
577             assert_nested_tx_error(e);
578             // default: rollback
579         }
580         {
581             let tx = db.unchecked_transaction()?;
582             tx.execute_batch("INSERT INTO foo VALUES(1)")?;
583             // Ensure this doesn't interfere with ongoing transaction
584             let e = tx.unchecked_transaction().unwrap_err();
585             assert_nested_tx_error(e);
586 
587             tx.execute_batch("INSERT INTO foo VALUES(1)")?;
588             tx.commit()?;
589         }
590 
591         assert_eq!(2i32, db.one_column::<i32>("SELECT SUM(x) FROM foo")?);
592         Ok(())
593     }
594 
595     #[test]
test_explicit_rollback_commit() -> Result<()>596     fn test_explicit_rollback_commit() -> Result<()> {
597         let mut db = checked_memory_handle()?;
598         {
599             let mut tx = db.transaction()?;
600             {
601                 let mut sp = tx.savepoint()?;
602                 sp.execute_batch("INSERT INTO foo VALUES(1)")?;
603                 sp.rollback()?;
604                 sp.execute_batch("INSERT INTO foo VALUES(2)")?;
605                 sp.commit()?;
606             }
607             tx.commit()?;
608         }
609         {
610             let tx = db.transaction()?;
611             tx.execute_batch("INSERT INTO foo VALUES(4)")?;
612             tx.commit()?;
613         }
614         {
615             let tx = db.transaction()?;
616             assert_eq!(6i32, tx.one_column::<i32>("SELECT SUM(x) FROM foo")?);
617         }
618         Ok(())
619     }
620 
621     #[test]
test_savepoint() -> Result<()>622     fn test_savepoint() -> Result<()> {
623         let mut db = checked_memory_handle()?;
624         {
625             let mut tx = db.transaction()?;
626             tx.execute_batch("INSERT INTO foo VALUES(1)")?;
627             assert_current_sum(1, &tx)?;
628             tx.set_drop_behavior(DropBehavior::Commit);
629             {
630                 let mut sp1 = tx.savepoint()?;
631                 sp1.execute_batch("INSERT INTO foo VALUES(2)")?;
632                 assert_current_sum(3, &sp1)?;
633                 // will rollback sp1
634                 {
635                     let mut sp2 = sp1.savepoint()?;
636                     sp2.execute_batch("INSERT INTO foo VALUES(4)")?;
637                     assert_current_sum(7, &sp2)?;
638                     // will rollback sp2
639                     {
640                         let sp3 = sp2.savepoint()?;
641                         sp3.execute_batch("INSERT INTO foo VALUES(8)")?;
642                         assert_current_sum(15, &sp3)?;
643                         sp3.commit()?;
644                         // committed sp3, but will be erased by sp2 rollback
645                     }
646                     assert_current_sum(15, &sp2)?;
647                 }
648                 assert_current_sum(3, &sp1)?;
649             }
650             assert_current_sum(1, &tx)?;
651         }
652         assert_current_sum(1, &db)?;
653         Ok(())
654     }
655 
656     #[test]
test_ignore_drop_behavior() -> Result<()>657     fn test_ignore_drop_behavior() -> Result<()> {
658         let mut db = checked_memory_handle()?;
659 
660         let mut tx = db.transaction()?;
661         {
662             let mut sp1 = tx.savepoint()?;
663             insert(1, &sp1)?;
664             sp1.rollback()?;
665             insert(2, &sp1)?;
666             {
667                 let mut sp2 = sp1.savepoint()?;
668                 sp2.set_drop_behavior(DropBehavior::Ignore);
669                 insert(4, &sp2)?;
670             }
671             assert_current_sum(6, &sp1)?;
672             sp1.commit()?;
673         }
674         assert_current_sum(6, &tx)?;
675         Ok(())
676     }
677 
678     #[test]
test_savepoint_names() -> Result<()>679     fn test_savepoint_names() -> Result<()> {
680         let mut db = checked_memory_handle()?;
681 
682         {
683             let mut sp1 = db.savepoint_with_name("my_sp")?;
684             insert(1, &sp1)?;
685             assert_current_sum(1, &sp1)?;
686             {
687                 let mut sp2 = sp1.savepoint_with_name("my_sp")?;
688                 sp2.set_drop_behavior(DropBehavior::Commit);
689                 insert(2, &sp2)?;
690                 assert_current_sum(3, &sp2)?;
691                 sp2.rollback()?;
692                 assert_current_sum(1, &sp2)?;
693                 insert(4, &sp2)?;
694             }
695             assert_current_sum(5, &sp1)?;
696             sp1.rollback()?;
697             {
698                 let mut sp2 = sp1.savepoint_with_name("my_sp")?;
699                 sp2.set_drop_behavior(DropBehavior::Ignore);
700                 insert(8, &sp2)?;
701             }
702             assert_current_sum(8, &sp1)?;
703             sp1.commit()?;
704         }
705         assert_current_sum(8, &db)?;
706         Ok(())
707     }
708 
709     #[test]
test_rc() -> Result<()>710     fn test_rc() -> Result<()> {
711         use std::rc::Rc;
712         let mut conn = Connection::open_in_memory()?;
713         let rc_txn = Rc::new(conn.transaction()?);
714 
715         // This will compile only if Transaction is Debug
716         Rc::try_unwrap(rc_txn).unwrap();
717         Ok(())
718     }
719 
insert(x: i32, conn: &Connection) -> Result<usize>720     fn insert(x: i32, conn: &Connection) -> Result<usize> {
721         conn.execute("INSERT INTO foo VALUES(?1)", [x])
722     }
723 
assert_current_sum(x: i32, conn: &Connection) -> Result<()>724     fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
725         let i = conn.one_column::<i32>("SELECT SUM(x) FROM foo")?;
726         assert_eq!(x, i);
727         Ok(())
728     }
729 
730     #[test]
731     #[cfg(feature = "modern_sqlite")]
txn_state() -> Result<()>732     fn txn_state() -> Result<()> {
733         use super::TransactionState;
734         use crate::DatabaseName;
735         let db = Connection::open_in_memory()?;
736         assert_eq!(
737             TransactionState::None,
738             db.transaction_state(Some(DatabaseName::Main))?
739         );
740         assert_eq!(TransactionState::None, db.transaction_state(None)?);
741         db.execute_batch("BEGIN")?;
742         assert_eq!(TransactionState::None, db.transaction_state(None)?);
743         let _: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
744         assert_eq!(TransactionState::Read, db.transaction_state(None)?);
745         db.pragma_update(None, "user_version", 1)?;
746         assert_eq!(TransactionState::Write, db.transaction_state(None)?);
747         db.execute_batch("ROLLBACK")?;
748         Ok(())
749     }
750 }
751