Skip to content

Instantly share code, notes, and snippets.

@harrisonturton
Last active June 2, 2022 03:31
Show Gist options
  • Save harrisonturton/cea948dfd47cba2ebef4cd95922c1aec to your computer and use it in GitHub Desktop.
Save harrisonturton/cea948dfd47cba2ebef4cd95922c1aec to your computer and use it in GitHub Desktop.
Generic, async database trait for tokio-postgres
#![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