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