Last active
June 2, 2022 03:31
-
-
Save harrisonturton/cea948dfd47cba2ebef4cd95922c1aec to your computer and use it in GitHub Desktop.
Generic, async database trait for tokio-postgres
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![feature(generic_associated_types)] | |
use anyhow::Error; | |
use async_trait::async_trait; | |
use tokio_postgres::{row::Row, types::ToSql}; | |
/// This is an example of how to abstract Rust database libraries (e.g. tokio-postgres) | |
/// behind a trait. This is useful to swap out a concrete implementation with a fake, | |
/// or for changing the underlying database implementation (e.g. switching to sqlx) | |
/// without needing to update the consumers of this module. | |
/// | |
/// This is implemented with generics because it results in faster runtime code, since | |
/// monomorphisation doesn't need to compute vtables, unlike using trait objects. This | |
/// was also required to allow [commit] and [rollback] to borrow `self`. If a trait | |
/// object was used, we'd have to hide the concrete type behind `&self`, and thus | |
/// woudln't be able to borrow it. | |
/// | |
/// However, these generics also result in much more complex type signatures for | |
/// whatever consumes this [Database] trait. | |
#[derive(Clone, PartialEq, Debug)] | |
pub struct Config { | |
pub name: String, | |
pub user: String, | |
pub host: String, | |
} | |
#[async_trait] | |
pub trait Database: Sync + Send { | |
// Connection from the database connection pool. | |
type Connection<'a>: Connection<Transaction<'a> = Self::Transaction<'a>> | |
where | |
Self: 'a; | |
// A transaction whose lifetime is tied to a connection. | |
type Transaction<'a>: Transaction | |
where | |
Self: 'a; | |
async fn setup(&mut self, config: Config) -> Result<(), Error>; | |
async fn connect<'a>(&mut self) -> Result<Self::Connection<'a>, Error>; | |
} | |
#[async_trait] | |
pub trait Connection: Sync + Send { | |
type Transaction<'a>: Transaction | |
where | |
Self: 'a; | |
async fn transaction<'b>(&'b mut self) -> Result<Self::Transaction<'b>, Error>; | |
} | |
#[async_trait] | |
pub trait Transaction: Sync + Send { | |
async fn query( | |
&self, | |
statement: &str, | |
params: &[&(dyn ToSql + Sync)], | |
) -> Result<Vec<Row>, Error>; | |
async fn query_one( | |
&self, | |
statement: &str, | |
params: &[&(dyn ToSql + Sync)], | |
) -> Result<Row, Error>; | |
async fn query_opt( | |
&self, | |
statement: &str, | |
params: &[&(dyn ToSql + Sync)], | |
) -> Result<Option<Row>, Error>; | |
async fn commit(self) -> Result<(), Error>; | |
async fn rollback(self) -> Result<(), Error>; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment