1use std::{
2 marker::PhantomData,
3 ops::{Deref, Not},
4};
5
6use tokio_postgres::types::ToSql;
7
8use crate::query::Where;
9
10#[derive(Clone, Copy, Debug)]
34pub struct TypedColumn<T: ToSql + Sync> {
35 column: Column,
36 rs_type: PhantomData<T>,
37}
38
39#[derive(Copy, Clone, Debug)]
45pub struct Column {
46 pub column_name: &'static str,
48 pub table_name: &'static str,
50 nullable: bool,
51 unique: bool,
52 primary_key: bool,
53 generated: bool,
54}
55
56macro_rules! impl_prop_typed_col {
57 ($($prop:ident),+) => {
58 $(
59 pub const fn $prop(mut self) -> TypedColumn<T> {
61 self.column.$prop = true;
62 self
63 }
64 )*
65 };
66}
67
68macro_rules! impl_prop_col {
69 ($($prop:ident),+) => {
70 $(
71 pub const fn $prop(&self) -> bool {
73 self.$prop
74 }
75 )*
76 };
77}
78
79impl<T: ToSql + Sync + Send + 'static> TypedColumn<T> {
80 pub const fn new(table_name: &'static str, column_name: &'static str) -> TypedColumn<T> {
82 TypedColumn {
83 column: Column::new(table_name, column_name),
84 rs_type: PhantomData::<T>,
85 }
86 }
87
88 impl_prop_typed_col!(nullable, unique, primary_key, generated);
89
90 pub fn eq<'a>(&self, other: &'a T) -> Where<'a> {
93 Where::new(
94 format!("{}.{} = ?", self.table_name, self.column_name),
95 vec![other],
96 )
97 }
98}
99
100impl<T: ToSql + Sync + Send + 'static + PartialOrd> TypedColumn<T> {
101 pub fn gt<'a>(&self, other: &'a T) -> Where<'a> {
104 Where::new(
105 format!("{}.{} > ?", self.table_name, self.column_name),
106 vec![other],
107 )
108 }
109
110 pub fn gte<'a>(&self, other: &'a T) -> Where<'a> {
113 Where::new(
114 format!("{}.{} >= ?", self.table_name, self.column_name),
115 vec![other],
116 )
117 }
118
119 pub fn lt<'a>(&self, other: &'a T) -> Where<'a> {
122 Where::new(
123 format!("{}.{} < ?", self.table_name, self.column_name),
124 vec![other],
125 )
126 }
127
128 pub fn lte<'a>(&self, other: &'a T) -> Where<'a> {
131 Where::new(
132 format!("{}.{} <= ?", self.table_name, self.column_name),
133 vec![other],
134 )
135 }
136}
137
138impl<'a, T: ToSql + Sync + 'a> TypedColumn<Option<T>> {
139 pub fn null(&self) -> Where<'a> {
141 Where::new(
142 format!("{}.{} IS NULL", self.table_name, self.column_name),
143 vec![],
144 )
145 }
146
147 pub fn not_noll(&self) -> Where<'a> {
149 self.null().not()
150 }
151}
152
153impl<'a, T: ToSql + Sync + 'a> TypedColumn<Vec<T>> {
154 pub fn contains(&self, value: &'a T) -> Where<'a> {
156 Where::new(
157 format!("? = ANY({}.{})", self.table_name, self.column_name),
158 vec![value],
159 )
160 }
161
162 pub fn contains_not(&self, value: &'a T) -> Where<'a> {
164 self.contains(value).not()
165 }
166
167 pub fn contains_any(&self, values: &'a Vec<&'a T>) -> Where<'a> {
170 Where::new(
171 format!("{}.{} && ?", self.table_name, self.column_name),
172 vec![values],
173 )
174 }
175
176 pub fn contains_all(&self, values: &'a Vec<&'a T>) -> Where<'a> {
179 Where::new(
180 format!("{}.{} @> ?", self.table_name, self.column_name),
181 vec![values],
182 )
183 }
184
185 pub fn contains_none(&self, values: &'a Vec<&'a T>) -> Where<'a> {
188 self.contains_any(values).not()
189 }
190}
191
192impl<'a> TypedColumn<String> {
193 pub fn contains(&self, other: &'a String) -> Where<'a> {
195 Where::new(format!("POSITION(? in {})", self.full_name()), vec![other])
196 }
197}
198
199impl<T: ToSql + Sync> Deref for TypedColumn<T> {
200 type Target = Column;
201
202 fn deref(&self) -> &Self::Target {
203 &self.column
204 }
205}
206
207impl Column {
208 const fn new(table_name: &'static str, column_name: &'static str) -> Column {
210 Column {
211 column_name,
212 table_name,
213 nullable: false,
214 unique: false,
215 primary_key: false,
216 generated: false,
217 }
218 }
219
220 impl_prop_col!(unique, nullable, primary_key, generated);
221
222 pub const fn column_name(&self) -> &'static str {
224 self.column_name
225 }
226
227 pub const fn table_name(&self) -> &'static str {
230 self.table_name
231 }
232
233 #[inline]
247 pub fn full_name(&self) -> String {
248 format!("{}.{}", self.table_name, self.column_name)
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 #![allow(dead_code)]
255
256 use crate::{
257 prelude::*,
258 query::{PushChunk, Where},
259 };
260
261 impl<'a> Where<'a> {
262 fn to_stmt(&mut self) -> String {
264 let mut q = Query::<u64>::default();
265 self.push_to_buffer(&mut q);
266
267 q.0
268 }
269 }
270
271 #[derive(Model)]
272 struct Book {
273 id: i64,
274 title: String,
275 pages: Vec<String>,
276 }
277
278 #[test]
279 fn equals() {
280 assert_eq!(Book::title.eq(&"ABC".into()).to_stmt(), "book.title = ?")
281 }
282
283 #[test]
284 fn greater_than() {
285 assert_eq!(Book::id.gt(&1).to_stmt(), "book.id > ?");
286 }
287
288 #[test]
289 fn greater_than_equals() {
290 assert_eq!(Book::id.gte(&1).to_stmt(), "book.id >= ?");
291 }
292
293 #[test]
294 fn less_than() {
295 assert_eq!(Book::id.lt(&1).to_stmt(), "book.id < ?")
296 }
297
298 #[test]
299 fn less_than_equals() {
300 assert_eq!(Book::id.lte(&1).to_stmt(), "book.id <= ?")
301 }
302
303 #[test]
304 fn complete_query() {
305 let q = Book::select()
306 .where_(Book::title.eq(&"The Communist Manifesto".into()))
307 .where_(Book::pages.contains(&"You have nothing to lose but your chains!".into()))
308 .where_(Book::id.gt(&3))
309 .to_query()
310 .0;
311 assert_eq!(q, "SELECT book.id, book.title, book.pages FROM book WHERE (book.title = $1) AND ($2 = ANY(book.pages)) AND (book.id > $3)");
312 }
313}