From 8647223021e89bc53f216c0ed1f12f5855715d7b Mon Sep 17 00:00:00 2001 From: sanmu <578595193@qq.com> Date: Mon, 18 Sep 2023 17:11:10 +0800 Subject: [PATCH] FEAT: add statements: filter, where, having, select --- arel-macros/src/arel/arel_trait.rs | 17 +- arel-macros/src/arel/mod.rs | 2 +- src/lib.rs | 6 +- src/manager/mod.rs | 3 + src/manager/select_manager.rs | 17 ++ src/prelude.rs | 3 +- src/sql/mod.rs | 55 ++++- src/statements/filter/and_filter.rs | 18 ++ src/statements/filter/mod.rs | 347 ++++++++++++++++++++++++++++ src/statements/filter/or_filter.rs | 18 ++ src/statements/having.rs | 51 ++++ src/statements/mod.rs | 13 ++ src/statements/select.rs | 89 +++++++ src/statements/where.rs | 51 ++++ src/traits/mod.rs | 5 +- src/value/from.rs | 10 +- src/value/mod.rs | 72 +++++- 17 files changed, 750 insertions(+), 27 deletions(-) create mode 100644 src/manager/mod.rs create mode 100644 src/manager/select_manager.rs create mode 100644 src/statements/filter/and_filter.rs create mode 100644 src/statements/filter/mod.rs create mode 100644 src/statements/filter/or_filter.rs create mode 100644 src/statements/having.rs create mode 100644 src/statements/mod.rs create mode 100644 src/statements/select.rs create mode 100644 src/statements/where.rs diff --git a/arel-macros/src/arel/arel_trait.rs b/arel-macros/src/arel/arel_trait.rs index 664c5d7..09fdf72 100644 --- a/arel-macros/src/arel/arel_trait.rs +++ b/arel-macros/src/arel/arel_trait.rs @@ -1,18 +1,19 @@ -// fn _table_name() -> &'static str; +// fn _table_name() -> String; pub(crate) fn impl_table_name(input: &crate::ItemInput) -> syn::Result { let mut ret_token_stream = proc_macro2::TokenStream::new(); if let Some((table_name, _)) = input.get_args_path_value(vec![], "table_name", None)? { ret_token_stream.extend(quote::quote!( - fn _table_name() -> &'static str { - #table_name + fn _table_name() -> String { + #table_name.into() } )); } - if ret_token_stream.is_empty() { - Err(syn::Error::new_spanned(&input.input, r#"Please set arel(table_name = "xxx")"#)) - } else { - Ok(ret_token_stream) - } + // if ret_token_stream.is_empty() { + // Err(syn::Error::new_spanned(&input.input, r#"Please set arel(table_name = "xxx")"#)) + // } else { + // Ok(ret_token_stream) + // } + Ok(ret_token_stream) } // fn _primary_keys() -> Vec<&'static str>; pub(crate) fn impl_primary_keys(input: &crate::ItemInput) -> syn::Result { diff --git a/arel-macros/src/arel/mod.rs b/arel-macros/src/arel/mod.rs index 7ae4d57..f6ff47f 100644 --- a/arel-macros/src/arel/mod.rs +++ b/arel-macros/src/arel/mod.rs @@ -54,7 +54,7 @@ fn do_expand(input: &crate::ItemInput) -> syn::Result } impl #impl_generics arel::SuperArel for #model_name_ident #type_generics #where_clause { - // fn _table_name() -> &'static str; + // fn _table_name() -> String; #arel_trait_impl_table_name // fn _primary_keys() -> Vec<&'static str>; #arel_trait_impl_primary_keys diff --git a/src/lib.rs b/src/lib.rs index eb27307..70f0e07 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,14 +7,17 @@ compile_error!("feature `sqlite` and `postgres` shouldn't be enabled both."); #[cfg(all(feature = "mysql", feature = "postgres"))] compile_error!("feature `mysql` and `postgres` shouldn't be enabled both."); +pub mod statements; + pub mod db; pub mod error; +pub mod manager; pub mod prelude; pub mod sql; pub mod traits; pub mod value; -pub use arel_macros::arel; +pub use arel_macros::{self, arel}; pub use bytes; pub use chrono; pub use serde_json; @@ -22,6 +25,7 @@ pub use sqlx; pub use bytes::Bytes; pub use error::Error; +pub use manager::SelectManager; pub use sql::Sql; pub use value::{sub_value, Value}; diff --git a/src/manager/mod.rs b/src/manager/mod.rs new file mode 100644 index 0000000..7cc73fe --- /dev/null +++ b/src/manager/mod.rs @@ -0,0 +1,3 @@ +pub mod select_manager; + +pub use select_manager::SelectManager; diff --git a/src/manager/select_manager.rs b/src/manager/select_manager.rs new file mode 100644 index 0000000..c398d8d --- /dev/null +++ b/src/manager/select_manager.rs @@ -0,0 +1,17 @@ +use crate::prelude::*; +use std::marker::PhantomData; +use std::ops::RangeBounds; + +#[derive(Debug)] +pub struct SelectManager { + // select: crate::statements::select::Select, + // join: Option>, + // r#where: Option>, + // group: Option>, + // having: Option>, + // order: Option>, + // limit: Option, + // offset: Option, + // lock: Option, + _marker: PhantomData, +} diff --git a/src/prelude.rs b/src/prelude.rs index 80b4db3..8e7688e 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,5 +2,6 @@ pub use crate::sqlx::{self, Row}; pub use chrono; pub use serde_json; -pub use crate::arel; +pub use crate::statements::ArelStatement; pub use crate::{Arel, ArelAttributeFromRow, SuperArel}; +pub use arel_macros::arel; diff --git a/src/sql/mod.rs b/src/sql/mod.rs index a2840b2..8c44cca 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -3,7 +3,7 @@ mod query_builder; use query_builder::QueryBuilder; use std::ops::{Bound, RangeBounds}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Sql { pub raw_value: String, pub bind_indexs: Vec, @@ -20,6 +20,19 @@ impl Default for Sql { } } +impl TryFrom for String { + type Error = crate::Error; + fn try_from(sql: Sql) -> Result { + sql.to_sql_string() + } +} + +impl From for Sql { + fn from(value: T) -> Self { + Self::new(value) + } +} + impl Sql { pub fn new(value: T) -> Self { Self { @@ -38,6 +51,16 @@ impl Sql { self.bind_values.push(bind_value.into()); self } + pub fn push_binds>(&mut self, bind_values: Vec, separated_str: &str) -> &mut Self { + let len = bind_values.len(); + for (idx, bind_value) in bind_values.into_iter().enumerate() { + self.push_bind(bind_value); + if idx < len - 1 { + self.push_str(separated_str); + } + } + self + } pub fn push_str_with_bind, V: Into>(&mut self, raw_str: T, bind_value: V) -> &mut Self { self.push_str(raw_str); self.push_bind(bind_value); @@ -50,7 +73,17 @@ impl Sql { self.bind_values.extend(sql.bind_values); self } - pub fn to_debug_sql_string(&self) -> crate::Result { + pub fn push_sqls(&mut self, sqls: Vec, separated_str: &str) -> &mut Self { + let len = sqls.len(); + for (idx, sql) in sqls.into_iter().enumerate() { + self.push_sql(sql); + if idx < len - 1 { + self.push_str(separated_str); + } + } + self + } + pub fn to_sql_string(&self) -> crate::Result { let query_builder: QueryBuilder = self.try_into()?; Ok(query_builder.sql().to_string()) } @@ -61,22 +94,22 @@ impl Sql { /// /// ``` /// let sql = arel::Sql::range_sql("age", ..18).unwrap(); - /// assert_eq!(sql.to_debug_sql_string().unwrap(), r#"age < 18"#); + /// assert_eq!(sql.to_sql_string().unwrap(), r#"age < 18"#); /// /// let sql = arel::Sql::range_sql("age", ..=18).unwrap(); - /// assert_eq!(sql.to_debug_sql_string().unwrap(), r#"age <= 18"#); + /// assert_eq!(sql.to_sql_string().unwrap(), r#"age <= 18"#); /// /// let sql = arel::Sql::range_sql("age", 18..20).unwrap(); - /// assert_eq!(sql.to_debug_sql_string().unwrap(), r#"age >= 18 AND age < 20"#); + /// assert_eq!(sql.to_sql_string().unwrap(), r#"age >= 18 AND age < 20"#); /// /// let sql = arel::Sql::range_sql("age", 18..=20).unwrap(); - /// assert_eq!(sql.to_debug_sql_string().unwrap(), r#"age BETWEEN 18 AND 20"#); + /// assert_eq!(sql.to_sql_string().unwrap(), r#"age BETWEEN 18 AND 20"#); /// /// let sql = arel::Sql::range_sql("age", (std::ops::Bound::Excluded(18), std::ops::Bound::Included(20))).unwrap(); - /// assert_eq!(sql.to_debug_sql_string().unwrap(), r#"age > 18 AND age <= 20"#); + /// assert_eq!(sql.to_sql_string().unwrap(), r#"age > 18 AND age <= 20"#); /// /// let sql = arel::Sql::range_sql("age", 18..).unwrap(); - /// assert_eq!(sql.to_debug_sql_string().unwrap(), r#"age >= 18"#); + /// assert_eq!(sql.to_sql_string().unwrap(), r#"age >= 18"#); /// /// ``` pub fn range_sql, V: ToString, R: RangeBounds>(key: K, range: R) -> Option { @@ -106,6 +139,8 @@ impl Sql { } } +impl Sql {} + #[cfg(test)] mod tests { use crate::sub_value::{ValueInt, ValueString}; @@ -125,8 +160,8 @@ mod tests { sql3.push_sql(sql1).push_sql(sql2); #[cfg(any(feature = "sqlite", feature = "mysql"))] - assert_eq!(sql3.to_debug_sql_string().unwrap(), r#"select * from users where users.id = ? and name = ?"#.to_owned()); + assert_eq!(sql3.to_sql_string().unwrap(), r#"select * from users where users.id = ? and name = ?"#.to_owned()); #[cfg(any(feature = "postgres"))] - assert_eq!(sql3.to_debug_sql_string().unwrap(), r#"select * from users where users.id = $1 and name = $2"#.to_owned()); + assert_eq!(sql3.to_sql_string().unwrap(), r#"select * from users where users.id = $1 and name = $2"#.to_owned()); } } diff --git a/src/statements/filter/and_filter.rs b/src/statements/filter/and_filter.rs new file mode 100644 index 0000000..803d18d --- /dev/null +++ b/src/statements/filter/and_filter.rs @@ -0,0 +1,18 @@ +use super::ArelSubFilterStatement; + +#[derive(Debug, Default)] +pub struct AndFilter { + pub(crate) sqls: Vec, +} + +impl ArelSubFilterStatement for AndFilter { + fn sqls(&self) -> &Vec { + &self.sqls + } + fn sqls_mut(&mut self) -> &mut Vec { + &mut self.sqls + } + fn join_str(&self) -> &'static str { + " AND " + } +} diff --git a/src/statements/filter/mod.rs b/src/statements/filter/mod.rs new file mode 100644 index 0000000..b1c5a66 --- /dev/null +++ b/src/statements/filter/mod.rs @@ -0,0 +1,347 @@ +mod and_filter; +mod or_filter; + +pub(crate) use and_filter::AndFilter; +pub(crate) use or_filter::OrFilter; + +use crate::{statements::ArelStatement, Arel}; +use std::{fmt::Debug, marker::PhantomData, ops::Deref}; + +trait ArelSubFilterStatement: Debug { + fn sqls(&self) -> &Vec; + fn sqls_mut(&mut self) -> &mut Vec; + fn join_str(&self) -> &'static str; + fn to_sql(&self) -> crate::Result> { + let mut final_sql = crate::Sql::default(); + let len = self.sqls().len(); + if len > 0 { + for (idx, sql) in self.sqls().iter().enumerate() { + final_sql.push_sql(sql.clone()); + if idx < len - 1 { + final_sql.push_str(self.join_str()); + } + } + } + Ok(Some(final_sql)) + } +} + +#[derive(Debug)] +pub struct Filter { + sub_filters: Vec>, + _marker: PhantomData, +} +impl Default for Filter { + fn default() -> Self { + Self { + sub_filters: vec![], + _marker: PhantomData, + } + } +} + +impl ArelStatement for Filter { + fn to_sql(&self) -> crate::Result> { + let sub_filters: Vec<&Box> = self.sub_filters.iter().collect(); + if self.sub_filters.len() > 0 { + let mut final_sql = crate::Sql::new(""); + for (idx, sub_filter) in sub_filters.into_iter().enumerate() { + let sql = sub_filter.to_sql()?; + if let Some(sql) = sql { + if idx >= 1 { + final_sql.push_str(sub_filter.join_str()); + } + if sub_filter.join_str() == " OR " && sub_filter.sqls().len() > 1 { + final_sql.push_str("(").push_sql(sql).push_str(")"); + } else { + final_sql.push_sql(sql); + } + } + } + Ok(Some(final_sql)) + } else { + Ok(None) + } + } +} + +impl Filter { + /// # Examples + /// + /// ``` + /// use arel::prelude::*; + /// use arel::statements::filter::Filter; + /// #[arel] + /// struct User {} + /// impl Arel for User {} + /// let mut filter = Filter::::default(); + /// filter.and_filter("username", "sanmu"); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#""user"."username" = ?"#); + /// + /// filter.and_filter("age", vec![18, 20]); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#""user"."username" = ? AND "user"."age" IN (?, ?)"#); + /// + /// ``` + pub fn and_filter, V: Into>(&mut self, key: K, value: V) -> &mut Self { + self.and_filter_pairs(vec![(key, value)]) + } + /// # Examples + /// + /// ``` + /// use arel::prelude::*; + /// use arel::statements::filter::Filter; + /// #[arel] + /// struct User {} + /// impl Arel for User {} + /// let mut filter = Filter::::default(); + /// filter.and_filter_pairs(vec![("username", Into::::into("sanmu")), ("age", Into::::into(vec![18, 20]))]); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#""user"."username" = ? AND "user"."age" IN (?, ?)"#); + /// + /// filter.and_filter("gender", "male"); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#""user"."username" = ? AND "user"."age" IN (?, ?) AND "user"."gender" = ?"#); + /// + /// ``` + pub fn and_filter_pairs, V: Into>(&mut self, pairs: Vec<(K, V)>) -> &mut Self { + let table_name = M::table_name(); + let mut and_filter = AndFilter::default(); + for (key, value) in pairs.into_iter() { + let mut sql = crate::Sql::default(); + let value: crate::Value = value.into(); + sql.push_str(format!(r#""{}"."{}""#, table_name, key.as_ref())); + match &value { + crate::Value::Array(arrary) => match arrary.deref() { + Some(arr) => { + sql.push_str(" IN (").push_binds(arr.clone(), ", ").push_str(")"); + } + None => { + sql.push_str(" IS NULL"); + } + }, + _ => { + if !value.is_null() { + sql.push_str(" = ").push_bind(value); + } else { + sql.push_str(" IS NULL"); + } + } + } + and_filter.sqls.push(sql); + } + self.sub_filters.push(Box::new(and_filter)); + self + } + pub fn and_filter_sql>(&mut self, sql: S) -> &mut Self { + let mut and_filter = AndFilter::default(); + and_filter.sqls.push(sql.into()); + self.sub_filters.push(Box::new(and_filter)); + self + } + /// # Examples + /// + /// ``` + /// use arel::prelude::*; + /// use arel::statements::filter::Filter; + /// #[arel] + /// struct User {} + /// impl Arel for User {} + /// let mut filter = Filter::::default(); + /// filter.and_not_filter("username", "sanmu"); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#""user"."username" != ?"#); + /// + /// filter.and_not_filter("aga", vec![18, 20]); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#""user"."username" != ? AND "user"."aga" NOT IN (?, ?)"#); + /// + /// ``` + pub fn and_not_filter, V: Into>(&mut self, key: K, value: V) -> &mut Self { + self.and_not_filter_pairs(vec![(key, value)]) + } + pub fn and_not_filter_pairs, V: Into>(&mut self, pairs: Vec<(K, V)>) -> &mut Self { + let table_name = M::table_name(); + let mut and_filter = AndFilter::default(); + for (key, value) in pairs.into_iter() { + let mut sql = crate::Sql::default(); + let value: crate::Value = value.into(); + sql.push_str(format!(r#""{}"."{}""#, table_name, key.as_ref())); + match &value { + crate::Value::Array(arrary) => match arrary.deref() { + Some(arr) => { + sql.push_str(" NOT IN (").push_binds(arr.clone(), ", ").push_str(")"); + } + None => { + sql.push_str(" IS NOT NULL"); + } + }, + _ => { + if !value.is_null() { + sql.push_str(" != ").push_bind(value); + } else { + sql.push_str(" IS NOT NULL"); + } + } + } + and_filter.sqls.push(sql); + } + self.sub_filters.push(Box::new(and_filter)); + self + } + /// # Examples + /// + /// ``` + /// use arel::prelude::*; + /// use arel::statements::filter::Filter; + /// #[arel] + /// struct User {} + /// impl Arel for User {} + /// let mut filter = Filter::::default(); + /// filter.or_filter("username", "sanmu"); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#""user"."username" = ?"#); + /// + /// filter.or_filter("age", vec![18, 20]); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#""user"."username" = ? OR "user"."age" IN (?, ?)"#); + /// + /// filter.and_filter("gender", "male"); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#""user"."username" = ? OR "user"."age" IN (?, ?) AND "user"."gender" = ?"#); + /// + /// ``` + pub fn or_filter, V: Into>(&mut self, key: K, value: V) -> &mut Self { + self.or_filter_pairs(vec![(key, value)]) + } + /// # Examples + /// + /// ``` + /// use arel::prelude::*; + /// use arel::statements::filter::Filter; + /// #[arel] + /// struct User {} + /// impl Arel for User {} + /// let mut filter = Filter::::default(); + /// filter.or_filter_pairs(vec![("username", Into::::into("sanmu")), ("age", Into::::into(vec![18, 20]))]); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#"("user"."username" = ? OR "user"."age" IN (?, ?))"#); + /// + /// filter.or_filter("gender", "male"); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#"("user"."username" = ? OR "user"."age" IN (?, ?)) OR "user"."gender" = ?"#); + /// + /// ``` + pub fn or_filter_pairs, V: Into>(&mut self, pairs: Vec<(K, V)>) -> &mut Self { + let table_name = M::table_name(); + let mut or_filter = OrFilter::default(); + for (key, value) in pairs.into_iter() { + let mut sql = crate::Sql::default(); + let value: crate::Value = value.into(); + sql.push_str(format!(r#""{}"."{}""#, table_name, key.as_ref())); + match &value { + crate::Value::Array(arrary) => match arrary.deref() { + Some(arr) => { + sql.push_str(" IN (").push_binds(arr.clone(), ", ").push_str(")"); + } + None => { + sql.push_str(" IS NULL"); + } + }, + _ => { + if !value.is_null() { + sql.push_str(" = ").push_bind(value); + } else { + sql.push_str(" IS NULL"); + } + } + } + or_filter.sqls.push(sql); + } + self.sub_filters.push(Box::new(or_filter)); + self + } + pub fn or_filter_sql>(&mut self, sql: S) -> &mut Self { + let mut or_filter = OrFilter::default(); + or_filter.sqls.push(sql.into()); + self.sub_filters.push(Box::new(or_filter)); + self + } + /// # Examples + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// ``` + /// use arel::prelude::*; + /// use arel::statements::filter::Filter; + /// #[arel] + /// struct User {} + /// impl Arel for User {} + /// let mut filter = Filter::::default(); + /// filter.or_not_filter("username", "sanmu"); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#""user"."username" != ?"#); + /// + /// filter.or_not_filter("aga", vec![18, 20]); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#""user"."username" != ? OR "user"."aga" NOT IN (?, ?)"#); + /// + /// ``` + pub fn or_not_filter, V: Into>(&mut self, key: K, value: V) -> &mut Self { + self.or_not_filter_pairs(vec![(key, value)]) + } + pub fn or_not_filter_pairs, V: Into>(&mut self, pairs: Vec<(K, V)>) -> &mut Self { + let table_name = M::table_name(); + let mut or_filter = OrFilter::default(); + for (key, value) in pairs.into_iter() { + let mut sql = crate::Sql::default(); + let value: crate::Value = value.into(); + sql.push_str(format!(r#""{}"."{}""#, table_name, key.as_ref())); + match &value { + crate::Value::Array(arrary) => match arrary.deref() { + Some(arr) => { + sql.push_str(" NOT IN (").push_binds(arr.clone(), ", ").push_str(")"); + } + None => { + sql.push_str(" IS NOT NULL"); + } + }, + _ => { + if !value.is_null() { + sql.push_str(" != ").push_bind(value); + } else { + sql.push_str(" IS NOT NULL"); + } + } + } + or_filter.sqls.push(sql); + } + self.sub_filters.push(Box::new(or_filter)); + self + } + pub fn unfilter_starts_with>(&mut self, start: K) -> &mut Self { + for sub_filter in self.sub_filters.iter_mut() { + sub_filter.sqls_mut().retain(|sql| !sql.raw_value.starts_with(start.as_ref())); + } + self + } + /// # Examples + /// + /// ``` + /// use arel::prelude::*; + /// use arel::statements::filter::Filter; + /// #[arel] + /// struct User {} + /// impl Arel for User {} + /// let mut filter = Filter::::default(); + /// filter.and_filter_pairs(vec![("username", Into::::into("sanmu")), ("age", Into::::into(vec![18, 20]))]); + /// filter.unfilter("age"); + /// #[cfg(any(feature = "sqlite", feature = "mysql"))] + /// assert_eq!(filter.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#""user"."username" = ?"#); + /// + /// ``` + pub fn unfilter(&mut self, key: K) -> &mut Self { + let table_name = M::table_name(); + let start_string = format!(r#""{}"."{}""#, table_name, key.to_string()); + self.unfilter_starts_with(start_string) + } +} diff --git a/src/statements/filter/or_filter.rs b/src/statements/filter/or_filter.rs new file mode 100644 index 0000000..8d24b9f --- /dev/null +++ b/src/statements/filter/or_filter.rs @@ -0,0 +1,18 @@ +use super::ArelSubFilterStatement; + +#[derive(Debug, Default)] +pub struct OrFilter { + pub(crate) sqls: Vec, +} + +impl ArelSubFilterStatement for OrFilter { + fn sqls(&self) -> &Vec { + &self.sqls + } + fn sqls_mut(&mut self) -> &mut Vec { + &mut self.sqls + } + fn join_str(&self) -> &'static str { + " OR " + } +} diff --git a/src/statements/having.rs b/src/statements/having.rs new file mode 100644 index 0000000..d5670c6 --- /dev/null +++ b/src/statements/having.rs @@ -0,0 +1,51 @@ +use crate::{ + prelude::Arel, + statements::{filter::Filter, ArelStatement}, +}; +use std::ops::{Deref, DerefMut}; + +#[derive(Debug)] +pub struct Having(Filter); + +impl ArelStatement for Having { + fn to_sql(&self) -> crate::Result> { + if let Some(filter_sql) = self.0.to_sql()? { + let mut final_sql = crate::Sql::new("HAVING "); + final_sql.push_sql(filter_sql); + Ok(Some(final_sql)) + } else { + Ok(None) + } + } +} + +impl Deref for Having { + type Target = Filter; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for Having { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// # Examples +/// +/// ``` +/// use arel::prelude::*; +/// use arel::statements::Having; +/// #[arel] +/// struct User {} +/// impl Arel for User {} +/// let mut having = Having::::default(); +/// having.and_filter("username", "sanmu"); +/// #[cfg(any(feature = "sqlite", feature = "mysql"))] +/// assert_eq!(having.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#"HAVING "user"."username" = ?"#); +/// ``` +impl Default for Having { + fn default() -> Self { + Self(Filter::::default()) + } +} diff --git a/src/statements/mod.rs b/src/statements/mod.rs new file mode 100644 index 0000000..b59e0fa --- /dev/null +++ b/src/statements/mod.rs @@ -0,0 +1,13 @@ +pub mod filter; + +pub mod having; +pub mod select; +pub mod r#where; + +pub use having::Having; +pub use r#where::Where; +pub use select::Select; + +pub trait ArelStatement { + fn to_sql(&self) -> crate::Result>; +} diff --git a/src/statements/select.rs b/src/statements/select.rs new file mode 100644 index 0000000..a36cc94 --- /dev/null +++ b/src/statements/select.rs @@ -0,0 +1,89 @@ +use crate::{statements::ArelStatement, Arel}; +use std::marker::PhantomData; + +#[derive(Debug)] +pub struct Select { + distinct: bool, + sqls: Vec, + _marker: PhantomData, +} + +impl ArelStatement for Select { + fn to_sql(&self) -> crate::Result> { + let mut final_sql = crate::Sql::new("SELECT "); + if self.distinct { + final_sql.push_str("DISTINCT "); + } + + if self.sqls.len() > 0 { + final_sql.push_sqls(self.sqls.clone(), ", "); + } else { + final_sql.push_str(format!(r#""{}".*"#, M::table_name())); + } + final_sql.push_str(format!(r#" FROM "{}""#, M::table_name())); + Ok(Some(final_sql)) + } +} + +impl Default for Select { + fn default() -> Self { + Self { + distinct: false, + sqls: vec![], + _marker: PhantomData::, + } + } +} + +impl Select { + /// # Examples + /// + /// ``` + /// use arel::prelude::*; + /// use arel::statements::Select; + /// #[arel] + /// struct User {} + /// impl Arel for User {} + /// let select = Select::::new(vec!["name", "age"]); + /// assert_eq!(select.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#"SELECT "user"."name", "user"."age" FROM "user""#); + /// + /// ``` + pub fn new>(columns: Vec) -> Self { + let table_name = M::table_name(); + Self { + distinct: false, + sqls: columns.iter().map(|column| crate::Sql::new(format!(r#""{}"."{}""#, table_name, column.as_ref()))).collect(), + _marker: PhantomData::, + } + } + pub fn new_sql>(sql: S) -> Self { + Self { + distinct: false, + sqls: vec![sql.into()], + _marker: PhantomData::, + } + } + /// # Examples + /// + /// ``` + /// use arel::prelude::*; + /// use arel::statements::select::Select; + /// #[arel] + /// struct User {} + /// impl Arel for User {} + /// let select = Select::::new_sqls(vec!["name", "age"]); + /// assert_eq!(select.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#"SELECT name, age FROM "user""#); + /// + /// ``` + pub fn new_sqls>(sqls: Vec) -> Self { + Self { + distinct: false, + sqls: sqls.into_iter().map(|sql| sql.into()).collect(), + _marker: PhantomData::, + } + } + pub fn distinct(&mut self) -> &mut Self { + self.distinct = true; + self + } +} diff --git a/src/statements/where.rs b/src/statements/where.rs new file mode 100644 index 0000000..73f35be --- /dev/null +++ b/src/statements/where.rs @@ -0,0 +1,51 @@ +use crate::{ + prelude::Arel, + statements::{filter::Filter, ArelStatement}, +}; +use std::ops::{Deref, DerefMut}; + +#[derive(Debug)] +pub struct Where(Filter); + +impl ArelStatement for Where { + fn to_sql(&self) -> crate::Result> { + if let Some(filter_sql) = self.0.to_sql()? { + let mut final_sql = crate::Sql::new("WHERE "); + final_sql.push_sql(filter_sql); + Ok(Some(final_sql)) + } else { + Ok(None) + } + } +} + +impl Deref for Where { + type Target = Filter; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for Where { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// # Examples +/// #[cfg(any(feature = "sqlite", feature = "mysql"))] +/// ``` +/// use arel::prelude::*; +/// use arel::statements::Where; +/// #[arel] +/// struct User {} +/// impl Arel for User {} +/// let mut r#where = Where::::default(); +/// r#where.and_filter("username", "sanmu"); +/// #[cfg(any(feature = "sqlite", feature = "mysql"))] +/// assert_eq!(r#where.to_sql().unwrap().unwrap().to_sql_string().unwrap(), r#"WHERE "user"."username" = ?"#); +/// ``` +impl Default for Where { + fn default() -> Self { + Self(Filter::::default()) + } +} diff --git a/src/traits/mod.rs b/src/traits/mod.rs index aed43de..9562127 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -4,13 +4,14 @@ use std::future::Future; use std::pin::Pin; pub trait SuperArel { - fn _table_name() -> &'static str { + fn _table_name() -> String { let struct_full_name = std::any::type_name::(); regex::Regex::new(r#"((\w+)$)|(\w+<.+)"#) .unwrap() .find(&struct_full_name) .expect(&format!("match {} fail", struct_full_name)) .as_str() + .to_lowercase() } fn _primary_keys() -> Vec<&'static str> { vec!["id"] @@ -23,7 +24,7 @@ pub trait SuperArel { #[async_trait::async_trait] pub trait Arel: SuperArel { - fn table_name() -> &'static str { + fn table_name() -> String { Self::_table_name() } fn primary_keys() -> Vec<&'static str> { diff --git a/src/value/from.rs b/src/value/from.rs index c8ca441..1f98e3b 100644 --- a/src/value/from.rs +++ b/src/value/from.rs @@ -358,7 +358,11 @@ where fn from(value: Option) -> Self { match value { Some(v) => v.into(), - None => T::default().into(), + None => { + let mut value: Value = T::default().into(); + value.set_null(); + value + } } } } @@ -381,9 +385,9 @@ mod tests { assert_eq!(value, Value::Int(sub_value::ValueInt(Some(1)))); let value: Value = Option::::None.into(); - assert_eq!(value, Value::Int(sub_value::ValueInt(Some(0)))); + assert_eq!(value, Value::Int(sub_value::ValueInt(None))); let value: Value = (&Option::::None).into(); - assert_eq!(value, Value::Int(sub_value::ValueInt(Some(0)))); + assert_eq!(value, Value::Int(sub_value::ValueInt(None))); let v1: sub_value::ValueInt = 1.into(); let value: Value = v1.clone().into(); diff --git a/src/value/mod.rs b/src/value/mod.rs index 2d556ba..76f8e50 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -5,7 +5,10 @@ mod ops; pub mod sub_value; use serde::{Deserialize, Serialize}; -use std::cmp::PartialEq; +use std::{ + cmp::PartialEq, + ops::{Deref, DerefMut}, +}; // https://docs.rs/sqlx/latest/sqlx/types/index.html #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] @@ -54,3 +57,70 @@ pub enum Value { #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] ChronoTime(sub_value::ValueChronoTime), } + +impl Value { + pub fn is_null(&self) -> bool { + match self { + crate::Value::Bool(val) => val.deref().is_none(), + crate::Value::TinyInt(val) => val.deref().is_none(), + crate::Value::SmallInt(val) => val.deref().is_none(), + crate::Value::Int(val) => val.deref().is_none(), + crate::Value::BigInt(val) => val.deref().is_none(), + #[cfg(any(feature = "sqlite", feature = "mysql"))] + crate::Value::TinyUnsigned(val) => val.deref().is_none(), + #[cfg(any(feature = "sqlite", feature = "mysql"))] + crate::Value::SmallUnsigned(val) => val.deref().is_none(), + #[cfg(any(feature = "sqlite", feature = "mysql"))] + crate::Value::Unsigned(val) => val.deref().is_none(), + #[cfg(any(feature = "mysql"))] + crate::Value::BigUnsigned(val) => val.deref().is_none(), + crate::Value::Float(val) => val.deref().is_none(), + crate::Value::Double(val) => val.deref().is_none(), + crate::Value::String(val) => val.deref().is_none(), + crate::Value::Bytes(val) => val.deref().is_none(), + crate::Value::Array(val) => val.deref().is_none(), + #[cfg(feature = "with-json")] + crate::Value::Json(val) => val.deref().is_none(), + #[cfg(feature = "with-chrono")] + crate::Value::ChronoTimestamp(val) => val.deref().is_none(), + #[cfg(feature = "with-chrono")] + crate::Value::ChronoDateTime(val) => val.deref().is_none(), + #[cfg(feature = "with-chrono")] + crate::Value::ChronoDate(val) => val.deref().is_none(), + #[cfg(feature = "with-chrono")] + crate::Value::ChronoTime(val) => val.deref().is_none(), + } + } + pub fn set_null(&mut self) { + match self { + crate::Value::Bool(val) => *val.deref_mut() = None, + crate::Value::TinyInt(val) => *val.deref_mut() = None, + crate::Value::SmallInt(val) => *val.deref_mut() = None, + crate::Value::Int(val) => *val.deref_mut() = None, + crate::Value::BigInt(val) => *val.deref_mut() = None, + #[cfg(any(feature = "sqlite", feature = "mysql"))] + crate::Value::TinyUnsigned(val) => *val.deref_mut() = None, + #[cfg(any(feature = "sqlite", feature = "mysql"))] + crate::Value::SmallUnsigned(val) => *val.deref_mut() = None, + #[cfg(any(feature = "sqlite", feature = "mysql"))] + crate::Value::Unsigned(val) => *val.deref_mut() = None, + #[cfg(any(feature = "mysql"))] + crate::Value::BigUnsigned(val) => *val.deref_mut() = None, + crate::Value::Float(val) => *val.deref_mut() = None, + crate::Value::Double(val) => *val.deref_mut() = None, + crate::Value::String(val) => *val.deref_mut() = None, + crate::Value::Bytes(val) => *val.deref_mut() = None, + crate::Value::Array(val) => *val.deref_mut() = None, + #[cfg(feature = "with-json")] + crate::Value::Json(val) => *val.deref_mut() = None, + #[cfg(feature = "with-chrono")] + crate::Value::ChronoTimestamp(val) => *val.deref_mut() = None, + #[cfg(feature = "with-chrono")] + crate::Value::ChronoDateTime(val) => *val.deref_mut() = None, + #[cfg(feature = "with-chrono")] + crate::Value::ChronoDate(val) => *val.deref_mut() = None, + #[cfg(feature = "with-chrono")] + crate::Value::ChronoTime(val) => *val.deref_mut() = None, + }; + } +}