From e50b891b34ad2d6a75b7e8af344d8722ca16fdc7 Mon Sep 17 00:00:00 2001 From: James Oakley Date: Mon, 18 Nov 2024 14:34:56 -0500 Subject: [PATCH] Gate async functionality behind a feature (#278) Add async (for async overall) and async-adapter (for the ability to use naturally-sync backends like sqlite from async) features --- Cargo.lock | 1 - Makefile | 3 ++ README.md | 2 + async_checklist.md | 2 +- butane/Cargo.toml | 5 ++- butane/src/lib.rs | 11 ++--- butane_codegen/Cargo.toml | 1 + butane_core/Cargo.toml | 12 +++--- butane_core/src/codegen/dbobj.rs | 35 ++++++++++++---- butane_core/src/db/connmethods.rs | 7 +++- butane_core/src/db/dummy.rs | 2 +- butane_core/src/db/macros.rs | 2 +- butane_core/src/db/mod.rs | 51 ++++++++++++++--------- butane_core/src/db/sqlite.rs | 13 ++++-- butane_core/src/fkey.rs | 11 +++-- butane_core/src/lib.rs | 16 ++++++- butane_core/src/many.rs | 19 ++++++--- butane_core/src/migrations/mod.rs | 10 +++-- butane_core/src/query/mod.rs | 16 ++++--- butane_core/src/util.rs | 3 ++ example/Cargo.toml | 2 +- examples/getting_started/Cargo.toml | 2 +- examples/getting_started_async/Cargo.toml | 2 +- examples/newtype/Cargo.toml | 2 +- 24 files changed, 155 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b433a8d..0a4133c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,7 +261,6 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" name = "butane" version = "0.7.0" dependencies = [ - "async-trait", "butane_codegen", "butane_core", "butane_test_helper", diff --git a/Makefile b/Makefile index 338752d8..9eaf5b89 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ build : cd butane && $(CARGO) check --features pg cd butane && $(CARGO) check --features pg,datetime cd butane && $(CARGO) check --features sqlite + cd examples/getting_started && $(CARGO) check --features "sqlite,sqlite-bundled" cargo build --all-features lint : @@ -21,6 +22,8 @@ check : build doclint lint spellcheck check-fmt test test : $(CARGO) test --all-features + # And run the example tests separately to avoid feature combinations + cd examples; for dir in *; do cargo +stable test -p $dir --all-features; done clean : $(CARGO) clean diff --git a/README.md b/README.md index b3c9af2f..b9f733e1 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,8 @@ Butane exposes several features to Cargo. By default, no backends are enabled: you will want to enable `sqlite` and/or `pg`: * `default`: Turns on `datetime`, `json` and `uuid`. +* `async`: Turns on async support. This is automatically enabled for the `pg` backend, which is implemented on the `tokio-postgres` crate. +* `async-adapter`: Enables the use of `async` with the `sqlite` backend, which is not natively async. * `debug`: Used in developing Butane, not expected to be enabled by consumers. * `datetime`: Support for timestamps (using [`chrono`](https://crates.io/crates/chrono) crate). * `fake`: Support for the [`fake`](https://crates.io/crates/fake) crate's generation of fake data. diff --git a/async_checklist.md b/async_checklist.md index 6f376456..0e9d5ef4 100644 --- a/async_checklist.md +++ b/async_checklist.md @@ -8,5 +8,5 @@ * [x] Fully support sync too. Using async should not be required * [ ] Clean up miscellaneous TODOs * [x] Establish soundness for unsafe sections of AsyncAdapter -* [ ] Should async and/or async_adapter be under a separate feature? +* [x] Should async and/or async_adapter be under a separate feature? * [ ] Integrate deadpool or bb8 for async connection pool diff --git a/butane/Cargo.toml b/butane/Cargo.toml index 4382bdef..0b927116 100644 --- a/butane/Cargo.toml +++ b/butane/Cargo.toml @@ -13,12 +13,14 @@ documentation = "https://docs.rs/butane/" build = "build.rs" [features] +async = ["butane_core/async", "butane_codegen/async"] +async-adapter = ["butane_core/async-adapter"] default = ["datetime", "json", "uuid"] fake = ["butane_core/fake"] json = ["butane_codegen/json", "butane_core/json"] sqlite = ["butane_core/sqlite"] sqlite-bundled = ["butane_core/sqlite-bundled"] -pg = ["butane_core/pg"] +pg = ["async", "butane_core/pg"] datetime = ["butane_codegen/datetime", "butane_core/datetime"] debug = ["butane_core/debug"] log = ["butane_core/log"] @@ -27,7 +29,6 @@ tls = ["butane_core/tls"] uuid = ["butane_codegen/uuid", "butane_core/uuid"] [dependencies] -async-trait = { workspace = true } butane_codegen = { workspace = true } butane_core = { workspace = true } diff --git a/butane/src/lib.rs b/butane/src/lib.rs index 4f5f0d21..5c1ed7b9 100644 --- a/butane/src/lib.rs +++ b/butane/src/lib.rs @@ -9,12 +9,14 @@ pub use butane_codegen::{butane_type, dataresult, model, FieldType, PrimaryKeyType}; pub use butane_core::custom; pub use butane_core::fkey::ForeignKey; -pub use butane_core::many::{Many, ManyOpsAsync, ManyOpsSync}; +pub use butane_core::many::{Many, ManyOpsSync}; pub use butane_core::migrations; pub use butane_core::query; +#[cfg(feature = "async")] +pub use butane_core::{many::ManyOpsAsync, DataObjectOpsAsync}; pub use butane_core::{ - AsPrimaryKey, AutoPk, DataObject, DataObjectOpsAsync, DataObjectOpsSync, DataResult, Error, - FieldType, FromSql, PrimaryKeyType, Result, SqlType, SqlVal, SqlValRef, ToSql, + AsPrimaryKey, AutoPk, DataObject, DataObjectOpsSync, DataResult, Error, FieldType, FromSql, + PrimaryKeyType, Result, SqlType, SqlVal, SqlValRef, ToSql, }; pub mod db { @@ -196,6 +198,7 @@ pub mod prelude { pub use butane_core::DataObjectOpsSync; } +#[cfg(feature = "async")] pub mod prelude_async { //! Prelude module to improve ergonomics in async operation. Brings certain traits into scope. //! @@ -214,7 +217,5 @@ pub mod internal { //! //! Do not use directly. Semver-exempt. - pub use async_trait::async_trait; - pub use butane_core::internal::*; } diff --git a/butane_codegen/Cargo.toml b/butane_codegen/Cargo.toml index 822098a0..d6e9a26c 100644 --- a/butane_codegen/Cargo.toml +++ b/butane_codegen/Cargo.toml @@ -9,6 +9,7 @@ license.workspace = true repository.workspace = true [features] +async = ["butane_core/async"] datetime = ["butane_core/datetime"] json = ["butane_core/json"] uuid = ["butane_core/uuid"] diff --git a/butane_core/Cargo.toml b/butane_core/Cargo.toml index 7975d799..96e93b44 100644 --- a/butane_core/Cargo.toml +++ b/butane_core/Cargo.toml @@ -10,14 +10,15 @@ repository.workspace = true [features] -async-adapter = [] +async-adapter = ["async", "crossbeam-channel"] +async = ["tokio"] datetime = ["chrono", "tokio-postgres?/with-chrono-0_4"] debug = ["log"] fake = ["dep:fake", "rand"] json = ["tokio-postgres?/with-serde_json-1", "rusqlite?/serde_json"] log = ["dep:log", "rusqlite?/trace"] -pg = ["bytes", "tokio-postgres"] -sqlite = ["rusqlite", "async-adapter"] +pg = ["async", "bytes", "tokio-postgres"] +sqlite = ["rusqlite"] sqlite-bundled = ["rusqlite/bundled"] tls = ["native-tls", "postgres-native-tls"] @@ -27,8 +28,7 @@ async-trait = { workspace = true} bytes = { version = "1.0", optional = true } cfg-if = { workspace = true } chrono = { optional = true, workspace = true } -# todo make adapter optional -crossbeam-channel = { workspace = true } +crossbeam-channel = { workspace = true, optional = true} dyn-clone = { version = "1.0" } fake = { workspace = true, optional = true } fallible-iterator = "0.3" @@ -42,7 +42,7 @@ native-tls = { version = "0.2", optional = true } nonempty.workspace = true once_cell = { workspace = true } pin-project = "1" -tokio = {workspace = true, features = ["rt", "sync", "rt-multi-thread"]} +tokio = {workspace = true, optional = true, features = ["rt", "sync", "rt-multi-thread"]} tokio-postgres = { optional = true, workspace = true } postgres-native-tls = { version = "0.5", optional = true } proc-macro2 = { workspace = true } diff --git a/butane_core/src/codegen/dbobj.rs b/butane_core/src/codegen/dbobj.rs index 5416b7fe..89a210bd 100644 --- a/butane_core/src/codegen/dbobj.rs +++ b/butane_core/src/codegen/dbobj.rs @@ -37,8 +37,8 @@ pub fn impl_dbobject(ast_struct: &ItemStruct, config: &Config) -> TokenStream2 { let values_no_pk: Vec = push_values(ast_struct, |f: &Field| f != &pk_field); let insert_cols = columns(ast_struct, |f| !is_auto(f)); - let many_save_async = impl_many_save(ast_struct, config, true); let many_save_sync = impl_many_save(ast_struct, config, false); + let save_many_to_many_async = def_for_save_many_to_many_async(ast_struct, config); let conn_arg_name = if many_save_sync.is_empty() { syn::Ident::new("_conn", Span::call_site()) @@ -83,13 +83,7 @@ pub fn impl_dbobject(ast_struct: &ItemStruct, config: &Config) -> TokenStream2 { fn pk_mut(&mut self) -> &mut impl butane::PrimaryKeyType { &mut self.#pkident } - async fn save_many_to_many_async( - &mut self, - #conn_arg_name: &impl butane::db::ConnectionMethodsAsync, - ) -> butane::Result<()> { - #many_save_async - Ok(()) - } + #save_many_to_many_async fn save_many_to_many_sync( &mut self, #conn_arg_name: &impl butane::db::ConnectionMethods, @@ -459,3 +453,28 @@ fn impl_many_save(ast_struct: &ItemStruct, config: &Config, is_async: bool) -> T }) .collect(); } + +#[cfg(feature = "async")] +fn def_for_save_many_to_many_async(ast_struct: &ItemStruct, config: &Config) -> TokenStream2 { + let many_save_async = impl_many_save(ast_struct, config, true); + let conn_arg_name = if many_save_async.is_empty() { + syn::Ident::new("_conn", Span::call_site()) + } else { + syn::Ident::new("conn", Span::call_site()) + }; + + quote!( + async fn save_many_to_many_async( + &mut self, + #conn_arg_name: &impl butane::db::ConnectionMethodsAsync, + ) -> butane::Result<()> { + #many_save_async + Ok(()) + } + ) +} + +#[cfg(not(feature = "async"))] +fn def_for_save_many_to_many_async(_ast_struct: &ItemStruct, _config: &Config) -> TokenStream2 { + quote!() +} diff --git a/butane_core/src/db/connmethods.rs b/butane_core/src/db/connmethods.rs index 934e021f..a440119d 100644 --- a/butane_core/src/db/connmethods.rs +++ b/butane_core/src/db/connmethods.rs @@ -14,7 +14,7 @@ use crate::{Result, SqlType, SqlVal, SqlValRef}; /// implemented by both database connections and transactions. #[maybe_async_cfg::maybe( sync(keep_self), - async(self = "ConnectionMethodsAsync"), + async(feature = "async", self = "ConnectionMethodsAsync"), idents(AsyncRequiresSync) )] #[async_trait] @@ -156,6 +156,7 @@ impl VecRows { } } +#[cfg(feature = "async-adapter")] pub(crate) fn vec_from_backend_rows<'a>( mut other: Box, columns: &[Column], @@ -191,11 +192,13 @@ impl<'a> BackendRows for Box { } } +#[cfg(feature = "async-adapter")] #[derive(Debug)] pub(crate) struct VecRow { values: Vec, } +#[cfg(feature = "async-adapter")] impl VecRow { fn new(original: &(dyn BackendRow), columns: &[Column]) -> Result { if original.len() != columns.len() { @@ -214,6 +217,8 @@ impl VecRow { }) } } + +#[cfg(feature = "async-adapter")] impl BackendRow for VecRow { fn get(&self, idx: usize, ty: SqlType) -> Result { self.values diff --git a/butane_core/src/db/dummy.rs b/butane_core/src/db/dummy.rs index 10a5905c..1fe85c51 100644 --- a/butane_core/src/db/dummy.rs +++ b/butane_core/src/db/dummy.rs @@ -116,7 +116,7 @@ impl ConnectionMethods for DummyConnection { ), keep_self, sync(), - async() + async(feature = "async") )] #[async_trait] impl BackendConnection for DummyConnection { diff --git a/butane_core/src/db/macros.rs b/butane_core/src/db/macros.rs index a2c26ca4..fc6a5b99 100644 --- a/butane_core/src/db/macros.rs +++ b/butane_core/src/db/macros.rs @@ -8,7 +8,7 @@ macro_rules! connection_method_wrapper { Transaction(sync = "Transaction") ), sync(keep_self), - async() + async(feature = "async") )] #[async_trait::async_trait] impl ConnectionMethods for $ty { diff --git a/butane_core/src/db/mod.rs b/butane_core/src/db/mod.rs index 40efd147..be05a1e6 100644 --- a/butane_core/src/db/mod.rs +++ b/butane_core/src/db/mod.rs @@ -28,17 +28,23 @@ use serde::{Deserialize, Serialize}; use crate::query::{BoolExpr, Order}; use crate::{migrations::adb, Error, Result, SqlVal, SqlValRef}; +#[cfg(feature = "async-adapter")] mod adapter; +#[cfg(feature = "async-adapter")] pub use adapter::connect_async_via_sync; +#[cfg(feature = "async")] pub(crate) mod dummy; -use dummy::DummyConnection; + +#[cfg(feature = "async")] mod sync_adapter; +#[cfg(feature = "async")] pub use sync_adapter::SyncAdapter; mod connmethods; +#[cfg(feature = "async")] +pub use connmethods::ConnectionMethodsAsync; pub use connmethods::{ - BackendRow, BackendRows, Column, ConnectionMethods, ConnectionMethodsAsync, MapDeref, - QueryResult, RawQueryResult, + BackendRow, BackendRows, Column, ConnectionMethods, MapDeref, QueryResult, RawQueryResult, }; mod helper; mod macros; @@ -63,9 +69,9 @@ mod internal { #[maybe_async_cfg::maybe(idents(AsyncRequiresSend), sync())] impl AsyncRequiresSend for T {} - #[maybe_async_cfg::maybe(async())] + #[maybe_async_cfg::maybe(async(feature = "async"))] pub trait AsyncRequiresSend: Send {} - #[maybe_async_cfg::maybe(idents(AsyncRequiresSend), async())] + #[maybe_async_cfg::maybe(idents(AsyncRequiresSend), async(feature = "async"))] impl AsyncRequiresSend for T {} #[maybe_async_cfg::maybe(sync())] @@ -73,9 +79,9 @@ mod internal { #[maybe_async_cfg::maybe(idents(AsyncRequiresSync), sync())] impl AsyncRequiresSync for T {} - #[maybe_async_cfg::maybe(async())] + #[maybe_async_cfg::maybe(async(feature = "async"))] pub trait AsyncRequiresSync: Sync {} - #[maybe_async_cfg::maybe(idents(AsyncRequiresSync), async())] + #[maybe_async_cfg::maybe(idents(AsyncRequiresSync), async(feature = "async"))] impl AsyncRequiresSync for T {} } @@ -86,7 +92,7 @@ mod internal { Transaction(sync = "Transaction", async = "TransactionAsync"), ), sync(self = "BackendConnection"), - async(self = "BackendConnectionAsync") + async(feature = "async", self = "BackendConnectionAsync") )] #[async_trait] pub trait BackendConnection: ConnectionMethods + Debug + Send { @@ -110,7 +116,7 @@ pub trait BackendConnection: ConnectionMethods + Debug + Send { ), keep_self, sync(), - async() + async(feature = "async") )] #[async_trait] impl BackendConnection for Box { @@ -135,7 +141,7 @@ impl BackendConnection for Box { ), keep_self, sync(), - async() + async(feature = "async") )] #[async_trait] impl ConnectionMethods for Box { @@ -208,7 +214,7 @@ impl ConnectionMethods for Box { #[maybe_async_cfg::maybe( idents(BackendConnection(sync = "BackendConnection")), sync(keep_self), - async(self = "ConnectionAsync") + async(self = "ConnectionAsync", feature = "async") )] #[derive(Debug)] pub struct Connection { @@ -218,7 +224,7 @@ pub struct Connection { #[maybe_async_cfg::maybe( idents(BackendConnection(sync = "BackendConnection")), sync(keep_self), - async() + async(feature = "async") )] impl Connection { pub fn new(conn: Box) -> Self { @@ -238,6 +244,7 @@ impl Connection { /// the synchronous connection on a separate thread -- it is not "natively" /// async. #[maybe_async_cfg::only_if(key = "sync")] + #[cfg(feature = "async-adapter")] pub fn into_async(self) -> Result { Ok(adapter::AsyncAdapter::new(|| Ok(self))?.into_connection()) } @@ -254,7 +261,7 @@ impl Connection { F: FnOnce(&mut SyncAdapter) -> Result + Send + 'static, T: Send + 'static, { - let mut conn2 = Connection::new(Box::new(DummyConnection::new())); + let mut conn2 = Connection::new(Box::new(dummy::DummyConnection::new())); std::mem::swap(&mut conn2, self); let ret: Result<(Result, Connection)> = tokio::task::spawn_blocking(|| { let mut sync_conn = SyncAdapter::new(conn2)?; @@ -274,6 +281,7 @@ impl Connection { } } +#[cfg(feature = "async")] impl ConnectionAsync { /// Consume this connection and convert it into a synchronous one. /// Note that the under the hood this adds an adapter layer which drives @@ -290,7 +298,7 @@ impl ConnectionAsync { Transaction(sync = "Transaction") ), sync(keep_self), - async() + async(feature = "async") )] #[async_trait] impl BackendConnection for Connection { @@ -312,7 +320,7 @@ connection_method_wrapper!(Connection); #[maybe_async_cfg::maybe( idents(ConnectionMethods(sync = "ConnectionMethods"), AsyncRequiresSend), sync(keep_self), - async() + async(feature = "async") )] #[async_trait] pub(super) trait BackendTransaction<'c>: @@ -338,7 +346,7 @@ pub(super) trait BackendTransaction<'c>: #[maybe_async_cfg::maybe( idents(BackendTransaction(sync = "BackendTransaction")), sync(self = "Transaction"), - async() + async(feature = "async") )] #[derive(Debug)] pub struct Transaction<'c> { @@ -351,7 +359,7 @@ pub struct Transaction<'c> { ConnectionMethods(sync = "ConnectionMethods") ), sync(keep_self), - async() + async(feature = "async") )] impl<'c> Transaction<'c> { // unused may occur if no backends are selected @@ -383,7 +391,7 @@ connection_method_wrapper!(Transaction<'_>); ConnectionMethods(sync = "ConnectionMethods") ), sync(keep_self), - async() + async(feature = "async") )] #[async_trait] impl<'c> BackendTransaction<'c> for Transaction<'c> { @@ -405,7 +413,7 @@ impl<'c> BackendTransaction<'c> for Transaction<'c> { ), keep_self, sync(), - async() + async(feature = "async") )] #[async_trait] impl<'c> BackendTransaction<'c> for Box + 'c> { @@ -427,7 +435,7 @@ impl<'c> BackendTransaction<'c> for Box + 'c> { ), keep_self, sync(), - async() + async(feature = "async") )] #[async_trait] impl<'bt> ConnectionMethods for Box + 'bt> { @@ -505,6 +513,7 @@ pub trait Backend: Send + Sync + DynClone { fn connect(&self, conn_str: &str) -> Result; /// Establish a new async connection. The format of the connection /// string is backend-dependent. + #[cfg(feature = "async")] async fn connect_async(&self, conn_str: &str) -> Result; } @@ -565,6 +574,7 @@ impl Backend for Box { fn connect(&self, conn_str: &str) -> Result { self.deref().connect(conn_str) } + #[cfg(feature = "async")] async fn connect_async(&self, conn_str: &str) -> Result { self.deref().connect_async(conn_str).await } @@ -593,6 +603,7 @@ pub fn connect(spec: &ConnectionSpec) -> Result { /// Connect to a database async. /// /// For non-boxed connections, see individual [`Backend`] implementations. +#[cfg(feature = "async")] pub async fn connect_async(spec: &ConnectionSpec) -> Result { get_backend(&spec.backend_name) .ok_or_else(|| Error::UnknownBackend(spec.backend_name.clone()))? diff --git a/butane_core/src/db/sqlite.rs b/butane_core/src/db/sqlite.rs index 76668e2a..efcc6a96 100644 --- a/butane_core/src/db/sqlite.rs +++ b/butane_core/src/db/sqlite.rs @@ -13,11 +13,10 @@ use chrono::naive::NaiveDateTime; use fallible_streaming_iterator::FallibleStreamingIterator; use pin_project::pin_project; +#[cfg(feature = "async")] +use super::ConnectionAsync; use super::{helper, Backend, BackendRow, Column, RawQueryResult}; -use super::{ - BackendConnection, BackendTransaction, Connection, ConnectionAsync, ConnectionMethods, - Transaction, -}; +use super::{BackendConnection, BackendTransaction, Connection, ConnectionMethods, Transaction}; use crate::db::connmethods::BackendRows; use crate::migrations::adb::ARef; use crate::migrations::adb::{AColumn, ATable, Operation, TypeIdentifier, ADB}; @@ -89,9 +88,15 @@ impl Backend for SQLiteBackend { conn: Box::new(self.connect(path)?), }) } + #[cfg(feature = "async-adapter")] async fn connect_async(&self, path: &str) -> Result { super::adapter::connect_async_via_sync(self, path).await } + + #[cfg(all(feature = "async", not(feature = "async-adapter")))] + async fn connect_async(&self, _path: &str) -> Result { + Err(Error::NoAsyncAdapter("sqlite")) + } } /// SQLite database connection. diff --git a/butane_core/src/fkey.rs b/butane_core/src/fkey.rs index e5e638e0..bef75e07 100644 --- a/butane_core/src/fkey.rs +++ b/butane_core/src/fkey.rs @@ -8,10 +8,12 @@ use std::sync::OnceLock; use fake::{Dummy, Faker}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use crate::util::{get_or_init_once_lock, get_or_init_once_lock_async}; +use crate::util::get_or_init_once_lock; +#[cfg(feature = "async")] +use crate::{util::get_or_init_once_lock_async, ConnectionMethodsAsync}; use crate::{ - AsPrimaryKey, ConnectionMethods, ConnectionMethodsAsync, DataObject, Error, FieldType, FromSql, - Result, SqlType, SqlVal, SqlValRef, ToSql, + AsPrimaryKey, ConnectionMethods, DataObject, Error, FieldType, FromSql, Result, SqlType, + SqlVal, SqlValRef, ToSql, }; /// Used to implement a relationship between models. @@ -92,7 +94,7 @@ impl ForeignKey { #[maybe_async_cfg::maybe( idents(ConnectionMethods(sync = "ConnectionMethods"),), sync(), - async() + async(feature = "async") )] pub trait ForeignKeyOps { /// Loads the value referred to by this foreign key from the @@ -102,6 +104,7 @@ pub trait ForeignKeyOps { T: 'a; } +#[cfg(feature = "async")] impl ForeignKeyOpsAsync for ForeignKey { async fn load<'a>(&'a self, conn: &impl ConnectionMethodsAsync) -> Result<&'a T> where diff --git a/butane_core/src/lib.rs b/butane_core/src/lib.rs index 6573dc11..773e00e1 100644 --- a/butane_core/src/lib.rs +++ b/butane_core/src/lib.rs @@ -26,10 +26,13 @@ mod util; pub use autopk::AutoPk; use custom::SqlTypeCustom; -use db::{BackendRow, Column, ConnectionMethods, ConnectionMethodsAsync}; +use db::{BackendRow, Column, ConnectionMethods}; pub use query::Query; pub use sqlval::{AsPrimaryKey, FieldType, FromSql, PrimaryKeyType, SqlVal, SqlValRef, ToSql}; +#[cfg(feature = "async")] +use db::ConnectionMethodsAsync; + /// Result type that uses [`crate::Error`]. pub type Result = std::result::Result; @@ -74,6 +77,7 @@ pub mod internal { /// Saves many-to-many relationships pointed to by fields on this model. /// Performed automatically by `save`. You do not need to call this directly. + #[cfg(feature = "async")] async fn save_many_to_many_async( &mut self, conn: &impl ConnectionMethodsAsync, @@ -120,7 +124,7 @@ pub trait DataObject: DataResult + internal::DataObjectInternal + Sy QueryOps, ), sync(), - async() + async(feature = "async") )] pub trait DataObjectOps { /// Find this object in the database based on primary key. @@ -223,6 +227,7 @@ pub trait DataObjectOps { } impl DataObjectOpsSync for T where T: DataObject {} +#[cfg(feature = "async")] impl DataObjectOpsAsync for T where T: DataObject {} /// Butane errors. @@ -275,6 +280,8 @@ pub enum Error { SaveDeterminationNotSupported, #[error("This is a dummy poisoned connection.")] PoisonedConnection, + #[error("Connect connect_async for synchronous backend {0}. To support this, enable the async-adapter feature.")] + NoAsyncAdapter(&'static str), #[error("(De)serialization error {0}")] SerdeJson(#[from] serde_json::Error), #[error("IO error {0}")] @@ -298,10 +305,13 @@ pub enum Error { TLS(#[from] native_tls::Error), #[error("Generic error {0}")] Generic(#[from] Box), + #[cfg(feature = "async")] #[error("Tokio join error {0}")] TokioJoin(#[from] tokio::task::JoinError), #[error("Tokio recv error {0}")] + #[cfg(feature = "async")] TokioRecv(#[from] tokio::sync::oneshot::error::RecvError), + #[cfg(feature = "async-adapter")] #[error("Crossbeam cannot send/recv, channel disconnected")] CrossbeamChannel, } @@ -322,12 +332,14 @@ impl From for Error { } } +#[cfg(feature = "async-adapter")] impl From> for Error { fn from(_e: crossbeam_channel::SendError) -> Self { Self::CrossbeamChannel } } +#[cfg(feature = "async-adapter")] impl From for Error { fn from(_e: crossbeam_channel::RecvError) -> Self { Self::CrossbeamChannel diff --git a/butane_core/src/many.rs b/butane_core/src/many.rs index 8f06eabf..a2b88d19 100644 --- a/butane_core/src/many.rs +++ b/butane_core/src/many.rs @@ -7,9 +7,13 @@ use std::sync::OnceLock; use fake::{Dummy, Faker}; use serde::{Deserialize, Serialize}; -use crate::db::{Column, ConnectionMethods, ConnectionMethodsAsync}; +#[cfg(feature = "async")] +use crate::db::ConnectionMethodsAsync; +use crate::db::{Column, ConnectionMethods}; use crate::query::{BoolExpr, Expr, OrderDirection, Query}; -use crate::util::{get_or_init_once_lock, get_or_init_once_lock_async}; +use crate::util::get_or_init_once_lock; +#[cfg(feature = "async")] +use crate::util::get_or_init_once_lock_async; use crate::{sqlval::PrimaryKeyType, DataObject, Error, FieldType, Result, SqlType, SqlVal, ToSql}; fn default_oc() -> OnceLock> { @@ -131,7 +135,7 @@ where #[maybe_async_cfg::maybe( idents(ConnectionMethods(sync, async = "ConnectionMethodsAsync"), QueryOps), sync(), - async() + async(feature = "async") )] /// Loads the values referred to by this many relationship from a /// database query if necessary and returns a reference to them. @@ -162,7 +166,10 @@ where #[maybe_async_cfg::maybe( idents(load_query_uncached(snake)), sync(), - async(idents(get_or_init_once_lock(snake), ConnectionMethods)) + async( + feature = "async", + idents(get_or_init_once_lock(snake), ConnectionMethods) + ) )] async fn load_query<'a, T>( many: &'a Many, @@ -182,7 +189,7 @@ where #[maybe_async_cfg::maybe( idents(ConnectionMethods(sync = "ConnectionMethods"),), sync(), - async() + async(feature = "async") )] pub trait ManyOps { /// Used by macro-generated code. You do not need to call this directly. @@ -220,7 +227,7 @@ pub trait ManyOps { ), keep_self, sync(), - async() + async(feature = "async") )] impl ManyOps for Many { async fn save(&mut self, conn: &impl ConnectionMethods) -> Result<()> { diff --git a/butane_core/src/migrations/mod.rs b/butane_core/src/migrations/mod.rs index 3efc4d8e..5d59cdca 100644 --- a/butane_core/src/migrations/mod.rs +++ b/butane_core/src/migrations/mod.rs @@ -9,10 +9,9 @@ use async_trait::async_trait; use fallible_iterator::FallibleIterator; use nonempty::NonEmpty; -use crate::db::{ - Backend, BackendConnection, BackendRows, Column, ConnectionAsync, ConnectionMethods, - ConnectionMethodsAsync, -}; +use crate::db::{Backend, BackendConnection, BackendRows, Column, ConnectionMethods}; +#[cfg(feature = "async")] +use crate::db::{ConnectionAsync, ConnectionMethodsAsync}; use crate::sqlval::{FromSql, SqlValRef, ToSql}; use crate::{db, query, DataObject, DataResult, Error, PrimaryKeyType, Result, SqlType}; @@ -124,6 +123,7 @@ pub trait Migrations: Clone { Ok(()) } + #[cfg(feature = "async")] /// Migrate connection forward. async fn migrate_async(&self, conn: &mut ConnectionAsync) -> Result<()> where @@ -156,6 +156,7 @@ pub trait Migrations: Clone { } /// Remove all applied migrations. + #[cfg(feature = "async")] async fn unmigrate_async(&self, conn: &mut ConnectionAsync) -> Result<()> where Self: Send + 'static, @@ -381,6 +382,7 @@ impl crate::internal::DataObjectInternal for ButaneMigration { } values } + #[cfg(feature = "async")] async fn save_many_to_many_async(&mut self, _conn: &impl ConnectionMethodsAsync) -> Result<()> { Ok(()) // no-op } diff --git a/butane_core/src/query/mod.rs b/butane_core/src/query/mod.rs index 10f7d670..f35f93e0 100644 --- a/butane_core/src/query/mod.rs +++ b/butane_core/src/query/mod.rs @@ -9,7 +9,9 @@ use std::marker::PhantomData; use fallible_iterator::FallibleIterator; -use crate::db::{BackendRows, ConnectionMethods, ConnectionMethodsAsync, QueryResult}; +#[cfg(feature = "async")] +use crate::db::ConnectionMethodsAsync; +use crate::db::{BackendRows, ConnectionMethods, QueryResult}; use crate::{DataResult, Result, SqlVal}; mod fieldexpr; @@ -200,7 +202,11 @@ impl Clone for Query { /// Internal QueryOps helpers. #[allow(async_fn_in_trait)] // Not truly a public trait -#[maybe_async_cfg::maybe(idents(ConnectionMethods(sync = "ConnectionMethods")), sync(), async())] +#[maybe_async_cfg::maybe( + idents(ConnectionMethods(sync = "ConnectionMethods")), + sync(), + async(feature = "async") +)] trait QueryOpsInternal { async fn fetch( self, @@ -212,7 +218,7 @@ trait QueryOpsInternal { idents(ConnectionMethods(sync = "ConnectionMethods"), QueryOpsInternal), keep_self, sync(), - async() + async(feature = "async") )] impl QueryOpsInternal for Query { async fn fetch( @@ -242,7 +248,7 @@ impl QueryOpsInternal for Query { #[maybe_async_cfg::maybe( idents(ConnectionMethods(sync = "ConnectionMethods"), QueryOpsInternal), sync(), - async() + async(feature = "async") )] pub trait QueryOps { /// Executes the query against `conn` and returns the first result (if any). @@ -263,7 +269,7 @@ pub trait QueryOps { ), keep_self, sync(), - async() + async(feature = "async") )] impl QueryOps for Query { async fn load_first(self, conn: &impl ConnectionMethods) -> Result> { diff --git a/butane_core/src/util.rs b/butane_core/src/util.rs index cf7b891f..3bd5502b 100644 --- a/butane_core/src/util.rs +++ b/butane_core/src/util.rs @@ -14,6 +14,9 @@ pub fn get_or_init_once_lock(cell: &OnceLock, f: impl FnOnce() -> Result( cell: &OnceLock, f: impl FnOnce() -> Fut, diff --git a/example/Cargo.toml b/example/Cargo.toml index 8eb4ed98..460f9e5f 100644 --- a/example/Cargo.toml +++ b/example/Cargo.toml @@ -11,7 +11,7 @@ build = "build.rs" sqlite-bundled = ["butane/sqlite-bundled"] [dependencies] -butane = { features = ["pg", "sqlite"], workspace = true } +butane = { features = ["async", "async-adapter", "pg", "sqlite"], workspace = true } tokio = { workspace = true, features = ["macros"] } [dev-dependencies] diff --git a/examples/getting_started/Cargo.toml b/examples/getting_started/Cargo.toml index 7621719b..bacec6f9 100644 --- a/examples/getting_started/Cargo.toml +++ b/examples/getting_started/Cargo.toml @@ -22,7 +22,7 @@ doc = false doc = false [features] -default = ["pg", "sqlite", "sqlite-bundled"] +default = ["sqlite", "sqlite-bundled"] pg = ["butane/pg"] sqlite = ["butane/sqlite"] sqlite-bundled = ["butane/sqlite-bundled"] diff --git a/examples/getting_started_async/Cargo.toml b/examples/getting_started_async/Cargo.toml index de59c2a8..2022df5b 100644 --- a/examples/getting_started_async/Cargo.toml +++ b/examples/getting_started_async/Cargo.toml @@ -36,7 +36,7 @@ sqlite = ["butane/sqlite"] sqlite-bundled = ["butane/sqlite-bundled"] [dependencies] -butane.workspace = true +butane = {features = ["async", "async-adapter"], workspace = true} tokio = { workspace = true, features = ["macros"] } [dev-dependencies] diff --git a/examples/newtype/Cargo.toml b/examples/newtype/Cargo.toml index 865ea478..3b377efa 100644 --- a/examples/newtype/Cargo.toml +++ b/examples/newtype/Cargo.toml @@ -15,7 +15,7 @@ sqlite = ["butane/sqlite"] sqlite-bundled = ["butane/sqlite-bundled"] [dependencies] -butane.workspace = true +butane = {workspace = true, features = ["async", "async-adapter"]} fake = { workspace = true, features = ["chrono", "derive", "uuid"] } garde = { version = "*", features = ["derive"] } serde.workspace = true