rust_query/
value.rs

1pub mod operations;
2
3use std::{marker::PhantomData, ops::Deref, rc::Rc};
4
5use operations::{Add, And, AsFloat, Eq, Glob, IsNotNull, Like, Lt, Not, Or, UnwrapOr};
6use ref_cast::RefCast;
7use sea_query::{Alias, Expr, Nullable, SelectStatement, SimpleExpr};
8
9use crate::{
10    alias::{Field, MyAlias, RawAlias},
11    ast::{MySelect, Source},
12    db::TableRow,
13    hash,
14    migrate::NoTable,
15    Table,
16};
17
18#[derive(Clone, Copy)]
19pub struct ValueBuilder<'x> {
20    pub(crate) inner: &'x MySelect,
21}
22
23impl<'x> ValueBuilder<'x> {
24    pub(crate) fn get_aggr(
25        self,
26        aggr: SelectStatement,
27        conds: Vec<(Field, SimpleExpr)>,
28    ) -> MyAlias {
29        let source = Source {
30            kind: crate::ast::SourceKind::Aggregate(aggr),
31            conds,
32        };
33        let new_alias = || self.inner.scope.new_alias();
34        *self.inner.extra.get_or_init(source, new_alias)
35    }
36
37    pub(crate) fn get_join<T: Table>(self, expr: SimpleExpr) -> MyAlias {
38        let source = Source {
39            kind: crate::ast::SourceKind::Implicit(T::NAME.to_owned()),
40            conds: vec![(Field::Str(T::ID), expr)],
41        };
42        let new_alias = || self.inner.scope.new_alias();
43        *self.inner.extra.get_or_init(source, new_alias)
44    }
45
46    pub fn get_unique(
47        self,
48        table: &'static str,
49        conds: Vec<(&'static str, SimpleExpr)>,
50    ) -> SimpleExpr {
51        let source = Source {
52            kind: crate::ast::SourceKind::Implicit(table.to_owned()),
53            conds: conds.into_iter().map(|x| (Field::Str(x.0), x.1)).collect(),
54        };
55
56        let new_alias = || self.inner.scope.new_alias();
57        let table = self.inner.extra.get_or_init(source, new_alias);
58        Expr::col((*table, Alias::new("id"))).into()
59    }
60}
61
62pub trait NumTyp: MyTyp + Clone + Copy {
63    const ZERO: Self;
64    fn into_sea_value(self) -> sea_query::Value;
65}
66
67impl NumTyp for i64 {
68    const ZERO: Self = 0;
69    fn into_sea_value(self) -> sea_query::Value {
70        sea_query::Value::BigInt(Some(self))
71    }
72}
73impl NumTyp for f64 {
74    const ZERO: Self = 0.;
75    fn into_sea_value(self) -> sea_query::Value {
76        sea_query::Value::Double(Some(self))
77    }
78}
79
80pub trait EqTyp {}
81
82impl EqTyp for String {}
83impl EqTyp for i64 {}
84impl EqTyp for f64 {}
85impl EqTyp for bool {}
86impl<T: Table> EqTyp for T {}
87
88/// Typ does not depend on scope, so it gets its own trait
89pub trait Typed {
90    /// TODO: somehow make this documentation visible?
91    type Typ;
92
93    #[doc(hidden)]
94    fn build_expr(&self, b: ValueBuilder) -> SimpleExpr;
95    #[doc(hidden)]
96    fn build_table(&self, b: crate::value::ValueBuilder) -> MyAlias
97    where
98        Self::Typ: Table,
99    {
100        b.get_join::<Self::Typ>(self.build_expr(b))
101    }
102}
103
104/// Trait for all values that can be used in queries.
105///
106/// This includes [Column]s from queries and rust values.
107/// - `'t` is the context in which this value is valid.
108/// - `S` is the schema in which this value is valid.
109///
110/// **You can not (yet) implement this trait yourself!**
111pub trait IntoColumn<'t, S>: Typed + Clone {
112    #[doc(hidden)]
113    type Owned: Typed<Typ = Self::Typ> + 't;
114
115    #[doc(hidden)]
116    fn into_owned(self) -> Self::Owned;
117
118    /// Turn this value into a [Column].
119    fn into_column(self) -> Column<'t, S, Self::Typ> {
120        Column(Rc::new(self.into_owned()), PhantomData)
121    }
122}
123
124impl<'t, S, T: NumTyp> Column<'t, S, T> {
125    /// Add two columns together.
126    pub fn add(&self, rhs: impl IntoColumn<'t, S, Typ = T>) -> Column<'t, S, T> {
127        Add(self, rhs).into_column()
128    }
129
130    /// Compute the less than operator of two columns.
131    pub fn lt(&self, rhs: impl IntoColumn<'t, S, Typ = T>) -> Column<'t, S, bool> {
132        Lt(self, rhs).into_column()
133    }
134}
135
136impl<'t, S, T: EqTyp + 't> Column<'t, S, T> {
137    /// Check whether two columns are equal.
138    pub fn eq(&self, rhs: impl IntoColumn<'t, S, Typ = T>) -> Column<'t, S, bool> {
139        Eq(self, rhs).into_column()
140    }
141}
142
143impl<'t, S> Column<'t, S, bool> {
144    /// Checks whether a column is false.
145    pub fn not(&self) -> Column<'t, S, bool> {
146        Not(self).into_column()
147    }
148
149    /// Check if two columns are both true.
150    pub fn and(&self, rhs: impl IntoColumn<'t, S, Typ = bool>) -> Column<'t, S, bool> {
151        And(self, rhs).into_column()
152    }
153
154    /// Check if one of two columns is true.
155    pub fn or(&self, rhs: impl IntoColumn<'t, S, Typ = bool>) -> Column<'t, S, bool> {
156        Or(self, rhs).into_column()
157    }
158}
159
160impl<'t, S, Typ: 't> Column<'t, S, Option<Typ>> {
161    /// Use the first column if it is [Some], otherwise use the second column.
162    pub fn unwrap_or(&self, rhs: impl IntoColumn<'t, S, Typ = Typ>) -> Column<'t, S, Typ>
163    where
164        Self: IntoColumn<'t, S, Typ = Option<Typ>>,
165    {
166        UnwrapOr(self, rhs).into_column()
167    }
168
169    /// Check that the column is [Some].
170    pub fn is_some(&self) -> Column<'t, S, bool> {
171        IsNotNull(self).into_column()
172    }
173}
174
175impl<'t, S> Column<'t, S, i64> {
176    /// Convert the [i64] column to [f64] type.
177    pub fn as_float(&self) -> Column<'t, S, f64> {
178        AsFloat(self).into_column()
179    }
180}
181
182impl<'t, S> Column<'t, S, String> {
183    /// Check if the column starts with the string pattern.
184    ///
185    /// Matches case-sensitive. The pattern gets automatically escaped.
186    pub fn starts_with(&self, pattern: impl AsRef<str>) -> Column<'t, S, bool> {
187        Glob(self, format!("{}*", escape_glob(pattern))).into_column()
188    }
189
190    /// Check if the column ends with the string pattern.
191    ///
192    /// Matches case-sensitive. The pattern gets automatically escaped.
193    pub fn ends_with(&self, pattern: impl AsRef<str>) -> Column<'t, S, bool> {
194        Glob(self, format!("*{}", escape_glob(pattern))).into_column()
195    }
196
197    /// Check if the column contains the string pattern.
198    ///
199    /// Matches case-sensitive. The pattern gets automatically escaped.
200    pub fn contains(&self, pattern: impl AsRef<str>) -> Column<'t, S, bool> {
201        Glob(self, format!("*{}*", escape_glob(pattern))).into_column()
202    }
203
204    /// Check if the column matches the pattern [docs](https://www.sqlite.org/lang_expr.html#like).
205    ///
206    /// As noted in the docs, it is **case-insensitive** for ASCII characters. Other characters are case-sensitive.
207    /// For creating patterns it uses `%` as a wildcard for any sequence of characters and `_` for any single character.
208    /// Special characters should be escaped with `\`.
209    pub fn like(&self, pattern: impl Into<String> + Clone + 't) -> Column<'t, S, bool> {
210        Like(self, pattern.into()).into_column()
211    }
212
213    /// Check if the column matches the pattern [docs](https://www.sqlite.org/lang_expr.html#like).
214    ///
215    /// This is a case-sensitive version of [like](Self::like). It uses Unix file globbing syntax for wild
216    /// cards. `*` matches any sequence of characters and `?` matches any single character. `[0-9]` matches
217    /// any single digit and `[a-z]` matches any single lowercase letter. `^` negates the pattern.
218    pub fn glob(&self, rhs: impl IntoColumn<'t, S, Typ = String>) -> Column<'t, S, bool> {
219        Glob(self, rhs).into_column()
220    }
221}
222
223impl<T: Typed<Typ = X>, X: MyTyp<Sql: Nullable>> Typed for Option<T> {
224    type Typ = Option<T::Typ>;
225
226    fn build_expr(&self, b: ValueBuilder) -> SimpleExpr {
227        self.as_ref()
228            .map(|x| T::build_expr(x, b))
229            .unwrap_or(X::Sql::null().into())
230    }
231}
232
233impl<'t, S, T: IntoColumn<'t, S, Typ = X>, X: MyTyp<Sql: Nullable>> IntoColumn<'t, S>
234    for Option<T>
235{
236    type Owned = Option<T::Owned>;
237    fn into_owned(self) -> Self::Owned {
238        self.map(IntoColumn::into_owned)
239    }
240}
241
242impl Typed for &str {
243    type Typ = String;
244    fn build_expr(&self, _: ValueBuilder) -> SimpleExpr {
245        SimpleExpr::from(*self)
246    }
247}
248
249impl<'t, S> IntoColumn<'t, S> for &str {
250    type Owned = String;
251    fn into_owned(self) -> Self::Owned {
252        self.to_owned()
253    }
254}
255
256impl Typed for String {
257    type Typ = String;
258    fn build_expr(&self, _: ValueBuilder) -> SimpleExpr {
259        SimpleExpr::from(self)
260    }
261}
262
263impl<'t, S> IntoColumn<'t, S> for String {
264    type Owned = String;
265    fn into_owned(self) -> Self::Owned {
266        self
267    }
268}
269
270impl Typed for bool {
271    type Typ = bool;
272    fn build_expr(&self, _: ValueBuilder) -> SimpleExpr {
273        SimpleExpr::from(*self)
274    }
275}
276
277impl<'t, S> IntoColumn<'t, S> for bool {
278    type Owned = Self;
279    fn into_owned(self) -> Self::Owned {
280        self
281    }
282}
283
284impl Typed for i64 {
285    type Typ = i64;
286    fn build_expr(&self, _: ValueBuilder) -> SimpleExpr {
287        SimpleExpr::from(*self)
288    }
289}
290
291impl<'t, S> IntoColumn<'t, S> for i64 {
292    type Owned = Self;
293    fn into_owned(self) -> Self::Owned {
294        self
295    }
296}
297
298impl Typed for f64 {
299    type Typ = f64;
300    fn build_expr(&self, _: ValueBuilder) -> SimpleExpr {
301        SimpleExpr::from(*self)
302    }
303}
304
305impl<'t, S> IntoColumn<'t, S> for f64 {
306    type Owned = Self;
307    fn into_owned(self) -> Self::Owned {
308        self
309    }
310}
311
312impl<T> Typed for &T
313where
314    T: Typed,
315{
316    type Typ = T::Typ;
317    fn build_expr(&self, b: ValueBuilder) -> SimpleExpr {
318        T::build_expr(self, b)
319    }
320    fn build_table(&self, b: crate::value::ValueBuilder) -> MyAlias
321    where
322        Self::Typ: Table,
323    {
324        T::build_table(self, b)
325    }
326}
327
328impl<'t, S, T> IntoColumn<'t, S> for &T
329where
330    T: IntoColumn<'t, S>,
331{
332    type Owned = T::Owned;
333    fn into_owned(self) -> Self::Owned {
334        T::into_owned(self.clone())
335    }
336}
337
338/// Use this a value in a query to get the current datetime as a number.
339#[derive(Clone, Copy)]
340pub struct UnixEpoch;
341
342impl Typed for UnixEpoch {
343    type Typ = i64;
344    fn build_expr(&self, _: ValueBuilder) -> SimpleExpr {
345        Expr::col(RawAlias("unixepoch('now')".to_owned())).into()
346    }
347}
348
349impl<'t, S> IntoColumn<'t, S> for UnixEpoch {
350    type Owned = Self;
351    fn into_owned(self) -> Self::Owned {
352        self
353    }
354}
355
356pub trait MyTyp: 'static {
357    #[doc(hidden)]
358    const NULLABLE: bool = false;
359    #[doc(hidden)]
360    const TYP: hash::ColumnType;
361    #[doc(hidden)]
362    const FK: Option<(&'static str, &'static str)> = None;
363    #[doc(hidden)]
364    type Out<'t>;
365    #[doc(hidden)]
366    type Sql;
367    #[doc(hidden)]
368    fn from_sql<'a>(
369        value: rusqlite::types::ValueRef<'_>,
370    ) -> rusqlite::types::FromSqlResult<Self::Out<'a>>;
371}
372
373impl<T: Table> MyTyp for T {
374    const TYP: hash::ColumnType = hash::ColumnType::Integer;
375    const FK: Option<(&'static str, &'static str)> = Some((T::NAME, T::ID));
376    type Out<'t> = TableRow<'t, Self>;
377    type Sql = i64;
378
379    fn from_sql<'a>(
380        value: rusqlite::types::ValueRef<'_>,
381    ) -> rusqlite::types::FromSqlResult<Self::Out<'a>> {
382        Ok(TableRow {
383            _p: PhantomData,
384            _local: PhantomData,
385            idx: value.as_i64()?,
386        })
387    }
388}
389
390impl MyTyp for i64 {
391    const TYP: hash::ColumnType = hash::ColumnType::Integer;
392    type Out<'t> = Self;
393    type Sql = i64;
394
395    fn from_sql<'a>(
396        value: rusqlite::types::ValueRef<'_>,
397    ) -> rusqlite::types::FromSqlResult<Self::Out<'a>> {
398        value.as_i64()
399    }
400}
401
402impl MyTyp for f64 {
403    const TYP: hash::ColumnType = hash::ColumnType::Float;
404    type Out<'t> = Self;
405    type Sql = f64;
406
407    fn from_sql<'a>(
408        value: rusqlite::types::ValueRef<'_>,
409    ) -> rusqlite::types::FromSqlResult<Self::Out<'a>> {
410        value.as_f64()
411    }
412}
413
414impl MyTyp for bool {
415    const TYP: hash::ColumnType = hash::ColumnType::Integer;
416    type Out<'t> = Self;
417    type Sql = bool;
418
419    fn from_sql<'a>(
420        value: rusqlite::types::ValueRef<'_>,
421    ) -> rusqlite::types::FromSqlResult<Self::Out<'a>> {
422        Ok(value.as_i64()? != 0)
423    }
424}
425
426impl MyTyp for String {
427    const TYP: hash::ColumnType = hash::ColumnType::String;
428    type Out<'t> = Self;
429    type Sql = String;
430
431    fn from_sql<'a>(
432        value: rusqlite::types::ValueRef<'_>,
433    ) -> rusqlite::types::FromSqlResult<Self::Out<'a>> {
434        Ok(value.as_str()?.to_owned())
435    }
436}
437
438impl<T: MyTyp> MyTyp for Option<T> {
439    const TYP: hash::ColumnType = T::TYP;
440    const NULLABLE: bool = true;
441    const FK: Option<(&'static str, &'static str)> = T::FK;
442    type Out<'t> = Option<T::Out<'t>>;
443    type Sql = T::Sql;
444
445    fn from_sql<'a>(
446        value: rusqlite::types::ValueRef<'_>,
447    ) -> rusqlite::types::FromSqlResult<Self::Out<'a>> {
448        if value.data_type() == rusqlite::types::Type::Null {
449            Ok(None)
450        } else {
451            Ok(Some(T::from_sql(value)?))
452        }
453    }
454}
455
456impl MyTyp for NoTable {
457    const TYP: hash::ColumnType = hash::ColumnType::Integer;
458    type Out<'t> = NoTable;
459    type Sql = i64;
460
461    fn from_sql<'a>(
462        _value: rusqlite::types::ValueRef<'_>,
463    ) -> rusqlite::types::FromSqlResult<Self::Out<'a>> {
464        unreachable!()
465    }
466}
467
468/// Values of this type reference a collumn in a query.
469///
470/// - The lifetime parameter `'t` specifies in which query the collumn exists.
471/// - The type parameter `S` specifies the expected schema of the query.
472/// - And finally the type paramter `T` specifies the type of the column.
473///
474/// [Column] implements [Deref] to have table extension methods in case the type is a table type.
475pub struct Column<'t, S, T>(
476    pub(crate) Rc<dyn Typed<Typ = T> + 't>,
477    pub(crate) PhantomData<fn(&'t S) -> &'t S>,
478);
479
480impl<'t, S, T> Clone for Column<'t, S, T> {
481    fn clone(&self) -> Self {
482        Self(self.0.clone(), PhantomData)
483    }
484}
485
486impl<'t, S, T> Typed for Column<'t, S, T> {
487    type Typ = T;
488
489    fn build_expr(&self, b: ValueBuilder) -> SimpleExpr {
490        self.0.as_ref().build_expr(b)
491    }
492
493    fn build_table(&self, b: crate::value::ValueBuilder) -> MyAlias
494    where
495        Self::Typ: Table,
496    {
497        self.0.as_ref().build_table(b)
498    }
499}
500
501impl<'t, S: 't, T: 't> IntoColumn<'t, S> for Column<'t, S, T> {
502    type Owned = Self;
503    fn into_owned(self) -> Self::Owned {
504        self
505    }
506}
507
508impl<'t, S, T: Table> Deref for Column<'t, S, T> {
509    type Target = T::Ext<Self>;
510
511    fn deref(&self) -> &Self::Target {
512        RefCast::ref_cast(self)
513    }
514}
515
516// This is a copy of the function from the glob crate https://github.com/rust-lang/glob/blob/49ee1e92bd6e8c5854c0b339634f9b4b733aba4f/src/lib.rs#L720-L737.
517fn escape_glob(s: impl AsRef<str>) -> String {
518    let mut escaped = String::new();
519    for c in s.as_ref().chars() {
520        match c {
521            // note that ! does not need escaping because it is only special
522            // inside brackets
523            '?' | '*' | '[' | ']' => {
524                escaped.push('[');
525                escaped.push(c);
526                escaped.push(']');
527            }
528            c => {
529                escaped.push(c);
530            }
531        }
532    }
533    escaped
534}