diff --git a/Cargo.toml b/Cargo.toml index 77eb826..0115efb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ default = [ "cockroach" ] bb8 = "0.8" async-trait = "0.1.73" diesel = { version = "2.1.0", default-features = false, features = [ "r2d2" ] } +futures = "0.3" thiserror = "1.0" tokio = { version = "1.32", default-features = false, features = [ "rt-multi-thread" ] } diff --git a/src/async_traits.rs b/src/async_traits.rs index d9543c7..f70ba24 100644 --- a/src/async_traits.rs +++ b/src/async_traits.rs @@ -14,6 +14,9 @@ use diesel::{ }, result::Error as DieselError, }; +use futures::future::BoxFuture; +use futures::future::FutureExt; +use std::any::Any; use std::future::Future; use std::sync::Arc; use std::sync::MutexGuard; @@ -164,12 +167,45 @@ where retry: RetryFunc, ) -> Result where - R: Send + 'static, - Fut: Future> + Send, - Func: Fn(SingleConnection) -> Fut + Send + Sync, - RetryFut: Future + Send, + R: Any + Send + 'static, + Fut: FutureExt> + Send, + Func: (Fn(SingleConnection) -> Fut) + Send + Sync, + RetryFut: FutureExt + Send, RetryFunc: Fn() -> RetryFut + Send + Sync, { + // This function sure has a bunch of generic parameters, which can cause + // a lot of code to be generated, and can slow down compile-time. + // + // This API intends to provide a convenient, generic, shim over the + // dynamic "transaction_async_with_retry_inner" function below, which + // should avoid being generic. The goal here is to instantiate only one + // significant "body" of this function, while retaining flexibility for + // clients using this library. + + // Box the functions, and box the return value. + let f = |conn| { + f(conn) + .map(|result| result.map(|r| Box::new(r) as Box)) + .boxed() + }; + let retry = || retry().boxed(); + + // Call the dynamically dispatched function, then retrieve the return + // value out of a Box. + self.transaction_async_with_retry_inner(&f, &retry) + .await + .map(|v| *v.downcast::().expect("Should be an 'R' type")) + } + + // NOTE: This function intentionally avoids all generics! + #[cfg(feature = "cockroach")] + async fn transaction_async_with_retry_inner( + &self, + f: &(dyn Fn(SingleConnection) -> BoxFuture<'_, Result, DieselError>> + + Send + + Sync), + retry: &(dyn Fn() -> BoxFuture<'_, bool> + Send + Sync), + ) -> Result, DieselError> { // Check out a connection once, and use it for the duration of the // operation. let conn = Arc::new(self.get_owned_connection()); @@ -253,6 +289,38 @@ where E: From + Send + 'static, Fut: Future> + Send, Func: FnOnce(SingleConnection) -> Fut + Send, + { + // This function sure has a bunch of generic parameters, which can cause + // a lot of code to be generated, and can slow down compile-time. + // + // This API intends to provide a convenient, generic, shim over the + // dynamic "transaction_async_with_retry_inner" function below, which + // should avoid being generic. The goal here is to instantiate only one + // significant "body" of this function, while retaining flexibility for + // clients using this library. + + let f = Box::new(move |conn| { + f(conn) + .map(|result| result.map(|r| Box::new(r) as Box)) + .boxed() + }); + + self.transaction_async_inner(f) + .await + .map(|v| *v.downcast::().expect("Should be an 'R' type")) + } + + // NOTE: This function intentionally avoids as many generic parameters as possible + async fn transaction_async_inner<'a, E>( + &'a self, + f: Box< + dyn FnOnce(SingleConnection) -> BoxFuture<'a, Result, E>> + + Send + + 'a, + >, + ) -> Result, E> + where + E: From + Send + 'static, { // Check out a connection once, and use it for the duration of the // operation.