Skip to main content

cratestack_sqlx/query/read/
projected_find_many.rs

1//! `find_many().select([...])` — projected multi-row read that
2//! returns `Vec<Projection<M>>`. Same partial-decode contract as
3//! [`super::projected_find_unique`].
4
5use cratestack_core::{CoolContext, CoolError};
6use cratestack_sql::IntoColumnName;
7
8use crate::query::support::{ReadPolicyKind, push_order_and_paging, push_scoped_conditions};
9use crate::{FilterExpr, ModelDescriptor, OrderClause, SqlxRuntime, sqlx};
10
11use super::find_many::FindMany;
12
13#[derive(Debug, Clone)]
14pub struct ProjectedFindMany<'a, M: 'static, PK: 'static> {
15    runtime: &'a SqlxRuntime,
16    descriptor: &'static ModelDescriptor<M, PK>,
17    filters: Vec<FilterExpr>,
18    order_by: Vec<OrderClause>,
19    limit: Option<i64>,
20    offset: Option<i64>,
21    for_update: bool,
22    selected: Vec<&'static str>,
23}
24
25impl<'a, M: 'static, PK: 'static> ProjectedFindMany<'a, M, PK> {
26    pub fn where_(mut self, filter: crate::Filter) -> Self {
27        self.filters.push(FilterExpr::from(filter));
28        self
29    }
30
31    pub fn where_expr(mut self, filter: FilterExpr) -> Self {
32        self.filters.push(filter);
33        self
34    }
35
36    pub fn where_any(mut self, filters: impl IntoIterator<Item = FilterExpr>) -> Self {
37        self.filters.push(FilterExpr::any(filters));
38        self
39    }
40
41    pub fn where_optional<F>(mut self, filter: Option<F>) -> Self
42    where
43        F: Into<FilterExpr>,
44    {
45        if let Some(filter) = filter {
46            self.filters.push(filter.into());
47        }
48        self
49    }
50
51    pub fn order_by(mut self, clause: OrderClause) -> Self {
52        self.order_by.push(clause);
53        self
54    }
55
56    pub fn limit(mut self, limit: i64) -> Self {
57        self.limit = Some(limit);
58        self
59    }
60
61    pub fn offset(mut self, offset: i64) -> Self {
62        self.offset = Some(offset);
63        self
64    }
65
66    pub fn for_update(mut self) -> Self {
67        self.for_update = true;
68        self
69    }
70
71    fn build_query<'q>(&self, ctx: &CoolContext) -> sqlx::QueryBuilder<'q, sqlx::Postgres> {
72        let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT ");
73        query
74            .push(self.descriptor.select_projection_subset(&self.selected))
75            .push(" FROM ")
76            .push(self.descriptor.table_name);
77        push_scoped_conditions(
78            &mut query,
79            self.descriptor,
80            &self.filters,
81            None::<(&'static str, i64)>,
82            ctx,
83            ReadPolicyKind::List,
84        );
85        push_order_and_paging(&mut query, &self.order_by, self.limit, self.offset);
86        if self.for_update {
87            query.push(" FOR UPDATE");
88        }
89        query
90    }
91
92    pub async fn run(
93        self,
94        ctx: &CoolContext,
95    ) -> Result<Vec<cratestack_sql::Projection<M>>, CoolError>
96    where
97        M: crate::FromPartialPgRow,
98    {
99        let mut query = self.build_query(ctx);
100        let rows = query
101            .build()
102            .fetch_all(self.runtime.pool())
103            .await
104            .map_err(|error| CoolError::Database(error.to_string()))?;
105        decode_many::<M>(rows, &self.selected)
106    }
107
108    pub async fn run_in_tx<'tx>(
109        self,
110        tx: &mut sqlx::Transaction<'tx, sqlx::Postgres>,
111        ctx: &CoolContext,
112    ) -> Result<Vec<cratestack_sql::Projection<M>>, CoolError>
113    where
114        M: crate::FromPartialPgRow,
115    {
116        let mut query = self.build_query(ctx);
117        let rows = query
118            .build()
119            .fetch_all(&mut **tx)
120            .await
121            .map_err(|error| CoolError::Database(error.to_string()))?;
122        decode_many::<M>(rows, &self.selected)
123    }
124}
125
126fn decode_many<M>(
127    rows: Vec<sqlx::postgres::PgRow>,
128    selected: &[&'static str],
129) -> Result<Vec<cratestack_sql::Projection<M>>, CoolError>
130where
131    M: crate::FromPartialPgRow,
132{
133    rows.into_iter()
134        .map(|row| {
135            M::decode_partial_pg_row(&row, selected)
136                .map(|value| cratestack_sql::Projection {
137                    value,
138                    selected: selected.to_vec(),
139                })
140                .map_err(|error| CoolError::Database(error.to_string()))
141        })
142        .collect()
143}
144
145impl<'a, M: 'static, PK: 'static> FindMany<'a, M, PK> {
146    /// Restrict the SELECT to the named columns. See
147    /// [`super::find_unique::FindUnique::select`] for the caller-side
148    /// contract.
149    pub fn select<I, C>(self, columns: I) -> ProjectedFindMany<'a, M, PK>
150    where
151        I: IntoIterator<Item = C>,
152        C: IntoColumnName,
153    {
154        ProjectedFindMany {
155            runtime: self.runtime,
156            descriptor: self.descriptor,
157            filters: self.filters,
158            order_by: self.order_by,
159            limit: self.limit,
160            offset: self.offset,
161            for_update: self.for_update,
162            selected: columns
163                .into_iter()
164                .map(IntoColumnName::into_column_name)
165                .collect(),
166        }
167    }
168}