use std::borrow::Cow;
use crate::value::Value;
use crate::writer::DynExpr;
use super::node::Expr;
pub trait IntoExpr {
fn into_expr(self) -> Expr;
}
pub trait IntoExprList {
fn into_expr_list(self) -> Vec<Expr>;
}
pub trait IntoIdent {
fn into_ident_parts(self) -> Vec<Cow<'static, str>>;
}
impl IntoExpr for Expr {
fn into_expr(self) -> Expr {
self
}
}
impl IntoExprList for Expr {
fn into_expr_list(self) -> Vec<Expr> {
vec![self]
}
}
impl IntoExprList for () {
fn into_expr_list(self) -> Vec<Expr> {
Vec::new()
}
}
impl IntoExpr for Value {
fn into_expr(self) -> Expr {
Expr::Arg(self)
}
}
impl IntoExprList for Value {
fn into_expr_list(self) -> Vec<Expr> {
vec![Expr::Arg(self)]
}
}
impl IntoExpr for DynExpr {
fn into_expr(self) -> Expr {
Expr::Custom(self)
}
}
impl IntoExprList for DynExpr {
fn into_expr_list(self) -> Vec<Expr> {
vec![Expr::Custom(self)]
}
}
macro_rules! impl_from_text {
($($t:ty),+ $(,)?) => {
$(
impl IntoExpr for $t {
fn into_expr(self) -> Expr {
Expr::Raw(self.into())
}
}
impl IntoExprList for $t {
fn into_expr_list(self) -> Vec<Expr> {
vec![Expr::Raw(self.into())]
}
}
)+
};
}
impl_from_text!(&'static str, String, Cow<'static, str>);
macro_rules! impl_from_scalar {
($($t:ty),+ $(,)?) => {
$(
impl IntoExpr for $t {
fn into_expr(self) -> Expr {
Expr::Raw(Cow::Owned(self.to_string()))
}
}
impl IntoExprList for $t {
fn into_expr_list(self) -> Vec<Expr> {
vec![self.into_expr()]
}
}
)+
};
}
impl_from_scalar!(
bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64
);
impl<T: IntoExpr, const N: usize> IntoExprList for [T; N] {
fn into_expr_list(self) -> Vec<Expr> {
self.into_iter().map(IntoExpr::into_expr).collect()
}
}
impl<T: IntoExpr> IntoExprList for Vec<T> {
fn into_expr_list(self) -> Vec<Expr> {
self.into_iter().map(IntoExpr::into_expr).collect()
}
}
macro_rules! impl_expr_list_tuple {
($($name:ident),+) => {
#[allow(non_snake_case)]
impl<$($name: IntoExpr),+> IntoExprList for ($($name,)+) {
fn into_expr_list(self) -> Vec<Expr> {
let ($($name,)+) = self;
vec![$($name.into_expr()),+]
}
}
};
}
impl_expr_list_tuple!(A);
impl_expr_list_tuple!(A, B);
impl_expr_list_tuple!(A, B, C);
impl_expr_list_tuple!(A, B, C, D);
impl_expr_list_tuple!(A, B, C, D, E);
impl_expr_list_tuple!(A, B, C, D, E, F);
impl_expr_list_tuple!(A, B, C, D, E, F, G);
impl_expr_list_tuple!(A, B, C, D, E, F, G, H);
impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I);
impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J);
impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J, K);
impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_expr_list_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
macro_rules! impl_ident_from_text {
($($t:ty),+ $(,)?) => {
$(
impl IntoIdent for $t {
fn into_ident_parts(self) -> Vec<Cow<'static, str>> {
vec![self.into()]
}
}
)+
};
}
impl_ident_from_text!(&'static str, String, Cow<'static, str>);
impl<T: Into<Cow<'static, str>>, const N: usize> IntoIdent for [T; N] {
fn into_ident_parts(self) -> Vec<Cow<'static, str>> {
self.into_iter().map(Into::into).collect()
}
}
impl<T: Into<Cow<'static, str>>> IntoIdent for Vec<T> {
fn into_ident_parts(self) -> Vec<Cow<'static, str>> {
self.into_iter().map(Into::into).collect()
}
}
macro_rules! impl_ident_tuple {
($($name:ident),+) => {
#[allow(non_snake_case)]
impl<$($name: Into<Cow<'static, str>>),+> IntoIdent for ($($name,)+) {
fn into_ident_parts(self) -> Vec<Cow<'static, str>> {
let ($($name,)+) = self;
vec![$($name.into()),+]
}
}
};
}
impl_ident_tuple!(A);
impl_ident_tuple!(A, B);
impl_ident_tuple!(A, B, C);
impl_ident_tuple!(A, B, C, D);
#[cfg(test)]
mod tests {
use keelson_sqlcheck::testing::assert_frag_sql;
use super::*;
use crate::dialect::testing::Numbered;
use crate::writer::build;
const COND: &str = r#"SELECT "id" FROM users WHERE {}"#;
const VALUE: &str = r#"SELECT {} FROM users"#;
const EQ: &str = r#"SELECT "id" FROM users WHERE "age" = {}"#;
fn rendered(e: Expr) -> (String, Vec<Value>) {
build(&Numbered, &e).expect("render")
}
#[test]
fn text_becomes_raw_sql() {
for e in [
"age = 1".into_expr(),
String::from("age = 1").into_expr(),
Cow::Borrowed("age = 1").into_expr(),
] {
assert!(matches!(e, Expr::Raw(_)));
assert_frag_sql(COND, &rendered(e).0, "age = 1");
}
}
#[test]
fn numbers_become_literals_and_values_become_arguments() {
let (sql, args) = rendered(20i64.into_expr());
assert_frag_sql(VALUE, &sql, "20");
assert!(args.is_empty(), "a literal binds nothing");
let (sql, args) = rendered(Value::I64(20).into_expr());
assert_frag_sql(EQ, &sql, "$1");
assert_eq!(args, vec![Value::I64(20)]);
}
#[test]
fn booleans_and_floats_render_as_written() {
assert_frag_sql(VALUE, &rendered(true.into_expr()).0, "true");
assert_frag_sql(VALUE, &rendered(1.5f64.into_expr()).0, "1.5");
}
#[test]
fn a_list_can_be_a_tuple_an_array_a_vec_or_nothing() {
assert_eq!(().into_expr_list().len(), 0);
assert_eq!(("a", 1, Value::I32(2)).into_expr_list().len(), 3);
assert_eq!(["a", "b"].into_expr_list().len(), 2);
assert_eq!(vec![1i32, 2, 3].into_expr_list().len(), 3);
assert_eq!(Expr::raw("a").into_expr_list().len(), 1);
assert_eq!("a".into_expr_list().len(), 1);
}
#[test]
fn a_heterogeneous_tuple_keeps_each_conversion() {
let list = ("created_date", 1i32, Value::Text("x".into())).into_expr_list();
assert!(matches!(list[0], Expr::Raw(_)));
assert!(matches!(list[1], Expr::Raw(_)));
assert!(matches!(list[2], Expr::Arg(_)));
}
#[test]
fn an_identifier_takes_one_part_or_several() {
assert_frag_sql(VALUE, &rendered(Expr::ident("age")).0, r#""age""#);
assert_frag_sql(
VALUE,
&rendered(Expr::ident(("users", "id"))).0,
r#""users"."id""#,
);
assert_frag_sql(
VALUE,
&rendered(Expr::ident(["public", "users", "id"])).0,
r#""public"."users"."id""#,
);
assert_frag_sql(
VALUE,
&rendered(Expr::ident(vec![String::from("users"), String::from("id")])).0,
r#""users"."id""#,
);
assert_frag_sql(VALUE, &rendered(Expr::ident(("", "id"))).0, r#""id""#);
}
#[test]
fn an_erased_expression_arrives_as_custom() {
let e: DynExpr = crate::writer::dyn_expr("1 + 1");
assert!(matches!(e.clone().into_expr(), Expr::Custom(_)));
assert_frag_sql(VALUE, &rendered(e.into_expr()).0, "1 + 1");
}
}