diesel-async Documentation

repository·main·Indexed 21 days ago

https://github.com/diesel-rs/diesel_async

An asynchronous interface and extension for the Diesel ORM and Query Builder. It provides async implementations of connections and query execution methods, acting as a drop-in replacement for standard Diesel methods while maintaining compatibility with Diesel's query builder. Supports PostgreSQL, MySQL, and SQLite, with integration for connection pooling via Deadpool, BB8, and Mobc.

Tokens
6.6K
Snippets
26
Records
31
Agent score
73%

What's inside diesel-async

  1. Configure connection pooling with Deadpool, BB8, or Mobc

    main

    Diesel-async supports several connection pooling crates via the AsyncDieselConnectionManager.

    Deadpool

    Requires the deadpool feature. Use Pool::builder(config).build()?.

    BB8

    Requires the bb8 feature. Use Pool::builder().build(config).await?.

    Mobc

    Requires the mobc feature. Use Pool::new(config).

    // Deadpool example
    use diesel_async::pooled_connection::AsyncDieselConnectionManager;
    use diesel_async::pooled_connection::deadpool::Pool;
    use diesel_async::RunQueryDsl;
    
    let config = AsyncDieselConnectionManager::<diesel_async::AsyncPgConnection>::new(std::env::var("DATABASE_URL")?);
    let pool = Pool::builder(config).build()?;
    let mut conn = pool.get().await?;
    let res = users::table.select(User::as_select()).load::(&mut conn).await?;
  2. Install diesel-async and configure dependencies

    main

    To use diesel-async, you must include both diesel and diesel-async in your Cargo.toml. diesel-async acts as an async drop-in replacement for database interaction, while diesel provides the query builder and model definitions. You do not need to enable backend features in the base diesel crate; instead, enable the specific database feature in diesel-async (e.g., postgres, mysql, or sqlite).

    [dependencies]
    diesel = "2.3.0" # no backend features need to be enabled
    diesel-async = { version = "0.7.0", features = ["postgres"] }
  3. How transaction management works in diesel-async

    main

    Transaction management is handled by a TransactionManager associated with an AsyncConnection.

    When using high-level transaction APIs (like AsyncConnection::transaction), the manager follows this lifecycle:

    1. Begin: Calls begin_transaction. If the current transaction depth is > 0, it creates a SAVEPOINT instead of a BEGIN.
    2. Execute: Runs the provided async closure.
    3. Commit/Rollback:
      • If the closure returns Ok, it calls commit_transaction. If depth > 1, this releases the savepoint.
      • If the closure returns Err, it calls rollback_transaction. If depth > 1, it rolls back to the most recent savepoint.

    This nesting mechanism allows for safe, composable transactions and savepoints.

  4. Configure connection recycling methods

    main

    When using connection pools with diesel-async, you can specify how the pool validates existing connections before handing them to your application using RecyclingMethod. This helps balance performance against connection reliability.

    Available methods:

    • Fast: Only checks for open transactions. This is the fastest but assumes the underlying database connection is still alive.
    • Verified (Default): Executes a test query (e.g., SELECT 1) to guarantee the connection is ready. This is slower but safer.
    • CustomQuery(Cow<'static, str>): Uses a specific SQL string as the validation query.
    • CustomFunction(Box<RecycleCheckCallback<C>>): Uses a custom asynchronous callback for complex validation logic.
    // Example of using a custom query for recycling
    let config = ManagerConfig {
        recycling_method: RecyclingMethod::CustomQuery("SELECT 1".into()),
        custom_setup: Box::new(|url| C::establish(url).boxed()),
    };
  5. How diesel-async works with Diesel

    main

    diesel-async is an extension to the main diesel crate. It provides async variants of core Diesel traits that perform actual I/O work. To use it, you typically replace synchronous Diesel trait method calls and connection types with their diesel-async counterparts.

    Key trait mappings:

    • diesel::prelude::RunQueryDsl $\rightarrow$ diesel_async::RunQueryDsl
    • diesel::connection::Connection $\rightarrow$ diesel_async::AsyncConnection
    • diesel::query_dsl::UpdateAndFetchResults $\rightarrow$ diesel_async::UpdateAndFetchResults
    use diesel::prelude::*;
    use diesel_async::{RunQueryDsl, AsyncConnection};
    
    // Use ordinary diesel query DSL, but execute via async RunQueryDsl
    let data = users
        .filter(id.gt(0))
        .load::<(i32, String)>(&mut connection)
        .await?;
  6. Understanding broken connection states

    main

    A connection is considered 'broken' if it is in an unreliable state, which is critical for connection pool implementations to know so they can discard the connection rather than reusing it.

    A connection is considered broken if:

    1. The TransactionManager is in an error state (TransactionManagerStatus::Error).
    2. The connection has an open transaction that was not started as a test_transaction (e.g., a transaction was left uncommitted/unrolled due to a future being dropped).
    3. A critical transaction operation (like BEGIN, COMMIT, or ROLLBACK) was interrupted or canceled mid-execution (tracked via the is_broken atomic flag in AnsiTransactionManager).
  7. Configure PostgreSQL transaction settings with TransactionBuilder

    main

    When using diesel_async with PostgreSQL, you can use AsyncPgConnection::build_transaction() to obtain a TransactionBuilder. This builder allows you to specify the isolation level, read mode, and deferrability of the transaction before executing it.

    Important: The builder does nothing until you call .run() with an async closure. If the closure returns an error, the transaction is automatically rolled back. If it returns Ok, the transaction is committed.

    // Example: Setting up a read-only, serializable transaction
    conn.build_transaction()
        .serializable()
        .read_only()
        .run(async |conn| {
            // Your database operations here
            Ok(())
        })
        .await?;
  8. Establish an AsyncMysqlConnection

    main

    To connect to a MySQL database asynchronously, use AsyncMysqlConnection::establish. Connection URLs must follow the format: mysql://[user[:password]@]host/database_name.

    Upon establishment, the connection automatically executes setup queries to set the time zone to UTC and configure the character set to utf8mb4 for client, connection, and results.

    use diesel_async::AsyncMysqlConnection;
    
    let mut conn = AsyncMysqlConnection::establish("mysql://user:pass@localhost/db_name").await?;
  9. Perform simple async queries with diesel-async

    main

    Use diesel_async::AsyncConnection to establish a connection and diesel_async::RunQueryDsl to execute queries asynchronously. You construct queries using the standard Diesel Query DSL and then call async methods like .load() or .execute() provided by the RunQueryDsl trait.

    use diesel::prelude::*;
    use diesel_async::{RunQueryDsl, AsyncConnection, AsyncPgConnection};
    
    table! {
        users {
            id -> Integer,
            name -> Text,
        }
    }
    
    #[derive(Queryable, Selectable)]
    #[diesel(table_name = users)]
    struct User {
        id: i32,
        name: String,
    }
    
    // create an async connection
    let mut connection = AsyncPgConnection::establish(&std::env::var("DATABASE_URL")?).await?;
    
    // use ordinary diesel query dsl to construct your query
    let data: Vec<User> = users::table
        .filter(users::id.gt(0))
        .or_filter(users::name.like("%Luke"))
        .select(User::as_select())
        // execute the query via the provided
        // async `diesel_async::RunQueryDsl`
        .load(&mut connection)
        .await?;
  10. Stream query results with load_stream

    main

    Instead of loading all results into a collection (like a Vec), you can use .load_stream() from the RunQueryDsl trait to return a Stream of QueryResult<T>. This allows you to process database rows one by one as they are received.

    // use ordinary diesel query dsl to construct your query
    let data: impl Stream<Item = QueryResult<User>> = users::table
        .filter(users::id.gt(0))
        .or_filter(users::name.like("%Luke"))
        .select(User::as_select())
        // execute the query via the provided
        // async `diesel_async::RunQueryDsl`
        .load_stream(&mut connection)
        .await?;
  11. Use async transactions in diesel-async

    main

    Wrap multiple database statements in a transaction using connection.transaction. The transaction automatically rolls back if the inner closure returns an error. Because the closure is async, you must use .scope_boxed() to handle the lifetime of the async block within the transaction.

    connection.transaction::<_, diesel::result::Error, _>(|conn| async move {
             diesel::insert_into(users::table)
                 .values(users::name.eq("Ruby"))
                 .execute(conn)
                 .await?;
    
             let all_names = users::table.select(users::name).load::<String>(conn).await?;
             Ok(())
           }.scope_boxed()
        ).await?;
  12. Configure diesel-async via crate features

    main

    The following feature flags control available connection types and functionality:

    FeatureDescription
    postgresEnables AsyncPgConnection
    mysqlEnables AsyncMysqlConnection
    sqliteEnables SyncConnectionWrapper and SQLite support
    sync-connection-wrapperEnables SyncConnectionWrapper to wrap sync diesel connections
    async-connection-wrapperEnables AsyncConnectionWrapper to use async connections as sync diesel::Connection
    migrationsEnables AsyncMigrationHarness for running migrations
    poolEnables general support for connection pools
    r2d2Enables r2d2 pooling support
    bb8Enables bb8 pooling support
    mobcEnables mobc pooling support
    deadpoolEnables deadpool pooling support