Skip to main content

siderite_orm/expr/
field.rs

1//! Typed column handles: [`Field`], [`Calc`] and the operand traits that make
2//! `User::age.eq(3)` compile only for values that fit the column.
3
4use super::related::Joined;
5use super::{Column, Expr, Ident};
6use crate::model::Model;
7use crate::plan::{OrderExpr, QueryPlan, SelectExpr};
8use crate::relations::ForeignKey;
9use crate::types::{DbType, SqlType};
10use crate::value::Value;
11use chrono::{DateTime, NaiveDate, NaiveTime, Utc};
12use rust_decimal::Decimal;
13use std::marker::PhantomData;
14use uuid::Uuid;
15
16/// Typed column handle for model `M` whose Rust type is `T`.
17///
18/// Generated as associated constants by the `Model` derive:
19/// `impl User { pub const name: Field<User, String> = Field::new("name"); }`.
20pub struct Field<M, T> {
21    name: &'static str,
22    _marker: PhantomData<fn() -> (M, T)>,
23}
24
25impl<M, T> Clone for Field<M, T> {
26    fn clone(&self) -> Self {
27        *self
28    }
29}
30impl<M, T> Copy for Field<M, T> {}
31
32impl<M, T> std::fmt::Debug for Field<M, T> {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        f.debug_tuple("Field").field(&self.name).finish()
35    }
36}
37
38impl<M, T> Field<M, T> {
39    /// Create a field handle. Intended for generated code.
40    pub const fn new(name: &'static str) -> Self {
41        Self {
42            name,
43            _marker: PhantomData,
44        }
45    }
46
47    /// Column name.
48    pub const fn name(&self) -> &'static str {
49        self.name
50    }
51
52    /// Reference to this column of the *enclosing* query, for use inside a
53    /// correlated subquery (Django `OuterRef`).
54    pub fn outer_ref(self) -> Expr {
55        Expr::OuterRef(Column::new(self.name))
56    }
57
58    /// `column = value` assignment for `QuerySet::update`.
59    pub fn set(self, value: impl Operand<M, T>) -> (Ident, Expr) {
60        (self.name.into(), value.into_expr())
61    }
62
63    /// `column = <expression>` assignment, e.g. `Post::likes.set_expr(Post::likes + 1)`.
64    pub fn set_expr(self, value: impl Into<Expr>) -> (Ident, Expr) {
65        (self.name.into(), value.into())
66    }
67
68    /// Project this column in `values(..)`, named after the column.
69    pub fn select(self) -> SelectExpr {
70        SelectExpr::new(Expr::col(self.name), Some(self.name.into()))
71    }
72}
73
74impl<M, T> From<Field<M, T>> for Expr {
75    fn from(f: Field<M, T>) -> Self {
76        Expr::col(f.name)
77    }
78}
79
80impl<M, T> From<Field<M, T>> for SelectExpr {
81    fn from(f: Field<M, T>) -> Self {
82        f.select()
83    }
84}
85
86/// Result of arithmetic on typed handles: an expression that is still known
87/// to have type `T`, so it can be compared with and assigned to `T` fields.
88pub struct Calc<M, T> {
89    expr: Expr,
90    _marker: PhantomData<fn() -> (M, T)>,
91}
92
93impl<M, T> Calc<M, T> {
94    fn new(expr: Expr) -> Self {
95        Self {
96            expr,
97            _marker: PhantomData,
98        }
99    }
100
101    /// Name this expression for `values(..)`.
102    pub fn alias(self, name: impl Into<Ident>) -> SelectExpr {
103        SelectExpr::new(self.expr, Some(name.into()))
104    }
105}
106
107impl<M, T> Clone for Calc<M, T> {
108    fn clone(&self) -> Self {
109        Self::new(self.expr.clone())
110    }
111}
112
113impl<M, T> std::fmt::Debug for Calc<M, T> {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        f.debug_tuple("Calc").field(&self.expr).finish()
116    }
117}
118
119impl<M, T> From<Calc<M, T>> for Expr {
120    fn from(c: Calc<M, T>) -> Self {
121        c.expr
122    }
123}
124
125/// A value that can stand for a `T` column in a comparison or assignment:
126/// a `T`, a `T` for an `Option<T>` column, a primary key or model reference
127/// for a foreign key, or another typed handle of type `T`.
128pub trait Operand<M, T> {
129    /// Convert into an expression.
130    fn into_expr(self) -> Expr;
131}
132
133/// A literal value that can stand for a `T` column (no column references),
134/// used by `is_in` and `range`.
135pub trait Literal<T> {
136    /// Convert into a bind value.
137    fn into_value(self) -> Value;
138}
139
140/// Implements [`Literal`] and [`Operand`] for one kind of literal.
141macro_rules! literal {
142    ([$($generics:tt)*] $from:ty => $to:ty, |$v:ident| $convert:expr) => {
143        impl<$($generics)*> Literal<$to> for $from {
144            fn into_value(self) -> Value {
145                let $v = self;
146                $convert
147            }
148        }
149        impl<$($generics)*, M> Operand<M, $to> for $from {
150            fn into_expr(self) -> Expr {
151                Expr::Value(Literal::<$to>::into_value(self))
152            }
153        }
154    };
155}
156
157literal!([T: DbType] T => T, |v| v.to_value());
158literal!([T: DbType] T => Option<T>, |v| v.to_value());
159literal!(['a] &'a str => String, |v| Value::Text(v.to_owned()));
160literal!(['a] &'a str => Option<String>, |v| Value::Text(v.to_owned()));
161literal!(['a, T: Model] &'a T => ForeignKey<T>, |v| v.pk().to_value());
162literal!(['a, T: Model] &'a T => Option<ForeignKey<T>>, |v| v.pk().to_value());
163
164/// Primary-key values compare with foreign keys pointing at models keyed by them.
165macro_rules! key_literal {
166    ($($key:ty),*) => {$(
167        literal!([T: Model<Pk = $key>] $key => ForeignKey<T>, |v| v.to_value());
168        literal!([T: Model<Pk = $key>] $key => Option<ForeignKey<T>>, |v| v.to_value());
169    )*};
170}
171key_literal!(i16, i32, i64, Uuid, String);
172literal!(['a, T: Model<Pk = String>] &'a str => ForeignKey<T>, |v| Value::Text(v.to_owned()));
173literal!(['a, T: Model<Pk = String>] &'a str => Option<ForeignKey<T>>, |v| Value::Text(v.to_owned()));
174
175/// Column types that support arithmetic.
176pub trait Numeric {}
177/// Column types that have a calendar date (`year`, `month`, ...).
178pub trait DateLike {}
179/// Column types that have a time of day (`hour`, `minute`, `second`).
180pub trait TimeLike {}
181/// Text column types (`icontains`, `starts_with`, ...).
182pub trait TextLike {}
183
184macro_rules! marker {
185    ($trait:ident: $($t:ty),*) => {$(
186        impl $trait for $t {}
187        impl $trait for Option<$t> {}
188    )*};
189}
190marker!(Numeric: i16, i32, i64, f32, f64, Decimal);
191marker!(DateLike: NaiveDate, DateTime<Utc>);
192marker!(TimeLike: NaiveTime, DateTime<Utc>);
193marker!(TextLike: String);
194
195/// Lookups shared by every typed handle `$handle<M, T>`.
196macro_rules! typed_lookups {
197    ($handle:ident) => {
198        impl<M, T> Operand<M, T> for $handle<M, T> {
199            fn into_expr(self) -> Expr {
200                self.into()
201            }
202        }
203
204        impl<M, T> $handle<M, T> {
205            /// Untyped expression for this handle.
206            pub fn expr(self) -> Expr {
207                self.into()
208            }
209
210            /// Ascending ordering.
211            pub fn asc(self) -> OrderExpr {
212                self.expr().asc()
213            }
214
215            /// Descending ordering.
216            pub fn desc(self) -> OrderExpr {
217                self.expr().desc()
218            }
219
220            /// `IS NULL`.
221            pub fn is_null(self) -> Expr {
222                self.expr().is_null(true)
223            }
224
225            /// `IS NOT NULL`.
226            pub fn is_not_null(self) -> Expr {
227                self.expr().is_null(false)
228            }
229
230            /// Membership in a list of literals.
231            pub fn is_in<L: Literal<T>>(self, values: impl IntoIterator<Item = L>) -> Expr {
232                self.expr().is_in(values.into_iter().map(Literal::into_value))
233            }
234
235            /// Membership in the single column a subquery selects.
236            pub fn in_subquery(self, plan: QueryPlan) -> Expr {
237                self.expr().in_subquery(plan)
238            }
239
240            /// Inclusive range.
241            pub fn range(self, lo: impl Literal<T>, hi: impl Literal<T>) -> Expr {
242                self.expr().range(lo.into_value(), hi.into_value())
243            }
244
245            /// `CAST(self AS ty)`.
246            pub fn cast(self, ty: SqlType) -> Expr {
247                self.expr().cast(ty)
248            }
249        }
250
251        impl<M, T: TextLike> $handle<M, T> {
252            /// Case-insensitive equality.
253            pub fn iexact(self, v: impl Into<String>) -> Expr {
254                self.expr().iexact(v.into())
255            }
256
257            /// Regex match (requires [`Feature::Regex`](crate::Feature::Regex)).
258            pub fn regex(self, pattern: impl Into<String>) -> Expr {
259                self.expr().regex(pattern)
260            }
261        }
262
263        impl<M, T: DateLike> $handle<M, T> {
264            /// Year component.
265            pub fn year(self) -> Expr {
266                self.expr().year()
267            }
268            /// Month component.
269            pub fn month(self) -> Expr {
270                self.expr().month()
271            }
272            /// Day-of-month component.
273            pub fn day(self) -> Expr {
274                self.expr().day()
275            }
276            /// ISO week number.
277            pub fn week(self) -> Expr {
278                self.expr().week()
279            }
280            /// Quarter `1..=4`.
281            pub fn quarter(self) -> Expr {
282                self.expr().quarter()
283            }
284            /// The date part of a timestamp.
285            pub fn date(self) -> Expr {
286                self.expr().date()
287            }
288        }
289
290        impl<M, T: TimeLike> $handle<M, T> {
291            /// Hour component.
292            pub fn hour(self) -> Expr {
293                self.expr().hour()
294            }
295            /// Minute component.
296            pub fn minute(self) -> Expr {
297                self.expr().minute()
298            }
299            /// Second component.
300            pub fn second(self) -> Expr {
301                self.expr().second()
302            }
303        }
304
305        impl<M, T: Numeric> std::ops::Neg for $handle<M, T> {
306            type Output = Calc<M, T>;
307            fn neg(self) -> Calc<M, T> {
308                Calc::new(-self.expr())
309            }
310        }
311
312        typed_lookups!(@arith $handle: Add add, Sub sub, Mul mul, Div div, Rem rem);
313        typed_lookups!(@compare $handle: eq, ne, lt, le, gt, ge);
314        typed_lookups!(@text $handle:
315            contains => Contains, false;
316            icontains => Contains, true;
317            starts_with => StartsWith, false;
318            istarts_with => StartsWith, true;
319            ends_with => EndsWith, false;
320            iends_with => EndsWith, true);
321    };
322    (@arith $handle:ident: $($trait:ident $method:ident),*) => {$(
323        impl<M, T: Numeric, R: Operand<M, T>> std::ops::$trait<R> for $handle<M, T> {
324            type Output = Calc<M, T>;
325            fn $method(self, rhs: R) -> Calc<M, T> {
326                Calc::new(std::ops::$trait::$method(self.expr(), rhs.into_expr()))
327            }
328        }
329    )*};
330    (@compare $handle:ident: $($method:ident),*) => {
331        impl<M, T> $handle<M, T> {$(
332            #[doc = concat!("Typed `", stringify!($method), "` comparison.")]
333            pub fn $method(self, rhs: impl Operand<M, T>) -> Expr {
334                self.expr().$method(rhs.into_expr())
335            }
336        )*}
337    };
338    (@text $handle:ident: $($method:ident => $variant:ident, $ci:expr);*) => {
339        impl<M, T: TextLike> $handle<M, T> {$(
340            #[doc = concat!("Text lookup `", stringify!($method), "`.")]
341            pub fn $method(self, needle: impl Into<String>) -> Expr {
342                Expr::Lookup {
343                    expr: Box::new(self.expr()),
344                    lookup: super::Lookup::$variant {
345                        needle: needle.into(),
346                        case_insensitive: $ci,
347                    },
348                }
349            }
350        )*}
351    };
352}
353
354typed_lookups!(Field);
355typed_lookups!(Calc);
356typed_lookups!(Joined);