use std::fmt;
use std::marker::PhantomData;
use super::value::{DbValue, ToDbValue};
pub struct Col<M, T> {
name: &'static str,
_p: PhantomData<fn() -> (M, T)>,
}
impl<M, T> Clone for Col<M, T> {
fn clone(&self) -> Self {
*self
}
}
impl<M, T> Copy for Col<M, T> {}
impl<M, T> fmt::Debug for Col<M, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Col").field(&self.name).finish()
}
}
impl<M, T> Col<M, T> {
pub const fn new(name: &'static str) -> Self {
Self {
name,
_p: PhantomData,
}
}
pub const fn name(&self) -> &'static str {
self.name
}
}
pub trait IntoColumn<M> {
fn column_name(&self) -> &str;
}
impl<M> IntoColumn<M> for &str {
fn column_name(&self) -> &str {
self
}
}
impl<M> IntoColumn<M> for &&str {
fn column_name(&self) -> &str {
self
}
}
impl<M> IntoColumn<M> for String {
fn column_name(&self) -> &str {
self
}
}
impl<M> IntoColumn<M> for &String {
fn column_name(&self) -> &str {
self
}
}
impl<M, T> IntoColumn<M> for Col<M, T> {
fn column_name(&self) -> &str {
self.name
}
}
pub trait Comparable<V> {}
impl<T: ToDbValue> Comparable<T> for T {}
impl<T: ToDbValue> Comparable<T> for Option<T> {}
impl Comparable<&str> for String {}
impl Comparable<&str> for Option<String> {}
#[non_exhaustive]
pub struct Condition<M> {
pub(super) kind: CondKind,
_p: PhantomData<fn() -> M>,
}
pub(super) enum CondKind {
Op(&'static str, &'static str, DbValue),
Null(&'static str, bool),
In(&'static str, Vec<DbValue>),
}
impl<M> fmt::Debug for Condition<M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Condition")
}
}
impl<M> Condition<M> {
fn new(kind: CondKind) -> Self {
Self {
kind,
_p: PhantomData,
}
}
}
macro_rules! comparison {
($($(#[$doc:meta])* $method:ident => $op:literal),* $(,)?) => {
$(
$(#[$doc])*
pub fn $method<V: ToDbValue>(self, value: V) -> Condition<M>
where
T: Comparable<V>,
{
Condition::new(CondKind::Op(self.name, $op, value.to_db_value()))
}
)*
};
}
impl<M, T> Col<M, T> {
comparison! {
eq => "=",
ne => "!=",
lt => "<",
lte => "<=",
gt => ">",
gte => ">=",
}
pub fn is_null(self) -> Condition<M> {
Condition::new(CondKind::Null(self.name, true))
}
pub fn is_not_null(self) -> Condition<M> {
Condition::new(CondKind::Null(self.name, false))
}
pub fn is_in<V: ToDbValue>(self, values: impl IntoIterator<Item = V>) -> Condition<M>
where
T: Comparable<V>,
{
let values = values.into_iter().map(|v| v.to_db_value()).collect();
Condition::new(CondKind::In(self.name, values))
}
}
impl<M> Col<M, String> {
pub fn like(self, pattern: &str) -> Condition<M> {
Condition::new(CondKind::Op(self.name, "like", pattern.to_db_value()))
}
}
impl<M> Col<M, Option<String>> {
pub fn like(self, pattern: &str) -> Condition<M> {
Condition::new(CondKind::Op(self.name, "like", pattern.to_db_value()))
}
}