use crate::Ident;
use crate::error::OrmResult;
use crate::ident::IntoIdent;
use crate::sql::Sql;
use std::sync::Arc;
use tokio_postgres::types::ToSql;
#[derive(Debug, Clone)]
pub enum Op<T> {
Eq(T),
Ne(T),
Gt(T),
Gte(T),
Lt(T),
Lte(T),
Like(T),
Ilike(T),
NotLike(T),
NotIlike(T),
IsNull,
IsNotNull,
In(Vec<T>),
NotIn(Vec<T>),
Between(T, T),
NotBetween(T, T),
}
impl<T> Op<T> {
pub fn eq(val: T) -> Self {
Op::Eq(val)
}
pub fn ne(val: T) -> Self {
Op::Ne(val)
}
pub fn gt(val: T) -> Self {
Op::Gt(val)
}
pub fn gte(val: T) -> Self {
Op::Gte(val)
}
pub fn lt(val: T) -> Self {
Op::Lt(val)
}
pub fn lte(val: T) -> Self {
Op::Lte(val)
}
pub fn like(val: T) -> Self {
Op::Like(val)
}
pub fn ilike(val: T) -> Self {
Op::Ilike(val)
}
pub fn not_like(val: T) -> Self {
Op::NotLike(val)
}
pub fn not_ilike(val: T) -> Self {
Op::NotIlike(val)
}
pub fn is_null() -> Self {
Op::IsNull
}
pub fn is_not_null() -> Self {
Op::IsNotNull
}
pub fn in_list(vals: Vec<T>) -> Self {
Op::In(vals)
}
pub fn not_in(vals: Vec<T>) -> Self {
Op::NotIn(vals)
}
pub fn between(from: T, to: T) -> Self {
Op::Between(from, to)
}
pub fn not_between(from: T, to: T) -> Self {
Op::NotBetween(from, to)
}
}
#[derive(Debug, Clone)]
enum ConditionValue {
Single(Arc<dyn ToSql + Send + Sync>),
Pair(Arc<dyn ToSql + Send + Sync>, Arc<dyn ToSql + Send + Sync>),
List(Vec<Arc<dyn ToSql + Send + Sync>>),
None,
}
#[derive(Debug, Clone)]
enum ConditionInner {
Raw(String),
Expr {
column: Ident,
operator: &'static str,
value: ConditionValue,
},
}
#[derive(Debug, Clone)]
pub struct Condition(ConditionInner);
impl Condition {
pub fn new<I, T>(column: I, op: Op<T>) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
let column = column.into_ident()?;
let (operator, value) = match op {
Op::Eq(v) => ("=", ConditionValue::Single(Arc::new(v))),
Op::Ne(v) => ("!=", ConditionValue::Single(Arc::new(v))),
Op::Gt(v) => (">", ConditionValue::Single(Arc::new(v))),
Op::Gte(v) => (">=", ConditionValue::Single(Arc::new(v))),
Op::Lt(v) => ("<", ConditionValue::Single(Arc::new(v))),
Op::Lte(v) => ("<=", ConditionValue::Single(Arc::new(v))),
Op::Like(v) => ("LIKE", ConditionValue::Single(Arc::new(v))),
Op::Ilike(v) => ("ILIKE", ConditionValue::Single(Arc::new(v))),
Op::NotLike(v) => ("NOT LIKE", ConditionValue::Single(Arc::new(v))),
Op::NotIlike(v) => ("NOT ILIKE", ConditionValue::Single(Arc::new(v))),
Op::IsNull => ("IS NULL", ConditionValue::None),
Op::IsNotNull => ("IS NOT NULL", ConditionValue::None),
Op::In(vals) => {
let values: Vec<Arc<dyn ToSql + Send + Sync>> =
vals.into_iter().map(|v| Arc::new(v) as _).collect();
("IN", ConditionValue::List(values))
}
Op::NotIn(vals) => {
let values: Vec<Arc<dyn ToSql + Send + Sync>> =
vals.into_iter().map(|v| Arc::new(v) as _).collect();
("NOT IN", ConditionValue::List(values))
}
Op::Between(from, to) => (
"BETWEEN",
ConditionValue::Pair(Arc::new(from), Arc::new(to)),
),
Op::NotBetween(from, to) => (
"NOT BETWEEN",
ConditionValue::Pair(Arc::new(from), Arc::new(to)),
),
};
Ok(Condition(ConditionInner::Expr {
column,
operator,
value,
}))
}
pub fn raw(sql: impl Into<String>) -> Self {
Condition(ConditionInner::Raw(sql.into()))
}
pub fn eq<I, T>(column: I, value: T) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::Eq(value))
}
pub fn ne<I, T>(column: I, value: T) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::Ne(value))
}
pub fn gt<I, T>(column: I, value: T) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::Gt(value))
}
pub fn gte<I, T>(column: I, value: T) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::Gte(value))
}
pub fn lt<I, T>(column: I, value: T) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::Lt(value))
}
pub fn lte<I, T>(column: I, value: T) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::Lte(value))
}
pub fn like<I, T>(column: I, pattern: T) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::Like(pattern))
}
pub fn ilike<I, T>(column: I, pattern: T) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::Ilike(pattern))
}
pub fn not_like<I, T>(column: I, pattern: T) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::NotLike(pattern))
}
pub fn not_ilike<I, T>(column: I, pattern: T) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::NotIlike(pattern))
}
pub fn is_null<I>(column: I) -> OrmResult<Self>
where
I: IntoIdent,
{
Ok(Condition(ConditionInner::Expr {
column: column.into_ident()?,
operator: "IS NULL",
value: ConditionValue::None,
}))
}
pub fn is_not_null<I>(column: I) -> OrmResult<Self>
where
I: IntoIdent,
{
Ok(Condition(ConditionInner::Expr {
column: column.into_ident()?,
operator: "IS NOT NULL",
value: ConditionValue::None,
}))
}
pub fn in_list<I, T>(column: I, values: Vec<T>) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::In(values))
}
pub fn not_in<I, T>(column: I, values: Vec<T>) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::NotIn(values))
}
pub fn between<I, T>(column: I, from: T, to: T) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::Between(from, to))
}
pub fn not_between<I, T>(column: I, from: T, to: T) -> OrmResult<Self>
where
I: IntoIdent,
T: ToSql + Send + Sync + 'static,
{
Self::new(column, Op::NotBetween(from, to))
}
pub fn build(&self, param_idx: &mut usize) -> (String, Vec<&(dyn ToSql + Sync)>) {
match &self.0 {
ConditionInner::Raw(s) => (s.clone(), Vec::new()),
ConditionInner::Expr {
column,
operator,
value,
} => {
let col = column.to_sql();
match value {
ConditionValue::Single(v) => {
*param_idx += 1;
let sql = format!("{} {} ${}", col, operator, *param_idx);
(sql, vec![&**v as &(dyn ToSql + Sync)])
}
ConditionValue::Pair(a, b) => {
*param_idx += 1;
let p1 = *param_idx;
*param_idx += 1;
let p2 = *param_idx;
let sql = format!("{col} {operator} ${p1} AND ${p2}");
(
sql,
vec![&**a as &(dyn ToSql + Sync), &**b as &(dyn ToSql + Sync)],
)
}
ConditionValue::List(vals) => {
if vals.is_empty() {
if *operator == "IN" {
return ("1=0".to_string(), Vec::new());
}
return ("1=1".to_string(), Vec::new());
}
let mut placeholders = Vec::new();
let mut params: Vec<&(dyn ToSql + Sync)> = Vec::new();
for v in vals {
*param_idx += 1;
placeholders.push(format!("${}", *param_idx));
params.push(&**v as &(dyn ToSql + Sync));
}
let sql = format!("{} {} ({})", col, operator, placeholders.join(", "));
(sql, params)
}
ConditionValue::None => {
let sql = format!("{col} {operator}");
(sql, Vec::new())
}
}
}
}
}
pub fn append_to_sql(&self, sql: &mut Sql) {
match &self.0 {
ConditionInner::Raw(s) => {
sql.push(s);
}
ConditionInner::Expr {
column,
operator,
value,
} => match value {
ConditionValue::List(vals) if vals.is_empty() => {
if *operator == "IN" {
sql.push("1=0");
} else {
sql.push("1=1");
}
}
ConditionValue::Single(v) => {
sql.push(&column.to_sql());
sql.push(" ");
sql.push(operator);
sql.push(" ");
sql.push_bind_value(v.clone());
}
ConditionValue::Pair(a, b) => {
sql.push(&column.to_sql());
sql.push(" ");
sql.push(operator);
sql.push(" ");
sql.push_bind_value(a.clone());
sql.push(" AND ");
sql.push_bind_value(b.clone());
}
ConditionValue::List(vals) => {
sql.push(&column.to_sql());
sql.push(" ");
sql.push(operator);
sql.push(" (");
for (i, v) in vals.iter().enumerate() {
if i > 0 {
sql.push(", ");
}
sql.push_bind_value(v.clone());
}
sql.push(")");
}
ConditionValue::None => {
sql.push(&column.to_sql());
sql.push(" ");
sql.push(operator);
}
},
}
}
}