use std::{
marker::PhantomData,
ops::{Deref, Not},
};
use tokio_postgres::types::ToSql;
use crate::query::Where;
#[derive(Clone, Copy, Debug)]
pub struct TypedColumn<T: ToSql + Sync> {
column: Column,
rs_type: PhantomData<T>,
}
#[derive(Copy, Clone, Debug)]
pub struct Column {
pub column_name: &'static str,
pub table_name: &'static str,
nullable: bool,
unique: bool,
primary_key: bool,
generated: bool,
}
macro_rules! impl_prop_typed_col {
($($prop:ident),+) => {
$(
pub const fn $prop(mut self) -> TypedColumn<T> {
self.column.$prop = true;
self
}
)*
};
}
macro_rules! impl_prop_col {
($($prop:ident),+) => {
$(
pub const fn $prop(&self) -> bool {
self.$prop
}
)*
};
}
impl<T: ToSql + Sync + Send + 'static> TypedColumn<T> {
pub const fn new(table_name: &'static str, column_name: &'static str) -> TypedColumn<T> {
TypedColumn {
column: Column::new(table_name, column_name),
rs_type: PhantomData::<T>,
}
}
impl_prop_typed_col!(nullable, unique, primary_key, generated);
pub fn eq<'a>(&self, other: &'a T) -> Where<'a> {
Where::new(
format!("{}.{} = ?", self.table_name, self.column_name),
vec![other],
)
}
}
impl<T: ToSql + Sync + Send + 'static + PartialOrd> TypedColumn<T> {
pub fn gt<'a>(&self, other: &'a T) -> Where<'a> {
Where::new(
format!("{}.{} > ?", self.table_name, self.column_name),
vec![other],
)
}
pub fn gte<'a>(&self, other: &'a T) -> Where<'a> {
Where::new(
format!("{}.{} >= ?", self.table_name, self.column_name),
vec![other],
)
}
pub fn lt<'a>(&self, other: &'a T) -> Where<'a> {
Where::new(
format!("{}.{} < ?", self.table_name, self.column_name),
vec![other],
)
}
pub fn lte<'a>(&self, other: &'a T) -> Where<'a> {
Where::new(
format!("{}.{} <= ?", self.table_name, self.column_name),
vec![other],
)
}
}
impl<'a, T: ToSql + Sync + 'a> TypedColumn<Option<T>> {
pub fn null(&self) -> Where<'a> {
Where::new(
format!("{}.{} IS NULL", self.table_name, self.column_name),
vec![],
)
}
pub fn not_noll(&self) -> Where<'a> {
self.null().not()
}
}
impl<'a, T: ToSql + Sync + 'a> TypedColumn<Vec<T>> {
pub fn contains(&self, value: &'a T) -> Where<'a> {
Where::new(
format!("? = ANY({}.{})", self.table_name, self.column_name),
vec![value],
)
}
pub fn contains_not(&self, value: &'a T) -> Where<'a> {
self.contains(value).not()
}
pub fn contains_any(&self, values: &'a Vec<&'a T>) -> Where<'a> {
Where::new(
format!("{}.{} && ?", self.table_name, self.column_name),
vec![values],
)
}
pub fn contains_all(&self, values: &'a Vec<&'a T>) -> Where<'a> {
Where::new(
format!("{}.{} @> ?", self.table_name, self.column_name),
vec![values],
)
}
pub fn contains_none(&self, values: &'a Vec<&'a T>) -> Where<'a> {
self.contains_any(values).not()
}
}
impl<'a> TypedColumn<String> {
pub fn contains(&self, other: &'a String) -> Where<'a> {
Where::new(format!("POSITION(? in {})", self.full_name()), vec![other])
}
}
impl<T: ToSql + Sync> Deref for TypedColumn<T> {
type Target = Column;
fn deref(&self) -> &Self::Target {
&self.column
}
}
impl Column {
const fn new(table_name: &'static str, column_name: &'static str) -> Column {
Column {
column_name,
table_name,
nullable: false,
unique: false,
primary_key: false,
generated: false,
}
}
impl_prop_col!(unique, nullable, primary_key, generated);
pub const fn column_name(&self) -> &'static str {
self.column_name
}
pub const fn table_name(&self) -> &'static str {
self.table_name
}
#[inline]
pub fn full_name(&self) -> String {
format!("{}.{}", self.table_name, self.column_name)
}
}
#[cfg(test)]
mod tests {
#![allow(dead_code)]
use crate::{
prelude::*,
query::{PushChunk, Where},
};
impl<'a> Where<'a> {
fn to_stmt(&mut self) -> String {
let mut q = Query::<u64>::default();
self.push_to_buffer(&mut q);
q.0
}
}
#[derive(Model)]
struct Book {
id: i64,
title: String,
pages: Vec<String>,
}
#[test]
fn equals() {
assert_eq!(Book::title.eq(&"ABC".into()).to_stmt(), "book.title = ?")
}
#[test]
fn greater_than() {
assert_eq!(Book::id.gt(&1).to_stmt(), "book.id > ?");
}
#[test]
fn greater_than_equals() {
assert_eq!(Book::id.gte(&1).to_stmt(), "book.id >= ?");
}
#[test]
fn less_than() {
assert_eq!(Book::id.lt(&1).to_stmt(), "book.id < ?")
}
#[test]
fn less_than_equals() {
assert_eq!(Book::id.lte(&1).to_stmt(), "book.id <= ?")
}
#[test]
fn complete_query() {
let q = Book::select()
.where_(Book::title.eq(&"The Communist Manifesto".into()))
.where_(Book::pages.contains(&"You have nothing to lose but your chains!".into()))
.where_(Book::id.gt(&3))
.to_query()
.0;
assert_eq!(q, "SELECT book.id, book.title, book.pages FROM book WHERE (book.title = $1) AND ($2 = ANY(book.pages)) AND (book.id > $3)");
}
}