Skip to content

Commit 622fa21

Browse files
committed
Expose the Sqlite immediate_transaction and exclusive_transaction functions from the sync connection wrapper
1 parent 6437e59 commit 622fa21

File tree

3 files changed

+170
-3
lines changed

3 files changed

+170
-3
lines changed

src/doctest_setup.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,6 @@ cfg_if::cfg_if! {
213213
accent VARCHAR(255) DEFAULT 'Blue'
214214
)").execute(connection).await.unwrap();
215215

216-
connection.begin_test_transaction().await.unwrap();
217216
diesel::sql_query("INSERT INTO users (name) VALUES ('Sean'), ('Tess')").execute(connection).await.unwrap();
218217
diesel::sql_query("INSERT INTO posts (user_id, title) VALUES
219218
(1, 'My first post'),
@@ -231,12 +230,22 @@ cfg_if::cfg_if! {
231230

232231
#[allow(dead_code)]
233232
async fn establish_connection() -> SyncConnectionWrapper<SqliteConnection> {
233+
use diesel_async::AsyncConnection;
234+
234235
let mut connection = connection_no_data().await;
236+
connection.begin_test_transaction().await.unwrap();
235237
create_tables(&mut connection).await;
238+
connection
239+
}
236240

241+
async fn connection_no_transaction() -> SyncConnectionWrapper<SqliteConnection> {
242+
use diesel_async::AsyncConnection;
237243

244+
let mut connection = SyncConnectionWrapper::<SqliteConnection>::establish(":memory:").await.unwrap();
245+
create_tables(&mut connection).await;
238246
connection
239247
}
248+
240249
} else {
241250
compile_error!(
242251
"At least one backend must be used to test this crate.\n \

src/sync_connection_wrapper.rs renamed to src/sync_connection_wrapper/mod.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ use std::marker::PhantomData;
2525
use std::sync::{Arc, Mutex};
2626
use tokio::task::JoinError;
2727

28+
#[cfg(feature = "sqlite")]
29+
mod sqlite;
30+
2831
fn from_tokio_join_error(join_error: JoinError) -> diesel::result::Error {
2932
diesel::result::Error::DatabaseError(
3033
diesel::result::DatabaseErrorKind::UnableToSendCommand,
@@ -48,7 +51,7 @@ fn from_tokio_join_error(join_error: JoinError) -> diesel::result::Error {
4851
/// # Examples
4952
///
5053
/// ```rust
51-
/// # include!("doctest_setup.rs");
54+
/// # include!("../doctest_setup.rs");
5255
/// use diesel_async::RunQueryDsl;
5356
/// use schema::users;
5457
///
@@ -232,7 +235,33 @@ impl<C> SyncConnectionWrapper<C> {
232235
}
233236
}
234237

235-
pub(self) fn spawn_blocking<'a, R>(
238+
/// Run a operation directly with the inner connection
239+
///
240+
/// This function is usful to register custom functions
241+
/// and collection for Sqlite for example
242+
///
243+
/// # Example
244+
///
245+
/// ```rust
246+
/// # include!("../doctest_setup.rs");
247+
/// # #[tokio::main]
248+
/// # async fn main() {
249+
/// # run_test().await.unwrap();
250+
/// # }
251+
/// #
252+
/// # async fn run_test() -> QueryResult<()> {
253+
/// # let mut conn = establish_connection().await;
254+
/// conn.spawn_blocking(|conn| {
255+
/// // sqlite.rs sqlite NOCASE only works for ASCII characters,
256+
/// // this collation allows handling UTF-8 (barring locale differences)
257+
/// conn.register_collation("RUSTNOCASE", |rhs, lhs| {
258+
/// rhs.to_lowercase().cmp(&lhs.to_lowercase())
259+
/// })
260+
/// }).await
261+
///
262+
/// # }
263+
/// ```
264+
pub fn spawn_blocking<'a, R>(
236265
&mut self,
237266
task: impl FnOnce(&mut C) -> QueryResult<R> + Send + 'static,
238267
) -> BoxFuture<'a, QueryResult<R>>

src/sync_connection_wrapper/sqlite.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use diesel::connection::AnsiTransactionManager;
2+
use diesel::SqliteConnection;
3+
use scoped_futures::ScopedBoxFuture;
4+
5+
use crate::sync_connection_wrapper::SyncTransactionManagerWrapper;
6+
use crate::TransactionManager;
7+
8+
use super::SyncConnectionWrapper;
9+
10+
impl SyncConnectionWrapper<SqliteConnection> {
11+
/// Run a transaction with `BEGIN IMMEDIATE`
12+
///
13+
/// This method will return an error if a transaction is already open.
14+
///
15+
/// **WARNING:** Canceling the returned future does currently **not**
16+
/// close an already open transaction. You may end up with a connection
17+
/// containing a dangling transaction.
18+
///
19+
/// # Example
20+
///
21+
/// ```rust
22+
/// # include!("../doctest_setup.rs");
23+
/// use diesel::result::Error;
24+
/// use scoped_futures::ScopedFutureExt;
25+
/// use diesel_async::{RunQueryDsl, AsyncConnection};
26+
/// #
27+
/// # #[tokio::main(flavor = "current_thread")]
28+
/// # async fn main() {
29+
/// # run_test().await.unwrap();
30+
/// # }
31+
/// #
32+
/// # async fn run_test() -> QueryResult<()> {
33+
/// # use schema::users::dsl::*;
34+
/// # let conn = &mut connection_no_transaction().await;
35+
/// conn.immediate_transaction(|conn| async move {
36+
/// diesel::insert_into(users)
37+
/// .values(name.eq("Ruby"))
38+
/// .execute(conn)
39+
/// .await?;
40+
///
41+
/// let all_names = users.select(name).load::<String>(conn).await?;
42+
/// assert_eq!(vec!["Sean", "Tess", "Ruby"], all_names);
43+
///
44+
/// Ok(())
45+
/// }.scope_boxed()).await
46+
/// # }
47+
/// ```
48+
pub async fn immediate_transaction<'a, R, E, F>(&mut self, f: F) -> Result<R, E>
49+
where
50+
F: for<'r> FnOnce(&'r mut Self) -> ScopedBoxFuture<'a, 'r, Result<R, E>> + Send + 'a,
51+
E: From<diesel::result::Error> + Send + 'a,
52+
R: Send + 'a,
53+
{
54+
self.transaction_sql(f, "BEGIN IMMEDIATE").await
55+
}
56+
57+
/// Run a transaction with `BEGIN EXCLUSIVE`
58+
///
59+
/// This method will return an error if a transaction is already open.
60+
///
61+
/// **WARNING:** Canceling the returned future does currently **not**
62+
/// close an already open transaction. You may end up with a connection
63+
/// containing a dangling transaction.
64+
///
65+
/// # Example
66+
///
67+
/// ```rust
68+
/// # include!("../doctest_setup.rs");
69+
/// use diesel::result::Error;
70+
/// use scoped_futures::ScopedFutureExt;
71+
/// use diesel_async::{RunQueryDsl, AsyncConnection};
72+
/// #
73+
/// # #[tokio::main(flavor = "current_thread")]
74+
/// # async fn main() {
75+
/// # run_test().await.unwrap();
76+
/// # }
77+
/// #
78+
/// # async fn run_test() -> QueryResult<()> {
79+
/// # use schema::users::dsl::*;
80+
/// # let conn = &mut connection_no_transaction().await;
81+
/// conn.exclusive_transaction(|conn| async move {
82+
/// diesel::insert_into(users)
83+
/// .values(name.eq("Ruby"))
84+
/// .execute(conn)
85+
/// .await?;
86+
///
87+
/// let all_names = users.select(name).load::<String>(conn).await?;
88+
/// assert_eq!(vec!["Sean", "Tess", "Ruby"], all_names);
89+
///
90+
/// Ok(())
91+
/// }.scope_boxed()).await
92+
/// # }
93+
/// ```
94+
pub async fn exclusive_transaction<'a, R, E, F>(&mut self, f: F) -> Result<R, E>
95+
where
96+
F: for<'r> FnOnce(&'r mut Self) -> ScopedBoxFuture<'a, 'r, Result<R, E>> + Send + 'a,
97+
E: From<diesel::result::Error> + Send + 'a,
98+
R: Send + 'a,
99+
{
100+
self.transaction_sql(f, "BEGIN EXCLUSIVE").await
101+
}
102+
103+
async fn transaction_sql<'a, R, E, F>(&mut self, f: F, sql: &'static str) -> Result<R, E>
104+
where
105+
F: for<'r> FnOnce(&'r mut Self) -> ScopedBoxFuture<'a, 'r, Result<R, E>> + Send + 'a,
106+
E: From<diesel::result::Error> + Send + 'a,
107+
R: Send + 'a,
108+
{
109+
self.spawn_blocking(|conn| AnsiTransactionManager::begin_transaction_sql(conn, sql))
110+
.await?;
111+
112+
match f(&mut *self).await {
113+
Ok(value) => {
114+
SyncTransactionManagerWrapper::<AnsiTransactionManager>::commit_transaction(
115+
&mut *self,
116+
)
117+
.await?;
118+
Ok(value)
119+
}
120+
Err(e) => {
121+
SyncTransactionManagerWrapper::<AnsiTransactionManager>::rollback_transaction(
122+
&mut *self,
123+
)
124+
.await?;
125+
Err(e)
126+
}
127+
}
128+
}
129+
}

0 commit comments

Comments
 (0)