use std::borrow::Cow;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::marker::PhantomData;
use serde::de::DeserializeOwned;
pub trait TableName {
fn table_name() -> &'static str;
}
pub trait Wrapper<T>
where
T: DeserializeOwned,
T: TableName,
{
fn generate_sql(&self) -> String;
}
#[derive(Debug, Clone, PartialEq)]
pub enum CompareOperator {
Eq, Ne, Gt, Ge, Lt, Le, Like, NotLike, LikeLeft, LikeRight, IsNull, IsNotNull, In, NotIn, Between, NotBetween, }
impl Display for CompareOperator {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
CompareOperator::Eq => write!(f, "="),
CompareOperator::Ne => write!(f, "<>"),
CompareOperator::Gt => write!(f, ">"),
CompareOperator::Ge => write!(f, ">="),
CompareOperator::Lt => write!(f, "<"),
CompareOperator::Le => write!(f, "<="),
CompareOperator::Like => write!(f, "LIKE"),
CompareOperator::NotLike => write!(f, "NOT LIKE"),
CompareOperator::LikeLeft => write!(f, "LIKE"),
CompareOperator::LikeRight => write!(f, "LIKE"),
CompareOperator::IsNull => write!(f, "IS NULL"),
CompareOperator::IsNotNull => write!(f, "IS NOT NULL"),
CompareOperator::In => write!(f, "IN"),
CompareOperator::NotIn => write!(f, "NOT IN"),
CompareOperator::Between => write!(f, "BETWEEN"),
CompareOperator::NotBetween => write!(f, "NOT BETWEEN"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LogicalOperator {
And,
Or,
}
impl Display for LogicalOperator {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
LogicalOperator::And => write!(f, "AND"),
LogicalOperator::Or => write!(f, "OR"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum OrderDirection {
Asc,
Desc,
}
impl Display for OrderDirection {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
OrderDirection::Asc => write!(f, "ASC"),
OrderDirection::Desc => write!(f, "DESC"),
}
}
}
#[derive(Debug, Clone)]
pub enum ConditionValue<'a> {
None,
Single(Cow<'a, str>),
Multiple(Vec<Cow<'a, str>>),
Range(Cow<'a, str>, Cow<'a, str>),
}
#[derive(Debug, Clone)]
pub struct Condition<'a> {
pub column: Cow<'a, str>,
pub operator: CompareOperator,
pub value: ConditionValue<'a>,
}
#[derive(Debug, Clone)]
pub struct OrderBy<'a> {
pub column: Cow<'a, str>,
pub direction: OrderDirection,
}
#[derive(Debug, Clone)]
pub enum ConditionNode<'a> {
Leaf(Condition<'a>),
Branch {
left: Box<ConditionNode<'a>>,
op: LogicalOperator,
right: Box<ConditionNode<'a>>,
},
Group(Box<ConditionNode<'a>>),
Empty,
}
impl<'a> Default for ConditionNode<'a> {
fn default() -> Self {
ConditionNode::Empty
}
}
#[derive(Debug, Clone, Default)]
pub struct QueryWrapper<'a, T>
where
T: DeserializeOwned,
T: TableName,
{
pub root_condition: ConditionNode<'a>,
pub order_by: Vec<OrderBy<'a>>,
pub group_by: Vec<Cow<'a, str>>,
pub having: Option<Cow<'a, str>>,
pub limit: Option<usize>,
pub offset: Option<usize>,
pub select_columns: Vec<Cow<'a, str>>,
pub custom_sql: Option<Cow<'a, str>>,
_marker: PhantomData<T>,
}
impl<'a, T> QueryWrapper<'a, T>
where
T: DeserializeOwned,
T: TableName,
{
pub fn new() -> Self {
Self {
root_condition: ConditionNode::Empty,
order_by: Vec::new(),
group_by: Vec::new(),
having: None,
limit: None,
offset: None,
select_columns: Vec::new(),
custom_sql: None,
_marker: PhantomData,
}
}
fn add_condition(&mut self, condition: Condition<'a>) {
let new_node = ConditionNode::Leaf(condition);
match &self.root_condition {
ConditionNode::Empty => {
self.root_condition = new_node;
}
_ => {
let old_root = std::mem::replace(&mut self.root_condition, ConditionNode::Empty);
self.root_condition = ConditionNode::Branch {
left: Box::new(old_root),
op: LogicalOperator::And,
right: Box::new(new_node),
};
}
}
}
pub fn eq<F, V>(mut self, column: F, value: V) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::Eq,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn ne<F, V>(mut self, column: F, value: V) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::Ne,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn gt<F, V>(mut self, column: F, value: V) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::Gt,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn ge<F, V>(mut self, column: F, value: V) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::Ge,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn lt<F, V>(mut self, column: F, value: V) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::Lt,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn le<F, V>(mut self, column: F, value: V) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::Le,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn like<F, V>(mut self, column: F, value: V) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
let value_str = value.into();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::Like,
value: ConditionValue::Single(value_str),
});
self
}
pub fn not_like<F, V>(mut self, column: F, value: V) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
let value_str = value.into();
let formatted_value = format!("%{}%", value_str);
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::NotLike,
value: ConditionValue::Single(Cow::Owned(formatted_value)),
});
self
}
pub fn like_left<F, V>(mut self, column: F, value: V) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
let value_str = value.into();
let formatted_value = format!("%{}", value_str);
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::LikeLeft,
value: ConditionValue::Single(Cow::Owned(formatted_value)),
});
self
}
pub fn like_right<F, V>(mut self, column: F, value: V) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
let value_str = value.into();
let formatted_value = format!("{}%", value_str);
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::LikeRight,
value: ConditionValue::Single(Cow::Owned(formatted_value)),
});
self
}
pub fn is_null<F, V>(mut self, column: F) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::IsNull,
value: ConditionValue::None,
});
self
}
pub fn is_not_null<F, V>(mut self, column: F) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::IsNotNull,
value: ConditionValue::None,
});
self
}
pub fn r#in<F, V, I>(mut self, column: F, values: I) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
I: IntoIterator<Item = V>,
{
let column_str = column().into();
let values_vec: Vec<Cow<'a, str>> = values.into_iter().map(|v| v.into()).collect();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::In,
value: ConditionValue::Multiple(values_vec),
});
self
}
pub fn not_in<F, V, I>(mut self, column: F, values: I) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
I: IntoIterator<Item = V>,
{
let column_str = column().into();
let values_vec: Vec<Cow<'a, str>> = values.into_iter().map(|v| v.into()).collect();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::NotIn,
value: ConditionValue::Multiple(values_vec),
});
self
}
pub fn between<F, V>(mut self, column: F, value1: V, value2: V) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::Between,
value: ConditionValue::Range(value1.into(), value2.into()),
});
self
}
pub fn not_between<F, V>(mut self, column: F, value1: V, value2: V) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
self.add_condition(Condition {
column: column_str,
operator: CompareOperator::NotBetween,
value: ConditionValue::Range(value1.into(), value2.into()),
});
self
}
pub fn or(mut self) -> Self {
self.change_last_operator(LogicalOperator::Or);
self
}
fn change_last_operator(&mut self, op: LogicalOperator) {
fn find_last_branch<'b>(node: &'b mut ConditionNode) -> Option<&'b mut LogicalOperator> {
match node {
ConditionNode::Branch { left: _, op, right } => {
if let Some(last_op) = find_last_branch(right) {
Some(last_op)
} else {
Some(op)
}
}
ConditionNode::Group(inner) => find_last_branch(inner),
_ => None,
}
}
if let Some(last_op) = find_last_branch(&mut self.root_condition) {
*last_op = op;
}
}
pub fn nested<F>(mut self, f: F) -> Self
where
F: FnOnce(QueryWrapper<'a, T>) -> QueryWrapper<'a, T>,
{
let nested = f(QueryWrapper::new());
if let ConditionNode::Empty = nested.root_condition {
return self;
}
let nested_node = ConditionNode::Group(Box::new(nested.root_condition));
match &self.root_condition {
ConditionNode::Empty => {
self.root_condition = nested_node;
}
_ => {
let old_root = std::mem::replace(&mut self.root_condition, ConditionNode::Empty);
self.root_condition = ConditionNode::Branch {
left: Box::new(old_root),
op: LogicalOperator::And,
right: Box::new(nested_node),
};
}
}
self
}
pub fn apply<V: Into<Cow<'a, str>>>(mut self, sql: V) -> Self {
self.custom_sql = Some(sql.into());
self
}
pub fn order_by<F, V>(mut self, column: F, direction: OrderDirection) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
let column_str = column().into();
self.order_by.push(OrderBy {
column: column_str,
direction,
});
self
}
pub fn order_by_asc<F, V>(self, column: F) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
self.order_by(column, OrderDirection::Asc)
}
pub fn order_by_desc<F, V>(self, column: F) -> Self
where
F: FnOnce() -> V,
V: Into<Cow<'a, str>>,
{
self.order_by(column, OrderDirection::Desc)
}
pub fn group_by<V, I>(mut self, columns: I) -> Self
where
V: Into<Cow<'a, str>>,
I: IntoIterator<Item = V>,
{
self.group_by = columns.into_iter().map(|c| c.into()).collect();
self
}
pub fn having<V: Into<Cow<'a, str>>>(mut self, condition: V) -> Self {
self.having = Some(condition.into());
self
}
pub fn select<V, I>(mut self, columns: I) -> Self
where
V: Into<Cow<'a, str>>,
I: IntoIterator<Item = V>,
{
self.select_columns = columns.into_iter().map(|c| c.into()).collect();
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn build_where_clause(&self) -> String {
fn build_condition_node(node: &ConditionNode) -> String {
match node {
ConditionNode::Empty => String::new(),
ConditionNode::Leaf(condition) => match &condition.operator {
CompareOperator::IsNull | CompareOperator::IsNotNull => {
format!("{} {}", condition.column, condition.operator)
}
CompareOperator::In | CompareOperator::NotIn => {
if let ConditionValue::Multiple(values) = &condition.value {
let values_str = values
.iter()
.map(|v| format!("'{}'", v))
.collect::<Vec<_>>()
.join(", ");
format!(
"{} {} ({})",
condition.column, condition.operator, values_str
)
} else {
String::new()
}
}
CompareOperator::Between | CompareOperator::NotBetween => {
if let ConditionValue::Range(value1, value2) = &condition.value {
format!(
"{} {} '{}' AND '{}'",
condition.column, condition.operator, value1, value2
)
} else {
String::new()
}
}
_ => {
if let ConditionValue::Single(value) = &condition.value {
format!("{} {} '{}'", condition.column, condition.operator, value)
} else {
String::new()
}
}
},
ConditionNode::Branch { left, op, right } => {
let left_str = build_condition_node(left);
let right_str = build_condition_node(right);
if left_str.is_empty() {
right_str
} else if right_str.is_empty() {
left_str
} else {
format!("{} {} {}", left_str, op, right_str)
}
}
ConditionNode::Group(inner) => {
let inner_str = build_condition_node(inner);
if inner_str.is_empty() {
inner_str
} else {
format!("({})", inner_str)
}
}
}
}
let condition_str = build_condition_node(&self.root_condition);
if condition_str.is_empty() && self.custom_sql.is_none() {
return String::new();
}
let mut where_clause = String::from("WHERE ");
if !condition_str.is_empty() {
where_clause.push_str(&condition_str);
}
if let Some(sql) = &self.custom_sql {
if !condition_str.is_empty() {
where_clause.push_str(" AND ");
}
where_clause.push_str(sql);
}
where_clause
}
pub fn build_order_by_clause(&self) -> String {
if self.order_by.is_empty() {
return String::new();
}
let order_by_str = self
.order_by
.iter()
.map(|o| format!("{} {}", o.column, o.direction))
.collect::<Vec<_>>()
.join(", ");
format!("ORDER BY {}", order_by_str)
}
pub fn build_group_by_clause(&self) -> String {
if self.group_by.is_empty() {
return String::new();
}
format!("GROUP BY {}", self.group_by.join(", "))
}
pub fn build_having_clause(&self) -> String {
if let Some(having) = &self.having {
format!("HAVING {}", having)
} else {
String::new()
}
}
pub fn build_limit_offset_clause(&self) -> String {
let mut clause = String::new();
if let Some(limit) = self.limit {
clause.push_str(&format!("LIMIT {}", limit));
}
if let Some(offset) = self.offset {
if !clause.is_empty() {
clause.push_str(" ");
}
clause.push_str(&format!("OFFSET {}", offset));
}
clause
}
pub fn build_select_clause(&self) -> String {
if self.select_columns.is_empty() {
return String::from("*");
}
self.select_columns.join(", ")
}
fn build_sql(&self, table_name: &str) -> String {
let select_clause = self.build_select_clause();
let where_clause = self.build_where_clause();
let group_by_clause = self.build_group_by_clause();
let having_clause = self.build_having_clause();
let order_by_clause = self.build_order_by_clause();
let limit_offset_clause = self.build_limit_offset_clause();
let mut sql = format!("SELECT {} FROM {}", select_clause, table_name);
if !where_clause.is_empty() {
sql.push_str(&format!(" {}", where_clause));
}
if !group_by_clause.is_empty() {
sql.push_str(&format!(" {}", group_by_clause));
}
if !having_clause.is_empty() {
sql.push_str(&format!(" {}", having_clause));
}
if !order_by_clause.is_empty() {
sql.push_str(&format!(" {}", order_by_clause));
}
if !limit_offset_clause.is_empty() {
sql.push_str(&format!(" {}", limit_offset_clause));
}
sql
}
fn to_snake_case(pascal_case: &str) -> Cow<'_, str> {
if pascal_case.chars().all(|c| c.is_lowercase() || c == '_') {
return Cow::Borrowed(pascal_case);
}
let mut name = String::with_capacity(pascal_case.len() + 4);
let mut chars = pascal_case.chars().peekable();
while let Some(c) = chars.next() {
if c.is_uppercase() {
if !name.is_empty() && chars.peek().map_or(false, |next| next.is_lowercase()) {
name.push('_');
}
name.push(c.to_lowercase().next().unwrap());
} else {
name.push(c);
}
}
Cow::Owned(name)
}
}
impl<'a, T> Wrapper<T> for QueryWrapper<'_, T>
where
T: DeserializeOwned,
T: TableName,
{
fn generate_sql(&self) -> String {
self.build_sql(T::table_name())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[derive(Clone, Default, Deserialize)]
struct Test;
impl Test {
pub fn name() -> &'static str {
"name"
}
}
impl TableName for Test {
fn table_name() -> &'static str {
"test"
}
}
#[test]
fn test_simple_query() {
let query = QueryWrapper::<Test>::new()
.eq(|| "name", "张三")
.gt(|| "age", "18")
.build_sql("users");
assert_eq!(
query,
"SELECT * FROM users WHERE name = '张三' AND age > '18'"
);
}
#[test]
fn test_complex_query() {
let query = QueryWrapper::<Test>::new()
.eq(|| "status", "active")
.or()
.nested(|q| q.eq(|| "role", "admin").gt(|| "level", "5"))
.order_by_desc(|| "created_at")
.limit(10)
.offset(20)
.build_sql("users");
assert_eq!(
query,
"SELECT * FROM users WHERE status = 'active' OR (role = 'admin' AND level > '5') ORDER BY created_at DESC LIMIT 10 OFFSET 20"
);
}
#[test]
fn test_in_condition() {
let query = QueryWrapper::<Test>::new()
.r#in(|| "id", vec!["1", "2", "3"])
.build_sql("users");
assert_eq!(query, "SELECT * FROM users WHERE id IN ('1', '2', '3')");
}
#[test]
fn test_between_condition() {
let query = QueryWrapper::<Test>::new()
.between(|| "age", "18", "30")
.build_sql("users");
assert_eq!(query, "SELECT * FROM users WHERE age BETWEEN '18' AND '30'");
}
#[test]
fn test_like_condition() {
let name = || { "张三"};
let query = QueryWrapper::<Test>::new()
.like(Test::name, "张")
.build_sql("users");
assert_eq!(query, "SELECT * FROM users WHERE name LIKE '%张%'");
}
#[test]
fn test_group_by_having() {
let query = QueryWrapper::<Test>::new()
.select(vec!["department", "COUNT(*) as count"])
.group_by(vec!["department"])
.having("COUNT(*) > 5")
.build_sql("employees");
assert_eq!(
query,
"SELECT department, COUNT(*) as count FROM employees GROUP BY department HAVING COUNT(*) > 5"
);
}
#[test]
fn test_count() {
let query = QueryWrapper::<Test>::new()
.select(vec!["COUNT(1) as count"])
.eq(||"name", "huihui")
.generate_sql();
assert_eq!(query, "SELECT COUNT(1) as count FROM test WHERE name = 'huihui'");
}
}