use std::future::Future;
use std::marker::{PhantomData, Sync};
use tokio_postgres::{types::ToSql, Error, GenericClient};
use crate::prelude::*;
use crate::Queryable;
pub trait Query {
type Output;
fn execute<C: GenericClient, Q: Queryable<C>>(
self,
ergol: &Q,
) -> impl Future<Output = Result<Self::Output, Error>>;
}
pub enum Filter {
Binary {
column: &'static str,
value: Box<dyn ToSql + Send + Sync + 'static>,
operator: Operator,
},
And(Box<Filter>, Box<Filter>),
Or(Box<Filter>, Box<Filter>),
}
impl Filter {
pub fn to_string<'a>(
&'a self,
first_index: i32,
) -> (String, i32, Vec<&'a (dyn ToSql + Sync + 'static)>) {
match self {
Filter::Binary {
column,
operator,
value,
} => (
format!("\"{}\" {} ${}", column, operator.to_str(), first_index),
first_index + 1,
vec![value.as_ref()],
),
Filter::And(a, b) => {
let (a, next, mut args1) = a.to_string(first_index);
let (b, next, args2) = b.to_string(next);
args1.extend(args2);
(format!("({} AND {})", a, b), next, args1)
}
Filter::Or(a, b) => {
let (a, next, mut args1) = a.to_string(first_index);
let (b, next, args2) = b.to_string(next);
args1.extend(args2);
(format!("({} OR {})", a, b), next, args1)
}
}
}
pub fn and(self, other: Filter) -> Filter {
Filter::And(Box::new(self), Box::new(other))
}
pub fn or(self, other: Filter) -> Filter {
Filter::Or(Box::new(self), Box::new(other))
}
}
#[derive(Copy, Clone)]
pub enum Order {
Ascend,
Descend,
}
impl Order {
pub fn to_str(self) -> &'static str {
match self {
Order::Ascend => "ASC",
Order::Descend => "DESC",
}
}
}
pub struct OrderBy {
pub column: &'static str,
pub order: Order,
}
pub struct Select<T: ToTable + ?Sized> {
_marker: PhantomData<T>,
limit: Option<usize>,
offset: Option<usize>,
order_by: Option<OrderBy>,
filter: Option<Filter>,
}
impl<T: ToTable + Sync> Select<T> {
pub fn new() -> Select<T> {
Select {
_marker: PhantomData,
limit: None,
offset: None,
order_by: None,
filter: None,
}
}
pub fn limit(mut self, limit: usize) -> Select<T> {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: usize) -> Select<T> {
self.offset = Some(offset);
self
}
pub fn order_by(mut self, order_by: OrderBy) -> Select<T> {
self.order_by = Some(order_by);
self
}
pub fn filter(mut self, filter: Filter) -> Select<T> {
self.filter = Some(filter);
self
}
}
#[derive(Copy, Clone)]
pub enum Operator {
Eq,
Geq,
Leq,
Gt,
Lt,
Neq,
Like,
SimilarTo,
}
impl Operator {
pub fn to_str(self) -> &'static str {
match self {
Operator::Eq => "=",
Operator::Geq => ">=",
Operator::Leq => "<=",
Operator::Gt => ">",
Operator::Lt => "<",
Operator::Neq => "!=",
Operator::Like => "LIKE",
Operator::SimilarTo => "SIMILAR TO",
}
}
}
impl<T: ToTable + Sync> Query for Select<T> {
type Output = Vec<T>;
async fn execute<C: GenericClient, Q: Queryable<C>>(
self,
ergol: &Q,
) -> Result<Self::Output, Error> {
let filter = self.filter.as_ref().map(|x| x.to_string(1));
let query = format!(
"SELECT * FROM \"{}\"{}{}{}{};",
T::table_name(),
if let Some((filter, _, _)) = filter.as_ref() {
format!(" WHERE {}", filter)
} else {
String::new()
},
if let Some(order_by) = self.order_by.as_ref() {
format!(
" ORDER BY \"{}\" {}",
order_by.column,
order_by.order.to_str()
)
} else {
String::new()
},
if let Some(limit) = self.limit {
format!(" LIMIT {}", limit)
} else {
String::new()
},
if let Some(offset) = self.offset {
format!(" OFFSET {}", offset)
} else {
String::new()
}
);
if let Some((_, _, args)) = filter {
Ok(ergol
.client()
.query(&query as &str, &args[..])
.await?
.iter()
.map(<T as ToTable>::from_row)
.collect::<Vec<_>>())
} else {
Ok(ergol
.client()
.query(&query as &str, &[])
.await?
.iter()
.map(<T as ToTable>::from_row)
.collect::<Vec<_>>())
}
}
}
macro_rules! make_string_query {
($i: ident) => {
pub struct $i(pub Vec<String>);
impl $i {
pub fn single(s: String) -> $i {
$i(vec![s])
}
}
impl Query for $i {
type Output = ();
async fn execute<C: GenericClient, Q: Queryable<C>>(
self,
ergol: &Q,
) -> Result<Self::Output, Error> {
for query in &self.0 {
ergol.client().query(query as &str, &[]).await?;
}
Ok(())
}
}
};
}
make_string_query!(CreateTable);
make_string_query!(DropTable);
make_string_query!(CreateType);
make_string_query!(DropType);