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