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}