Skip to main content

rango_orm_core/
column.rs

1//! Column and ordering types for Rango ORM.
2
3use std::marker::PhantomData;
4
5use crate::Model;
6use crate::fields::ForeignKey;
7use crate::query::{SqlValue, ToSqlValue};
8
9/// A typed column reference: `M` is the model, `T` the field type.
10///
11/// `#[derive(Model)]` generates one per field (`User::EMAIL`), so filters check
12/// both the column name and the value type at compile time.
13pub struct Column<M, T> {
14    name: &'static str,
15    _marker: PhantomData<fn() -> (M, T)>,
16}
17
18impl<M, T> Copy for Column<M, T> {}
19
20impl<M, T> Clone for Column<M, T> {
21    fn clone(&self) -> Self {
22        *self
23    }
24}
25
26impl<M, T> std::fmt::Debug for Column<M, T> {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        f.write_fmt(format_args!("Column({:?})", self.name))
29    }
30}
31
32impl<M, T> Column<M, T> {
33    /// Used by `#[derive(Model)]`; you rarely call it directly.
34    pub const fn new(name: &'static str) -> Self {
35        Self {
36            name,
37            _marker: PhantomData,
38        }
39    }
40
41    /// Returns the column name.
42    pub const fn name(&self) -> &'static str {
43        self.name
44    }
45
46    /// Returns an ascending ordering for this column.
47    pub const fn asc(self) -> Ordering<M> {
48        Ordering::new(self.name, false)
49    }
50
51    /// Returns a descending ordering for this column.
52    pub const fn desc(self) -> Ordering<M> {
53        Ordering::new(self.name, true)
54    }
55}
56
57/// An ordering for a column.
58pub struct Ordering<M> {
59    name: &'static str,
60    descending: bool,
61    _model: PhantomData<fn() -> M>,
62}
63
64impl<M> Ordering<M> {
65    const fn new(name: &'static str, descending: bool) -> Self {
66        Self {
67            name,
68            descending,
69            _model: PhantomData,
70        }
71    }
72}
73
74impl<M> Copy for Ordering<M> {}
75
76impl<M> Clone for Ordering<M> {
77    fn clone(&self) -> Self {
78        *self
79    }
80}
81
82impl<M> std::fmt::Debug for Ordering<M> {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        f.write_fmt(format_args!(
85            "Ordering({:?}, descending={})",
86            self.name, self.descending
87        ))
88    }
89}
90
91/// A column reference accepted by query builder methods: either a typed `Column<M, T>`
92/// generated by `#[derive(Model)]` (checked at compile time) or a `&str`
93/// (checked by the database at runtime).
94pub trait ColumnRef<M> {
95    fn column_name(&self) -> &str;
96}
97
98impl<M, T> ColumnRef<M> for Column<M, T> {
99    fn column_name(&self) -> &str {
100        self.name
101    }
102}
103
104impl<M> ColumnRef<M> for &str {
105    fn column_name(&self) -> &str {
106        self
107    }
108}
109
110/// A foreign key column on `M` pointing to `T`: `Post::AUTHOR_ID` (an `author_id: ForeignKey<User>` field)
111/// is a `ForeignKeyRef<Post, User>`, so relation methods infer the related model and reject
112/// a column that points elsewhere. `&str` is accepted too (then name the model with `::<T>`).
113pub trait ForeignKeyRef<M, T>: ColumnRef<M> {}
114
115impl<M, T: Model> ForeignKeyRef<M, T> for Column<M, ForeignKey<T>> {}
116
117impl<M, T> ForeignKeyRef<M, T> for &str {}
118
119/// A `ManyToMany<R>` field on `M`: `#[derive(Model)]` generates `Article::TAGS` for a
120/// `tags: ManyToMany<Tag>` field. Not a column, so it only works with `prefetch_related`.
121pub struct ManyToManyField<M, R>(PhantomData<fn() -> (M, R)>);
122
123impl<M, R> ManyToManyField<M, R> {
124    /// Used by `#[derive(Model)]`; you rarely call it directly.
125    pub const fn new() -> Self {
126        Self(PhantomData)
127    }
128}
129
130impl<M, R> Default for ManyToManyField<M, R> {
131    fn default() -> Self {
132        Self::new()
133    }
134}
135
136/// How `prefetch_related` finds the `R` rows of each `M`.
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub enum PrefetchKind<'a> {
139    /// A column on R's table pointing back to M's primary key.
140    ReverseFk(&'a str),
141    /// Through the M2M pivot table (see [`crate::related::m2m_pivot`]).
142    ManyToMany,
143}
144
145/// Accepted by `prefetch_related`: a reverse foreign key (`Comment::POST_ID`, a
146/// `post_id: ForeignKey<Post>` field on `Comment`), a M2M field (`Article::TAGS`), or `&str`
147/// naming the reverse FK column (then name the model with `::<R>`).
148pub trait PrefetchRef<M, R> {
149    fn prefetch_kind(&self) -> PrefetchKind<'_>;
150}
151
152impl<M: Model, R> PrefetchRef<M, R> for Column<R, ForeignKey<M>> {
153    fn prefetch_kind(&self) -> PrefetchKind<'_> {
154        PrefetchKind::ReverseFk(self.name)
155    }
156}
157
158impl<M, R> PrefetchRef<M, R> for &str {
159    fn prefetch_kind(&self) -> PrefetchKind<'_> {
160        PrefetchKind::ReverseFk(self)
161    }
162}
163
164impl<M, R> PrefetchRef<M, R> for ManyToManyField<M, R> {
165    fn prefetch_kind(&self) -> PrefetchKind<'_> {
166        PrefetchKind::ManyToMany
167    }
168}
169
170/// Accepted by `order_by`: `Column<M, T>` (ascending), `Ordering<M>` (from `.asc()` / `.desc()`),
171/// or `&str` where a leading `-` means descending (Django style).
172pub trait OrderRef<M> {
173    fn order_parts(&self) -> (&str, bool);
174}
175
176impl<M, T> OrderRef<M> for Column<M, T> {
177    fn order_parts(&self) -> (&str, bool) {
178        (self.name, false)
179    }
180}
181
182impl<M> OrderRef<M> for Ordering<M> {
183    fn order_parts(&self) -> (&str, bool) {
184        (self.name, self.descending)
185    }
186}
187
188impl<M> OrderRef<M> for &str {
189    fn order_parts(&self) -> (&str, bool) {
190        match self.strip_prefix("-") {
191            Some(name) => (name, true),
192            None => (*self, false),
193        }
194    }
195}
196
197/// A value that can be compared to a column of field type `T`:
198/// the field type itself, or its plain Rust equivalent (`i32` for `FieldInt`,
199/// `&str` for `FieldEmail`, ...). Filter values are not validated like field
200/// constructors are: `"not-an-email"` is a valid value to search for.
201#[diagnostic::on_unimplemented(
202    message = "`{Self}` cannot be compared to a `{T}` column",
203    label = "expected a `{T}` value or its plain Rust equivalent"
204)]
205pub trait FilterValue<T> {
206    fn into_sql_value(self) -> SqlValue;
207}
208
209#[diagnostic::do_not_recommend]
210impl<T: ToSqlValue> FilterValue<T> for T {
211    fn into_sql_value(self) -> SqlValue {
212        self.to_sql_value()
213    }
214}
215
216/// A column accepted by comparison filters (`eq`, `gt`, ...) together with its value type `V`.
217///
218/// `Column<M, T>` only accepts values of its field type (`.eq(User::AGE, "x")` does
219/// not compile); `&str` accepts anything convertible to `SqlValue`.
220#[diagnostic::on_unimplemented(
221    message = "`{Self}` is not a column of this model that accepts a `{V}` value",
222    note = "pass a typed column such as `Model::NAME` with a value of its field type, or a column name as `&str`"
223)]
224pub trait FilterColumn<M, V>: ColumnRef<M> {
225    fn bind(&self, val: V) -> SqlValue;
226}
227
228#[diagnostic::do_not_recommend]
229impl<M, T, V: FilterValue<T>> FilterColumn<M, V> for Column<M, T> {
230    fn bind(&self, val: V) -> SqlValue {
231        val.into_sql_value()
232    }
233}
234
235#[diagnostic::do_not_recommend]
236impl<M, V: Into<SqlValue>> FilterColumn<M, V> for &str {
237    fn bind(&self, val: V) -> SqlValue {
238        val.into()
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245    use crate::fields::{FieldEmail, FieldInt, FieldText};
246
247    struct User;
248
249    #[test]
250    fn filter_values_are_typed() {
251        let age = Column::<User, FieldInt>::new("age");
252        assert_eq!(age.bind(18), SqlValue::Int(18));
253        assert_eq!(age.bind(FieldInt(18)), SqlValue::Int(18));
254        let email = Column::<User, FieldEmail>::new("email");
255        assert_eq!(email.bind("a@b.c"), SqlValue::Text("a@b.c".into()));
256        assert_eq!(
257            FilterColumn::<User, _>::bind(&"age", 18i64),
258            SqlValue::BigInt(18)
259        );
260    }
261
262    #[test]
263    fn column_name() {
264        assert_eq!(
265            Column::<User, FieldText>::new("email").column_name(),
266            "email"
267        );
268        assert_eq!(ColumnRef::<User>::column_name(&"email"), "email");
269    }
270
271    #[test]
272    fn order_parts() {
273        let column = Column::<User, FieldText>::new("name");
274        assert_eq!(column.order_parts(), ("name", false));
275        assert_eq!(column.desc().order_parts(), ("name", true));
276        assert_eq!(column.asc().order_parts(), ("name", false));
277        assert_eq!(OrderRef::<User>::order_parts(&"-name"), ("name", true));
278        assert_eq!(OrderRef::<User>::order_parts(&"name"), ("name", false));
279    }
280
281    fn assert_traits<T: Send + Sync + Copy>() {}
282
283    #[test]
284    fn column_is_send_sync_copy() {
285        assert_traits::<Column<*const u8, *const u8>>();
286    }
287}