Skip to main content

cratestack_sqlx/query/read/
aggregate_count.rs

1//! `aggregate.count()` — `COUNT(*)` with filter + read policy.
2
3use cratestack_core::{CoolContext, CoolError};
4use cratestack_sql::ReadSource;
5
6use crate::query::support::{ReadPolicyKind, push_scoped_conditions};
7use crate::{FilterExpr, SqlxRuntime, sqlx};
8
9#[derive(Clone)]
10pub struct AggregateCount<'a, M: 'static, PK: 'static> {
11    runtime: &'a SqlxRuntime,
12    descriptor: &'static dyn ReadSource<M, PK>,
13    filters: Vec<FilterExpr>,
14}
15
16impl<'a, M: 'static, PK: 'static> AggregateCount<'a, M, PK> {
17    pub(super) fn new(
18        runtime: &'a SqlxRuntime,
19        descriptor: &'static dyn ReadSource<M, PK>,
20    ) -> Self {
21        Self {
22            runtime,
23            descriptor,
24            filters: Vec::new(),
25        }
26    }
27
28    pub fn where_(mut self, filter: crate::Filter) -> Self {
29        self.filters.push(FilterExpr::from(filter));
30        self
31    }
32
33    pub fn where_expr(mut self, filter: FilterExpr) -> Self {
34        self.filters.push(filter);
35        self
36    }
37
38    pub fn where_any(mut self, filters: impl IntoIterator<Item = FilterExpr>) -> Self {
39        self.filters.push(FilterExpr::any(filters));
40        self
41    }
42
43    pub fn where_optional<F>(mut self, filter: Option<F>) -> Self
44    where
45        F: Into<FilterExpr>,
46    {
47        if let Some(filter) = filter {
48            self.filters.push(filter.into());
49        }
50        self
51    }
52
53    fn build_query<'q>(&self, ctx: &CoolContext) -> sqlx::QueryBuilder<'q, sqlx::Postgres> {
54        let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT COUNT(*) FROM ");
55        query.push(self.descriptor.table_name());
56        push_scoped_conditions(
57            &mut query,
58            self.descriptor,
59            &self.filters,
60            None::<(&'static str, i64)>,
61            ctx,
62            ReadPolicyKind::List,
63        );
64        query
65    }
66
67    pub async fn run(self, ctx: &CoolContext) -> Result<i64, CoolError> {
68        let mut query = self.build_query(ctx);
69        let value: (i64,) = query
70            .build_query_as::<(i64,)>()
71            .fetch_one(self.runtime.pool())
72            .await
73            .map_err(|error| CoolError::Database(error.to_string()))?;
74        Ok(value.0)
75    }
76
77    pub async fn run_in_tx<'tx>(
78        self,
79        tx: &mut sqlx::Transaction<'tx, sqlx::Postgres>,
80        ctx: &CoolContext,
81    ) -> Result<i64, CoolError> {
82        let mut query = self.build_query(ctx);
83        let value: (i64,) = query
84            .build_query_as::<(i64,)>()
85            .fetch_one(&mut **tx)
86            .await
87            .map_err(|error| CoolError::Database(error.to_string()))?;
88        Ok(value.0)
89    }
90}