Skip to main content

drizzle_core/row/
count.rs

1//! How many rows a query returns, as far as its type knows.
2//!
3//! `.get()` returns the first row. Most queries can return none, so it
4//! returns `Option<R>`. A query whose type proves it returns exactly one row
5//! returns `R` instead: a SELECT of only aggregates (`count`, `sum`, `max`,
6//! ...) with no GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET or set operation,
7//! which returns one row even when no row matches its WHERE.
8
9use super::{AggStatus, PkGroup, Scoped, SelectAs, SelectCols, SelectExpr, SelectStar};
10use crate::error::DrizzleError;
11use crate::expr::{AllAgg, AllScalar, MixedAgg};
12use crate::{Cons, ExecutableState};
13
14mod sealed {
15    pub trait Sealed {}
16
17    impl Sealed for super::ExactlyOne {}
18    impl Sealed for super::ZeroOrMore {}
19}
20
21/// How many rows a query returns, as far as its type knows: [`ExactlyOne`]
22/// or [`ZeroOrMore`]. It decides what `.get()` returns.
23pub trait RowCount: sealed::Sealed {
24    /// What `.get()` returns for rows of type `R`.
25    type Get<R>;
26
27    /// The first row of a result as [`Self::Get`].
28    ///
29    /// # Errors
30    ///
31    /// [`ExactlyOne`] returns [`DrizzleError::NotFound`] when the database
32    /// returned no row.
33    fn first<R>(row: Option<R>) -> Result<Self::Get<R>, DrizzleError>;
34}
35
36/// The query always returns one row, so `.get()` returns it as `R`.
37#[derive(Debug, Clone, Copy, Default)]
38pub struct ExactlyOne;
39
40impl RowCount for ExactlyOne {
41    type Get<R> = R;
42
43    fn first<R>(row: Option<R>) -> Result<R, DrizzleError> {
44        row.ok_or(DrizzleError::NotFound)
45    }
46}
47
48/// The query can return no row, so `.get()` returns `Option<R>`.
49#[derive(Debug, Clone, Copy, Default)]
50pub struct ZeroOrMore;
51
52impl RowCount for ZeroOrMore {
53    type Get<R> = Option<R>;
54
55    fn first<R>(row: Option<R>) -> Result<Option<R>, DrizzleError> {
56        Ok(row)
57    }
58}
59
60/// The [`RowCount`] of a query that selects `Self`, grouped by `Grouped`, in
61/// builder state `State`.
62pub trait QueryRows<Grouped, State> {
63    /// [`ExactlyOne`] or [`ZeroOrMore`].
64    type Rows: RowCount;
65}
66
67impl<Mk, Grouped, State> QueryRows<Grouped, State> for Mk
68where
69    Mk: SelectionRows,
70    Grouped: GroupingRows,
71    State: ExecutableState,
72{
73    type Rows = State::Rows<Grouped::Rows<Mk::Rows>>;
74}
75
76/// What `.get()` returns for a query that selects `Mk`, grouped by
77/// `Grouped`, in state `State`: `R` or `Option<R>`.
78pub type GetRow<Mk, Grouped, State, R> =
79    <<Mk as QueryRows<Grouped, State>>::Rows as RowCount>::Get<R>;
80
81/// The [`RowCount`] a selection gives without GROUP BY: [`ExactlyOne`] for
82/// a list of only aggregates, else [`ZeroOrMore`].
83pub trait SelectionRows {
84    /// [`ExactlyOne`] or [`ZeroOrMore`].
85    type Rows: RowCount;
86}
87
88impl<M: SelectionRows, Scope, Used> SelectionRows for Scoped<M, Scope, Used> {
89    type Rows = M::Rows;
90}
91
92impl SelectionRows for SelectStar {
93    type Rows = ZeroOrMore;
94}
95
96impl SelectionRows for SelectExpr {
97    type Rows = ZeroOrMore;
98}
99
100impl<R> SelectionRows for SelectAs<R> {
101    type Rows = ZeroOrMore;
102}
103
104impl<Cols> SelectionRows for SelectCols<Cols>
105where
106    Cols: AggStatus,
107    Cols::Status: StatusRows,
108{
109    type Rows = <Cols::Status as StatusRows>::Rows;
110}
111
112/// The [`RowCount`] after GROUP BY: unchanged without one (`()`), else
113/// [`ZeroOrMore`].
114pub trait GroupingRows {
115    /// The row count of a selection with [`RowCount`] `R`.
116    type Rows<R: RowCount>: RowCount;
117}
118
119impl GroupingRows for () {
120    type Rows<R: RowCount> = R;
121}
122
123impl<Head, Tail> GroupingRows for Cons<Head, Tail> {
124    type Rows<R: RowCount> = ZeroOrMore;
125}
126
127impl<Table> GroupingRows for PkGroup<Table> {
128    type Rows<R: RowCount> = ZeroOrMore;
129}
130
131/// The [`RowCount`] a SELECT-list status gives without GROUP BY.
132pub trait StatusRows {
133    /// [`ExactlyOne`] or [`ZeroOrMore`].
134    type Rows: RowCount;
135}
136
137impl StatusRows for AllAgg {
138    type Rows = ExactlyOne;
139}
140
141impl StatusRows for AllScalar {
142    type Rows = ZeroOrMore;
143}
144
145impl StatusRows for MixedAgg {
146    type Rows = ZeroOrMore;
147}
148
149/// Raw SQL in a SELECT list counts as scalar: drizzle cannot see an
150/// aggregate inside it, so the query may return no row.
151impl<V: crate::SQLParam> crate::expr::HasAggStatus for crate::sql::SQL<'_, V> {
152    type Status = AllScalar;
153}
154
155/// The first row of a query's result as `.get()` returns it: `R` when the
156/// query selects `Mk` in a way that returns exactly one row, else
157/// `Option<R>`.
158///
159/// # Errors
160///
161/// Returns [`DrizzleError::NotFound`] when the query should return exactly
162/// one row but the database returned none.
163#[doc(hidden)]
164pub fn first_row<Mk, Grouped, State, R>(
165    row: Option<R>,
166) -> Result<GetRow<Mk, Grouped, State, R>, DrizzleError>
167where
168    Mk: QueryRows<Grouped, State>,
169{
170    <Mk::Rows as RowCount>::first(row)
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use crate::builder::{SelectFromSet, SelectOrderSet, SelectSetOpSet, SelectWhereSet};
177
178    fn rows_of<S: ExecutableState>() -> &'static str {
179        core::any::type_name::<S::Rows<ExactlyOne>>()
180    }
181
182    #[test]
183    fn only_clauses_that_keep_every_row_keep_an_aggregate_s_one_row() {
184        assert!(rows_of::<SelectFromSet>().ends_with("ExactlyOne"));
185        assert!(rows_of::<SelectWhereSet>().ends_with("ExactlyOne"));
186        // ORDER BY is reachable after a set operation, which can remove the row.
187        assert!(rows_of::<SelectSetOpSet>().ends_with("ZeroOrMore"));
188        assert!(rows_of::<SelectOrderSet>().ends_with("ZeroOrMore"));
189    }
190}