• 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.
new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>>105     pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>> {
106         Self::new_unchecked(conn, behavior)
107     }
108 
109     /// Begin a new transaction, failing if a transaction is open.
110     ///
111     /// If a transaction is already open, this will return an error. Where
112     /// possible, [`Transaction::new`] should be preferred, as it provides a
113     /// compile-time guarantee that transactions are not nested.
new_unchecked( conn: &Connection, behavior: TransactionBehavior, ) -> Result<Transaction<'_>>114     pub fn new_unchecked(
115         conn: &Connection,
116         behavior: TransactionBehavior,
117     ) -> Result<Transaction<'_>> {
118         let query = match behavior {
119             TransactionBehavior::Deferred => "BEGIN DEFERRED",
120             TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
121             TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
122         };
123         conn.execute_batch(query).map(move |_| Transaction {
124             conn,
125             drop_behavior: DropBehavior::Rollback,
126         })
127     }
128 
129     /// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested
130     /// transactions.
131     ///
132     /// ## Note
133     ///
134     /// Just like outer level transactions, savepoint transactions rollback by
135     /// default.
136     ///
137     /// ## Example
138     ///
139     /// ```rust,no_run
140     /// # use rusqlite::{Connection, Result};
141     /// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true }
142     /// fn perform_queries(conn: &mut Connection) -> Result<()> {
143     ///     let mut tx = conn.transaction()?;
144     ///
145     ///     {
146     ///         let sp = tx.savepoint()?;
147     ///         if perform_queries_part_1_succeeds(&sp) {
148     ///             sp.commit()?;
149     ///         }
150     ///         // otherwise, sp will rollback
151     ///     }
152     ///
153     ///     tx.commit()
154     /// }
155     /// ```
savepoint(&mut self) -> Result<Savepoint<'_>>156     pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
157         Savepoint::with_depth(self.conn, 1)
158     }
159 
160     /// Create a new savepoint with a custom savepoint name. See `savepoint()`.
savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>161     pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
162         Savepoint::with_depth_and_name(self.conn, 1, name)
163     }
164 
165     /// Get the current setting for what happens to the transaction when it is
166     /// dropped.
drop_behavior(&self) -> DropBehavior167     pub fn drop_behavior(&self) -> DropBehavior {
168         self.drop_behavior
169     }
170 
171     /// Configure the transaction to perform the specified action when it is
172     /// dropped.
set_drop_behavior(&mut self, drop_behavior: DropBehavior)173     pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
174         self.drop_behavior = drop_behavior
175     }
176 
177     /// A convenience method which consumes and commits a transaction.
commit(mut self) -> Result<()>178     pub fn commit(mut self) -> Result<()> {
179         self.commit_()
180     }
181 
commit_(&mut self) -> Result<()>182     fn commit_(&mut self) -> Result<()> {
183         self.conn.execute_batch("COMMIT")?;
184         Ok(())
185     }
186 
187     /// A convenience method which consumes and rolls back a transaction.
rollback(mut self) -> Result<()>188     pub fn rollback(mut self) -> Result<()> {
189         self.rollback_()
190     }
191 
rollback_(&mut self) -> Result<()>192     fn rollback_(&mut self) -> Result<()> {
193         self.conn.execute_batch("ROLLBACK")?;
194         Ok(())
195     }
196 
197     /// Consumes the transaction, committing or rolling back according to the
198     /// current setting (see `drop_behavior`).
199     ///
200     /// Functionally equivalent to the `Drop` implementation, but allows
201     /// callers to see any errors that occur.
finish(mut self) -> Result<()>202     pub fn finish(mut self) -> Result<()> {
203         self.finish_()
204     }
205 
finish_(&mut self) -> Result<()>206     fn finish_(&mut self) -> Result<()> {
207         if self.conn.is_autocommit() {
208             return Ok(());
209         }
210         match self.drop_behavior() {
211             DropBehavior::Commit => self.commit_().or_else(|_| self.rollback_()),
212             DropBehavior::Rollback => self.rollback_(),
213             DropBehavior::Ignore => Ok(()),
214             DropBehavior::Panic => panic!("Transaction dropped unexpectedly."),
215         }
216     }
217 }
218 
219 impl Deref for Transaction<'_> {
220     type Target = Connection;
221 
deref(&self) -> &Connection222     fn deref(&self) -> &Connection {
223         self.conn
224     }
225 }
226 
227 #[allow(unused_must_use)]
228 impl Drop for Transaction<'_> {
drop(&mut self)229     fn drop(&mut self) {
230         self.finish_();
231     }
232 }
233 
234 impl Savepoint<'_> {
with_depth_and_name<T: Into<String>>( conn: &Connection, depth: u32, name: T, ) -> Result<Savepoint<'_>>235     fn with_depth_and_name<T: Into<String>>(
236         conn: &Connection,
237         depth: u32,
238         name: T,
239     ) -> Result<Savepoint<'_>> {
240         let name = name.into();
241         conn.execute_batch(&format!("SAVEPOINT {}", name))
242             .map(|_| Savepoint {
243                 conn,
244                 name,
245                 depth,
246                 drop_behavior: DropBehavior::Rollback,
247                 committed: false,
248             })
249     }
250 
with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>>251     fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>> {
252         let name = format!("_rusqlite_sp_{}", depth);
253         Savepoint::with_depth_and_name(conn, depth, name)
254     }
255 
256     /// Begin a new savepoint. Can be nested.
new(conn: &mut Connection) -> Result<Savepoint<'_>>257     pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
258         Savepoint::with_depth(conn, 0)
259     }
260 
261     /// Begin a new savepoint with a user-provided savepoint name.
with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>>262     pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> {
263         Savepoint::with_depth_and_name(conn, 0, name)
264     }
265 
266     /// Begin a nested savepoint.
savepoint(&mut self) -> Result<Savepoint<'_>>267     pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
268         Savepoint::with_depth(self.conn, self.depth + 1)
269     }
270 
271     /// Begin a nested savepoint with a user-provided savepoint name.
savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>272     pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
273         Savepoint::with_depth_and_name(self.conn, self.depth + 1, name)
274     }
275 
276     /// Get the current setting for what happens to the savepoint when it is
277     /// dropped.
drop_behavior(&self) -> DropBehavior278     pub fn drop_behavior(&self) -> DropBehavior {
279         self.drop_behavior
280     }
281 
282     /// Configure the savepoint to perform the specified action when it is
283     /// dropped.
set_drop_behavior(&mut self, drop_behavior: DropBehavior)284     pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
285         self.drop_behavior = drop_behavior
286     }
287 
288     /// A convenience method which consumes and commits a savepoint.
commit(mut self) -> Result<()>289     pub fn commit(mut self) -> Result<()> {
290         self.commit_()
291     }
292 
commit_(&mut self) -> Result<()>293     fn commit_(&mut self) -> Result<()> {
294         self.conn.execute_batch(&format!("RELEASE {}", self.name))?;
295         self.committed = true;
296         Ok(())
297     }
298 
299     /// A convenience method which rolls back a savepoint.
300     ///
301     /// ## Note
302     ///
303     /// Unlike `Transaction`s, savepoints remain active after they have been
304     /// rolled back, and can be rolled back again or committed.
rollback(&mut self) -> Result<()>305     pub fn rollback(&mut self) -> Result<()> {
306         self.conn
307             .execute_batch(&format!("ROLLBACK TO {}", self.name))
308     }
309 
310     /// Consumes the savepoint, committing or rolling back according to the
311     /// current setting (see `drop_behavior`).
312     ///
313     /// Functionally equivalent to the `Drop` implementation, but allows
314     /// callers to see any errors that occur.
finish(mut self) -> Result<()>315     pub fn finish(mut self) -> Result<()> {
316         self.finish_()
317     }
318 
finish_(&mut self) -> Result<()>319     fn finish_(&mut self) -> Result<()> {
320         if self.committed {
321             return Ok(());
322         }
323         match self.drop_behavior() {
324             DropBehavior::Commit => self.commit_().or_else(|_| self.rollback()),
325             DropBehavior::Rollback => self.rollback(),
326             DropBehavior::Ignore => Ok(()),
327             DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
328         }
329     }
330 }
331 
332 impl Deref for Savepoint<'_> {
333     type Target = Connection;
334 
deref(&self) -> &Connection335     fn deref(&self) -> &Connection {
336         self.conn
337     }
338 }
339 
340 #[allow(unused_must_use)]
341 impl Drop for Savepoint<'_> {
drop(&mut self)342     fn drop(&mut self) {
343         self.finish_();
344     }
345 }
346 
347 impl Connection {
348     /// Begin a new transaction with the default behavior (DEFERRED).
349     ///
350     /// The transaction defaults to rolling back when it is dropped. If you
351     /// want the transaction to commit, you must call `commit` or
352     /// `set_drop_behavior(DropBehavior::Commit)`.
353     ///
354     /// ## Example
355     ///
356     /// ```rust,no_run
357     /// # use rusqlite::{Connection, Result};
358     /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
359     /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
360     /// fn perform_queries(conn: &mut Connection) -> Result<()> {
361     ///     let tx = conn.transaction()?;
362     ///
363     ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails
364     ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails
365     ///
366     ///     tx.commit()
367     /// }
368     /// ```
369     ///
370     /// # Failure
371     ///
372     /// Will return `Err` if the underlying SQLite call fails.
transaction(&mut self) -> Result<Transaction<'_>>373     pub fn transaction(&mut self) -> Result<Transaction<'_>> {
374         Transaction::new(self, TransactionBehavior::Deferred)
375     }
376 
377     /// Begin a new transaction with a specified behavior.
378     ///
379     /// See `transaction`.
380     ///
381     /// # Failure
382     ///
383     /// Will return `Err` if the underlying SQLite call fails.
transaction_with_behavior( &mut self, behavior: TransactionBehavior, ) -> Result<Transaction<'_>>384     pub fn transaction_with_behavior(
385         &mut self,
386         behavior: TransactionBehavior,
387     ) -> Result<Transaction<'_>> {
388         Transaction::new(self, behavior)
389     }
390 
391     /// Begin a new transaction with the default behavior (DEFERRED).
392     ///
393     /// Attempt to open a nested transaction will result in a SQLite error.
394     /// `Connection::transaction` prevents this at compile time by taking `&mut
395     /// self`, but `Connection::unchecked_transaction()` may be used to defer
396     /// the checking until runtime.
397     ///
398     /// See [`Connection::transaction`] and [`Transaction::new_unchecked`]
399     /// (which can be used if the default transaction behavior is undesirable).
400     ///
401     /// ## Example
402     ///
403     /// ```rust,no_run
404     /// # use rusqlite::{Connection, Result};
405     /// # use std::rc::Rc;
406     /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
407     /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
408     /// fn perform_queries(conn: Rc<Connection>) -> Result<()> {
409     ///     let tx = conn.unchecked_transaction()?;
410     ///
411     ///     do_queries_part_1(&tx)?; // tx causes rollback if this fails
412     ///     do_queries_part_2(&tx)?; // tx causes rollback if this fails
413     ///
414     ///     tx.commit()
415     /// }
416     /// ```
417     ///
418     /// # Failure
419     ///
420     /// Will return `Err` if the underlying SQLite call fails. The specific
421     /// error returned if transactions are nested is currently unspecified.
unchecked_transaction(&self) -> Result<Transaction<'_>>422     pub fn unchecked_transaction(&self) -> Result<Transaction<'_>> {
423         Transaction::new_unchecked(self, TransactionBehavior::Deferred)
424     }
425 
426     /// Begin a new savepoint with the default behavior (DEFERRED).
427     ///
428     /// The savepoint defaults to rolling back when it is dropped. If you want
429     /// the savepoint to commit, you must call `commit` or
430     /// `set_drop_behavior(DropBehavior::Commit)`.
431     ///
432     /// ## Example
433     ///
434     /// ```rust,no_run
435     /// # use rusqlite::{Connection, Result};
436     /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
437     /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
438     /// fn perform_queries(conn: &mut Connection) -> Result<()> {
439     ///     let sp = conn.savepoint()?;
440     ///
441     ///     do_queries_part_1(&sp)?; // sp causes rollback if this fails
442     ///     do_queries_part_2(&sp)?; // sp causes rollback if this fails
443     ///
444     ///     sp.commit()
445     /// }
446     /// ```
447     ///
448     /// # Failure
449     ///
450     /// Will return `Err` if the underlying SQLite call fails.
savepoint(&mut self) -> Result<Savepoint<'_>>451     pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
452         Savepoint::new(self)
453     }
454 
455     /// Begin a new savepoint with a specified name.
456     ///
457     /// See `savepoint`.
458     ///
459     /// # Failure
460     ///
461     /// Will return `Err` if the underlying SQLite call fails.
savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>>462     pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
463         Savepoint::with_name(self, name)
464     }
465 }
466 
467 #[cfg(test)]
468 mod test {
469     use super::DropBehavior;
470     use crate::{Connection, Error, NO_PARAMS};
471 
checked_memory_handle() -> Connection472     fn checked_memory_handle() -> Connection {
473         let db = Connection::open_in_memory().unwrap();
474         db.execute_batch("CREATE TABLE foo (x INTEGER)").unwrap();
475         db
476     }
477 
478     #[test]
test_drop()479     fn test_drop() {
480         let mut db = checked_memory_handle();
481         {
482             let tx = db.transaction().unwrap();
483             tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
484             // default: rollback
485         }
486         {
487             let mut tx = db.transaction().unwrap();
488             tx.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
489             tx.set_drop_behavior(DropBehavior::Commit)
490         }
491         {
492             let tx = db.transaction().unwrap();
493             assert_eq!(
494                 2i32,
495                 tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
496                     .unwrap()
497             );
498         }
499     }
assert_nested_tx_error(e: crate::Error)500     fn assert_nested_tx_error(e: crate::Error) {
501         if let Error::SqliteFailure(e, Some(m)) = &e {
502             assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR);
503             // FIXME: Not ideal...
504             assert_eq!(e.code, crate::ErrorCode::Unknown);
505             assert!(m.contains("transaction"));
506         } else {
507             panic!("Unexpected error type: {:?}", e);
508         }
509     }
510 
511     #[test]
test_unchecked_nesting()512     fn test_unchecked_nesting() {
513         let db = checked_memory_handle();
514 
515         {
516             let tx = db.unchecked_transaction().unwrap();
517             let e = tx.unchecked_transaction().unwrap_err();
518             assert_nested_tx_error(e);
519             // default: rollback
520         }
521         {
522             let tx = db.unchecked_transaction().unwrap();
523             tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
524             // Ensure this doesn't interfere with ongoing transaction
525             let e = tx.unchecked_transaction().unwrap_err();
526             assert_nested_tx_error(e);
527 
528             tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
529             tx.commit().unwrap();
530         }
531 
532         assert_eq!(
533             2i32,
534             db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
535                 .unwrap()
536         );
537     }
538 
539     #[test]
test_explicit_rollback_commit()540     fn test_explicit_rollback_commit() {
541         let mut db = checked_memory_handle();
542         {
543             let mut tx = db.transaction().unwrap();
544             {
545                 let mut sp = tx.savepoint().unwrap();
546                 sp.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
547                 sp.rollback().unwrap();
548                 sp.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
549                 sp.commit().unwrap();
550             }
551             tx.commit().unwrap();
552         }
553         {
554             let tx = db.transaction().unwrap();
555             tx.execute_batch("INSERT INTO foo VALUES(4)").unwrap();
556             tx.commit().unwrap();
557         }
558         {
559             let tx = db.transaction().unwrap();
560             assert_eq!(
561                 6i32,
562                 tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
563                     .unwrap()
564             );
565         }
566     }
567 
568     #[test]
test_savepoint()569     fn test_savepoint() {
570         let mut db = checked_memory_handle();
571         {
572             let mut tx = db.transaction().unwrap();
573             tx.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
574             assert_current_sum(1, &tx);
575             tx.set_drop_behavior(DropBehavior::Commit);
576             {
577                 let mut sp1 = tx.savepoint().unwrap();
578                 sp1.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
579                 assert_current_sum(3, &sp1);
580                 // will rollback sp1
581                 {
582                     let mut sp2 = sp1.savepoint().unwrap();
583                     sp2.execute_batch("INSERT INTO foo VALUES(4)").unwrap();
584                     assert_current_sum(7, &sp2);
585                     // will rollback sp2
586                     {
587                         let sp3 = sp2.savepoint().unwrap();
588                         sp3.execute_batch("INSERT INTO foo VALUES(8)").unwrap();
589                         assert_current_sum(15, &sp3);
590                         sp3.commit().unwrap();
591                         // committed sp3, but will be erased by sp2 rollback
592                     }
593                     assert_current_sum(15, &sp2);
594                 }
595                 assert_current_sum(3, &sp1);
596             }
597             assert_current_sum(1, &tx);
598         }
599         assert_current_sum(1, &db);
600     }
601 
602     #[test]
test_ignore_drop_behavior()603     fn test_ignore_drop_behavior() {
604         let mut db = checked_memory_handle();
605 
606         let mut tx = db.transaction().unwrap();
607         {
608             let mut sp1 = tx.savepoint().unwrap();
609             insert(1, &sp1);
610             sp1.rollback().unwrap();
611             insert(2, &sp1);
612             {
613                 let mut sp2 = sp1.savepoint().unwrap();
614                 sp2.set_drop_behavior(DropBehavior::Ignore);
615                 insert(4, &sp2);
616             }
617             assert_current_sum(6, &sp1);
618             sp1.commit().unwrap();
619         }
620         assert_current_sum(6, &tx);
621     }
622 
623     #[test]
test_savepoint_names()624     fn test_savepoint_names() {
625         let mut db = checked_memory_handle();
626 
627         {
628             let mut sp1 = db.savepoint_with_name("my_sp").unwrap();
629             insert(1, &sp1);
630             assert_current_sum(1, &sp1);
631             {
632                 let mut sp2 = sp1.savepoint_with_name("my_sp").unwrap();
633                 sp2.set_drop_behavior(DropBehavior::Commit);
634                 insert(2, &sp2);
635                 assert_current_sum(3, &sp2);
636                 sp2.rollback().unwrap();
637                 assert_current_sum(1, &sp2);
638                 insert(4, &sp2);
639             }
640             assert_current_sum(5, &sp1);
641             sp1.rollback().unwrap();
642             {
643                 let mut sp2 = sp1.savepoint_with_name("my_sp").unwrap();
644                 sp2.set_drop_behavior(DropBehavior::Ignore);
645                 insert(8, &sp2);
646             }
647             assert_current_sum(8, &sp1);
648             sp1.commit().unwrap();
649         }
650         assert_current_sum(8, &db);
651     }
652 
653     #[test]
test_rc()654     fn test_rc() {
655         use std::rc::Rc;
656         let mut conn = Connection::open_in_memory().unwrap();
657         let rc_txn = Rc::new(conn.transaction().unwrap());
658 
659         // This will compile only if Transaction is Debug
660         Rc::try_unwrap(rc_txn).unwrap();
661     }
662 
insert(x: i32, conn: &Connection)663     fn insert(x: i32, conn: &Connection) {
664         conn.execute("INSERT INTO foo VALUES(?)", &[x]).unwrap();
665     }
666 
assert_current_sum(x: i32, conn: &Connection)667     fn assert_current_sum(x: i32, conn: &Connection) {
668         let i = conn
669             .query_row::<i32, _, _>("SELECT SUM(x) FROM foo", NO_PARAMS, |r| r.get(0))
670             .unwrap();
671         assert_eq!(x, i);
672     }
673 }
674