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}