pub trait Connection: SimpleConnection + Sized + Send where
    Self: for<'a, 'b> ConnectionGatWorkaround<'a, 'b, Self::Backend>, 
{ type Backend: Backend; type TransactionManager: TransactionManager<Self>; fn establish(database_url: &str) -> ConnectionResult<Self>; fn load<'conn, 'query, T>(
        &'conn mut self,
        source: T
    ) -> QueryResult<LoadRowIter<'conn, 'query, Self, Self::Backend>>
    where
        T: Query + QueryFragment<Self::Backend> + QueryId + 'query,
        Self::Backend: QueryMetadata<T::SqlType>
; fn execute_returning_count<T>(&mut self, source: &T) -> QueryResult<usize>
    where
        T: QueryFragment<Self::Backend> + QueryId
; fn transaction_state(
        &mut self
    ) -> &mut <Self::TransactionManager as TransactionManager<Self>>::TransactionStateData; fn transaction<T, E, F>(&mut self, f: F) -> Result<T, E>
    where
        F: FnOnce(&mut Self) -> Result<T, E>,
        E: From<Error>
, { ... } fn begin_test_transaction(&mut self) -> QueryResult<()> { ... } fn test_transaction<T, E, F>(&mut self, f: F) -> T
    where
        F: FnOnce(&mut Self) -> Result<T, E>,
        E: Debug
, { ... } }
Expand description

A connection to a database

This trait represents a database connection. It can be used to query the database through the query dsl provided by diesel, custom extensions or raw sql queries.

Implementing a custom connection

There are several reasons why you would want to implement a custom connection implementation:

  • To wrap an existing connection for instrumentation purposes
  • To use a different underlying library to provide a connection implementation for already existing backends.
  • To add support for an unsupported database system

Implementing a Connection in a third party crate requires enabling the i-implement-a-third-party-backend-and-opt-into-breaking-changes crate feature which grants access to some of diesel’s implementation details.

Wrapping an existing connection impl

Wrapping an existing connection allows you to customize the implementation to add additional functionality, like for example instrumentation. For this use case you only need to implement Connection and all super traits. You should forward any method call to the wrapped connection type. It is important to also forward any method where diesel provides a default implementation, as the wrapped connection implementation may contain a customized implementation.

To allow the integration of your new connection type with other diesel features it may be useful to also implement R2D2Connection and MigrationConnection.

Provide a new connection implementation for an existing backend

Implementing a new connection based on an existing backend can enable the usage of other methods to connect to the database. One example here would be to replace the offical diesel provided connection implementations with an implementation based on a pure rust connection crate.

It’s important to use prepared statements to implement the following methods:

For performance reasons it may also be meaningful to cache already prepared statements. See StatementCache for a helper type to implement prepared statement caching. The statement_cache module documentation contains details about efficient prepared statement caching based on diesels query builder.

It is required to implement at least the following parts:

  • A row type that describes how to receive values form a database row. This type needs to implement Row
  • A field type that describes a database field value. This type needs to implement Field
  • A connection type that wraps the connection + the nessesary state managment.
  • Maybe a TransactionManager implementation matching the interface provided by the database connection crate. Otherwise the implementation used by the corresponding Connection in diesel can be reused.

To allow the integration of your new connection type with other diesel features it may be useful to also implement R2D2Connection and MigrationConnection.

The exact implementation of the Connection trait depends on the interface provided by the connection crate/library. A struct implementing Connection should likely contain a StatementCache to cache prepared statements efficiently.

As implementations differ significantly between the supported backends we cannot give a one for all description here. Generally it’s likely a good idea to follow the implementation of the corresponding connection in diesel at a heigh level to gain some idea how to implement your custom implementation.

Implement support for an unsupported database system

Additionally to anything mentioned in the previous section the following steps are required:

  • Implement a custom backend type. See the documentation of Backend for details
  • Implement appropriate FromSql/ ToSql conversions. At least the following impls should be considered:
    • i16: FromSql<SmallInt, YourBackend>
    • i32: FromSql<Integer, YourBackend>
    • i64: FromSql<BigInt, YourBackend>
    • f32: FromSql<Float, YourBackend>
    • f64: FromSql<Double, YourBackend>
    • bool: FromSql<Bool, YourBackend>
    • String: FromSql<Text, YourBackend>
    • Vec<u8>: FromSql<Binary, YourBackend>
    • i16: ToSql<SmallInt, YourBackend>
    • i32: ToSql<Integer, YourBackend>
    • i64: ToSql<BigInt, YourBackend>
    • f32: ToSql<Float, YourBackend>
    • f64: ToSql<Double, YourBackend>
    • bool: ToSql<Bool, YourBackend>
    • String: ToSql<Text, YourBackend>
    • Vec<u8>: ToSql<Binary, YourBackend>
  • Maybe a TransactionManager implementation matching the interface provided by the database connection crate. Otherwise the implementation used by the corresponding Connection in diesel can be reused.

As these implementations will vary depending on the backend being used, we cannot give concrete examples here. We recommend looking at our existing implementations to see how you can implement your own connection.

Required Associated Types

The backend this type connects to

Available on crate feature i-implement-a-third-party-backend-and-opt-into-breaking-changes only.

The transaction manager implementation used by this connection

Required Methods

Establishes a new connection to the database

The argument to this method and the method’s behavior varies by backend. See the documentation for that backend’s connection class for details about what it accepts and how it behaves.

Available on crate feature i-implement-a-third-party-backend-and-opt-into-breaking-changes only.

Executes a given query and returns any requested values

This function executes a given query and returns the query result as given by the database. Normal users should not use this function. Use QueryDsl::load instead.

This function is useful for people trying to build an alternative dsl on top of diesel. It returns an LoadRowIter, which is essentially an Iterator<Item = QueryResult<&impl Row<Self::Backend>>. This type can be used to iterate over all rows returned by the database.

Available on crate feature i-implement-a-third-party-backend-and-opt-into-breaking-changes only.

Execute a single SQL statements given by a query and return number of affected rows

Available on crate feature i-implement-a-third-party-backend-and-opt-into-breaking-changes only.

Get access to the current transaction state of this connection

This function should be used from TransactionManager to access internally required state.

Provided Methods

Executes the given function inside of a database transaction

This function executes the provided closure f inside a database transaction. If there is already an open transaction for the current connection savepoints will be used instead. The connection is commited if the closure returns Ok(_), it will be rolled back if it returns Err(_). For both cases the original result value will be returned from this function.

If the transaction fails to commit due to a SerializationFailure or a ReadOnlyTransaction a rollback will be attempted. In this case a Error::CommitTransactionFailed error is returned, which contains details about the original error and the success of the rollback attempt. If the rollback failed the connection should be considered broken as it contains a uncommitted unabortable open transaction. Any further interaction with the transaction system will result in an returned error in this cases.

If the closure returns an Err(_) and the rollback fails the function will return a Error::RollbackError wrapping the error generated by the rollback operation instead. In this case the connection should be considered broken as it contains an unabortable open transaction.

If a nested transaction fails to release the corresponding savepoint a rollback will be attempted. In this case a Error::CommitTransactionFailed error is returned, which contains the original error and details about the success of the rollback attempt.

Example
use diesel::result::Error;

conn.transaction::<_, Error, _>(|conn| {
    diesel::insert_into(users)
        .values(name.eq("Ruby"))
        .execute(conn)?;

    let all_names = users.select(name).load::<String>(conn)?;
    assert_eq!(vec!["Sean", "Tess", "Ruby"], all_names);

    Ok(())
})?;

conn.transaction::<(), _, _>(|conn| {
    diesel::insert_into(users)
        .values(name.eq("Pascal"))
        .execute(conn)?;

    let all_names = users.select(name).load::<String>(conn)?;
    assert_eq!(vec!["Sean", "Tess", "Ruby", "Pascal"], all_names);

    // If we want to roll back the transaction, but don't have an
    // actual error to return, we can return `RollbackTransaction`.
    Err(Error::RollbackTransaction)
});

let all_names = users.select(name).load::<String>(conn)?;
assert_eq!(vec!["Sean", "Tess", "Ruby"], all_names);

Creates a transaction that will never be committed. This is useful for tests. Panics if called while inside of a transaction or if called with a connection containing a broken transaction

Executes the given function inside a transaction, but does not commit it. Panics if the given function returns an error.

Example
use diesel::result::Error;

conn.test_transaction::<_, Error, _>(|conn| {
    diesel::insert_into(users)
        .values(name.eq("Ruby"))
        .execute(conn)?;

    let all_names = users.select(name).load::<String>(conn)?;
    assert_eq!(vec!["Sean", "Tess", "Ruby"], all_names);

    Ok(())
});

// Even though we returned `Ok`, the transaction wasn't committed.
let all_names = users.select(name).load::<String>(conn)?;
assert_eq!(vec!["Sean", "Tess"], all_names);

Implementors