use std::borrow::Cow;
use keelson_core::expr::{Chain, Expr, IntoExpr};
#[allow(clippy::wrong_self_convention)]
pub trait SqliteOps: Chain {
#[must_use]
fn glob(self, rhs: impl IntoExpr) -> Self {
self.op("GLOB", rhs)
}
#[must_use]
fn not_glob(self, rhs: impl IntoExpr) -> Self {
self.op("NOT GLOB", rhs)
}
#[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 match_(self, rhs: impl IntoExpr) -> Self {
self.op("MATCH", rhs)
}
#[must_use]
fn not_match(self, rhs: impl IntoExpr) -> Self {
self.op("NOT MATCH", rhs)
}
#[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 not_like_escape(self, pattern: impl IntoExpr, escape: impl IntoExpr) -> Self {
self.step(move |lhs| {
Expr::join((
lhs,
Expr::raw("NOT LIKE"),
pattern,
Expr::raw("ESCAPE"),
escape,
))
})
}
#[must_use]
fn json_get(self, rhs: impl IntoExpr) -> Self {
self.op("->", rhs)
}
#[must_use]
fn json_get_text(self, rhs: impl IntoExpr) -> Self {
self.op("->>", rhs)
}
#[must_use]
fn is_(self, rhs: impl IntoExpr) -> Self {
self.op("IS", rhs)
}
#[must_use]
fn is_not(self, rhs: impl IntoExpr) -> Self {
self.op("IS NOT", rhs)
}
#[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))))
}
}
impl<T: Chain> SqliteOps for T {}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Sqlite, arg, quote, s};
use keelson_core::build;
fn sql(e: Expr) -> String {
build(&Sqlite, &e).expect("render").0
}
#[test]
fn every_operator_renders_with_one_set_of_parentheses() {
let cases = [
(quote("a").glob(arg("x")), r#"("a" GLOB ?1)"#),
(quote("a").not_glob(arg("x")), r#"("a" NOT GLOB ?1)"#),
(quote("a").regexp(arg("x")), r#"("a" REGEXP ?1)"#),
(quote("a").not_regexp(arg("x")), r#"("a" NOT REGEXP ?1)"#),
(quote("a").match_(arg("x")), r#"("a" MATCH ?1)"#),
(quote("a").not_match(arg("x")), r#"("a" NOT MATCH ?1)"#),
(quote("a").not_like(arg("x")), r#"("a" NOT LIKE ?1)"#),
(quote("a").json_get(s("$.b")), r#"("a" -> '$.b')"#),
(quote("a").json_get_text(s("$.b")), r#"("a" ->> '$.b')"#),
(quote("a").is_(quote("b")), r#"("a" IS "b")"#),
(quote("a").is_not(quote("b")), r#"("a" IS NOT "b")"#),
];
for (e, expected) in cases {
assert_eq!(sql(e), expected);
}
}
#[test]
fn escape_is_a_third_operand_of_like() {
assert_eq!(
sql(quote("a").like_escape(s("100\\%"), s("\\"))),
r#"("a" LIKE '100\%' ESCAPE '\')"#
);
assert_eq!(
sql(quote("a").not_like_escape(s("100\\%"), s("\\"))),
r#"("a" NOT LIKE '100\%' ESCAPE '\')"#
);
}
#[test]
fn collate_quotes_its_sequence_name() {
assert_eq!(
sql(quote("a").collate("NOCASE")),
r#"("a" COLLATE "NOCASE")"#
);
}
#[test]
fn a_sqlite_operator_still_applies_after_a_core_one() {
assert_eq!(
sql(quote("a").is_null().or(quote("b").glob(s("x*")))),
r#"(("a" IS NULL) OR ("b" GLOB 'x*'))"#
);
}
}