Skip to main content

keelson_core/clause/
having.rs

1use crate::expr::{Expr, IntoExpr};
2use crate::writer::{Expression, SqlWriter};
3
4/// `HAVING a AND b`
5///
6/// Structurally identical to [`Where`](super::Where) and deliberately a separate
7/// type: a statement has at most one of each, and a `having` mod must not be
8/// accepted where only a `WHERE` exists.
9#[derive(Debug, Clone, Default)]
10pub struct Having {
11    /// The conditions, in the order they were appended.
12    pub conditions: Vec<Expr>,
13}
14
15impl Having {
16    /// Append one condition.
17    pub fn append_having(&mut self, condition: impl IntoExpr) {
18        self.conditions.push(condition.into_expr());
19    }
20
21    /// Whether the clause is absent.
22    pub fn is_empty(&self) -> bool {
23        self.conditions.is_empty()
24    }
25}
26
27impl Expression for Having {
28    fn write_sql(&self, w: &mut SqlWriter<'_>) {
29        w.write_slice(&self.conditions, "HAVING ", " AND ", "");
30    }
31}
32
33/// A statement with a `HAVING` clause.
34pub trait HasHaving {
35    /// The `HAVING` clause to modify.
36    fn having_mut(&mut self) -> &mut Having;
37}
38
39impl HasHaving for Having {
40    fn having_mut(&mut self) -> &mut Having {
41        self
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use keelson_sqlcheck::testing::assert_frag_sql;
48
49    use super::*;
50    use crate::dialect::testing::Numbered;
51    use crate::expr::{Chain, arg};
52    use crate::value::Value;
53    use crate::writer::build;
54
55    /// `HAVING` is a fragment. The frame's select list is an aggregate so that a
56    /// real PostgreSQL accepts a `HAVING` with no `GROUP BY`, which is the shape
57    /// these cases are about.
58    const FRAME: &str = "SELECT count(*) FROM users {}";
59
60    #[test]
61    fn an_empty_having_writes_nothing() {
62        assert_frag_sql(FRAME, &build(&Numbered, &Having::default()).unwrap().0, "");
63        assert!(Having::default().is_empty());
64    }
65
66    #[test]
67    fn conditions_are_and_joined() {
68        let mut h = Having::default();
69        h.append_having(Expr::func("count", "1").gt(arg(1i32)));
70        h.append_having("sum(age) < 9");
71        let (sql, args) = build(&Numbered, &h).unwrap();
72        assert_frag_sql(FRAME, &sql, "HAVING (count(1) > $1) AND sum(age) < 9");
73        assert_eq!(args, vec![Value::I32(1)]);
74    }
75}