sqlorm_core/qb/
condition.rs

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