pub trait Connection:
Sized
+ SimpleConnection
+ Send
+ ConnectionSealed {
type Backend: Backend;
type TransactionManager: TransactionManager<Self>;
// Required methods
fn establish(database_url: &str) -> Result<Self, ConnectionError>;
fn execute_returning_count<T>(&mut self, source: &T) -> Result<usize, Error>
where T: QueryFragment<Self::Backend> + QueryId;
fn transaction_state(
&mut self,
) -> &mut <Self::TransactionManager as TransactionManager<Self>>::TransactionStateData;
// Provided methods
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) -> Result<(), Error> { ... }
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
, LoadConnection
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 official 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 necessary state management.
- Maybe a
TransactionManager
implementation matching the interface provided by the database connection crate. Otherwise the implementation used by the correspondingConnection
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 high 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 correspondingConnection
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§
Sourcetype TransactionManager: TransactionManager<Self>
type TransactionManager: TransactionManager<Self>
The transaction manager implementation used by this connection
Required Methods§
Sourcefn establish(database_url: &str) -> Result<Self, ConnectionError>
fn establish(database_url: &str) -> Result<Self, ConnectionError>
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.
Sourcefn execute_returning_count<T>(&mut self, source: &T) -> Result<usize, Error>
fn execute_returning_count<T>(&mut self, source: &T) -> Result<usize, Error>
Execute a single SQL statements given by a query and return number of affected rows
Sourcefn transaction_state(
&mut self,
) -> &mut <Self::TransactionManager as TransactionManager<Self>>::TransactionStateData
fn transaction_state( &mut self, ) -> &mut <Self::TransactionManager as TransactionManager<Self>>::TransactionStateData
Get access to the current transaction state of this connection
This function should be used from TransactionManager
to access
internally required state.
Provided Methods§
Sourcefn transaction<T, E, F>(&mut self, f: F) -> Result<T, E>
fn transaction<T, E, F>(&mut self, f: F) -> Result<T, E>
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 committed 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.
If the rollback fails, the error will be returned in a
Error::RollbackErrorOnCommit
,
from which you will be able to extract both the original commit error and
the rollback error.
In addition, the connection will 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 case.
If the closure returns an Err(_)
and the rollback fails the function
will return that rollback error directly, and the transaction manager will
be marked as broken as it contains a uncommitted unabortable open transaction.
If a nested transaction fails to release the corresponding savepoint the error will be returned directly.
§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);
Sourcefn begin_test_transaction(&mut self) -> Result<(), Error>
fn begin_test_transaction(&mut self) -> Result<(), Error>
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
Sourcefn test_transaction<T, E, F>(&mut self, f: F) -> T
fn test_transaction<T, E, F>(&mut self, f: F) -> T
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);
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.