//! 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) -> () { () } 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, } impl Client { /// Attempt to connect to the database using the provided configuration. pub async fn new (_: Config) -> Result { todo!() } pub async fn query <'e, T: 'e> (&self, query: Query<'e, T>) -> Pin> + 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> { self.query(query) .await .next() .await .transpose() } /// Handles the getting-a-connection logic. async fn with_conn (&self, f: F) -> Result where F: FnOnce (&mut PoolConnection) -> O, O: Future>, { self.pool .acquire() .map_err(err) .and_then(|mut c| { f(&mut c) }) .await } } type Q<'a> = sqlx::query::Query<'a, Database, >::Arguments>; type Row = ::Row; pub struct Query <'a, T> (Q<'a>, fn (Row) -> Result); /// 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) }) }