use std::borrow::Cow;
use keelson_core::expr::{Chain, Expr, IntoExpr, IntoExprList};
#[allow(clippy::wrong_self_convention)]
pub trait MysqlOps: Chain {
#[must_use]
fn not_like(self, rhs: impl IntoExpr) -> Self {
self.op("NOT LIKE", rhs)
}
#[must_use]
fn like_escape(self, pattern: impl IntoExpr, escape: impl IntoExpr) -> Self {
self.step(move |lhs| {
Expr::join((lhs, Expr::raw("LIKE"), pattern, Expr::raw("ESCAPE"), escape))
})
}
#[must_use]
fn regexp(self, rhs: impl IntoExpr) -> Self {
self.op("REGEXP", rhs)
}
#[must_use]
fn not_regexp(self, rhs: impl IntoExpr) -> Self {
self.op("NOT REGEXP", rhs)
}
#[must_use]
fn rlike(self, rhs: impl IntoExpr) -> Self {
self.op("RLIKE", rhs)
}
#[must_use]
fn sounds_like(self, rhs: impl IntoExpr) -> Self {
self.op("SOUNDS LIKE", rhs)
}
#[must_use]
fn null_safe_eq(self, rhs: impl IntoExpr) -> Self {
self.op("<=>", rhs)
}
#[must_use]
fn bang_eq(self, rhs: impl IntoExpr) -> Self {
self.op("!=", rhs)
}
#[must_use]
fn xor(self, rhs: impl IntoExpr) -> Self {
self.op("XOR", rhs)
}
#[must_use]
fn is_true(self) -> Self {
self.step(|lhs| Expr::postfix(lhs, "IS TRUE"))
}
#[must_use]
fn is_not_true(self) -> Self {
self.step(|lhs| Expr::postfix(lhs, "IS NOT TRUE"))
}
#[must_use]
fn is_false(self) -> Self {
self.step(|lhs| Expr::postfix(lhs, "IS FALSE"))
}
#[must_use]
fn is_not_false(self) -> Self {
self.step(|lhs| Expr::postfix(lhs, "IS NOT FALSE"))
}
#[must_use]
fn is_unknown(self) -> Self {
self.step(|lhs| Expr::postfix(lhs, "IS UNKNOWN"))
}
#[must_use]
fn is_not_unknown(self) -> Self {
self.step(|lhs| Expr::postfix(lhs, "IS NOT UNKNOWN"))
}
#[must_use]
fn times(self, rhs: impl IntoExpr) -> Self {
self.op("*", rhs)
}
#[must_use]
fn divide(self, rhs: impl IntoExpr) -> Self {
self.op("/", rhs)
}
#[must_use]
fn div(self, rhs: impl IntoExpr) -> Self {
self.op("DIV", rhs)
}
#[must_use]
fn modulo(self, rhs: impl IntoExpr) -> Self {
self.op("MOD", rhs)
}
#[must_use]
fn bit_and(self, rhs: impl IntoExpr) -> Self {
self.op("&", rhs)
}
#[must_use]
fn bit_or(self, rhs: impl IntoExpr) -> Self {
self.op("|", rhs)
}
#[must_use]
fn bit_xor(self, rhs: impl IntoExpr) -> Self {
self.op("^", rhs)
}
#[must_use]
fn shift_left(self, rhs: impl IntoExpr) -> Self {
self.op("<<", rhs)
}
#[must_use]
fn shift_right(self, rhs: impl IntoExpr) -> Self {
self.op(">>", rhs)
}
#[must_use]
fn json_get(self, path: impl IntoExpr) -> Self {
self.op("->", path)
}
#[must_use]
fn json_get_text(self, path: impl IntoExpr) -> Self {
self.op("->>", path)
}
#[must_use]
fn member_of(self, array: impl IntoExpr) -> Self {
self.step(move |lhs| Expr::binary(lhs, "MEMBER OF", Expr::group(array.into_expr())))
}
#[must_use]
fn eq_any(self, subquery: impl IntoExprList) -> Self {
self.any("=", subquery)
}
#[must_use]
fn ne_all(self, subquery: impl IntoExprList) -> Self {
self.all("<>", subquery)
}
#[must_use]
fn any(self, op: &'static str, subquery: impl IntoExprList) -> Self {
self.step(move |lhs| {
Expr::join((
Expr::binary(lhs, op, Expr::raw("ANY")),
Expr::group(subquery),
))
})
}
#[must_use]
fn all(self, op: &'static str, subquery: impl IntoExprList) -> Self {
self.step(move |lhs| {
Expr::join((
Expr::binary(lhs, op, Expr::raw("ALL")),
Expr::group(subquery),
))
})
}
#[must_use]
fn collate(self, name: impl Into<Cow<'static, str>>) -> Self {
let name = name.into();
self.step(move |lhs| Expr::join((lhs, Expr::raw("COLLATE"), Expr::ident(name))))
}
#[must_use]
fn binary(self) -> Self {
self.step(|lhs| Expr::prefix("BINARY", lhs))
}
}
impl<C: Chain> MysqlOps for C {}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Mysql, arg, quote, s};
use keelson_core::build;
fn sql(e: Expr) -> String {
build(&Mysql, &e).expect("render").0
}
#[test]
fn every_operator_renders_with_one_set_of_parentheses() {
let col = || quote("name");
for (produced, expected) in [
(col().not_like(arg("a%")), "(`name` NOT LIKE ?)"),
(col().regexp(arg("^a")), "(`name` REGEXP ?)"),
(col().not_regexp(arg("^a")), "(`name` NOT REGEXP ?)"),
(col().rlike(arg("^a")), "(`name` RLIKE ?)"),
(col().sounds_like(arg("robert")), "(`name` SOUNDS LIKE ?)"),
(col().null_safe_eq(arg("a")), "(`name` <=> ?)"),
(col().bang_eq(arg("a")), "(`name` != ?)"),
(col().xor(arg(true)), "(`name` XOR ?)"),
(col().is_true(), "(`name` IS TRUE)"),
(col().is_not_true(), "(`name` IS NOT TRUE)"),
(col().is_false(), "(`name` IS FALSE)"),
(col().is_not_false(), "(`name` IS NOT FALSE)"),
(col().is_unknown(), "(`name` IS UNKNOWN)"),
(col().is_not_unknown(), "(`name` IS NOT UNKNOWN)"),
(col().times(2i32), "(`name` * 2)"),
(col().divide(2i32), "(`name` / 2)"),
(col().div(2i32), "(`name` DIV 2)"),
(col().modulo(2i32), "(`name` MOD 2)"),
(col().bit_and(3i32), "(`name` & 3)"),
(col().bit_or(3i32), "(`name` | 3)"),
(col().bit_xor(3i32), "(`name` ^ 3)"),
(col().shift_left(1i32), "(`name` << 1)"),
(col().shift_right(1i32), "(`name` >> 1)"),
(col().json_get(s("$.a")), "(`name` -> '$.a')"),
(col().json_get_text(s("$.a")), "(`name` ->> '$.a')"),
(col().binary(), "(BINARY `name`)"),
] {
assert_eq!(sql(produced), expected);
}
}
#[test]
fn the_multi_token_operators_keep_their_shape() {
assert_eq!(
sql(quote("name").like_escape(arg("a!_b"), s("!"))),
"(`name` LIKE ? ESCAPE '!')"
);
assert_eq!(
sql(arg(3i32).member_of(quote("body"))),
"(? MEMBER OF (`body`))"
);
assert_eq!(
sql(quote("id").eq_any(quote("sub"))),
"(`id` = ANY (`sub`))"
);
assert_eq!(
sql(quote("id").ne_all(quote("sub"))),
"(`id` <> ALL (`sub`))"
);
assert_eq!(
sql(quote("id").any(">", quote("sub"))),
"(`id` > ANY (`sub`))"
);
assert_eq!(
sql(quote("name").collate("utf8mb4_bin")),
"(`name` COLLATE `utf8mb4_bin`)"
);
}
}