2020-06-27 01:35:14 +08:00
|
|
|
use crate::{Connection, Result};
|
2018-10-31 03:13:41 +08:00
|
|
|
use std::ops::Deref;
|
2014-10-20 07:56:41 +08:00
|
|
|
|
2014-11-04 06:11:00 +08:00
|
|
|
/// Options for transaction behavior. See [BEGIN
|
|
|
|
/// TRANSACTION](http://www.sqlite.org/lang_transaction.html) for details.
|
2018-08-11 18:48:21 +08:00
|
|
|
#[derive(Copy, Clone)]
|
2020-04-07 03:01:39 +08:00
|
|
|
#[non_exhaustive]
|
2015-12-13 03:17:43 +08:00
|
|
|
pub enum TransactionBehavior {
|
2020-05-17 17:21:10 +08:00
|
|
|
/// DEFERRED means that the transaction does not actually start until the
|
|
|
|
/// database is first accessed.
|
2015-12-13 03:22:50 +08:00
|
|
|
Deferred,
|
2020-05-17 17:21:10 +08:00
|
|
|
/// IMMEDIATE cause the database connection to start a new write
|
|
|
|
/// immediately, without waiting for a writes statement.
|
2015-12-13 03:22:50 +08:00
|
|
|
Immediate,
|
2020-05-17 17:21:10 +08:00
|
|
|
/// EXCLUSIVE prevents other database connections from reading the database
|
|
|
|
/// while the transaction is underway.
|
2015-12-13 03:22:50 +08:00
|
|
|
Exclusive,
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
|
2016-05-19 05:38:09 +08:00
|
|
|
/// Options for how a Transaction or Savepoint should behave when it is dropped.
|
2019-01-27 13:29:42 +08:00
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
2020-04-07 03:01:39 +08:00
|
|
|
#[non_exhaustive]
|
2016-05-19 05:38:09 +08:00
|
|
|
pub enum DropBehavior {
|
|
|
|
/// Roll back the changes. This is the default.
|
|
|
|
Rollback,
|
|
|
|
|
|
|
|
/// Commit the changes.
|
|
|
|
Commit,
|
|
|
|
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Do not commit or roll back changes - this will leave the transaction or
|
|
|
|
/// savepoint open, so should be used with care.
|
2016-05-19 05:38:09 +08:00
|
|
|
Ignore,
|
2018-01-12 08:52:32 +08:00
|
|
|
|
|
|
|
/// Panic. Used to enforce intentional behavior during development.
|
|
|
|
Panic,
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
|
|
|
|
2014-11-04 06:11:00 +08:00
|
|
|
/// Represents a transaction on a database connection.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Transactions will roll back by default. Use `commit` method to explicitly
|
|
|
|
/// commit the transaction, or use `set_drop_behavior` to change what happens
|
|
|
|
/// when the transaction is dropped.
|
2014-11-04 06:11:00 +08:00
|
|
|
///
|
|
|
|
/// ## Example
|
|
|
|
///
|
|
|
|
/// ```rust,no_run
|
2015-12-13 03:06:03 +08:00
|
|
|
/// # use rusqlite::{Connection, Result};
|
2016-08-15 18:41:15 +08:00
|
|
|
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
|
|
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
2016-05-20 03:25:39 +08:00
|
|
|
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
2018-10-31 03:11:35 +08:00
|
|
|
/// let tx = conn.transaction()?;
|
2014-11-04 06:11:00 +08:00
|
|
|
///
|
2018-10-31 03:11:35 +08:00
|
|
|
/// do_queries_part_1(&tx)?; // tx causes rollback if this fails
|
|
|
|
/// do_queries_part_2(&tx)?; // tx causes rollback if this fails
|
2014-11-04 06:11:00 +08:00
|
|
|
///
|
|
|
|
/// tx.commit()
|
|
|
|
/// }
|
|
|
|
/// ```
|
2019-01-27 13:29:42 +08:00
|
|
|
#[derive(Debug)]
|
2015-12-13 03:17:43 +08:00
|
|
|
pub struct Transaction<'conn> {
|
2015-12-13 02:50:12 +08:00
|
|
|
conn: &'conn Connection,
|
2016-05-19 05:38:09 +08:00
|
|
|
drop_behavior: DropBehavior,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents a savepoint on a database connection.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Savepoints will roll back by default. Use `commit` method to explicitly
|
|
|
|
/// commit the savepoint, or use `set_drop_behavior` to change what happens
|
|
|
|
/// when the savepoint is dropped.
|
2016-05-19 05:38:09 +08:00
|
|
|
///
|
|
|
|
/// ## Example
|
|
|
|
///
|
|
|
|
/// ```rust,no_run
|
|
|
|
/// # use rusqlite::{Connection, Result};
|
2016-08-15 18:41:15 +08:00
|
|
|
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
|
|
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
2016-05-20 03:25:39 +08:00
|
|
|
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
2018-10-31 03:11:35 +08:00
|
|
|
/// let sp = conn.savepoint()?;
|
2016-05-19 05:38:09 +08:00
|
|
|
///
|
2018-10-31 03:11:35 +08:00
|
|
|
/// do_queries_part_1(&sp)?; // sp causes rollback if this fails
|
|
|
|
/// do_queries_part_2(&sp)?; // sp causes rollback if this fails
|
2016-05-19 05:38:09 +08:00
|
|
|
///
|
|
|
|
/// sp.commit()
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
pub struct Savepoint<'conn> {
|
|
|
|
conn: &'conn Connection,
|
|
|
|
name: String,
|
2014-10-20 07:56:41 +08:00
|
|
|
depth: u32,
|
2016-05-19 05:38:09 +08:00
|
|
|
drop_behavior: DropBehavior,
|
|
|
|
committed: bool,
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
|
2019-02-03 18:02:38 +08:00
|
|
|
impl Transaction<'_> {
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested
|
|
|
|
/// transactions.
|
2020-04-10 03:24:29 +08:00
|
|
|
///
|
2018-09-06 00:08:39 +08:00
|
|
|
/// Even though we don't mutate the connection, we take a `&mut Connection`
|
2020-04-10 03:24:29 +08:00
|
|
|
/// so as to prevent nested transactions on the same connection. For cases
|
|
|
|
/// where this is unacceptable, [`Transaction::new_unchecked`] is available.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-12-08 04:57:04 +08:00
|
|
|
pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction<'_>> {
|
2020-04-10 03:24:29 +08:00
|
|
|
Self::new_unchecked(conn, behavior)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Begin a new transaction, failing if a transaction is open.
|
|
|
|
///
|
|
|
|
/// If a transaction is already open, this will return an error. Where
|
|
|
|
/// possible, [`Transaction::new`] should be preferred, as it provides a
|
|
|
|
/// compile-time guarantee that transactions are not nested.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2020-04-10 03:24:29 +08:00
|
|
|
pub fn new_unchecked(
|
|
|
|
conn: &Connection,
|
|
|
|
behavior: TransactionBehavior,
|
|
|
|
) -> Result<Transaction<'_>> {
|
2014-10-24 05:57:02 +08:00
|
|
|
let query = match behavior {
|
2015-12-13 03:22:50 +08:00
|
|
|
TransactionBehavior::Deferred => "BEGIN DEFERRED",
|
|
|
|
TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
|
|
|
|
TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
|
2014-10-20 07:56:41 +08:00
|
|
|
};
|
2020-06-27 01:35:14 +08:00
|
|
|
conn.execute_batch(query).map(move |_| Transaction {
|
2018-08-11 18:48:21 +08:00
|
|
|
conn,
|
|
|
|
drop_behavior: DropBehavior::Rollback,
|
|
|
|
})
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
|
2014-11-04 06:11:00 +08:00
|
|
|
/// Starts a new [savepoint](http://www.sqlite.org/lang_savepoint.html), allowing nested
|
|
|
|
/// transactions.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Just like outer level transactions, savepoint transactions rollback by
|
|
|
|
/// default.
|
2014-11-04 06:11:00 +08:00
|
|
|
///
|
|
|
|
/// ## Example
|
|
|
|
///
|
|
|
|
/// ```rust,no_run
|
2015-12-13 03:06:03 +08:00
|
|
|
/// # use rusqlite::{Connection, Result};
|
2016-08-15 18:41:15 +08:00
|
|
|
/// # fn perform_queries_part_1_succeeds(_conn: &Connection) -> bool { true }
|
2016-05-20 03:25:39 +08:00
|
|
|
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
2018-10-31 03:11:35 +08:00
|
|
|
/// let mut tx = conn.transaction()?;
|
2014-11-04 06:11:00 +08:00
|
|
|
///
|
|
|
|
/// {
|
2018-10-31 03:11:35 +08:00
|
|
|
/// let sp = tx.savepoint()?;
|
2016-05-20 03:25:39 +08:00
|
|
|
/// if perform_queries_part_1_succeeds(&sp) {
|
2018-10-31 03:11:35 +08:00
|
|
|
/// sp.commit()?;
|
2014-11-04 06:11:00 +08:00
|
|
|
/// }
|
|
|
|
/// // otherwise, sp will rollback
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// tx.commit()
|
|
|
|
/// }
|
|
|
|
/// ```
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-12-08 04:57:04 +08:00
|
|
|
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
|
2016-05-19 05:38:09 +08:00
|
|
|
Savepoint::with_depth(self.conn, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a new savepoint with a custom savepoint name. See `savepoint()`.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-12-08 04:57:04 +08:00
|
|
|
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
2016-05-19 05:38:09 +08:00
|
|
|
Savepoint::with_depth_and_name(self.conn, 1, name)
|
|
|
|
}
|
|
|
|
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Get the current setting for what happens to the transaction when it is
|
|
|
|
/// dropped.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 05:38:09 +08:00
|
|
|
pub fn drop_behavior(&self) -> DropBehavior {
|
|
|
|
self.drop_behavior
|
|
|
|
}
|
|
|
|
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Configure the transaction to perform the specified action when it is
|
|
|
|
/// dropped.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 05:38:09 +08:00
|
|
|
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
|
|
|
|
self.drop_behavior = drop_behavior
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A convenience method which consumes and commits a transaction.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 05:38:09 +08:00
|
|
|
pub fn commit(mut self) -> Result<()> {
|
|
|
|
self.commit_()
|
|
|
|
}
|
|
|
|
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 05:38:09 +08:00
|
|
|
fn commit_(&mut self) -> Result<()> {
|
2020-06-27 01:35:14 +08:00
|
|
|
self.conn.execute_batch("COMMIT")?;
|
2018-07-28 00:13:11 +08:00
|
|
|
Ok(())
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A convenience method which consumes and rolls back a transaction.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 05:38:09 +08:00
|
|
|
pub fn rollback(mut self) -> Result<()> {
|
|
|
|
self.rollback_()
|
|
|
|
}
|
|
|
|
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 05:38:09 +08:00
|
|
|
fn rollback_(&mut self) -> Result<()> {
|
2020-06-27 01:35:14 +08:00
|
|
|
self.conn.execute_batch("ROLLBACK")?;
|
2018-07-28 00:13:11 +08:00
|
|
|
Ok(())
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
|
|
|
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Consumes the transaction, committing or rolling back according to the
|
|
|
|
/// current setting (see `drop_behavior`).
|
2016-05-19 05:38:09 +08:00
|
|
|
///
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Functionally equivalent to the `Drop` implementation, but allows
|
|
|
|
/// callers to see any errors that occur.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 05:38:09 +08:00
|
|
|
pub fn finish(mut self) -> Result<()> {
|
|
|
|
self.finish_()
|
|
|
|
}
|
|
|
|
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 05:38:09 +08:00
|
|
|
fn finish_(&mut self) -> Result<()> {
|
2018-07-28 22:04:42 +08:00
|
|
|
if self.conn.is_autocommit() {
|
2016-05-19 05:38:09 +08:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
match self.drop_behavior() {
|
2018-07-28 00:17:30 +08:00
|
|
|
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback_()),
|
2016-05-19 05:38:09 +08:00
|
|
|
DropBehavior::Rollback => self.rollback_(),
|
|
|
|
DropBehavior::Ignore => Ok(()),
|
2018-01-12 08:52:32 +08:00
|
|
|
DropBehavior::Panic => panic!("Transaction dropped unexpectedly."),
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-03 18:02:38 +08:00
|
|
|
impl Deref for Transaction<'_> {
|
2016-05-19 05:38:09 +08:00
|
|
|
type Target = Connection;
|
|
|
|
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 05:38:09 +08:00
|
|
|
fn deref(&self) -> &Connection {
|
|
|
|
self.conn
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(unused_must_use)]
|
2019-02-03 18:02:38 +08:00
|
|
|
impl Drop for Transaction<'_> {
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 05:38:09 +08:00
|
|
|
fn drop(&mut self) {
|
|
|
|
self.finish_();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-03 18:02:38 +08:00
|
|
|
impl Savepoint<'_> {
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-08-11 18:48:21 +08:00
|
|
|
fn with_depth_and_name<T: Into<String>>(
|
|
|
|
conn: &Connection,
|
|
|
|
depth: u32,
|
|
|
|
name: T,
|
2018-12-08 04:57:04 +08:00
|
|
|
) -> Result<Savepoint<'_>> {
|
2016-05-19 05:38:09 +08:00
|
|
|
let name = name.into();
|
2020-06-27 01:35:14 +08:00
|
|
|
conn.execute_batch(&format!("SAVEPOINT {}", name))
|
2018-08-11 18:48:21 +08:00
|
|
|
.map(|_| Savepoint {
|
|
|
|
conn,
|
|
|
|
name,
|
|
|
|
depth,
|
|
|
|
drop_behavior: DropBehavior::Rollback,
|
|
|
|
committed: false,
|
2017-04-08 01:43:24 +08:00
|
|
|
})
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-12-08 04:57:04 +08:00
|
|
|
fn with_depth(conn: &Connection, depth: u32) -> Result<Savepoint<'_>> {
|
2016-05-19 05:38:09 +08:00
|
|
|
let name = format!("_rusqlite_sp_{}", depth);
|
|
|
|
Savepoint::with_depth_and_name(conn, depth, name)
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
|
2016-05-19 05:38:09 +08:00
|
|
|
/// Begin a new savepoint. Can be nested.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-12-08 04:57:04 +08:00
|
|
|
pub fn new(conn: &mut Connection) -> Result<Savepoint<'_>> {
|
2016-05-19 05:38:09 +08:00
|
|
|
Savepoint::with_depth(conn, 0)
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
|
2016-05-19 05:38:09 +08:00
|
|
|
/// Begin a new savepoint with a user-provided savepoint name.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-12-08 04:57:04 +08:00
|
|
|
pub fn with_name<T: Into<String>>(conn: &mut Connection, name: T) -> Result<Savepoint<'_>> {
|
2016-05-19 05:38:09 +08:00
|
|
|
Savepoint::with_depth_and_name(conn, 0, name)
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
|
2016-05-19 05:38:09 +08:00
|
|
|
/// Begin a nested savepoint.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-12-08 04:57:04 +08:00
|
|
|
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
|
2016-05-19 05:38:09 +08:00
|
|
|
Savepoint::with_depth(self.conn, self.depth + 1)
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
|
2016-05-19 05:38:09 +08:00
|
|
|
/// Begin a nested savepoint with a user-provided savepoint name.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-12-08 04:57:04 +08:00
|
|
|
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
2016-05-19 05:38:09 +08:00
|
|
|
Savepoint::with_depth_and_name(self.conn, self.depth + 1, name)
|
|
|
|
}
|
|
|
|
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Get the current setting for what happens to the savepoint when it is
|
|
|
|
/// dropped.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 05:38:09 +08:00
|
|
|
pub fn drop_behavior(&self) -> DropBehavior {
|
|
|
|
self.drop_behavior
|
|
|
|
}
|
|
|
|
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Configure the savepoint to perform the specified action when it is
|
|
|
|
/// dropped.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 05:38:09 +08:00
|
|
|
pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) {
|
|
|
|
self.drop_behavior = drop_behavior
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A convenience method which consumes and commits a savepoint.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2015-12-13 03:06:03 +08:00
|
|
|
pub fn commit(mut self) -> Result<()> {
|
2014-10-20 07:56:41 +08:00
|
|
|
self.commit_()
|
|
|
|
}
|
|
|
|
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2015-12-13 03:06:03 +08:00
|
|
|
fn commit_(&mut self) -> Result<()> {
|
2020-06-27 01:35:14 +08:00
|
|
|
self.conn.execute_batch(&format!("RELEASE {}", self.name))?;
|
2018-07-28 00:13:11 +08:00
|
|
|
self.committed = true;
|
|
|
|
Ok(())
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
|
2016-05-19 05:38:09 +08:00
|
|
|
/// A convenience method which rolls back a savepoint.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Unlike `Transaction`s, savepoints remain active after they have been
|
|
|
|
/// rolled back, and can be rolled back again or committed.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-19 03:25:57 +08:00
|
|
|
pub fn rollback(&mut self) -> Result<()> {
|
2017-04-08 01:43:24 +08:00
|
|
|
self.conn
|
2020-06-27 01:35:14 +08:00
|
|
|
.execute_batch(&format!("ROLLBACK TO {}", self.name))
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Consumes the savepoint, committing or rolling back according to the
|
|
|
|
/// current setting (see `drop_behavior`).
|
2014-11-04 06:11:00 +08:00
|
|
|
///
|
2018-08-17 00:29:46 +08:00
|
|
|
/// Functionally equivalent to the `Drop` implementation, but allows
|
|
|
|
/// callers to see any errors that occur.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2015-12-13 03:06:03 +08:00
|
|
|
pub fn finish(mut self) -> Result<()> {
|
2014-10-20 07:56:41 +08:00
|
|
|
self.finish_()
|
|
|
|
}
|
|
|
|
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2015-12-13 03:06:03 +08:00
|
|
|
fn finish_(&mut self) -> Result<()> {
|
2016-05-19 05:38:09 +08:00
|
|
|
if self.committed {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
match self.drop_behavior() {
|
2018-07-28 00:17:30 +08:00
|
|
|
DropBehavior::Commit => self.commit_().or_else(|_| self.rollback()),
|
2016-05-19 05:38:09 +08:00
|
|
|
DropBehavior::Rollback => self.rollback(),
|
|
|
|
DropBehavior::Ignore => Ok(()),
|
2018-01-12 08:52:32 +08:00
|
|
|
DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-03 18:02:38 +08:00
|
|
|
impl Deref for Savepoint<'_> {
|
2016-05-18 08:53:53 +08:00
|
|
|
type Target = Connection;
|
|
|
|
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2016-05-18 08:53:53 +08:00
|
|
|
fn deref(&self) -> &Connection {
|
|
|
|
self.conn
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-20 07:56:41 +08:00
|
|
|
#[allow(unused_must_use)]
|
2019-02-03 18:02:38 +08:00
|
|
|
impl Drop for Savepoint<'_> {
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2014-10-20 07:56:41 +08:00
|
|
|
fn drop(&mut self) {
|
|
|
|
self.finish_();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-19 05:38:09 +08:00
|
|
|
impl Connection {
|
|
|
|
/// Begin a new transaction with the default behavior (DEFERRED).
|
|
|
|
///
|
2018-08-17 00:29:46 +08:00
|
|
|
/// The transaction defaults to rolling back when it is dropped. If you
|
|
|
|
/// want the transaction to commit, you must call `commit` or
|
|
|
|
/// `set_drop_behavior(DropBehavior::Commit)`.
|
2016-05-19 05:38:09 +08:00
|
|
|
///
|
|
|
|
/// ## Example
|
|
|
|
///
|
|
|
|
/// ```rust,no_run
|
|
|
|
/// # use rusqlite::{Connection, Result};
|
2016-08-15 18:41:15 +08:00
|
|
|
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
|
|
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
2016-05-20 03:25:39 +08:00
|
|
|
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
2018-10-31 03:11:35 +08:00
|
|
|
/// let tx = conn.transaction()?;
|
2016-05-19 05:38:09 +08:00
|
|
|
///
|
2018-10-31 03:11:35 +08:00
|
|
|
/// do_queries_part_1(&tx)?; // tx causes rollback if this fails
|
|
|
|
/// do_queries_part_2(&tx)?; // tx causes rollback if this fails
|
2016-05-19 05:38:09 +08:00
|
|
|
///
|
|
|
|
/// tx.commit()
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// # Failure
|
|
|
|
///
|
|
|
|
/// Will return `Err` if the underlying SQLite call fails.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-12-08 04:57:04 +08:00
|
|
|
pub fn transaction(&mut self) -> Result<Transaction<'_>> {
|
2016-05-19 05:38:09 +08:00
|
|
|
Transaction::new(self, TransactionBehavior::Deferred)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Begin a new transaction with a specified behavior.
|
|
|
|
///
|
|
|
|
/// See `transaction`.
|
|
|
|
///
|
|
|
|
/// # Failure
|
|
|
|
///
|
|
|
|
/// Will return `Err` if the underlying SQLite call fails.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-08-11 18:48:21 +08:00
|
|
|
pub fn transaction_with_behavior(
|
|
|
|
&mut self,
|
|
|
|
behavior: TransactionBehavior,
|
2018-12-08 04:57:04 +08:00
|
|
|
) -> Result<Transaction<'_>> {
|
2016-05-19 05:38:09 +08:00
|
|
|
Transaction::new(self, behavior)
|
|
|
|
}
|
|
|
|
|
2020-04-10 03:24:29 +08:00
|
|
|
/// Begin a new transaction with the default behavior (DEFERRED).
|
|
|
|
///
|
|
|
|
/// Attempt to open a nested transaction will result in a SQLite error.
|
|
|
|
/// `Connection::transaction` prevents this at compile time by taking `&mut
|
|
|
|
/// self`, but `Connection::unchecked_transaction()` may be used to defer
|
|
|
|
/// the checking until runtime.
|
|
|
|
///
|
|
|
|
/// See [`Connection::transaction`] and [`Transaction::new_unchecked`]
|
|
|
|
/// (which can be used if the default transaction behavior is undesirable).
|
|
|
|
///
|
|
|
|
/// ## Example
|
|
|
|
///
|
|
|
|
/// ```rust,no_run
|
|
|
|
/// # use rusqlite::{Connection, Result};
|
|
|
|
/// # use std::rc::Rc;
|
|
|
|
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
|
|
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
|
|
/// fn perform_queries(conn: Rc<Connection>) -> Result<()> {
|
|
|
|
/// let tx = conn.unchecked_transaction()?;
|
|
|
|
///
|
|
|
|
/// do_queries_part_1(&tx)?; // tx causes rollback if this fails
|
|
|
|
/// do_queries_part_2(&tx)?; // tx causes rollback if this fails
|
|
|
|
///
|
|
|
|
/// tx.commit()
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// # Failure
|
|
|
|
///
|
|
|
|
/// Will return `Err` if the underlying SQLite call fails. The specific
|
|
|
|
/// error returned if transactions are nested is currently unspecified.
|
|
|
|
pub fn unchecked_transaction(&self) -> Result<Transaction<'_>> {
|
|
|
|
Transaction::new_unchecked(self, TransactionBehavior::Deferred)
|
|
|
|
}
|
|
|
|
|
2016-05-19 05:38:09 +08:00
|
|
|
/// Begin a new savepoint with the default behavior (DEFERRED).
|
|
|
|
///
|
2018-08-17 00:29:46 +08:00
|
|
|
/// The savepoint defaults to rolling back when it is dropped. If you want
|
|
|
|
/// the savepoint to commit, you must call `commit` or
|
|
|
|
/// `set_drop_behavior(DropBehavior::Commit)`.
|
2016-05-19 05:38:09 +08:00
|
|
|
///
|
|
|
|
/// ## Example
|
|
|
|
///
|
|
|
|
/// ```rust,no_run
|
|
|
|
/// # use rusqlite::{Connection, Result};
|
2016-08-15 18:41:15 +08:00
|
|
|
/// # fn do_queries_part_1(_conn: &Connection) -> Result<()> { Ok(()) }
|
|
|
|
/// # fn do_queries_part_2(_conn: &Connection) -> Result<()> { Ok(()) }
|
2016-05-20 03:25:39 +08:00
|
|
|
/// fn perform_queries(conn: &mut Connection) -> Result<()> {
|
2018-10-31 03:11:35 +08:00
|
|
|
/// let sp = conn.savepoint()?;
|
2016-05-19 05:38:09 +08:00
|
|
|
///
|
2018-10-31 03:11:35 +08:00
|
|
|
/// do_queries_part_1(&sp)?; // sp causes rollback if this fails
|
|
|
|
/// do_queries_part_2(&sp)?; // sp causes rollback if this fails
|
2016-05-19 05:38:09 +08:00
|
|
|
///
|
|
|
|
/// sp.commit()
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// # Failure
|
|
|
|
///
|
|
|
|
/// Will return `Err` if the underlying SQLite call fails.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-12-08 04:57:04 +08:00
|
|
|
pub fn savepoint(&mut self) -> Result<Savepoint<'_>> {
|
2016-05-19 05:38:09 +08:00
|
|
|
Savepoint::new(self)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Begin a new savepoint with a specified name.
|
|
|
|
///
|
|
|
|
/// See `savepoint`.
|
|
|
|
///
|
|
|
|
/// # Failure
|
|
|
|
///
|
|
|
|
/// Will return `Err` if the underlying SQLite call fails.
|
2020-11-04 11:10:23 +08:00
|
|
|
#[inline]
|
2018-12-08 04:57:04 +08:00
|
|
|
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
2016-05-19 05:38:09 +08:00
|
|
|
Savepoint::with_name(self, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-20 07:56:41 +08:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2016-05-19 05:38:09 +08:00
|
|
|
use super::DropBehavior;
|
2020-11-06 05:14:00 +08:00
|
|
|
use crate::{Connection, Error, Result};
|
2014-10-20 07:56:41 +08:00
|
|
|
|
2020-11-06 05:14:00 +08:00
|
|
|
fn checked_memory_handle() -> Result<Connection> {
|
|
|
|
let db = Connection::open_in_memory()?;
|
|
|
|
db.execute_batch("CREATE TABLE foo (x INTEGER)")?;
|
|
|
|
Ok(db)
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-11-06 05:14:00 +08:00
|
|
|
fn test_drop() -> Result<()> {
|
|
|
|
let mut db = checked_memory_handle()?;
|
2014-10-20 07:56:41 +08:00
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let tx = db.transaction()?;
|
|
|
|
tx.execute_batch("INSERT INTO foo VALUES(1)")?;
|
2014-10-20 07:56:41 +08:00
|
|
|
// default: rollback
|
|
|
|
}
|
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut tx = db.transaction()?;
|
|
|
|
tx.execute_batch("INSERT INTO foo VALUES(2)")?;
|
2016-05-19 05:38:09 +08:00
|
|
|
tx.set_drop_behavior(DropBehavior::Commit)
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let tx = db.transaction()?;
|
2018-08-11 18:48:21 +08:00
|
|
|
assert_eq!(
|
|
|
|
2i32,
|
2020-11-06 05:14:00 +08:00
|
|
|
tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
|
2018-08-11 18:48:21 +08:00
|
|
|
);
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
2020-11-06 05:14:00 +08:00
|
|
|
Ok(())
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
2020-04-10 03:24:29 +08:00
|
|
|
fn assert_nested_tx_error(e: crate::Error) {
|
|
|
|
if let Error::SqliteFailure(e, Some(m)) = &e {
|
|
|
|
assert_eq!(e.extended_code, crate::ffi::SQLITE_ERROR);
|
|
|
|
// FIXME: Not ideal...
|
|
|
|
assert_eq!(e.code, crate::ErrorCode::Unknown);
|
|
|
|
assert!(m.contains("transaction"));
|
|
|
|
} else {
|
|
|
|
panic!("Unexpected error type: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-11-06 05:14:00 +08:00
|
|
|
fn test_unchecked_nesting() -> Result<()> {
|
|
|
|
let db = checked_memory_handle()?;
|
2020-04-10 03:24:29 +08:00
|
|
|
|
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let tx = db.unchecked_transaction()?;
|
2020-04-10 03:24:29 +08:00
|
|
|
let e = tx.unchecked_transaction().unwrap_err();
|
|
|
|
assert_nested_tx_error(e);
|
|
|
|
// default: rollback
|
|
|
|
}
|
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let tx = db.unchecked_transaction()?;
|
|
|
|
tx.execute_batch("INSERT INTO foo VALUES(1)")?;
|
2020-04-10 03:24:29 +08:00
|
|
|
// Ensure this doesn't interfere with ongoing transaction
|
|
|
|
let e = tx.unchecked_transaction().unwrap_err();
|
|
|
|
assert_nested_tx_error(e);
|
|
|
|
|
2020-11-06 05:14:00 +08:00
|
|
|
tx.execute_batch("INSERT INTO foo VALUES(1)")?;
|
|
|
|
tx.commit()?;
|
2020-04-10 03:24:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
2i32,
|
2020-11-06 05:14:00 +08:00
|
|
|
db.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
|
2020-04-10 03:24:29 +08:00
|
|
|
);
|
2020-11-06 05:14:00 +08:00
|
|
|
Ok(())
|
2020-04-10 03:24:29 +08:00
|
|
|
}
|
2014-10-20 07:56:41 +08:00
|
|
|
|
|
|
|
#[test]
|
2020-11-06 05:14:00 +08:00
|
|
|
fn test_explicit_rollback_commit() -> Result<()> {
|
|
|
|
let mut db = checked_memory_handle()?;
|
2014-10-20 07:56:41 +08:00
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut tx = db.transaction()?;
|
2016-05-19 03:25:57 +08:00
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut sp = tx.savepoint()?;
|
|
|
|
sp.execute_batch("INSERT INTO foo VALUES(1)")?;
|
|
|
|
sp.rollback()?;
|
|
|
|
sp.execute_batch("INSERT INTO foo VALUES(2)")?;
|
|
|
|
sp.commit()?;
|
2016-05-19 03:25:57 +08:00
|
|
|
}
|
2020-11-06 05:14:00 +08:00
|
|
|
tx.commit()?;
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let tx = db.transaction()?;
|
|
|
|
tx.execute_batch("INSERT INTO foo VALUES(4)")?;
|
|
|
|
tx.commit()?;
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let tx = db.transaction()?;
|
2018-08-11 18:48:21 +08:00
|
|
|
assert_eq!(
|
|
|
|
6i32,
|
2020-11-06 05:14:00 +08:00
|
|
|
tx.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?
|
2018-08-11 18:48:21 +08:00
|
|
|
);
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
2020-11-06 05:14:00 +08:00
|
|
|
Ok(())
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-11-06 05:14:00 +08:00
|
|
|
fn test_savepoint() -> Result<()> {
|
|
|
|
let mut db = checked_memory_handle()?;
|
2014-10-20 07:56:41 +08:00
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut tx = db.transaction()?;
|
|
|
|
tx.execute_batch("INSERT INTO foo VALUES(1)")?;
|
|
|
|
assert_current_sum(1, &tx)?;
|
2016-05-19 05:38:09 +08:00
|
|
|
tx.set_drop_behavior(DropBehavior::Commit);
|
2014-10-20 07:56:41 +08:00
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut sp1 = tx.savepoint()?;
|
|
|
|
sp1.execute_batch("INSERT INTO foo VALUES(2)")?;
|
|
|
|
assert_current_sum(3, &sp1)?;
|
2016-05-18 09:19:44 +08:00
|
|
|
// will rollback sp1
|
2014-10-20 07:56:41 +08:00
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut sp2 = sp1.savepoint()?;
|
|
|
|
sp2.execute_batch("INSERT INTO foo VALUES(4)")?;
|
|
|
|
assert_current_sum(7, &sp2)?;
|
2014-10-20 07:56:41 +08:00
|
|
|
// will rollback sp2
|
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let sp3 = sp2.savepoint()?;
|
|
|
|
sp3.execute_batch("INSERT INTO foo VALUES(8)")?;
|
|
|
|
assert_current_sum(15, &sp3)?;
|
|
|
|
sp3.commit()?;
|
2014-10-20 07:56:41 +08:00
|
|
|
// committed sp3, but will be erased by sp2 rollback
|
|
|
|
}
|
2020-11-06 05:14:00 +08:00
|
|
|
assert_current_sum(15, &sp2)?;
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
2020-11-06 05:14:00 +08:00
|
|
|
assert_current_sum(3, &sp1)?;
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
2020-11-06 05:14:00 +08:00
|
|
|
assert_current_sum(1, &tx)?;
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
2020-11-06 05:14:00 +08:00
|
|
|
assert_current_sum(1, &db)?;
|
|
|
|
Ok(())
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|
2016-05-19 05:38:09 +08:00
|
|
|
|
|
|
|
#[test]
|
2020-11-06 05:14:00 +08:00
|
|
|
fn test_ignore_drop_behavior() -> Result<()> {
|
|
|
|
let mut db = checked_memory_handle()?;
|
2016-05-19 05:38:09 +08:00
|
|
|
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut tx = db.transaction()?;
|
2016-05-19 05:38:09 +08:00
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut sp1 = tx.savepoint()?;
|
|
|
|
insert(1, &sp1)?;
|
|
|
|
sp1.rollback()?;
|
|
|
|
insert(2, &sp1)?;
|
2016-05-19 05:38:09 +08:00
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut sp2 = sp1.savepoint()?;
|
2016-05-19 05:38:09 +08:00
|
|
|
sp2.set_drop_behavior(DropBehavior::Ignore);
|
2020-11-06 05:14:00 +08:00
|
|
|
insert(4, &sp2)?;
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
2020-11-06 05:14:00 +08:00
|
|
|
assert_current_sum(6, &sp1)?;
|
|
|
|
sp1.commit()?;
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
2020-11-06 05:14:00 +08:00
|
|
|
assert_current_sum(6, &tx)?;
|
|
|
|
Ok(())
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-11-06 05:14:00 +08:00
|
|
|
fn test_savepoint_names() -> Result<()> {
|
|
|
|
let mut db = checked_memory_handle()?;
|
2016-05-19 05:38:09 +08:00
|
|
|
|
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut sp1 = db.savepoint_with_name("my_sp")?;
|
|
|
|
insert(1, &sp1)?;
|
|
|
|
assert_current_sum(1, &sp1)?;
|
2016-05-19 05:38:09 +08:00
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut sp2 = sp1.savepoint_with_name("my_sp")?;
|
2016-05-19 05:38:09 +08:00
|
|
|
sp2.set_drop_behavior(DropBehavior::Commit);
|
2020-11-06 05:14:00 +08:00
|
|
|
insert(2, &sp2)?;
|
|
|
|
assert_current_sum(3, &sp2)?;
|
|
|
|
sp2.rollback()?;
|
|
|
|
assert_current_sum(1, &sp2)?;
|
|
|
|
insert(4, &sp2)?;
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
2020-11-06 05:14:00 +08:00
|
|
|
assert_current_sum(5, &sp1)?;
|
|
|
|
sp1.rollback()?;
|
2016-05-19 05:38:09 +08:00
|
|
|
{
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut sp2 = sp1.savepoint_with_name("my_sp")?;
|
2016-05-19 05:38:09 +08:00
|
|
|
sp2.set_drop_behavior(DropBehavior::Ignore);
|
2020-11-06 05:14:00 +08:00
|
|
|
insert(8, &sp2)?;
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
2020-11-06 05:14:00 +08:00
|
|
|
assert_current_sum(8, &sp1)?;
|
|
|
|
sp1.commit()?;
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
2020-11-06 05:14:00 +08:00
|
|
|
assert_current_sum(8, &db)?;
|
|
|
|
Ok(())
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
|
|
|
|
2019-01-30 03:01:09 +08:00
|
|
|
#[test]
|
2020-11-06 05:14:00 +08:00
|
|
|
fn test_rc() -> Result<()> {
|
2019-01-30 03:01:09 +08:00
|
|
|
use std::rc::Rc;
|
2020-11-06 05:14:00 +08:00
|
|
|
let mut conn = Connection::open_in_memory()?;
|
|
|
|
let rc_txn = Rc::new(conn.transaction()?);
|
2019-01-30 03:01:09 +08:00
|
|
|
|
|
|
|
// This will compile only if Transaction is Debug
|
|
|
|
Rc::try_unwrap(rc_txn).unwrap();
|
2020-11-06 05:14:00 +08:00
|
|
|
Ok(())
|
2019-01-30 03:01:09 +08:00
|
|
|
}
|
|
|
|
|
2020-11-06 05:14:00 +08:00
|
|
|
fn insert(x: i32, conn: &Connection) -> Result<usize> {
|
|
|
|
conn.execute("INSERT INTO foo VALUES(?)", [x])
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
|
|
|
|
2020-11-06 05:14:00 +08:00
|
|
|
fn assert_current_sum(x: i32, conn: &Connection) -> Result<()> {
|
|
|
|
let i = conn.query_row::<i32, _, _>("SELECT SUM(x) FROM foo", [], |r| r.get(0))?;
|
2016-05-19 05:38:09 +08:00
|
|
|
assert_eq!(x, i);
|
2020-11-06 05:14:00 +08:00
|
|
|
Ok(())
|
2016-05-19 05:38:09 +08:00
|
|
|
}
|
2014-10-20 07:56:41 +08:00
|
|
|
}
|