use crate::expr::{Expr, IntoExpr};
use crate::writer::{Expression, SqlWriter};
#[derive(Debug, Clone, Default)]
pub struct Where {
pub conditions: Vec<Expr>,
}
impl Where {
pub fn append_where(&mut self, condition: impl IntoExpr) {
self.conditions.push(condition.into_expr());
}
pub fn is_empty(&self) -> bool {
self.conditions.is_empty()
}
}
impl Expression for Where {
fn write_sql(&self, w: &mut SqlWriter<'_>) {
w.write_slice(&self.conditions, "WHERE ", " AND ", "");
}
}
pub trait HasWhere {
fn where_mut(&mut self) -> &mut Where;
}
impl HasWhere for Where {
fn where_mut(&mut self) -> &mut Where {
self
}
}
#[cfg(test)]
mod tests {
use keelson_sqlcheck::testing::assert_frag_sql;
use super::*;
use crate::dialect::testing::Numbered;
use crate::expr::{Chain, arg, or, quote};
use crate::value::Value;
use crate::writer::build;
const FRAME: &str = r#"SELECT "id" FROM users {}"#;
fn sql(e: &impl Expression) -> String {
build(&Numbered, e).expect("render").0
}
#[test]
fn an_empty_where_writes_nothing_not_even_the_keyword() {
let (sql, args) = build(&Numbered, &Where::default()).unwrap();
assert_frag_sql(FRAME, &sql, "");
assert!(args.is_empty());
assert!(Where::default().is_empty());
}
#[test]
fn conditions_are_and_joined_and_numbered_in_order() {
let mut wh = Where::default();
wh.append_where(quote("age").eq(arg(30i32)));
wh.append_where(quote("name").eq(arg("kubo")));
wh.append_where("email IS NULL");
let (sql, args) = build(&Numbered, &wh).unwrap();
assert_frag_sql(
FRAME,
&sql,
r#"WHERE ("age" = $1) AND ("name" = $2) AND email IS NULL"#,
);
assert_eq!(args, vec![Value::I32(30), Value::Text("kubo".into())]);
}
#[test]
fn a_disjunction_is_one_condition() {
let mut wh = Where::default();
wh.append_where(or((
quote("age").eq(arg(30i32)),
quote("name").eq(arg("kubo")),
)));
assert_frag_sql(FRAME, &sql(&wh), r#"WHERE (("age" = $1) OR ("name" = $2))"#);
}
}