Trait diesel::prelude::Connection
source · pub trait Connection: SimpleConnection + Sized + Sendwhere
for<'conn, 'query> Self: ConnectionGatWorkaround<'conn, 'query, Self::Backend, DefaultLoadingMode>,{
type Backend: Backend;
fn establish(database_url: &str) -> ConnectionResult<Self>;
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
, 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 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 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 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 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§
Required Methods§
sourcefn establish(database_url: &str) -> ConnectionResult<Self>
fn establish(database_url: &str) -> ConnectionResult<Self>
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.
Provided Methods§
sourcefn transaction<T, E, F>(&mut self, f: F) -> Result<T, E>where
F: FnOnce(&mut Self) -> Result<T, E>,
E: From<Error>,
fn transaction<T, E, F>(&mut self, f: F) -> Result<T, E>where
F: FnOnce(&mut Self) -> Result<T, E>,
E: From<Error>,
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) -> QueryResult<()>
fn begin_test_transaction(&mut self) -> QueryResult<()>
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) -> Twhere
F: FnOnce(&mut Self) -> Result<T, E>,
E: Debug,
fn test_transaction<T, E, F>(&mut self, f: F) -> Twhere
F: FnOnce(&mut Self) -> Result<T, E>,
E: Debug,
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);