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}