mod case;
mod chain;
mod convert;
mod func;
mod node;
mod raw;
pub use case::CaseBuilder;
pub use chain::Chain;
pub use convert::{IntoExpr, IntoExprList, IntoIdent};
pub use func::FuncExpr;
pub use node::Expr;
pub use raw::RawArg;
use std::borrow::Cow;
use crate::value::ToValue;
pub fn raw(sql: impl Into<Cow<'static, str>>) -> Expr {
Expr::raw(sql)
}
pub fn template(sql: impl Into<Cow<'static, str>>, args: impl IntoIterator<Item = RawArg>) -> Expr {
Expr::template(sql, args)
}
pub fn literal(s: impl Into<Cow<'static, str>>) -> Expr {
Expr::literal(s)
}
pub fn quote(parts: impl IntoIdent) -> Expr {
Expr::ident(parts)
}
pub fn arg(v: impl ToValue) -> Expr {
Expr::arg(v)
}
pub fn args<V: ToValue>(vals: impl IntoIterator<Item = V>) -> Expr {
Expr::args(vals)
}
pub fn arg_group<V: ToValue>(vals: impl IntoIterator<Item = V>) -> Expr {
Expr::group(Expr::args(vals))
}
pub fn named(name: impl Into<Cow<'static, str>>) -> Expr {
Expr::named_arg(name)
}
pub fn placeholders(n: usize) -> Expr {
Expr::placeholders(n)
}
pub fn group(items: impl IntoExprList) -> Expr {
Expr::group(items)
}
pub fn f(name: impl Into<Cow<'static, str>>, args: impl IntoExprList) -> FuncExpr {
FuncExpr::new(name, args)
}
pub fn case() -> CaseBuilder {
CaseBuilder::new()
}
pub fn cast(expr: impl IntoExpr, type_name: impl Into<Cow<'static, str>>) -> Expr {
Expr::cast(expr, type_name)
}
pub fn not(e: impl IntoExpr) -> Expr {
Expr::prefix("NOT", e.into_expr().grouped())
}
pub fn and(items: impl IntoExprList) -> Expr {
Expr::join_with(" AND ", items).grouped()
}
pub fn or(items: impl IntoExprList) -> Expr {
Expr::join_with(" OR ", items).grouped()
}
#[cfg(test)]
mod tests {
use keelson_sqlcheck::testing::assert_frag_sql;
use super::*;
use crate::dialect::testing::Numbered;
use crate::value::Value;
use crate::writer::build;
const COND: &str = r#"SELECT "id" FROM users WHERE {}"#;
const VALUE: &str = r#"SELECT {} FROM users"#;
const IN_LIST: &str = r#"SELECT "id" FROM users WHERE "id" IN ({})"#;
const ROW: &str = r#"SELECT "id" FROM users WHERE ("id", "age") = {}"#;
const EQ: &str = r#"SELECT "id" FROM users WHERE "id" = {}"#;
fn sql(e: Expr) -> String {
build(&Numbered, &e).expect("render").0
}
#[test]
fn the_atomic_entry_points_render_as_themselves() {
assert_frag_sql(COND, &sql(raw("age = 1")), "age = 1");
assert_frag_sql(VALUE, &sql(literal("A")), "'A'");
assert_frag_sql(VALUE, &sql(quote(("users", "id"))), r#""users"."id""#);
assert_frag_sql(EQ, &sql(arg(1i32)), "$1");
assert_frag_sql(IN_LIST, &sql(args([1i32, 2])), "$1, $2");
assert_frag_sql(ROW, &sql(arg_group([1i32, 2])), "($1, $2)");
assert_frag_sql(IN_LIST, &sql(placeholders(2)), "$1, $2");
assert_frag_sql(ROW, &sql(group(("id", "age"))), "(id, age)");
}
#[test]
fn boolean_combinators_parenthesise_their_result() {
assert_frag_sql(
COND,
&sql(and(("age > 1", "age < 9"))),
"(age > 1 AND age < 9)",
);
assert_frag_sql(
COND,
&sql(or(("age > 1", "age < 9"))),
"(age > 1 OR age < 9)",
);
}
#[test]
fn not_parenthesises_its_operand_but_not_itself() {
assert_frag_sql(
COND,
&sql(not(Expr::binary("age", "=", arg(1i32)))),
"NOT (age = $1)",
);
assert_frag_sql(COND, &sql(not(quote("is_active"))), r#"NOT "is_active""#);
assert_frag_sql(
COND,
&sql(not(quote("age").eq(arg(1i32)))),
r#"NOT ("age" = $1)"#,
);
}
#[test]
fn cast_is_not_wrapped_because_it_is_already_self_delimiting() {
assert_frag_sql(
VALUE,
&sql(cast(quote("age"), "int")),
r#"CAST("age" AS int)"#,
);
}
#[test]
fn a_named_argument_binds_nothing() {
let (s, a) = build(&crate::dialect::testing::TestDialect, &named("id")).unwrap();
assert_eq!(s, ":id");
assert!(a.is_empty());
}
#[test]
fn every_entry_point_shares_one_argument_counter() {
let e = Expr::join((
arg(1i32),
arg_group([2i32, 3]),
template("f(?)", [RawArg::value(4i32)]),
f("g", (arg(5i32),)).into_expr(),
));
let (s, a) = build(&Numbered, &e).unwrap();
assert_eq!(s, "$1 ($2, $3) f($4) g($5)");
assert_eq!(
a,
vec![
Value::I32(1),
Value::I32(2),
Value::I32(3),
Value::I32(4),
Value::I32(5)
]
);
}
}