use std::marker::PhantomData;
use crate::expr::{Expr, OrderDirection, OrderExpr};
use crate::value::Value;
#[derive(Debug, Clone)]
pub struct Column<T> {
name: &'static str,
table: &'static str,
_phantom: PhantomData<T>,
}
impl<T> Column<T> {
pub const fn new(name: &'static str, table: &'static str) -> Self {
Self {
name,
table,
_phantom: PhantomData,
}
}
pub const fn name(&self) -> &'static str {
self.name
}
pub const fn table(&self) -> &'static str {
self.table
}
pub const fn into_any(&self) -> AnyColumn {
AnyColumn {
name: self.name,
table: self.table,
}
}
fn any(&self) -> AnyColumn {
self.into_any()
}
}
impl<T> Column<T>
where
T: Into<Value> + Clone,
{
pub fn eq(&self, val: impl Into<Value>) -> Expr {
Expr::Eq(self.any(), val.into())
}
pub fn ne(&self, val: impl Into<Value>) -> Expr {
Expr::Ne(self.any(), val.into())
}
pub fn gt(&self, val: impl Into<Value>) -> Expr {
Expr::Gt(self.any(), val.into())
}
pub fn gte(&self, val: impl Into<Value>) -> Expr {
Expr::Gte(self.any(), val.into())
}
pub fn lt(&self, val: impl Into<Value>) -> Expr {
Expr::Lt(self.any(), val.into())
}
pub fn lte(&self, val: impl Into<Value>) -> Expr {
Expr::Lte(self.any(), val.into())
}
pub fn between(&self, low: impl Into<Value>, high: impl Into<Value>) -> Expr {
Expr::Between(self.any(), low.into(), high.into())
}
pub fn in_list(&self, vals: Vec<impl Into<Value>>) -> Expr {
let values: Vec<Value> = vals.into_iter().map(|v| v.into()).collect();
Expr::InList(self.any(), values)
}
pub fn not_in(&self, vals: Vec<impl Into<Value>>) -> Expr {
let values: Vec<Value> = vals.into_iter().map(|v| v.into()).collect();
Expr::NotIn(self.any(), values)
}
}
impl<T> Column<T> {
pub fn like(&self, pattern: impl Into<Value>) -> Expr {
Expr::Like(self.any(), pattern.into())
}
pub fn ilike(&self, pattern: impl Into<Value>) -> Expr {
Expr::ILike(self.any(), pattern.into())
}
pub fn contains(&self, val: &str) -> Expr {
Expr::Like(self.any(), Value::String(format!("%{}%", val)))
}
pub fn starts_with(&self, val: &str) -> Expr {
Expr::Like(self.any(), Value::String(format!("{}%", val)))
}
pub fn ends_with(&self, val: &str) -> Expr {
Expr::Like(self.any(), Value::String(format!("%{}", val)))
}
}
impl<T> Column<T> {
pub fn is_null(&self) -> Expr {
Expr::IsNull(self.any())
}
pub fn is_not_null(&self) -> Expr {
Expr::IsNotNull(self.any())
}
}
impl<T> Column<T> {
pub fn asc(&self) -> OrderExpr {
OrderExpr::new(self.any(), OrderDirection::Asc)
}
pub fn desc(&self) -> OrderExpr {
OrderExpr::new(self.any(), OrderDirection::Desc)
}
pub fn asc_nulls_last(&self) -> OrderExpr {
OrderExpr::new(self.any(), OrderDirection::AscNullsLast)
}
pub fn desc_nulls_first(&self) -> OrderExpr {
OrderExpr::new(self.any(), OrderDirection::DescNullsFirst)
}
}
#[derive(Debug, Clone)]
pub struct AnyColumn {
name: &'static str,
table: &'static str,
}
impl AnyColumn {
pub const fn new(name: &'static str, table: &'static str) -> Self {
Self { name, table }
}
pub const fn name(&self) -> &'static str {
self.name
}
pub const fn table(&self) -> &'static str {
self.table
}
}
#[cfg(test)]
mod tests {
use super::*;
fn col_id() -> Column<i32> {
Column::new("id", "users")
}
fn col_age() -> Column<i32> {
Column::new("age", "users")
}
fn col_name() -> Column<String> {
Column::new("name", "users")
}
fn col_email() -> Column<String> {
Column::new("email", "users")
}
#[test]
fn column_name_and_table() {
let col = col_age();
assert_eq!(col.name(), "age");
assert_eq!(col.table(), "users");
}
#[test]
fn column_into_any() {
let any = col_age().into_any();
assert_eq!(any.name(), "age");
assert_eq!(any.table(), "users");
}
#[test]
fn column_eq() {
let expr = col_age().eq(25);
match expr {
Expr::Eq(col, Value::Int(25)) => {
assert_eq!(col.name(), "age");
}
_ => panic!("Expected Eq with Int(25)"),
}
}
#[test]
fn column_ne() {
let expr = col_age().ne(0);
assert!(matches!(expr, Expr::Ne(_, Value::Int(0))));
}
#[test]
fn column_gt() {
let expr = col_age().gt(25);
assert!(matches!(expr, Expr::Gt(_, Value::Int(25))));
}
#[test]
fn column_gte() {
let expr = col_age().gte(18);
assert!(matches!(expr, Expr::Gte(_, Value::Int(18))));
}
#[test]
fn column_lt() {
let expr = col_age().lt(65);
assert!(matches!(expr, Expr::Lt(_, Value::Int(65))));
}
#[test]
fn column_lte() {
let expr = col_age().lte(100);
assert!(matches!(expr, Expr::Lte(_, Value::Int(100))));
}
#[test]
fn column_between() {
let expr = col_age().between(18, 65);
match expr {
Expr::Between(col, Value::Int(18), Value::Int(65)) => {
assert_eq!(col.name(), "age");
}
_ => panic!("Expected Between(18, 65)"),
}
}
#[test]
fn column_in_list() {
let expr = col_id().in_list(vec![1, 2, 3]);
match expr {
Expr::InList(col, vals) => {
assert_eq!(col.name(), "id");
assert_eq!(vals.len(), 3);
assert_eq!(vals[0], Value::Int(1));
assert_eq!(vals[1], Value::Int(2));
assert_eq!(vals[2], Value::Int(3));
}
_ => panic!("Expected InList"),
}
}
#[test]
fn column_in_list_empty() {
let expr = col_id().in_list(Vec::<i32>::new());
match expr {
Expr::InList(_, vals) => assert!(vals.is_empty()),
_ => panic!("Expected InList"),
}
}
#[test]
fn column_not_in() {
let expr = col_id().not_in(vec![4, 5]);
match expr {
Expr::NotIn(col, vals) => {
assert_eq!(col.name(), "id");
assert_eq!(vals.len(), 2);
}
_ => panic!("Expected NotIn"),
}
}
#[test]
fn column_like() {
let expr = col_name().like("A%");
match expr {
Expr::Like(col, Value::String(ref s)) => {
assert_eq!(col.name(), "name");
assert_eq!(s, "A%");
}
_ => panic!("Expected Like"),
}
}
#[test]
fn column_ilike() {
let expr = col_name().ilike("alice%");
assert!(matches!(expr, Expr::ILike(_, _)));
}
#[test]
fn column_contains() {
let expr = col_name().contains("foo");
match expr {
Expr::Like(_, Value::String(ref s)) => {
assert_eq!(s, "%foo%");
}
_ => panic!("Expected Like with %foo%"),
}
}
#[test]
fn column_starts_with() {
let expr = col_name().starts_with("A");
match expr {
Expr::Like(_, Value::String(ref s)) => {
assert_eq!(s, "A%");
}
_ => panic!("Expected Like with A%"),
}
}
#[test]
fn column_ends_with() {
let expr = col_name().ends_with("z");
match expr {
Expr::Like(_, Value::String(ref s)) => {
assert_eq!(s, "%z");
}
_ => panic!("Expected Like with %z"),
}
}
#[test]
fn column_is_null() {
let expr = col_email().is_null();
match expr {
Expr::IsNull(col) => assert_eq!(col.name(), "email"),
_ => panic!("Expected IsNull"),
}
}
#[test]
fn column_is_not_null() {
let expr = col_email().is_not_null();
match expr {
Expr::IsNotNull(col) => assert_eq!(col.name(), "email"),
_ => panic!("Expected IsNotNull"),
}
}
#[test]
fn column_asc() {
let o = col_name().asc();
assert_eq!(o.column.name(), "name");
assert_eq!(o.direction, OrderDirection::Asc);
}
#[test]
fn column_desc() {
let o = col_age().desc();
assert_eq!(o.direction, OrderDirection::Desc);
}
#[test]
fn column_asc_nulls_last() {
let o = col_age().asc_nulls_last();
assert_eq!(o.direction, OrderDirection::AscNullsLast);
}
#[test]
fn column_desc_nulls_first() {
let o = col_age().desc_nulls_first();
assert_eq!(o.direction, OrderDirection::DescNullsFirst);
}
#[test]
fn column_chain_and() {
let expr = col_age().gt(25).and(col_name().eq("Alice"));
assert!(matches!(expr, Expr::And(_, _)));
}
#[test]
fn column_chain_or() {
let expr = col_age().lt(18).or(col_age().gt(65));
assert!(matches!(expr, Expr::Or(_, _)));
}
#[test]
fn column_chain_not() {
let expr = col_email().is_null().not();
assert!(matches!(expr, Expr::Not(_)));
}
#[test]
fn column_complex_filter() {
let expr = col_age()
.between(18, 65)
.and(col_email().is_not_null())
.or(col_name().eq("admin"));
match expr {
Expr::Or(left, right) => {
assert!(matches!(*left, Expr::And(_, _)));
assert!(matches!(*right, Expr::Eq(_, _)));
}
_ => panic!("Expected Or(And(...), Eq(...))"),
}
}
#[test]
fn column_string_eq() {
let expr = col_name().eq("Alice");
match expr {
Expr::Eq(_, Value::String(ref s)) => assert_eq!(s, "Alice"),
_ => panic!("Expected Eq with String"),
}
}
#[test]
fn column_i16() {
let col: Column<i16> = Column::new("age", "users");
let expr = col.eq(25i16);
assert!(matches!(expr, Expr::Eq(_, Value::Short(25))));
}
#[test]
fn column_i64() {
let col: Column<i64> = Column::new("big_id", "users");
let expr = col.eq(999i64);
assert!(matches!(expr, Expr::Eq(_, Value::BigInt(999))));
}
#[test]
fn column_bool() {
let col: Column<bool> = Column::new("is_active", "users");
let expr = col.eq(true);
assert!(matches!(expr, Expr::Eq(_, Value::Bool(true))));
}
}