Skip to main content

rust_query/
rows.rs

1use std::{marker::PhantomData, rc::Rc};
2
3use sea_query::{Alias, ExprTrait, IntoIden};
4
5use crate::{
6    CustomJoin, Expr, IntoExpr, Table, TableRow,
7    alias::{JoinableTable, MyAlias, TmpTable},
8    ast::MySelect,
9    joinable::IntoJoinable,
10    private::Joinable,
11    value::{DbTyp, DynTypedExpr, EqTyp, MyTableRef},
12};
13
14/// [Rows] keeps track of all rows in the current query.
15///
16/// This is the base type for other query types like [crate::args::Aggregate] and [crate::args::Query].
17/// It contains basic query functionality like joining tables and filters.
18///
19/// [Rows] mutability is only about which rows are included.
20/// Adding new columns does not require mutating [Rows].
21pub struct Rows<'inner, S> {
22    // we might store 'inner
23    pub(crate) phantom: PhantomData<fn(&'inner ()) -> &'inner ()>,
24    pub(crate) _p: PhantomData<S>,
25    pub(crate) ast: Rc<MySelect>,
26}
27
28impl<'inner, S> Rows<'inner, S> {
29    /// Join a table, this is like a super simple [Iterator::flat_map] but for queries.
30    ///
31    /// After this operation [Rows] has rows for the combinations of each original row with each row of the table.
32    /// (Also called the "Carthesian product")
33    /// The expression that is returned refers to the joined table.
34    ///
35    /// The parameter must be a table name from the schema like `v0::User`.
36    /// This table can be filtered by `#[index]`: `rows.join(v0::User.score(100))`.
37    ///
38    /// See also [Self::filter_some] if you want to join a table that is filtered by `#[unique]`.
39    pub fn join<T: DbTyp>(
40        &mut self,
41        j: impl IntoJoinable<'inner, S, Typ = T>,
42    ) -> Expr<'inner, S, T> {
43        let joinable = j.into_joinable();
44
45        let table_idx = self.ast.tables.len();
46        Rc::make_mut(&mut self.ast)
47            .tables
48            .push(joinable.table.clone());
49        for (name, val) in joinable.conds {
50            self.filter(Expr::adhoc(move |b| {
51                // it is fine to directly use the alias here because the filter is in the same scope as the join
52                sea_query::Expr::col((MyAlias::new(table_idx), name)).eq((val.func)(b))
53            }));
54        }
55
56        let table_idx = MyTableRef {
57            scope_rc: self.ast.scope_rc.clone(),
58            idx: table_idx,
59            table_name: joinable.table,
60        };
61
62        Expr::adhoc_promise(
63            move |b| {
64                sea_query::Expr::col((
65                    b.get_table(table_idx.clone()),
66                    Alias::new(table_idx.table_name.main_column()),
67                ))
68            },
69            false, // the table is joined so this column is not null
70        )
71    }
72
73    #[doc(hidden)]
74    pub fn join_private<T: Table<Schema = S>>(&mut self) -> Expr<'inner, S, TableRow<T>> {
75        self.join(Joinable::table())
76    }
77
78    pub(crate) fn join_custom<T: CustomJoin<Schema = S>>(
79        &mut self,
80        t: T,
81    ) -> Expr<'inner, S, TableRow<T>> {
82        self.join(Joinable::new(t.name()))
83    }
84
85    pub(crate) fn join_tmp<T: Table<Schema = S>>(
86        &mut self,
87        tmp: TmpTable,
88    ) -> Expr<'inner, S, TableRow<T>> {
89        let tmp_string = tmp.into_iden();
90        self.join(Joinable::new(JoinableTable::Normal(tmp_string)))
91    }
92
93    /// Filter rows based on an expression.
94    pub fn filter(&mut self, prop: impl IntoExpr<'inner, S, Typ = bool>) {
95        let prop = DynTypedExpr::erase(prop);
96        Rc::make_mut(&mut self.ast).filters.push(prop);
97    }
98
99    /// Filter out rows where this expression is [None].
100    ///
101    /// Returns a new expression with the unwrapped type.
102    pub fn filter_some<Typ: EqTyp>(
103        &mut self,
104        val: impl IntoExpr<'inner, S, Typ = Option<Typ>>,
105    ) -> Expr<'inner, S, Typ> {
106        let val = val.into_expr();
107        Rc::make_mut(&mut self.ast)
108            .filters
109            .push(DynTypedExpr::erase(val.is_some()));
110
111        // we already removed all rows with null, so this is ok.
112        Expr::adhoc_promise(move |b| val.inner.build_expr(b), false)
113    }
114}