hermit/src/db/mod.rs

96 lines
2.4 KiB
Rust

//! Database abstraction layer used by Hermit.
use std::borrow::Borrow;
use std::{marker::PhantomData, pin::Pin};
use crate::{ Id, Result, ap::Actor, err };
use futures::prelude::*;
use sqlx::{Executor, pool::PoolConnection, database::HasArguments};
use sqlx::{ FromRow, Either::Right };
/// `const ()` but in Rust
fn void <T> (_: T) -> () { () }
pub(crate) type Database = sqlx::Postgres;
/// Specifies how to connect to the database.
pub struct Config {}
/// A database client.
///
/// Cloning this client is cheap.
#[derive(Clone)]
pub struct Client {
/// The internal connection pool.
pool: sqlx::Pool<Database>,
}
impl Client {
/// Attempt to connect to the database using the provided configuration.
pub async fn new (_: Config) -> Result<Client> {
todo!()
}
pub async fn query <'e, T: 'e> (&self, query: Query<'e, T>) -> Pin<Box<dyn Stream<Item = Result<T>> + Send + 'e>> {
let Query (q, f) = query;
let stream = q
.fetch_many(&self.pool)
.filter_map(async move |r| {
match r.map_err(err) {
Ok (Right (row)) => Some (f(row)),
Err (error) => Some (Err (error)),
_ => None,
}
});
Box::pin (stream)
}
pub async fn get <'q, T: 'q> (&self, query: Query<'q, T>) -> Result<Option<T>> {
self.query(query)
.await
.next()
.await
.transpose()
}
/// Handles the getting-a-connection logic.
async fn with_conn <F, O, T> (&self, f: F) -> Result<T>
where
F: FnOnce (&mut PoolConnection<Database>) -> O,
O: Future<Output = Result<T>>,
{
self.pool
.acquire()
.map_err(err)
.and_then(|mut c| {
f(&mut c)
})
.await
}
}
type Q<'a> = sqlx::query::Query<'a, Database, <Database as HasArguments<'a>>::Arguments>;
type Row = <Database as sqlx::Database>::Row;
pub struct Query <'a, T> (Q<'a>, fn (Row) -> Result<T>);
/// Generate a query that gets an [`Actor`] by its [`Id`].
pub fn get_actor <'a> (id: &'a Id) -> Query<'a, Actor> {
// Prepare a query
let query = sqlx::query("select * from actors where id = $1")
.bind(id);
// Return an sql query which will result in a series of rows,
// and a decoder function that will translate each row to a
// value of type `Actor`.
Query (query, |row: Row| {
let data = Actor::from_row(&row)?;
Ok (data)
})
}