sqlorm_core/qb/
condition.rs

1use crate::driver::Driver;
2use crate::qb::BindValue;
3use sqlx::QueryBuilder;
4
5/// Represents a SQL condition fragment with its associated bound values.
6///
7/// A `Condition` is essentially a piece of SQL (e.g. `"id = $1"`)
8/// along with one or more values that should be bound into the query.
9/// It is designed to be used with [`sqlx::QueryBuilder`] for dynamic
10/// query construction.
11pub struct Condition {
12    /// The raw SQL fragment (e.g. `"id = $1"`, `"name IN (...)"`).
13    pub sql: String,
14
15    /// The values to be bound into the SQL fragment.
16    ///
17    /// Each value is stored as a boxed [`AnyValue`] trait object,
18    /// which allows heterogeneous types to be stored in the same vector.
19    pub values: Vec<Box<dyn AnyValue>>,
20}
21
22/// Trait representing a value that can be bound into a SQL query.
23///
24/// This trait abstracts over different types that implement
25/// [`BindValue`] and allows them to be stored in a type-erased form
26/// (`Box<dyn AnyValue>`).
27pub trait AnyValue: Send + Sync {
28    /// Bind this value into the given [`QueryBuilder`].
29    fn bind(&self, builder: &mut QueryBuilder<'static, Driver>);
30}
31
32impl<T> AnyValue for T
33where
34    T: BindValue + Clone + std::fmt::Debug + 'static,
35{
36    fn bind(&self, builder: &mut QueryBuilder<'static, Driver>) {
37        builder.push_bind(self.clone());
38    }
39}
40
41impl Condition {
42    /// Create a new `Condition` with a single bound value.
43    ///
44    /// # Example
45    /// ```ignore
46    /// use sqlorm_core::qb::condition::Condition;
47    /// use sqlorm_core::qb::BindValue;
48    ///
49    /// let cond = Condition::new("id = ?".to_string(), 42);
50    /// assert_eq!(cond.sql, "id = ?");
51    /// assert_eq!(cond.values.len(), 1);
52    /// ```
53    pub fn new<T: BindValue + Clone + 'static>(sql: String, val: T) -> Self {
54        Self {
55            sql,
56            values: vec![Box::new(val)],
57        }
58    }
59
60    /// Create a new `Condition` with multiple bound values.
61    ///
62    /// Useful for `IN` clauses or other multi-value conditions.
63    ///
64    /// # Example
65    /// ```ignore
66    /// use sqlorm_core::qb::condition::Condition;
67    /// use sqlorm_core::qb::BindValue;
68    ///
69    /// let cond = Condition::multi("id IN (?, ?, ?)".to_string(), vec![1, 2, 3]);
70    /// assert_eq!(cond.sql, "id IN (?, ?, ?)");
71    /// assert_eq!(cond.values.len(), 3);
72    /// ```
73    pub fn multi<T: BindValue + Clone + 'static>(sql: String, vals: Vec<T>) -> Self {
74        Self {
75            sql,
76            values: vals
77                .into_iter()
78                .map(|v| Box::new(v) as Box<dyn AnyValue>)
79                .collect(),
80        }
81    }
82
83    /// Create a new `Condition` with no bound values.
84    ///
85    /// Useful for static SQL fragments that don’t require parameters.
86    ///
87    /// # Example
88    /// ```
89    /// use sqlorm_core::qb::condition::Condition;
90    ///
91    /// let cond = Condition::none("deleted_at IS NULL".to_string());
92    /// assert_eq!(cond.sql, "deleted_at IS NULL");
93    /// assert!(cond.values.is_empty());
94    /// ```
95    pub fn none(sql: String) -> Self {
96        Self {
97            sql,
98            values: vec![],
99        }
100    }
101
102    /// Combine two conditions with `AND`
103    ///
104    /// Wraps both conditions in parentheses to preserve operator precedence.
105    pub fn and(self, other: Condition) -> Self {
106        let sql = format!("({}) AND ({})", self.sql, other.sql);
107        let mut values = self.values;
108        values.extend(other.values);
109        Self { sql, values }
110    }
111
112    /// Combine two conditions with `OR`
113    ///
114    /// Wraps both conditions in parentheses to preserve operator precedence.
115    pub fn or(self, other: Condition) -> Self {
116        let sql = format!("({}) OR ({})", self.sql, other.sql);
117        let mut values = self.values;
118        values.extend(other.values);
119        Self { sql, values }
120    }
121}
122
123impl std::fmt::Debug for Condition {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        f.debug_struct("Condition")
126            .field("sql", &self.sql)
127            .field("values_len", &self.values.len())
128            .finish()
129    }
130}