Skip to main content

cratestack_sqlx/query/
read.rs

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