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}