#![deny(missing_docs)]
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::marker::{PhantomData, Unpin};
use std::sync::Arc;
use actix::prelude::*;
use diesel::associations::HasTable;
use diesel::connection::Connection;
use diesel::deserialize::Queryable;
use diesel::insertable::CanInsertInSingleQuery;
use diesel::prelude::*;
use diesel::query_builder::{AsQuery, QueryFragment, QueryId};
use diesel::sql_types::HasSqlType;
pub type Error = diesel::result::Error;
pub type Result<V> = std::result::Result<V, Error>;
pub trait Cache<Conn, Table>:
Queryable<Table::SqlType, Conn::Backend> + Sized + Debug + Clone + 'static
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
{
type Id: Hash + Eq + Clone;
fn get_id(&self) -> Self::Id;
fn read_all(c: &Conn) -> Result<HashMap<Self::Id, Self>> {
let vec: Vec<Self> = Table::table().load(c)?;
let mut out = HashMap::with_capacity(vec.len());
for it in vec {
let id = it.get_id();
out.insert(id, it);
}
Ok(out)
}
fn write_one<W>(w: W, c: &Conn) -> Result<usize>
where
Table::FromClause: QueryFragment<Conn::Backend>,
W: Insertable<Table>,
W::Values: CanInsertInSingleQuery<Conn::Backend>
+ QueryFragment<Conn::Backend>,
{
diesel::insert_into(Table::table()).values(w).execute(c)
}
}
pub struct CacheDbActor<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
C: Cache<Conn, Table>,
{
conn: Conn,
cache: Arc<HashMap<C::Id, C>>,
t: PhantomData<Table>,
}
#[derive(Debug)]
pub struct Save<T>(pub T);
impl<T: 'static> actix::Message for Save<T> {
type Result = Result<()>;
}
#[derive(Debug)]
pub struct Get<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
C: Cache<Conn, Table>,
{
pub id: C::Id,
}
impl<Conn, Table, C> Clone for Get<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
C: Cache<Conn, Table>,
C::Id: Clone,
{
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
}
}
}
impl<Conn, Table, C> Copy for Get<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
C: Cache<Conn, Table>,
C::Id: Clone + Copy,
{
}
impl<Conn, Table, C> actix::Message for Get<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
C: Cache<Conn, Table>,
{
type Result = Result<Option<C>>;
}
#[derive(Debug, Clone, Copy)]
pub struct GetAll<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery + Unpin + 'static,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
C: Cache<Conn, Table> + 'static,
{
_c: PhantomData<(Conn, Table, C)>,
}
impl<Conn, Table, C> Default for GetAll<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery + Unpin + 'static,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
C: Cache<Conn, Table> + 'static,
{
fn default() -> Self {
GetAll {
_c: Default::default(),
}
}
}
impl<Conn, Table, C> actix::Message for GetAll<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery + Unpin + 'static,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
C: Cache<Conn, Table> + 'static,
{
type Result = Result<Arc<HashMap<C::Id, C>>>;
}
impl<Conn, Table, C> CacheDbActor<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery + Unpin + 'static,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
C: Cache<Conn, Table>,
{
pub fn new(conn: Conn) -> Result<Self> {
let (cache, t) = Default::default();
let mut s = Self { conn, cache, t };
s.update()?;
Ok(s)
}
fn update(&mut self) -> Result<()> {
self.cache = Arc::new(C::read_all(&self.conn)?);
Ok(())
}
fn get(&self, id: C::Id) -> Option<C> {
self.cache.get(&id).cloned()
}
fn timer_update(&mut self, context: &mut Context<Self>) {
let dur = std::time::Duration::from_secs(60);
let _ = self.update();
TimerFunc::new(dur, Self::timer_update).spawn(context);
}
}
impl<Conn, Table, C> Actor for CacheDbActor<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery + Unpin + 'static,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
C: Cache<Conn, Table>,
{
type Context = Context<Self>;
fn started(&mut self, context: &mut Context<Self>) {
self.timer_update(context)
}
}
impl<Conn, Table, C> Handler<GetAll<Conn, Table, C>>
for CacheDbActor<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery + Unpin + 'static,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
C: Cache<Conn, Table>,
{
type Result = Result<Arc<HashMap<C::Id, C>>>;
fn handle(
&mut self,
_: GetAll<Conn, Table, C>,
_: &mut Context<Self>,
) -> Self::Result {
self.update()?;
Ok(Arc::clone(&self.cache))
}
}
impl<Conn, Table, C, W> Handler<Save<W>> for CacheDbActor<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery + Unpin + 'static,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
Table::FromClause: QueryFragment<Conn::Backend>,
C: Cache<Conn, Table>,
W: Insertable<Table> + 'static,
W::Values:
CanInsertInSingleQuery<Conn::Backend> + QueryFragment<Conn::Backend>,
{
type Result = Result<()>;
fn handle(&mut self, pred: Save<W>, _: &mut Context<Self>) -> Self::Result {
C::write_one(pred.0, &self.conn)?;
self.update()?;
Ok(())
}
}
impl<Conn, Table, C> Handler<Get<Conn, Table, C>>
for CacheDbActor<Conn, Table, C>
where
Conn: Connection + Unpin + 'static,
Conn::Backend: HasSqlType<Table::SqlType>,
Table: diesel::Table + HasTable<Table = Table> + AsQuery + Unpin + 'static,
Table::Query: QueryId + QueryFragment<Conn::Backend>,
C: Cache<Conn, Table>,
{
type Result = Result<Option<C>>;
fn handle(
&mut self,
Get { id }: Get<Conn, Table, C>,
_: &mut Context<Self>,
) -> Self::Result {
match self.get(id.clone()) {
Some(out) => Ok(Some(out)),
None => {
self.update()?;
Ok(self.get(id))
}
}
}
}