use super::{binary, case_when, cast, col, count_filter, int, now_minus};
use crate::ast::BinaryOp;
use crate::ast::{Condition, Expr, Operator, Value};
pub fn or_expr(left: impl Into<Expr>, right: impl Into<Expr>) -> Expr {
Expr::Binary {
left: Box::new(left.into()),
op: BinaryOp::Or,
right: Box::new(right.into()),
alias: None,
}
}
pub fn and_expr(left: impl Into<Expr>, right: impl Into<Expr>) -> Expr {
Expr::Binary {
left: Box::new(left.into()),
op: BinaryOp::And,
right: Box::new(right.into()),
alias: None,
}
}
pub fn add_expr(left: impl Into<Expr>, right: impl Into<Expr>) -> Expr {
binary(left, BinaryOp::Add, right).build()
}
pub fn inc(column: impl AsRef<str>, by: i64) -> Expr {
add_expr(col(column.as_ref()), int(by))
}
pub fn is_not_null_expr(column: impl AsRef<str>) -> Expr {
Expr::Binary {
left: Box::new(Expr::Named(column.as_ref().to_string())),
op: BinaryOp::IsNotNull,
right: Box::new(Expr::Literal(Value::Null)), alias: None,
}
}
pub fn is_null_expr(column: impl AsRef<str>) -> Expr {
Expr::Binary {
left: Box::new(Expr::Named(column.as_ref().to_string())),
op: BinaryOp::IsNull,
right: Box::new(Expr::Literal(Value::Null)),
alias: None,
}
}
pub fn all<I>(conditions: I) -> Vec<Condition>
where
I: IntoIterator<Item = Condition>,
{
conditions.into_iter().collect()
}
pub fn and(a: Condition, b: Condition) -> Vec<Condition> {
vec![a, b]
}
pub fn and3(a: Condition, b: Condition, c: Condition) -> Vec<Condition> {
vec![a, b, c]
}
pub fn count_where(condition: Condition) -> super::AggregateBuilder {
count_filter(vec![condition])
}
pub fn count_where_all<I>(conditions: I) -> super::AggregateBuilder
where
I: IntoIterator<Item = Condition>,
{
count_filter(conditions.into_iter().collect())
}
pub fn recent(duration: &str) -> Condition {
recent_col("created_at", duration)
}
pub fn recent_col(column: &str, duration: &str) -> Condition {
Condition {
left: Expr::Named(column.to_string()),
op: Operator::Gt,
value: Value::Expr(Box::new(now_minus(duration))),
is_array_unnest: false,
}
}
pub fn in_list<I, S>(column: &str, values: I) -> Condition
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let list: Vec<Value> = values
.into_iter()
.map(|v| Value::String(v.as_ref().to_string()))
.collect();
Condition {
left: Expr::Named(column.to_string()),
op: Operator::In,
value: Value::Array(list),
is_array_unnest: false,
}
}
pub fn percentage(numerator: &str, denominator: &str) -> super::CaseBuilder {
let division = binary(
cast(col(numerator), "float8").build(),
BinaryOp::Div,
cast(col(denominator), "float8").build(),
)
.build();
let multiplied = binary(division, BinaryOp::Mul, Expr::Literal(Value::Float(100.0))).build();
case_when(super::gt(denominator, 0), multiplied).otherwise(Expr::Literal(Value::Float(0.0)))
}
pub fn exists(query: crate::ast::Qail) -> Expr {
Expr::Exists {
query: Box::new(query),
negated: false,
alias: None,
}
}
pub fn not_exists(query: crate::ast::Qail) -> Expr {
Expr::Exists {
query: Box::new(query),
negated: true,
alias: None,
}
}
pub fn subquery(query: crate::ast::Qail) -> Expr {
Expr::Subquery {
query: Box::new(query),
alias: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::builders::eq;
#[test]
fn test_count_where() {
let agg = count_where(eq("status", "active"));
let expr = agg.alias("active_count");
assert!(matches!(expr, Expr::Aggregate { alias: Some(a), .. } if a == "active_count"));
}
#[test]
fn test_recent_is_ast_native() {
let cond = recent("24 hours");
assert!(matches!(cond.op, Operator::Gt));
assert!(matches!(cond.value, Value::Expr(_)));
}
#[test]
fn test_in_list() {
let cond = in_list("status", ["a", "b", "c"]);
assert!(matches!(cond.op, Operator::In));
}
#[test]
fn test_percentage() {
let builder = percentage("delivered", "sent");
let expr = builder.alias("rate");
assert!(matches!(expr, Expr::Case { alias: Some(a), .. } if a == "rate"));
}
#[test]
fn test_add_expr() {
let expr = add_expr(col("a"), int(2));
assert!(matches!(
expr,
Expr::Binary {
op: BinaryOp::Add,
..
}
));
}
#[test]
fn test_inc() {
let expr = inc("counter", 1);
assert!(matches!(
expr,
Expr::Binary {
op: BinaryOp::Add,
..
}
));
}
}