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 table alias to use when generating SQL conditions.
25    pub table_alias: &'static str,
26
27    /// Marker to carry the type information for the column.
28    pub _marker: PhantomData<T>,
29}
30impl<T> AsRef<str> for Column<T> {
31    fn as_ref(&self) -> &str {
32        self.name
33    }
34}
35
36impl<T> Copy for Column<T> {}
37impl<T> Clone for Column<T> {
38    fn clone(&self) -> Self {
39        *self
40    }
41}
42
43impl<T> Column<T>
44where
45    T: BindValue + Clone,
46{
47    /// Get the fully qualified column name (with table alias)
48    fn qualified_name(&self) -> String {
49        format!("{}.{}", self.table_alias, self.name)
50    }
51
52    /// Create a condition: `column = ?`
53    pub fn eq(self, val: T) -> Condition {
54        Condition::new(format!("{} = ?", self.qualified_name()), val)
55    }
56
57    /// Create a condition: `column <> ?`
58    pub fn ne(self, val: T) -> Condition {
59        Condition::new(format!("{} <> ?", self.qualified_name()), val)
60    }
61
62    /// Create a condition: `column > ?`
63    pub fn gt(self, val: T) -> Condition {
64        Condition::new(format!("{} > ?", self.qualified_name()), val)
65    }
66
67    /// Create a condition: `column >= ?`
68    pub fn ge(self, val: T) -> Condition {
69        Condition::new(format!("{} >= ?", self.qualified_name()), val)
70    }
71
72    /// Create a condition: `column < ?`
73    pub fn lt(self, val: T) -> Condition {
74        Condition::new(format!("{} < ?", self.qualified_name()), val)
75    }
76
77    /// Create a condition: `column <= ?`
78    pub fn le(self, val: T) -> Condition {
79        Condition::new(format!("{} <= ?", self.qualified_name()), val)
80    }
81
82    /// Create a condition: `column LIKE ?`
83    pub fn like(self, val: T) -> Condition {
84        Condition::new(format!("{} LIKE ?", self.qualified_name()), val)
85    }
86
87    /// Create a condition: `column IN (?, ?, ...)`
88    ///
89    /// The number of placeholders matches the number of values provided.
90    pub fn in_(self, vals: Vec<T>) -> Condition {
91        if vals.is_empty() {
92            panic!("Cannot create IN condition with empty value list. At least one value must be specified.");
93        }
94        let placeholders: Vec<String> = (0..vals.len()).map(|_| "?".to_string()).collect();
95        let sql = format!("{} IN ({})", self.qualified_name(), placeholders.join(", "));
96        Condition::multi(sql, vals)
97    }
98
99    /// Create a condition: `column NOT IN (?, ?, ...)`
100    pub fn not_in(self, vals: Vec<T>) -> Condition {
101        if vals.is_empty() {
102            panic!("Cannot create NOT IN condition with empty value list. At least one value must be specified.");
103        }
104        let placeholders: Vec<String> = (0..vals.len()).map(|_| "?".to_string()).collect();
105        let sql = format!("{} NOT IN ({})", self.qualified_name(), placeholders.join(", "));
106        Condition::multi(sql, vals)
107    }
108
109    /// Create a condition: `column IS NULL`
110    pub fn is_null(self) -> Condition {
111        Condition::none(format!("{} IS NULL", self.qualified_name()))
112    }
113
114    /// Create a condition: `column IS NOT NULL`
115    pub fn is_not_null(self) -> Condition {
116        Condition::none(format!("{} IS NOT NULL", self.qualified_name()))
117    }
118
119    /// Create a condition: `column BETWEEN ? AND ?`
120    pub fn between(self, start: T, end: T) -> Condition {
121        let sql = format!("{} BETWEEN ? AND ?", self.qualified_name());
122        Condition::multi(sql, vec![start, end])
123    }
124
125    /// Create a condition: `column NOT BETWEEN ? AND ?`
126    pub fn not_between(self, start: T, end: T) -> Condition {
127        let sql = format!("{} NOT BETWEEN ? AND ?", self.qualified_name());
128        Condition::multi(sql, vec![start, end])
129    }
130}