pg_worm/query/
table.rs

1use std::{
2    marker::PhantomData,
3    ops::{Deref, Not},
4};
5
6use tokio_postgres::types::ToSql;
7
8use crate::query::Where;
9
10/// A wrapper around the [`Column`] struct which includes
11/// the rust type of the field.
12///
13/// For each field of a [`pg_worm::Model`] a `TypedColumn` is automatically
14/// generated.
15///
16/// A `TypedColumn` can be used to access information about
17/// the column and create `Filter`s regarding this column.
18///
19/// # Example
20///
21/// ```
22/// use pg_worm::prelude::*;
23///
24/// #[derive(Model)]
25/// struct Foo {
26///     baz: i64
27/// }
28///
29/// assert_eq!(Foo::baz.column_name(), "baz");
30///
31/// ```
32///
33#[derive(Clone, Copy, Debug)]
34pub struct TypedColumn<T: ToSql + Sync> {
35    column: Column,
36    rs_type: PhantomData<T>,
37}
38
39/// This type represents a column.
40///  
41/// It can be used to retrieve information about the column.
42///
43/// It is mostly seen in it's wrapped form [`TypedColumn`].
44#[derive(Copy, Clone, Debug)]
45pub struct Column {
46    /// The name of this column.
47    pub column_name: &'static str,
48    /// The name of the table this columnn belongs to.
49    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            /// Set this property so `true`.
60            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            /// Returns this propertie's value.
72            pub const fn $prop(&self) -> bool {
73                self.$prop
74            }
75        )*
76    };
77}
78
79impl<T: ToSql + Sync + Send + 'static> TypedColumn<T> {
80    /// Creates anew `TypedColumn<T>`.
81    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    /// Returns a [`Where`] clause which checks whether
91    /// this column is equal to some value.
92    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    /// Check whether this column's value is **g**reater **t**han some
102    /// other value.
103    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    /// Check whether this colum's value is **g**reater **t**han or **e**qual
111    /// to another value.
112    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    /// Check whether this column's value is **l**ess **t**han some
120    /// other value.
121    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    /// Check whether this colum's value is **l**ess **t**han or **e**qual
129    /// to another value.
130    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    /// Check whether this column's value is `NULL`.
140    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    /// Check whether this column's value is `NOT NULL`
148    pub fn not_noll(&self) -> Where<'a> {
149        self.null().not()
150    }
151}
152
153impl<'a, T: ToSql + Sync + 'a> TypedColumn<Vec<T>> {
154    /// Check whether this column's array contains some value.
155    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    /// Check whether this column's array does `NOT` contain some value.
163    pub fn contains_not(&self, value: &'a T) -> Where<'a> {
164        self.contains(value).not()
165    }
166
167    /// Check whether this column's array contains any value of
168    /// another array.
169    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    /// Check whether this column's array contains all values of
177    /// another array.
178    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    /// Check whether this column's array does not overlap
186    /// with another array, i.e. contains none of its values.
187    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    /// Check whethre a string contains a substring. Case sensitive.
194    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    /// Creates a new column
209    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    /// Get the column name.
223    pub const fn column_name(&self) -> &'static str {
224        self.column_name
225    }
226
227    /// Get the name of the table this column
228    /// is part of.
229    pub const fn table_name(&self) -> &'static str {
230        self.table_name
231    }
232
233    /// Get the full name of the column.
234    ///
235    /// # Example
236    ///
237    /// ```
238    /// use pg_worm::prelude::*;
239    ///
240    /// #[derive(Model)]
241    /// struct Foo {
242    ///     baz: String
243    /// }
244    /// assert_eq!(Foo::baz.full_name(), "foo.baz");
245    /// ```
246    #[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        /// This is a convieniance function for testing purposes.
263        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}