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