rusqlite/transaction.rs
1use crate::{Connection, Result};
2use 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]
8pub 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]
23pub 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)]
62pub 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)]
91pub struct Savepoint<'conn> {
92 conn: &'conn Connection,
93 name: String,
94 drop_behavior: DropBehavior,
95 committed: bool,
96}
97
98impl 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]
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]
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]
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]
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]
173 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]
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]
186 pub fn commit(mut self) -> Result<()> {
187 self.commit_()
188 }
189
190 #[inline]
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]
198 pub fn rollback(mut self) -> Result<()> {
199 self.rollback_()
200 }
201
202 #[inline]
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]
214 pub fn finish(mut self) -> Result<()> {
215 self.finish_()
216 }
217
218 #[inline]
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
232impl Deref for Transaction<'_> {
233 type Target = Connection;
234
235 #[inline]
236 fn deref(&self) -> &Connection {
237 self.conn
238 }
239}
240
241#[expect(unused_must_use)]
242impl Drop for Transaction<'_> {
243 #[inline]
244 fn drop(&mut self) {
245 self.finish_();
246 }
247}
248
249impl Savepoint<'_> {
250 #[inline]
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]
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]
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]
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]
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]
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]
295 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]
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]
308 pub fn commit(mut self) -> Result<()> {
309 self.commit_()
310 }
311
312 #[inline]
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]
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]
337 pub fn finish(mut self) -> Result<()> {
338 self.finish_()
339 }
340
341 #[inline]
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
357impl Deref for Savepoint<'_> {
358 type Target = Connection;
359
360 #[inline]
361 fn deref(&self) -> &Connection {
362 self.conn
363 }
364}
365
366#[expect(unused_must_use)]
367impl Drop for Savepoint<'_> {
368 #[inline]
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
378pub enum TransactionState {
379 /// Equivalent to `SQLITE_TXN_NONE`
380 None,
381 /// Equivalent to `SQLITE_TXN_READ`
382 Read,
383 /// Equivalent to `SQLITE_TXN_WRITE`
384 Write,
385}
386
387impl Connection {
388 /// Begin a new transaction with the default behavior (DEFERRED).
389 ///
390 /// The transaction defaults to rolling back when it is dropped. If you
391 /// want the transaction to commit, you must call
392 /// [`commit`](Transaction::commit) or
393 /// [`set_drop_behavior(DropBehavior::Commit)`](Transaction::set_drop_behavior).
394 ///
395 /// ## Example
396 ///
397 /// ```rust,no_run
398 /// # use rusqlite::{Connection, Result};
399 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
400 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
401 /// fn perform_queries(conn: &mut Connection) -> Result<()> {
402 /// let tx = conn.transaction()?;
403 ///
404 /// do_queries_part_1(&tx)?; // tx causes rollback if this fails
405 /// do_queries_part_2(&tx)?; // tx causes rollback if this fails
406 ///
407 /// tx.commit()
408 /// }
409 /// ```
410 ///
411 /// # Failure
412 ///
413 /// Will return `Err` if the underlying SQLite call fails.
414 #[inline]
415 pub fn transaction(&mut self) -> Result<Transaction<'_>> {
416 Transaction::new(self, self.transaction_behavior)
417 }
418
419 /// Begin a new transaction with a specified behavior.
420 ///
421 /// See [`transaction`](Connection::transaction).
422 ///
423 /// # Failure
424 ///
425 /// Will return `Err` if the underlying SQLite call fails.
426 #[inline]
427 pub fn transaction_with_behavior(
428 &mut self,
429 behavior: TransactionBehavior,
430 ) -> Result<Transaction<'_>> {
431 Transaction::new(self, behavior)
432 }
433
434 /// Begin a new transaction with the default behavior (DEFERRED).
435 ///
436 /// Attempt to open a nested transaction will result in a SQLite error.
437 /// `Connection::transaction` prevents this at compile time by taking `&mut
438 /// self`, but `Connection::unchecked_transaction()` may be used to defer
439 /// the checking until runtime.
440 ///
441 /// See [`Connection::transaction`] and [`Transaction::new_unchecked`]
442 /// (which can be used if the default transaction behavior is undesirable).
443 ///
444 /// ## Example
445 ///
446 /// ```rust,no_run
447 /// # use rusqlite::{Connection, Result};
448 /// # use std::rc::Rc;
449 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
450 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
451 /// fn perform_queries(conn: Rc<Connection>) -> Result<()> {
452 /// let tx = conn.unchecked_transaction()?;
453 ///
454 /// do_queries_part_1(&tx)?; // tx causes rollback if this fails
455 /// do_queries_part_2(&tx)?; // tx causes rollback if this fails
456 ///
457 /// tx.commit()
458 /// }
459 /// ```
460 ///
461 /// # Failure
462 ///
463 /// Will return `Err` if the underlying SQLite call fails. The specific
464 /// error returned if transactions are nested is currently unspecified.
465 pub fn unchecked_transaction(&self) -> Result<Transaction<'_>> {
466 Transaction::new_unchecked(self, self.transaction_behavior)
467 }
468
469 /// Begin a new savepoint with the default behavior (DEFERRED).
470 ///
471 /// The savepoint defaults to rolling back when it is dropped. If you want
472 /// the savepoint to commit, you must call [`commit`](Savepoint::commit) or
473 /// [`set_drop_behavior(DropBehavior::Commit)`](Savepoint::set_drop_behavior).
474 ///
475 /// ## Example
476 ///
477 /// ```rust,no_run
478 /// # use rusqlite::{Connection, Result};
479 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
480 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
481 /// fn perform_queries(conn: &mut Connection) -> Result<()> {
482 /// let sp = conn.savepoint()?;
483 ///
484 /// do_queries_part_1(&sp)?; // sp causes rollback if this fails
485 /// do_queries_part_2(&sp)?; // sp causes rollback if this fails
486 ///
487 /// sp.commit()
488 /// }
489 /// ```
490 ///
491 /// # Failure
492 ///
493 /// Will return `Err` if the underlying SQLite call fails.
494 #[inline]
495 pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
496 Savepoint::new(self)
497 }
498
499 /// Begin a new savepoint with a specified name.
500 ///
501 /// See [`savepoint`](Connection::savepoint).
502 ///
503 /// # Failure
504 ///
505 /// Will return `Err` if the underlying SQLite call fails.
506 #[inline]
507 pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
508 Savepoint::with_name(self, name)
509 }
510
511 /// Determine the transaction state of a database
512 #[cfg(feature = "modern_sqlite")] // 3.37.0
513 pub fn transaction_state<N: crate::Name>(
514 &self,
515 db_name: Option<N>,
516 ) -> Result<TransactionState> {
517 self.db.borrow().txn_state(db_name)
518 }
519
520 /// Set the default transaction behavior for the connection.
521 ///
522 /// ## Note
523 ///
524 /// This will only apply to transactions initiated by [`transaction`](Connection::transaction)
525 /// or [`unchecked_transaction`](Connection::unchecked_transaction).
526 ///
527 /// ## Example
528 ///
529 /// ```rust,no_run
530 /// # use rusqlite::{Connection, Result, TransactionBehavior};
531 /// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
532 /// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
533 /// fn perform_queries(conn: &mut Connection) -> Result<()> {
534 /// conn.set_transaction_behavior(TransactionBehavior::Immediate);
535 ///
536 /// let tx = conn.transaction()?;
537 ///
538 /// do_queries_part_1(&tx)?; // tx causes rollback if this fails
539 /// do_queries_part_2(&tx)?; // tx causes rollback if this fails
540 ///
541 /// tx.commit()
542 /// }
543 /// ```
544 pub fn set_transaction_behavior(&mut self, behavior: TransactionBehavior) {
545 self.transaction_behavior = behavior;
546 }
547}
548
549#[cfg(test)]
550mod test {
551 #[cfg(all(target_family = "wasm", target_os = "unknown"))]
552 use wasm_bindgen_test::wasm_bindgen_test as test;
553
554 use super::DropBehavior;
555 use crate::{Connection, Error, Result};
556
557 fn checked_memory_handle() -> Result<Connection> {
558 let db = Connection::open_in_memory()?;
559 db.execute_batch("CREATE TABLE foo (x INTEGER)")?;
560 Ok(db)
561 }
562
563 #[test]
564 fn test_drop() -> Result<()> {
565 let mut db = checked_memory_handle()?;
566 {
567 let tx = db.transaction()?;
568 tx.execute_batch("INSERT INTO foo VALUES(1)")?;
569 // default: rollback
570 }
571 {
572 let mut tx = db.transaction()?;
573 tx.execute_batch("INSERT INTO foo VALUES(2)")?;
574 tx.set_drop_behavior(DropBehavior::Commit)
575 }
576 {
577 let tx = db.transaction()?;
578 assert_eq!(2, tx.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
579 }
580 Ok(())
581 }
582 fn assert_nested_tx_error(e: Error) {
583 if let Error::SqliteFailure(e, Some(m)) = &e {
584 assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR);
585 // FIXME: Not ideal...
586 assert_eq!(e.code, crate::ErrorCode::Unknown);
587 assert!(m.contains("transaction"));
588 } else {
589 panic!("Unexpected error type: {e:?}");
590 }
591 }
592
593 #[test]
594 fn test_unchecked_nesting() -> Result<()> {
595 let db = checked_memory_handle()?;
596
597 {
598 let tx = db.unchecked_transaction()?;
599 let e = tx.unchecked_transaction().unwrap_err();
600 assert_nested_tx_error(e);
601 // default: rollback
602 }
603 {
604 let tx = db.unchecked_transaction()?;
605 tx.execute_batch("INSERT INTO foo VALUES(1)")?;
606 // Ensure this doesn't interfere with ongoing transaction
607 let e = tx.unchecked_transaction().unwrap_err();
608 assert_nested_tx_error(e);
609
610 tx.execute_batch("INSERT INTO foo VALUES(1)")?;
611 tx.commit()?;
612 }
613
614 assert_eq!(2, db.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
615 Ok(())
616 }
617
618 #[test]
619 fn test_explicit_rollback_commit() -> Result<()> {
620 let mut db = checked_memory_handle()?;
621 {
622 let mut tx = db.transaction()?;
623 {
624 let mut sp = tx.savepoint()?;
625 sp.execute_batch("INSERT INTO foo VALUES(1)")?;
626 sp.rollback()?;
627 sp.execute_batch("INSERT INTO foo VALUES(2)")?;
628 sp.commit()?;
629 }
630 tx.commit()?;
631 }
632 {
633 let tx = db.transaction()?;
634 tx.execute_batch("INSERT INTO foo VALUES(4)")?;
635 tx.commit()?;
636 }
637 {
638 let tx = db.transaction()?;
639 assert_eq!(6, tx.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
640 }
641 Ok(())
642 }
643
644 #[test]
645 fn test_savepoint() -> Result<()> {
646 let mut db = checked_memory_handle()?;
647 {
648 let mut tx = db.transaction()?;
649 tx.execute_batch("INSERT INTO foo VALUES(1)")?;
650 assert_current_sum(1, &tx)?;
651 tx.set_drop_behavior(DropBehavior::Commit);
652 {
653 let mut sp1 = tx.savepoint()?;
654 sp1.execute_batch("INSERT INTO foo VALUES(2)")?;
655 assert_current_sum(3, &sp1)?;
656 // will roll back sp1
657 {
658 let mut sp2 = sp1.savepoint()?;
659 sp2.execute_batch("INSERT INTO foo VALUES(4)")?;
660 assert_current_sum(7, &sp2)?;
661 // will roll back sp2
662 {
663 let sp3 = sp2.savepoint()?;
664 sp3.execute_batch("INSERT INTO foo VALUES(8)")?;
665 assert_current_sum(15, &sp3)?;
666 sp3.commit()?;
667 // committed sp3, but will be erased by sp2 rollback
668 }
669 assert_current_sum(15, &sp2)?;
670 }
671 assert_current_sum(3, &sp1)?;
672 }
673 assert_current_sum(1, &tx)?;
674 }
675 assert_current_sum(1, &db)?;
676 Ok(())
677 }
678
679 #[test]
680 fn test_ignore_drop_behavior() -> Result<()> {
681 let mut db = checked_memory_handle()?;
682
683 let mut tx = db.transaction()?;
684 {
685 let mut sp1 = tx.savepoint()?;
686 insert(1, &sp1)?;
687 sp1.rollback()?;
688 insert(2, &sp1)?;
689 {
690 let mut sp2 = sp1.savepoint()?;
691 sp2.set_drop_behavior(DropBehavior::Ignore);
692 insert(4, &sp2)?;
693 }
694 assert_current_sum(6, &sp1)?;
695 sp1.commit()?;
696 }
697 assert_current_sum(6, &tx)?;
698 Ok(())
699 }
700
701 #[test]
702 fn test_savepoint_drop_behavior_releases() -> Result<()> {
703 let mut db = checked_memory_handle()?;
704
705 {
706 let mut sp = db.savepoint()?;
707 sp.set_drop_behavior(DropBehavior::Commit);
708 }
709 assert!(db.is_autocommit());
710 {
711 let mut sp = db.savepoint()?;
712 sp.set_drop_behavior(DropBehavior::Rollback);
713 }
714 assert!(db.is_autocommit());
715
716 Ok(())
717 }
718
719 #[test]
720 fn test_savepoint_release_error() -> Result<()> {
721 let mut db = checked_memory_handle()?;
722
723 db.pragma_update(None, "foreign_keys", true)?;
724 db.execute_batch("CREATE TABLE r(n INTEGER PRIMARY KEY NOT NULL); CREATE TABLE f(n REFERENCES r(n) DEFERRABLE INITIALLY DEFERRED);")?;
725 {
726 let mut sp = db.savepoint()?;
727 sp.execute("INSERT INTO f VALUES (0)", [])?;
728 sp.set_drop_behavior(DropBehavior::Commit);
729 }
730 assert!(db.is_autocommit());
731
732 Ok(())
733 }
734
735 #[test]
736 fn test_savepoint_names() -> Result<()> {
737 let mut db = checked_memory_handle()?;
738
739 {
740 let mut sp1 = db.savepoint_with_name("my_sp")?;
741 insert(1, &sp1)?;
742 assert_current_sum(1, &sp1)?;
743 {
744 let mut sp2 = sp1.savepoint_with_name("my_sp")?;
745 sp2.set_drop_behavior(DropBehavior::Commit);
746 insert(2, &sp2)?;
747 assert_current_sum(3, &sp2)?;
748 sp2.rollback()?;
749 assert_current_sum(1, &sp2)?;
750 insert(4, &sp2)?;
751 }
752 assert_current_sum(5, &sp1)?;
753 sp1.rollback()?;
754 {
755 let mut sp2 = sp1.savepoint_with_name("my_sp")?;
756 sp2.set_drop_behavior(DropBehavior::Ignore);
757 insert(8, &sp2)?;
758 }
759 assert_current_sum(8, &sp1)?;
760 sp1.commit()?;
761 }
762 assert_current_sum(8, &db)?;
763 Ok(())
764 }
765
766 #[test]
767 fn test_rc() -> Result<()> {
768 use std::rc::Rc;
769 let mut conn = Connection::open_in_memory()?;
770 let rc_txn = Rc::new(conn.transaction()?);
771
772 // This will compile only if Transaction is Debug
773 Rc::try_unwrap(rc_txn).unwrap();
774 Ok(())
775 }
776
777 fn insert(x: i32, conn: &Connection) -> Result<usize> {
778 conn.execute("INSERT INTO foo VALUES(?1)", [x])
779 }
780
781 fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
782 assert_eq!(x, conn.one_column::<i32, _>("SELECT SUM(x) FROM foo", [])?);
783 Ok(())
784 }
785
786 #[test]
787 #[cfg(feature = "modern_sqlite")]
788 fn txn_state() -> Result<()> {
789 use super::TransactionState;
790 use crate::MAIN_DB;
791 let db = Connection::open_in_memory()?;
792 assert_eq!(TransactionState::None, db.transaction_state(Some(MAIN_DB))?);
793 assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
794 db.execute_batch("BEGIN")?;
795 assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
796 let _: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
797 assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
798 db.pragma_update(None, "user_version", 1)?;
799 assert_eq!(TransactionState::Write, db.transaction_state::<&str>(None)?);
800 db.execute_batch("ROLLBACK")?;
801 Ok(())
802 }
803
804 #[test]
805 #[cfg(feature = "modern_sqlite")]
806 fn auto_commit() -> Result<()> {
807 use super::TransactionState;
808 let db = Connection::open_in_memory()?;
809 db.execute_batch("CREATE TABLE t(i UNIQUE);")?;
810 assert!(db.is_autocommit());
811 let mut stmt = db.prepare("SELECT name FROM sqlite_master")?;
812 assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
813 {
814 let mut rows = stmt.query([])?;
815 assert!(rows.next()?.is_some()); // start reading
816 assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
817 db.execute("INSERT INTO t VALUES (1)", [])?; // auto-commit
818 assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
819 assert!(rows.next()?.is_some()); // still reading
820 assert_eq!(TransactionState::Read, db.transaction_state::<&str>(None)?);
821 assert!(rows.next()?.is_none()); // end
822 assert_eq!(TransactionState::None, db.transaction_state::<&str>(None)?);
823 }
824 Ok(())
825 }
826}