use crate::{AkitaValue, DetectionResult, DetectionSeverity, SqlInjectionDetector};
use std::fmt;
use std::fmt::{Display, Formatter};
use tracing::trace;
#[derive(Debug, Clone, PartialEq)]
pub struct Wrapper {
table: Option<String>,
alias: Option<String>,
select_columns: Vec<String>,
join_clauses: Vec<JoinClause>,
where_conditions: Vec<Condition>,
group_by_columns: Vec<String>,
having_conditions: Vec<Condition>,
order_by_clauses: Vec<OrderByClause>,
set_operations: Vec<SetOperation>,
distinct: bool,
limit_value: Option<u64>,
offset_value: Option<u64>,
comment: Option<String>,
param_name_seq: i32,
parameters: Vec<AkitaValue>,
next_condition_active: bool,
skip_mode: bool,
option_mode: OptionState,
last_sql: Option<String>,
apply_conditions: Vec<String>,
sql_injection_detector: SqlInjectionDetector,
}
#[allow(unused)]
#[derive(Debug, Clone, PartialEq)]
enum OptionState {
Normal,
ExpectingValue,
}
#[derive(Debug, Clone, PartialEq)]
pub struct JoinClause {
join_type: JoinType,
pub table: String,
alias: Option<String>,
pub condition: Condition,
}
#[derive(Debug, Clone, PartialEq)]
pub enum JoinType {
Inner,
Left,
Right,
Full,
}
impl Display for JoinType {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
JoinType::Inner => write!(f, "INNER JOIN"),
JoinType::Left => write!(f, "LEFT JOIN"),
JoinType::Right => write!(f, "RIGHT JOIN"),
JoinType::Full => write!(f, "FULL JOIN"),
}
}
}
impl ToString for Wrapper {
fn to_string(&self) -> String {
format!("{:?}", self)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Condition {
pub column: String,
pub operator: SqlOperator,
pub value: AkitaValue,
and_or: AndOr,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OrderByClause {
pub column: String,
direction: OrderDirection,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SetOperation {
pub column: String,
pub value: AkitaValue,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SqlOperator {
Eq,
Ne,
Gt,
Ge,
Lt,
Le,
Like,
NotLike,
IsNull,
IsNotNull,
In,
NotIn,
Between,
NotBetween,
}
impl SqlOperator {
pub fn is_null_check(&self) -> bool {
matches!(self, SqlOperator::IsNull | SqlOperator::IsNotNull)
}
}
impl Display for SqlOperator {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
SqlOperator::Eq => write!(f, "="),
SqlOperator::Ne => write!(f, "!="),
SqlOperator::Gt => write!(f, ">"),
SqlOperator::Ge => write!(f, ">="),
SqlOperator::Lt => write!(f, "<"),
SqlOperator::Le => write!(f, "<="),
SqlOperator::Like => write!(f, "LIKE"),
SqlOperator::NotLike => write!(f, "NOT LIKE"),
SqlOperator::IsNull => write!(f, "IS NULL"),
SqlOperator::IsNotNull => write!(f, "IS NOT NULL"),
SqlOperator::In => write!(f, "IN"),
SqlOperator::NotIn => write!(f, "NOT IN"),
SqlOperator::Between => write!(f, "BETWEEN"),
SqlOperator::NotBetween => write!(f, "NOT BETWEEN"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum AndOr {
And,
Or,
}
impl Display for AndOr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
AndOr::And => write!(f, "AND"),
AndOr::Or => write!(f, "OR"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum OrderDirection {
Asc,
Desc,
}
impl Display for OrderDirection {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
OrderDirection::Asc => write!(f, "ASC"),
OrderDirection::Desc => write!(f, "DESC"),
}
}
}
impl Wrapper {
pub fn new() -> Self {
Self {
table: None,
alias: None,
select_columns: Vec::new(),
join_clauses: Vec::new(),
where_conditions: Vec::new(),
group_by_columns: Vec::new(),
having_conditions: Vec::new(),
order_by_clauses: Vec::new(),
set_operations: Vec::new(),
distinct: false,
sql_injection_detector: SqlInjectionDetector::new(),
limit_value: None,
offset_value: None,
comment: None,
param_name_seq: 0,
parameters: Vec::new(),
apply_conditions: Vec::new(),
last_sql: None,
next_condition_active: true,
skip_mode: false,
option_mode: OptionState::Normal,
}
}
pub fn get_where_conditions(&self) -> &Vec<Condition> {
self.where_conditions.as_ref()
}
pub fn get_join_clauses(&self) -> &Vec<JoinClause> {
self.join_clauses.as_ref()
}
pub fn get_having_conditions(&self) -> &Vec<Condition> {
self.having_conditions.as_ref()
}
pub fn get_select_columns(&self) -> Vec<String> {
self.select_columns.clone()
}
pub fn get_set_operations(&self) -> &Vec<SetOperation> {
self.set_operations.as_ref()
}
pub fn where_conditions(&mut self, where_conditions: Vec<Condition>) {
self.where_conditions = where_conditions;
}
pub fn join_clauses(&mut self, join_clauses: Vec<JoinClause>) {
self.join_clauses = join_clauses;
}
pub fn apply_conditions(&mut self, apply_conditions: Vec<String>) {
self.apply_conditions = apply_conditions;
}
pub fn having_conditions(&mut self, having_conditions: Vec<Condition>) {
self.having_conditions = having_conditions;
}
pub fn order_by_clauses(&mut self, order_by_clauses: Vec<OrderByClause>) {
self.order_by_clauses = order_by_clauses;
}
pub fn set_operations(&mut self, set_operations: Vec<SetOperation>) {
self.set_operations = set_operations;
}
pub fn table<S: Into<String>>(mut self, table: S) -> Self {
self.table = Some(table.into());
self
}
pub fn get_table(&self) -> Option<&String> {
self.table.as_ref()
}
pub fn alias<S: Into<String>>(mut self, alias: S) -> Self {
self.alias = Some(alias.into());
self
}
pub fn last<S: Into<String>>(mut self, sql: S) -> Self {
self.last_sql = Some(sql.into());
self
}
pub fn apply<S, V, I>(mut self, sql: S, params: Option<I>) -> Self
where
S: Into<String>,
V: Into<AkitaValue>,
I: IntoIterator<Item = V>,
{
if !self.should_add_condition() {
return self;
}
let sql_template = sql.into();
let params_vec: Option<Vec<AkitaValue>> =
params.map(|iter| iter.into_iter().map(|v| v.into()).collect());
if let Some(ref params_vec) = params_vec {
let param_list: Vec<(String, String)> = params_vec
.iter()
.enumerate()
.filter_map(|(i, param)| {
if let Some(str_val) = param.as_str() {
Some((format!("param_{}", i), str_val.to_string()))
} else {
Some((format!("param_{}", i), param.to_string()))
}
})
.collect();
let security_result = self
.sql_injection_detector
.detect_sql_security(&sql_template, Some(¶m_list));
self.handle_security_result(&sql_template, &security_result);
if security_result.is_dangerous
&& matches!(security_result.severity, DetectionSeverity::Critical)
{
tracing::error!(
"Serious security threats, skip condition additions: {}",
sql_template
);
return self;
}
} else {
let security_result = self
.sql_injection_detector
.detect_sql_security(&sql_template, None);
self.handle_security_result(&sql_template, &security_result);
if security_result.is_dangerous
&& matches!(security_result.severity, DetectionSeverity::Critical)
{
tracing::error!(
"Serious security threats, skip condition addition: {}",
sql_template
);
return self;
}
}
let placeholder_count = sql_template.matches('?').count();
if let Some(params_vec) = params_vec {
if params_vec.len() != placeholder_count {
tracing::warn!("Number of parameters does not match - SQL has {} placeholders, but {} parameters are provided",placeholder_count,params_vec.len());
}
for param in params_vec {
if !self.should_skip_condition(¶m) {
self.param_name_seq += 1;
self.parameters.push(param);
}
}
} else if placeholder_count > 0 {
tracing::warn!(
"SQL template contains {} placeholders but no parameters provided",
placeholder_count
);
}
self.apply_conditions.push(sql_template);
self
}
fn handle_security_result(&self, sql: &str, result: &DetectionResult) {
if !result.is_dangerous {
return;
}
match result.severity {
DetectionSeverity::Critical => {
tracing::error!(
"Critical Security Threat - SQL: {}, Cause: {}, Pattern: {:?}",
sql,
result.reason,
result.patterns
);
}
DetectionSeverity::High => {
tracing::warn!(
"High Risk SQL Pattern - SQL: {}, Reason: {}, Recommendation: {:?}",
sql,
result.reason,
result.suggestions
);
}
DetectionSeverity::Medium => {
tracing::info!(
"Medium Risk SQL Pattern - SQL: {}, Reason: {}",
sql,
result.reason
);
}
DetectionSeverity::Low => {
trace!(
"Low Risk SQL Warning - SQL: {}, Reason: {}",
sql,
result.reason
);
}
}
}
pub fn apply_raw<S: Into<String>>(self, sql: S) -> Self {
self.apply(sql, None::<Vec<AkitaValue>>)
}
pub fn select<T: Into<String>>(mut self, columns: Vec<T>) -> Self {
self.select_columns = columns.into_iter().map(|c| c.into()).collect();
self
}
pub fn select_distinct<T: Into<String>>(mut self, columns: Vec<T>) -> Self {
self.select_columns = columns.into_iter().map(|c| c.into()).collect();
self.distinct = true;
self
}
fn add_condition<T, V>(mut self, column: T, operator: SqlOperator, value: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
let value = value.into();
if !self.should_add_condition() {
return self;
}
if operator.is_null_check() {
self.where_conditions.push(Condition {
column: column.into(),
operator,
value,
and_or: AndOr::And,
});
return self;
}
if !self.should_skip_condition(&value) {
self.where_conditions.push(Condition {
column: column.into(),
operator,
value,
and_or: AndOr::And,
});
}
self
}
pub fn eq<T, V>(self, column: T, value: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
self.add_condition(column, SqlOperator::Eq, value)
}
pub fn ne<T, V>(self, column: T, value: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
self.add_condition(column, SqlOperator::Ne, value)
}
pub fn gt<T, V>(self, column: T, value: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
self.add_condition(column, SqlOperator::Gt, value)
}
pub fn ge<T, V>(self, column: T, value: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
self.add_condition(column, SqlOperator::Ge, value)
}
pub fn lt<T, V>(self, column: T, value: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
self.add_condition(column, SqlOperator::Lt, value)
}
pub fn le<T, V>(self, column: T, value: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
self.add_condition(column, SqlOperator::Le, value)
}
pub fn like<T, V>(self, column: T, value: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
self.add_condition(column, SqlOperator::Like, value)
}
pub fn not_like<T, V>(self, column: T, value: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
self.add_condition(column, SqlOperator::NotLike, value)
}
pub fn is_null<T: Into<String>>(self, column: T) -> Self {
self.add_condition(column, SqlOperator::IsNull, AkitaValue::Null)
}
pub fn is_not_null<T: Into<String>>(self, column: T) -> Self {
self.add_condition(column, SqlOperator::IsNotNull, AkitaValue::Null)
}
pub fn r#in<T, V, I>(self, column: T, values: I) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
I: IntoIterator<Item = V>,
{
let values: Vec<AkitaValue> = values.into_iter().map(|v| v.into()).collect();
self.add_condition(column, SqlOperator::In, AkitaValue::List(values))
}
pub fn not_in<T, V, I>(self, column: T, values: I) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
I: IntoIterator<Item = V>,
{
let values: Vec<AkitaValue> = values.into_iter().map(|v| v.into()).collect();
self.add_condition(column, SqlOperator::NotIn, AkitaValue::List(values))
}
pub fn between<T, V>(self, column: T, start: V, end: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
self.add_condition(
column,
SqlOperator::Between,
AkitaValue::List(vec![start.into(), end.into()]),
)
}
pub fn not_between<T, V>(self, column: T, start: V, end: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
self.add_condition(
column,
SqlOperator::NotBetween,
AkitaValue::List(vec![start.into(), end.into()]),
)
}
pub fn and<F>(mut self, func: F) -> Self
where
F: FnOnce(Wrapper) -> Wrapper,
{
let nested = func(Wrapper::new());
self.where_conditions.extend(nested.where_conditions);
self
}
pub fn or<F>(mut self, func: F) -> Self
where
F: FnOnce(Wrapper) -> Wrapper,
{
let mut nested = func(Wrapper::new());
for condition in &mut nested.where_conditions {
condition.and_or = AndOr::Or;
}
self.where_conditions.extend(nested.where_conditions);
self
}
pub fn or_direct(mut self) -> Self {
if let Some(last_condition) = self.where_conditions.last_mut() {
last_condition.and_or = AndOr::Or;
}
self
}
fn add_join(mut self, join_type: JoinType, table: String, condition: String) -> Self {
if self.should_add_condition() {
self.join_clauses.push(JoinClause {
join_type,
table,
alias: None,
condition: Condition {
column: condition,
operator: SqlOperator::Eq,
value: AkitaValue::Null, and_or: AndOr::And,
},
});
}
self
}
pub fn inner_join<T, C>(self, table: T, condition: C) -> Self
where
T: Into<String>,
C: Into<String>,
{
self.add_join(JoinType::Inner, table.into(), condition.into())
}
pub fn left_join<T, C>(self, table: T, condition: C) -> Self
where
T: Into<String>,
C: Into<String>,
{
self.add_join(JoinType::Left, table.into(), condition.into())
}
pub fn right_join<T, C>(self, table: T, condition: C) -> Self
where
T: Into<String>,
C: Into<String>,
{
self.add_join(JoinType::Right, table.into(), condition.into())
}
pub fn full_join<T, C>(self, table: T, condition: C) -> Self
where
T: Into<String>,
C: Into<String>,
{
self.add_join(JoinType::Full, table.into(), condition.into())
}
pub fn group_by<T: Into<String>>(mut self, columns: Vec<T>) -> Self {
if self.should_add_condition() {
self.group_by_columns = columns.into_iter().map(|c| c.into()).collect();
}
self
}
pub fn having<T, V>(mut self, column: T, operator: SqlOperator, value: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
let value = value.into();
if self.should_add_condition() && !self.should_skip_condition(&value) {
self.having_conditions.push(Condition {
column: column.into(),
operator,
value,
and_or: AndOr::And,
});
}
self
}
fn add_order_by<T: Into<String>>(mut self, columns: Vec<T>, direction: OrderDirection) -> Self {
if self.should_add_condition() {
for column in columns {
self.order_by_clauses.push(OrderByClause {
column: column.into(),
direction: direction.clone(),
});
}
}
self
}
pub fn order_by_asc<T: Into<String>>(self, columns: Vec<T>) -> Self {
self.add_order_by(columns, OrderDirection::Asc)
}
pub fn order_by_desc<T: Into<String>>(self, columns: Vec<T>) -> Self {
self.add_order_by(columns, OrderDirection::Desc)
}
pub fn set<T, V>(mut self, column: T, value: V) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
{
let value = value.into();
if self.should_add_condition() && !self.should_skip_condition(&value) {
self.set_operations.push(SetOperation {
column: column.into(),
value,
});
}
self
}
pub fn set_multiple<T, V, I>(mut self, operations: I) -> Self
where
T: Into<String>,
V: Into<AkitaValue>,
I: IntoIterator<Item = (T, V)>,
{
if self.should_add_condition() {
for (column, value) in operations {
self.set_operations.push(SetOperation {
column: column.into(),
value: value.into(),
});
}
}
self
}
pub fn limit(mut self, limit: u64) -> Self {
self.limit_value = Some(limit);
self
}
pub fn offset(mut self, offset: u64) -> Self {
self.offset_value = Some(offset);
self
}
pub fn page(mut self, page: u64, page_size: u64) -> Self {
let offset = (page - 1) * page_size;
self.limit_value = Some(page_size);
self.offset_value = Some(offset);
self
}
pub fn when(mut self, condition: bool) -> Self {
self.next_condition_active = condition;
self
}
pub fn unless(mut self, condition: bool) -> Self {
self.next_condition_active = !condition;
self
}
pub fn skip_next(mut self) -> Self {
self.skip_mode = true;
self
}
fn should_add_condition(&mut self) -> bool {
if self.skip_mode {
self.skip_mode = false;
return false;
}
let should_add = self.next_condition_active;
self.next_condition_active = true; should_add
}
fn should_skip_condition(&self, value: &AkitaValue) -> bool {
match value {
AkitaValue::Null => true,
AkitaValue::List(list) if list.is_empty() => true,
AkitaValue::Text(text) if text.is_empty() => true,
_ => false,
}
}
pub fn build_select_clause(&self) -> String {
if self.distinct {
format!("DISTINCT {}", self.build_column_list())
} else {
self.build_column_list()
}
}
pub fn build_column_list(&self) -> String {
if self.select_columns.is_empty() {
"*".to_string()
} else {
self.select_columns.join(", ")
}
}
pub fn build_from_clause(&self) -> Option<String> {
self.table.as_ref().map(|table| {
if let Some(alias) = &self.alias {
format!("{} AS {}", table, alias)
} else {
table.clone()
}
})
}
pub fn build_join_clauses(&self) -> Vec<String> {
self.join_clauses
.iter()
.map(|join| {
let mut clause = format!("{} {}", join.join_type, join.table);
if let Some(alias) = &join.alias {
clause.push_str(&format!(" AS {}", alias));
}
clause.push_str(&format!(" ON {}", join.condition.column));
clause
})
.collect()
}
pub fn build_where_clause(&self) -> String {
self.build_conditions_clause(&self.where_conditions, &self.apply_conditions)
}
pub fn build_having_clause(&self) -> String {
self.build_conditions_clause(&self.having_conditions, &[])
}
fn build_conditions_clause(
&self,
conditions: &[Condition],
apply_conditions: &[String],
) -> String {
let mut parts = Vec::new();
if !conditions.is_empty() {
let condition_parts: Vec<String> = conditions
.iter()
.map(|cond| self.format_condition_fragment(cond))
.collect();
parts.push(self.join_condition_fragments(condition_parts));
}
parts.extend_from_slice(apply_conditions);
if parts.is_empty() {
return String::new();
}
parts.join(" AND ")
}
fn format_condition_fragment(&self, condition: &Condition) -> String {
match &condition.operator {
SqlOperator::IsNull | SqlOperator::IsNotNull => {
format!("{} {}", condition.column, condition.operator)
}
SqlOperator::In | SqlOperator::NotIn => match &condition.value {
AkitaValue::List(values) => {
let placeholders: Vec<String> =
values.iter().map(|_| "?".to_string()).collect();
format!(
"{} {} ({})",
condition.column,
condition.operator,
placeholders.join(", ")
)
}
AkitaValue::RawSql(sql) => {
format!("{} {} ({})", condition.column, condition.operator, sql)
}
_ => {
format!("{} {} (?)", condition.column, condition.operator)
}
},
SqlOperator::Between | SqlOperator::NotBetween => match &condition.value {
AkitaValue::List(values) if values.len() == 2 => {
format!("{} {} ? AND ?", condition.column, condition.operator)
}
_ => {
format!("{} {} ? AND ?", condition.column, condition.operator)
}
},
_ => match &condition.value {
AkitaValue::RawSql(sql) => {
format!("{} {} {}", condition.column, condition.operator, sql)
}
AkitaValue::Column(col) => {
format!("{} {} {}", condition.column, condition.operator, col)
}
_ => {
format!("{} {} ?", condition.column, condition.operator)
}
},
}
}
fn join_condition_fragments(&self, conditions: Vec<String>) -> String {
if conditions.is_empty() {
return String::new();
}
let mut result = conditions[0].clone();
for i in 1..conditions.len() {
if i - 1 < self.where_conditions.len() {
match self.where_conditions[i - 1].and_or {
AndOr::And => result.push_str(" AND "),
AndOr::Or => result.push_str(" OR "),
}
} else {
result.push_str(" AND ");
}
result.push_str(&conditions[i]);
}
result
}
pub fn build_group_by_clause(&self) -> String {
if self.group_by_columns.is_empty() {
String::new()
} else {
self.group_by_columns.join(", ")
}
}
pub fn build_order_by_clause(&self) -> String {
if self.order_by_clauses.is_empty() {
String::new()
} else {
let orders: Vec<String> = self
.order_by_clauses
.iter()
.map(|order| format!("{} {}", order.column, order.direction))
.collect();
orders.join(", ")
}
}
pub fn build_set_clause(&self) -> String {
self.set_operations
.iter()
.map(|op| match &op.value {
AkitaValue::RawSql(sql_expr) => format!("{} = {}", op.column, sql_expr),
AkitaValue::Column(col_name) => format!("{} = {}", op.column, col_name),
_ => format!("{} = ?", op.column),
})
.collect::<Vec<_>>()
.join(", ")
}
pub fn get_limit(&self) -> Option<u64> {
self.limit_value
}
pub fn get_offset(&self) -> Option<u64> {
self.offset_value
}
pub fn get_pagination(&self) -> (Option<u64>, Option<u64>) {
(self.limit_value, self.offset_value)
}
pub fn get_last_sql(&self) -> Option<&String> {
self.last_sql.as_ref()
}
#[deprecated(since = "0.6.0", note = "Use SqlBuilder.build_query_sql instead")]
pub fn build_select_sql(&self) -> String {
let select = self.build_select_clause();
let from = self.build_from_clause().unwrap_or_default();
let joins = self.build_join_clauses().join(" ");
let where_clause = self.build_where_clause();
let group_by = self.build_group_by_clause();
let having = self.build_having_clause();
let order_by = self.build_order_by_clause();
let mut sql = format!("SELECT {}", select);
if !from.is_empty() {
sql.push_str(&format!(" FROM {}", from));
}
if !joins.is_empty() {
sql.push_str(&format!(" {}", joins));
}
if !where_clause.is_empty() {
sql.push_str(&format!(" WHERE {}", where_clause));
}
if !group_by.is_empty() {
sql.push_str(&format!(" GROUP BY {}", group_by));
}
if !having.is_empty() {
sql.push_str(&format!(" HAVING {}", having));
}
if !order_by.is_empty() {
sql.push_str(&format!(" ORDER BY {}", order_by));
}
sql
}
#[deprecated(since = "0.6.0", note = "Use SqlBuilder.build_count_sql instead")]
pub fn build_count_sql(&self) -> String {
let from = self.build_from_clause().unwrap_or_default();
let where_clause = self.build_where_clause();
let mut sql = format!("SELECT COUNT(*) FROM {}", from);
if !where_clause.is_empty() {
sql.push_str(&format!(" WHERE {}", where_clause));
}
sql
}
#[deprecated(since = "0.6.0", note = "Use SqlBuilder.build_update_sql instead")]
pub fn build_update_sql(&self) -> Option<String> {
let table = self.table.as_ref()?;
let set_clause = self.build_set_clause();
let where_clause = self.build_where_clause();
let mut sql = format!("UPDATE {} SET {}", table, set_clause);
if !where_clause.is_empty() {
sql.push_str(&format!(" WHERE {}", where_clause));
}
Some(sql)
}
#[deprecated(since = "0.6.0", note = "Use SqlBuilder.build_delete_sql instead")]
pub fn build_delete_sql(&self) -> Option<String> {
let table = self.table.as_ref()?;
let where_clause = self.build_where_clause();
let mut sql = format!("DELETE FROM {}", table);
if !where_clause.is_empty() {
sql.push_str(&format!(" WHERE {}", where_clause));
}
Some(sql)
}
pub fn get_query_data(&self) -> QueryData {
QueryData {
select: self.build_select_clause(),
from: self.build_from_clause(),
joins: self.build_join_clauses(),
where_clause: self.build_where_clause(),
group_by: self.build_group_by_clause(),
having: self.build_having_clause(),
order_by: self.build_order_by_clause(),
limit: self.limit_value,
offset: self.offset_value,
last_sql: self.last_sql.clone(),
distinct: self.distinct,
}
}
pub fn get_update_data(&self) -> Option<UpdateData> {
Some(UpdateData {
table: self.table.clone()?,
set_clause: self.build_set_clause(),
where_clause: self.build_where_clause(),
})
}
pub fn get_delete_data(&self) -> Option<DeleteData> {
Some(DeleteData {
table: self.table.clone()?,
where_clause: self.build_where_clause(),
})
}
#[deprecated(since = "0.6.0", note = "Use Wrapper.build_conditions_clause instead")]
fn build_conditions(&self, prefix: &str, conditions: &[Condition]) -> String {
if conditions.is_empty() && self.apply_conditions.is_empty() {
return String::new();
}
let mut all_parts = Vec::new();
if !conditions.is_empty() {
let condition_parts: Vec<String> = conditions
.iter()
.map(|cond| self.format_condition(cond))
.collect();
let joined = self.join_conditions(condition_parts);
all_parts.push(joined);
}
if !self.apply_conditions.is_empty() {
all_parts.extend(self.apply_conditions.iter().cloned());
}
if all_parts.is_empty() {
return String::new();
}
format!("{} {}", prefix, all_parts.join(" AND "))
}
fn format_condition(&self, condition: &Condition) -> String {
match &condition.operator {
SqlOperator::IsNull | SqlOperator::IsNotNull => {
format!("{} {}", condition.column, condition.operator)
}
SqlOperator::In | SqlOperator::NotIn => {
match &condition.value {
AkitaValue::List(values) => {
let placeholders: Vec<String> = values
.iter()
.map(|v| match v {
AkitaValue::RawSql(sql) => sql.clone(),
AkitaValue::Column(col) => col.clone(),
_ => "?".to_string(),
})
.collect();
format!(
"{} {} ({})",
condition.column,
condition.operator,
placeholders.join(", ")
)
}
AkitaValue::RawSql(sql) => {
format!("{} {} ({})", condition.column, condition.operator, sql)
}
_ => {
format!("{} {} (?)", condition.column, condition.operator)
}
}
}
SqlOperator::Between | SqlOperator::NotBetween => match &condition.value {
AkitaValue::List(values) if values.len() == 2 => {
let start = match &values[0] {
AkitaValue::RawSql(sql) => sql.clone(),
AkitaValue::Column(col) => col.clone(),
_ => "?".to_string(),
};
let end = match &values[1] {
AkitaValue::RawSql(sql) => sql.clone(),
AkitaValue::Column(col) => col.clone(),
_ => "?".to_string(),
};
format!(
"{} {} {} AND {}",
condition.column, condition.operator, start, end
)
}
_ => {
format!("{} {} ? AND ?", condition.column, condition.operator)
}
},
_ => match &condition.value {
AkitaValue::RawSql(sql) => {
format!("{} {} {}", condition.column, condition.operator, sql)
}
AkitaValue::Column(col) => {
format!("{} {} {}", condition.column, condition.operator, col)
}
_ => {
format!("{} {} ?", condition.column, condition.operator)
}
},
}
}
fn join_conditions(&self, conditions: Vec<String>) -> String {
if conditions.is_empty() {
return String::new();
}
let mut result = conditions[0].clone();
for (_i, condition) in conditions.iter().enumerate().skip(1) {
result.push_str(" AND ");
result.push_str(condition);
}
result
}
pub fn get_parameters(&self) -> Vec<AkitaValue> {
let mut params = Vec::new();
for operation in &self.set_operations {
match &operation.value {
AkitaValue::RawSql(_) | AkitaValue::Column(_) => {
}
_ => {
params.push(operation.value.clone());
}
}
}
for condition in &self.where_conditions {
match &condition.operator {
SqlOperator::IsNull | SqlOperator::IsNotNull => {
}
SqlOperator::In | SqlOperator::NotIn => {
if let AkitaValue::List(values) = &condition.value {
for value in values {
if !matches!(value, AkitaValue::RawSql(_) | AkitaValue::Column(_)) {
params.push(value.clone());
}
}
} else if !matches!(&condition.value, AkitaValue::RawSql(_)) {
params.push(condition.value.clone());
}
}
SqlOperator::Between | SqlOperator::NotBetween => {
if let AkitaValue::List(values) = &condition.value {
for value in values {
if !matches!(value, AkitaValue::RawSql(_) | AkitaValue::Column(_)) {
params.push(value.clone());
}
}
}
}
_ => {
match &condition.value {
AkitaValue::RawSql(_) | AkitaValue::Column(_) => {
}
_ => {
params.push(condition.value.clone());
}
}
}
}
}
params.extend(self.parameters.clone());
params
}
#[deprecated(since = "0.6.0", note = "Use Wrapper.build_where_clause instead")]
pub fn get_sql_segment(&self) -> String {
if self.where_conditions.is_empty() {
return String::new();
}
self.build_conditions("", &self.where_conditions)
.trim()
.to_string()
}
pub fn get_order_by(&self) -> Vec<String> {
let orders: Vec<String> = self
.order_by_clauses
.iter()
.map(|order| format!("{} {}", order.column, order.direction))
.collect();
orders
}
pub fn get_group_by(&self) -> &Vec<String> {
&self.group_by_columns
}
pub fn get_order_by_clauses(&self) -> &Vec<OrderByClause> {
&self.order_by_clauses
}
pub fn get_apply_conditions(&self) -> &Vec<String> {
&self.apply_conditions
}
pub fn get_select_sql(&self) -> String {
if self.select_columns.is_empty() {
"*".to_string()
} else {
self.select_columns.join(", ")
}
}
}
#[derive(Debug, Clone)]
pub struct QueryData {
pub select: String,
pub from: Option<String>,
pub joins: Vec<String>,
pub where_clause: String,
pub group_by: String,
pub having: String,
pub order_by: String,
pub limit: Option<u64>,
pub offset: Option<u64>,
pub last_sql: Option<String>,
pub distinct: bool,
}
#[derive(Debug, Clone)]
pub struct UpdateData {
pub table: String,
pub set_clause: String,
pub where_clause: String,
}
#[derive(Debug, Clone)]
pub struct DeleteData {
pub table: String,
pub where_clause: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_eq_condition() {
let wrapper = Wrapper::new().eq("name", "Alice");
let sql = wrapper.build_select_sql();
assert!(
sql.contains("name ="),
"Expected eq condition in SQL: {}",
sql
);
}
#[test]
fn test_ne_condition() {
let wrapper = Wrapper::new().ne("status", "inactive");
let sql = wrapper.build_select_sql();
assert!(
sql.contains("status !="),
"Expected ne condition in SQL: {}",
sql
);
}
#[test]
fn test_gt_condition() {
let wrapper = Wrapper::new().gt("age", 18);
let sql = wrapper.build_select_sql();
assert!(
sql.contains("age >"),
"Expected gt condition in SQL: {}",
sql
);
}
#[test]
fn test_ge_condition() {
let wrapper = Wrapper::new().ge("score", 100);
let sql = wrapper.build_select_sql();
assert!(
sql.contains("score >="),
"Expected ge condition in SQL: {}",
sql
);
}
#[test]
fn test_lt_condition() {
let wrapper = Wrapper::new().lt("price", 50.0);
let sql = wrapper.build_select_sql();
assert!(
sql.contains("price <"),
"Expected lt condition in SQL: {}",
sql
);
}
#[test]
fn test_le_condition() {
let wrapper = Wrapper::new().le("quantity", 10);
let sql = wrapper.build_select_sql();
assert!(
sql.contains("quantity <="),
"Expected le condition in SQL: {}",
sql
);
}
#[test]
fn test_like_condition() {
let wrapper = Wrapper::new().like("name", "%test%");
let sql = wrapper.build_select_sql();
assert!(
sql.contains("name LIKE"),
"Expected LIKE condition in SQL: {}",
sql
);
}
#[test]
fn test_not_like_condition() {
let wrapper = Wrapper::new().not_like("name", "%test%");
let sql = wrapper.build_select_sql();
assert!(
sql.contains("name NOT LIKE"),
"Expected NOT LIKE condition in SQL: {}",
sql
);
}
#[test]
fn test_is_null_condition() {
let wrapper = Wrapper::new().is_null("deleted_at");
let sql = wrapper.build_select_sql();
assert!(
sql.contains("deleted_at IS NULL"),
"Expected IS NULL in SQL: {}",
sql
);
}
#[test]
fn test_is_not_null_condition() {
let wrapper = Wrapper::new().is_not_null("email");
let sql = wrapper.build_select_sql();
assert!(
sql.contains("email IS NOT NULL"),
"Expected IS NOT NULL in SQL: {}",
sql
);
}
#[test]
fn test_in_condition() {
let wrapper = Wrapper::new().r#in("status", vec!["active", "pending"]);
let sql = wrapper.build_select_sql();
assert!(
sql.contains("status IN"),
"Expected IN condition in SQL: {}",
sql
);
}
#[test]
fn test_not_in_condition() {
let wrapper = Wrapper::new().not_in("id", vec![1, 2, 3]);
let sql = wrapper.build_select_sql();
assert!(
sql.contains("id NOT IN"),
"Expected NOT IN condition in SQL: {}",
sql
);
}
#[test]
fn test_between_condition() {
let wrapper = Wrapper::new().between("age", 18, 65);
let sql = wrapper.build_select_sql();
assert!(
sql.contains("age BETWEEN"),
"Expected BETWEEN in SQL: {}",
sql
);
}
#[test]
fn test_not_between_condition() {
let wrapper = Wrapper::new().not_between("price", 100, 200);
let sql = wrapper.build_select_sql();
assert!(
sql.contains("price NOT BETWEEN"),
"Expected NOT BETWEEN in SQL: {}",
sql
);
}
#[test]
fn test_and_conditions() {
let wrapper = Wrapper::new().eq("status", "active").gt("age", 18);
let sql = wrapper.build_select_sql();
assert!(sql.contains("AND"), "Expected AND in SQL: {}", sql);
}
#[test]
fn test_or_conditions() {
let wrapper = Wrapper::new()
.eq("status", "active")
.or_direct()
.eq("status", "pending");
let sql = wrapper.build_select_sql();
assert!(sql.contains("OR"), "Expected OR in SQL: {}", sql);
}
#[test]
fn test_nested_and_or() {
let wrapper = Wrapper::new()
.eq("type", "user")
.and(|w| w.eq("status", "active").or_direct().eq("status", "pending"));
let sql = wrapper.build_select_sql();
assert!(sql.contains("AND"), "Expected AND in SQL: {}", sql);
assert!(
sql.contains("OR"),
"Expected OR in nested group in SQL: {}",
sql
);
}
#[test]
fn test_inner_join() {
let wrapper = Wrapper::new().inner_join("orders", "users.id = orders.user_id");
let sql = wrapper.build_select_sql();
assert!(
sql.contains("INNER JOIN"),
"Expected INNER JOIN in SQL: {}",
sql
);
}
#[test]
fn test_left_join() {
let wrapper = Wrapper::new().left_join("profiles", "users.id = profiles.user_id");
let sql = wrapper.build_select_sql();
assert!(
sql.contains("LEFT JOIN"),
"Expected LEFT JOIN in SQL: {}",
sql
);
}
#[test]
fn test_select_columns() {
let wrapper = Wrapper::new().select(vec!["id", "name", "email"]);
let sql = wrapper.build_select_sql();
assert!(sql.contains("id"), "Expected 'id' in SELECT: {}", sql);
assert!(sql.contains("name"), "Expected 'name' in SELECT: {}", sql);
assert!(sql.contains("email"), "Expected 'email' in SELECT: {}", sql);
}
#[test]
fn test_select_distinct() {
let wrapper = Wrapper::new().select_distinct(vec!["category"]);
let sql = wrapper.build_select_sql();
assert!(wrapper.distinct, "Expected distinct flag to be true");
}
#[test]
fn test_order_by_asc() {
let wrapper = Wrapper::new().order_by_asc(vec!["name"]);
assert_eq!(wrapper.get_order_by().len(), 1);
}
#[test]
fn test_order_by_desc() {
let wrapper = Wrapper::new().order_by_desc(vec!["created_at"]);
assert_eq!(wrapper.get_order_by().len(), 1);
}
#[test]
fn test_group_by() {
let wrapper = Wrapper::new().group_by(vec!["category", "status"]);
assert_eq!(wrapper.get_group_by().len(), 2);
}
#[test]
fn test_limit() {
let wrapper = Wrapper::new().limit(10);
assert_eq!(wrapper.limit_value, Some(10));
}
#[test]
fn test_offset() {
let wrapper = Wrapper::new().offset(20);
assert_eq!(wrapper.offset_value, Some(20));
}
#[test]
fn test_set_single() {
let wrapper = Wrapper::new().set("name", "Alice");
assert_eq!(wrapper.get_set_operations().len(), 1);
}
#[test]
fn test_set_multiple() {
let wrapper = Wrapper::new().set("name", "Alice").set("age", 30);
assert_eq!(wrapper.get_set_operations().len(), 2);
}
#[test]
fn test_when_true() {
let wrapper = Wrapper::new().when(true).eq("status", "active");
let sql = wrapper.build_select_sql();
assert!(
sql.contains("status ="),
"Expected condition to be applied when(true): {}",
sql
);
}
#[test]
fn test_when_false() {
let wrapper = Wrapper::new().when(false).eq("status", "active");
let sql = wrapper.build_select_sql();
assert!(
sql.is_empty() || !sql.contains("status ="),
"Expected condition to be skipped when(false): {}",
sql
);
}
#[test]
fn test_unless_true() {
let wrapper = Wrapper::new().unless(true).eq("status", "active");
let sql = wrapper.build_select_sql();
assert!(
sql.is_empty() || !sql.contains("status ="),
"Expected condition to be skipped unless(true): {}",
sql
);
}
#[test]
fn test_unless_false() {
let wrapper = Wrapper::new().unless(false).eq("status", "active");
let sql = wrapper.build_select_sql();
assert!(
sql.contains("status ="),
"Expected condition to be applied unless(false): {}",
sql
);
}
#[test]
fn test_empty_wrapper() {
let wrapper = Wrapper::new();
let sql = wrapper.build_select_sql();
assert!(
sql.contains("SELECT"),
"Expected SELECT in empty wrapper SQL: {}",
sql
);
}
#[test]
fn test_multiple_conditions_same_column() {
let wrapper = Wrapper::new().ge("age", 18).le("age", 65);
let sql = wrapper.build_select_sql();
assert!(sql.contains("age >="), "Expected >= in SQL: {}", sql);
assert!(sql.contains("age <="), "Expected <= in SQL: {}", sql);
}
#[test]
fn test_parameters_collected() {
let wrapper = Wrapper::new().eq("name", "Alice").gt("age", 18);
let params = wrapper.get_parameters();
assert!(
params.len() >= 2,
"Expected at least 2 parameters, got {}",
params.len()
);
}
#[test]
fn test_build_update_sql() {
let wrapper = Wrapper::new()
.table("users")
.set("name", "Alice")
.eq("id", 1);
let sql = wrapper.build_update_sql().expect("Expected Some(sql)");
assert!(sql.contains("UPDATE"), "Expected UPDATE in SQL: {}", sql);
assert!(sql.contains("SET"), "Expected SET in SQL: {}", sql);
assert!(sql.contains("WHERE"), "Expected WHERE in SQL: {}", sql);
}
#[test]
fn test_build_delete_sql() {
let wrapper = Wrapper::new().table("users").eq("id", 1);
let sql = wrapper.build_delete_sql().expect("Expected Some(sql)");
assert!(sql.contains("DELETE"), "Expected DELETE in SQL: {}", sql);
assert!(sql.contains("WHERE"), "Expected WHERE in SQL: {}", sql);
}
}