Skip to main content

cratestack_sqlx/query/
read.rs

1use crate::sqlx;
2
3use cratestack_core::{CoolContext, CoolError};
4
5use crate::{
6    FilterExpr, ModelDescriptor, OrderClause, SqlxRuntime, render::render_read_policy_sql,
7    render::render_scoped_select_sql,
8};
9
10use super::support::{push_order_and_paging, push_scoped_conditions};
11
12#[derive(Debug, Clone)]
13pub struct FindMany<'a, M: 'static, PK: 'static> {
14    pub(crate) runtime: &'a SqlxRuntime,
15    pub(crate) descriptor: &'static ModelDescriptor<M, PK>,
16    pub(crate) filters: Vec<FilterExpr>,
17    pub(crate) order_by: Vec<OrderClause>,
18    pub(crate) limit: Option<i64>,
19    pub(crate) offset: Option<i64>,
20}
21
22impl<'a, M: 'static, PK: 'static> FindMany<'a, M, PK> {
23    pub fn where_(mut self, filter: crate::Filter) -> Self {
24        self.filters.push(FilterExpr::from(filter));
25        self
26    }
27
28    pub fn where_expr(mut self, filter: FilterExpr) -> Self {
29        self.filters.push(filter);
30        self
31    }
32
33    pub fn where_any(mut self, filters: impl IntoIterator<Item = FilterExpr>) -> Self {
34        self.filters.push(FilterExpr::any(filters));
35        self
36    }
37
38    pub fn order_by(mut self, clause: OrderClause) -> Self {
39        self.order_by.push(clause);
40        self
41    }
42
43    pub fn limit(mut self, limit: i64) -> Self {
44        self.limit = Some(limit);
45        self
46    }
47
48    pub fn offset(mut self, offset: i64) -> Self {
49        self.offset = Some(offset);
50        self
51    }
52
53    pub fn preview_sql(&self) -> String {
54        let mut sql = format!(
55            "SELECT {} FROM {}",
56            self.descriptor.select_projection(),
57            self.descriptor.table_name,
58        );
59        let order_by = self.effective_order_by();
60
61        let mut bind_index = 1usize;
62        if !self.filters.is_empty() {
63            sql.push_str(" WHERE ");
64            for (index, filter) in self.filters.iter().enumerate() {
65                if index > 0 {
66                    sql.push_str(" AND ");
67                }
68                crate::render::render_filter_expr_sql(filter, &mut sql, &mut bind_index);
69            }
70        }
71
72        if !order_by.is_empty() {
73            sql.push_str(" ORDER BY ");
74            for (index, clause) in order_by.iter().enumerate() {
75                if index > 0 {
76                    sql.push_str(", ");
77                }
78                crate::render::render_order_clause_sql(clause, &mut sql);
79            }
80        }
81
82        match (self.limit, self.offset) {
83            (Some(_), Some(_)) => {
84                sql.push_str(&format!(" LIMIT ${bind_index} OFFSET ${}", bind_index + 1));
85            }
86            (Some(_), None) => {
87                sql.push_str(&format!(" LIMIT ${bind_index}"));
88            }
89            (None, Some(_)) => {
90                sql.push_str(&format!(" OFFSET ${bind_index}"));
91            }
92            (None, None) => {}
93        }
94
95        sql
96    }
97
98    pub fn preview_scoped_sql(&self, ctx: &CoolContext) -> String {
99        let order_by = self.effective_order_by();
100        render_scoped_select_sql(
101            self.descriptor,
102            &self.filters,
103            &order_by,
104            self.limit,
105            self.offset,
106            ctx,
107        )
108    }
109
110    pub async fn run(self, ctx: &CoolContext) -> Result<Vec<M>, CoolError>
111    where
112        for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow>,
113    {
114        let order_by = self.effective_order_by();
115        let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT ");
116        query
117            .push(self.descriptor.select_projection())
118            .push(" FROM ")
119            .push(self.descriptor.table_name);
120
121        push_scoped_conditions(
122            &mut query,
123            self.descriptor,
124            &self.filters,
125            None::<(&'static str, i64)>,
126            ctx,
127        );
128        push_order_and_paging(&mut query, &order_by, self.limit, self.offset);
129
130        query
131            .build_query_as::<M>()
132            .fetch_all(self.runtime.pool())
133            .await
134            .map_err(|error| CoolError::Database(error.to_string()))
135    }
136
137    fn effective_order_by(&self) -> Vec<OrderClause> {
138        let mut order_by = self.order_by.clone();
139        let Some(direction) = order_by
140            .iter()
141            .find(|clause| clause.is_relation_scalar())
142            .map(OrderClause::direction)
143        else {
144            return order_by;
145        };
146
147        if order_by
148            .iter()
149            .any(|clause| clause.targets_column(self.descriptor.primary_key))
150        {
151            return order_by;
152        }
153
154        order_by.push(OrderClause::column(self.descriptor.primary_key, direction));
155        order_by
156    }
157}
158
159#[derive(Debug, Clone)]
160pub struct FindUnique<'a, M: 'static, PK: 'static> {
161    pub(crate) runtime: &'a SqlxRuntime,
162    pub(crate) descriptor: &'static ModelDescriptor<M, PK>,
163    pub(crate) id: PK,
164}
165
166impl<'a, M: 'static, PK: 'static> FindUnique<'a, M, PK> {
167    pub fn preview_sql(&self) -> String {
168        format!(
169            "SELECT {} FROM {} WHERE {} = $1 LIMIT 1",
170            self.descriptor.select_projection(),
171            self.descriptor.table_name,
172            self.descriptor.primary_key,
173        )
174    }
175
176    pub fn preview_scoped_sql(&self, ctx: &CoolContext) -> String {
177        let mut sql = format!(
178            "SELECT {} FROM {}",
179            self.descriptor.select_projection(),
180            self.descriptor.table_name,
181        );
182        let mut bind_index = 1usize;
183        if let Some(policy_clause) = render_read_policy_sql(
184            self.descriptor.detail_allow_policies,
185            self.descriptor.detail_deny_policies,
186            ctx,
187            &mut bind_index,
188        ) {
189            sql.push_str(&format!(
190                " WHERE ({policy_clause}) AND {} = ${bind_index} LIMIT 1",
191                self.descriptor.primary_key
192            ));
193        } else {
194            sql.push_str(&format!(
195                " WHERE {} = ${bind_index} LIMIT 1",
196                self.descriptor.primary_key
197            ));
198        }
199        sql
200    }
201
202    pub async fn run(self, ctx: &CoolContext) -> Result<Option<M>, CoolError>
203    where
204        for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow>,
205        PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
206    {
207        let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT ");
208        query
209            .push(self.descriptor.select_projection())
210            .push(" FROM ")
211            .push(self.descriptor.table_name);
212        push_scoped_conditions(
213            &mut query,
214            self.descriptor,
215            &[],
216            Some((self.descriptor.primary_key, self.id)),
217            ctx,
218        );
219        query.push(" LIMIT 1");
220
221        query
222            .build_query_as::<M>()
223            .fetch_optional(self.runtime.pool())
224            .await
225            .map_err(|error| CoolError::Database(error.to_string()))
226    }
227}