sqlorm_core/qb/
column.rs

1use crate::qb::{bind::BindValue, condition::Condition};
2use std::marker::PhantomData;
3
4/// Represents a database column in a type-safe way.
5///
6/// `Column<T>` is a lightweight wrapper around a column name (`&'static str`)
7/// with a phantom type parameter `T` that indicates the type of values
8/// that can be bound to conditions involving this column.
9///
10/// This allows you to write type-safe query conditions such as:
11///
12/// ```ignore
13/// use sqlorm_core::qb::{Column, Condition};
14/// use std::marker::PhantomData;
15///
16/// static ID: Column<i32> = Column { name: "id", table_alias: "user__", _marker: PhantomData };
17/// let cond: Condition = ID.eq(42);
18/// assert_eq!(cond.sql, "user__.id = ?");
19/// ```
20pub struct Column<T> {
21    /// The column name as it appears in SQL.
22    pub name: &'static str,
23
24    /// The column name with table alias, as it appears in SQL.
25    /// example: `__user.id`
26    pub aliased_name: &'static str,
27
28    /// The table alias to use when generating SQL conditions.
29    pub table_alias: &'static str,
30
31    /// Marker to carry the type information for the column.
32    pub _marker: PhantomData<T>,
33}
34impl<T> AsRef<str> for Column<T> {
35    fn as_ref(&self) -> &str {
36        self.name
37    }
38}
39
40impl<T> Copy for Column<T> {}
41impl<T> Clone for Column<T> {
42    fn clone(&self) -> Self {
43        *self
44    }
45}
46
47impl<T> Column<T>
48where
49    T: BindValue + Clone,
50{
51    /// Get the fully qualified column name (with table alias)
52    fn qualified_name(&self) -> String {
53        format!("{}.{}", self.table_alias, self.name)
54    }
55
56    /// Create a condition: `column = ?`
57    pub fn eq(self, val: T) -> Condition {
58        Condition::new(format!("{} = ?", self.qualified_name()), val)
59    }
60
61    /// Create a condition: `column <> ?`
62    pub fn ne(self, val: T) -> Condition {
63        Condition::new(format!("{} <> ?", self.qualified_name()), val)
64    }
65
66    /// Create a condition: `column > ?`
67    pub fn gt(self, val: T) -> Condition {
68        Condition::new(format!("{} > ?", self.qualified_name()), val)
69    }
70
71    /// Create a condition: `column >= ?`
72    pub fn ge(self, val: T) -> Condition {
73        Condition::new(format!("{} >= ?", self.qualified_name()), val)
74    }
75
76    /// Create a condition: `column < ?`
77    pub fn lt(self, val: T) -> Condition {
78        Condition::new(format!("{} < ?", self.qualified_name()), val)
79    }
80
81    /// Create a condition: `column <= ?`
82    pub fn le(self, val: T) -> Condition {
83        Condition::new(format!("{} <= ?", self.qualified_name()), val)
84    }
85
86    /// Create a condition: `column LIKE ?`
87    pub fn like(self, val: T) -> Condition {
88        Condition::new(format!("{} LIKE ?", self.qualified_name()), val)
89    }
90
91    /// Create a condition: `column IN (?, ?, ...)`
92    ///
93    /// The number of placeholders matches the number of values provided.
94    ///
95    /// Panics if `vals` is empty
96    pub fn in_(self, vals: Vec<T>) -> Condition {
97        if vals.is_empty() {
98            panic!(
99                "Cannot create IN condition with empty value list. At least one value must be specified."
100            );
101        }
102        let placeholders: Vec<String> = (0..vals.len()).map(|_| "?".to_string()).collect();
103        let sql = format!("{} IN ({})", self.qualified_name(), placeholders.join(", "));
104        Condition::multi(sql, vals)
105    }
106
107    /// Create a condition: `column NOT IN (?, ?, ...)`
108    ///
109    /// Panics if `vals` is empty
110    pub fn not_in(self, vals: Vec<T>) -> Condition {
111        if vals.is_empty() {
112            panic!(
113                "Cannot create NOT IN condition with empty value list. At least one value must be specified."
114            );
115        }
116        let placeholders: Vec<String> = (0..vals.len()).map(|_| "?".to_string()).collect();
117        let sql = format!(
118            "{} NOT IN ({})",
119            self.qualified_name(),
120            placeholders.join(", ")
121        );
122        Condition::multi(sql, vals)
123    }
124
125    /// Create a condition: `column IS NULL`
126    pub fn is_null(self) -> Condition {
127        Condition::none(format!("{} IS NULL", self.qualified_name()))
128    }
129
130    /// Create a condition: `column IS NOT NULL`
131    pub fn is_not_null(self) -> Condition {
132        Condition::none(format!("{} IS NOT NULL", self.qualified_name()))
133    }
134
135    /// Create a condition: `column BETWEEN ? AND ?`
136    pub fn between(self, start: T, end: T) -> Condition {
137        let sql = format!("{} BETWEEN ? AND ?", self.qualified_name());
138        Condition::multi(sql, vec![start, end])
139    }
140
141    /// Create a condition: `column NOT BETWEEN ? AND ?`
142    pub fn not_between(self, start: T, end: T) -> Condition {
143        let sql = format!("{} NOT BETWEEN ? AND ?", self.qualified_name());
144        Condition::multi(sql, vec![start, end])
145    }
146}