1use std::marker::PhantomData;
4
5use super::backend::Backend;
6use super::pool::DbPool;
7use super::repository::{extract_count, live_condition, placeholder, timestamp_placeholder};
8use super::timestamp::Timestamp;
9use super::{DbError, Model, ToColumn, Value};
10use crate::pagination::{CursorPage, Page};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum Order {
17 Asc,
18 Desc,
19}
20
21impl Order {
22 fn as_sql(self) -> &'static str {
23 match self {
24 Order::Asc => "ASC",
25 Order::Desc => "DESC",
26 }
27 }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34enum Deleted {
35 Exclude,
37 Include,
39 Only,
41}
42
43pub struct QueryBuilder<'a, T: Model> {
74 pool: &'a DbPool,
75 filters: Vec<(String, Vec<Value>)>,
78 order: Option<(String, Order)>,
79 limit: Option<u64>,
80 offset: Option<u64>,
81 deleted: Deleted,
82 _phantom: PhantomData<T>,
83}
84
85impl<'a, T: Model> Clone for QueryBuilder<'a, T> {
90 fn clone(&self) -> Self {
91 QueryBuilder {
92 pool: self.pool,
93 filters: self.filters.clone(),
94 order: self.order.clone(),
95 limit: self.limit,
96 offset: self.offset,
97 deleted: self.deleted,
98 _phantom: PhantomData,
99 }
100 }
101}
102
103impl<'a, T: Model> QueryBuilder<'a, T> {
104 pub fn new(pool: &'a DbPool) -> Self {
105 QueryBuilder {
106 pool,
107 filters: Vec::new(),
108 order: None,
109 limit: None,
110 offset: None,
111 deleted: Deleted::Exclude,
112 _phantom: PhantomData,
113 }
114 }
115
116 pub fn with_deleted(mut self) -> Self {
118 self.deleted = Deleted::Include;
119 self
120 }
121
122 pub fn only_deleted(mut self) -> Self {
124 self.deleted = Deleted::Only;
125 self
126 }
127
128 fn scoped_filters(&self) -> Vec<(String, Vec<Value>)> {
130 let mut filters = self.filters.clone();
131 if let Some(col) = T::soft_delete_column() {
132 match self.deleted {
133 Deleted::Exclude => filters.extend(live_condition::<T>().map(|c| (c, Vec::new()))),
134 Deleted::Include => {}
135 Deleted::Only => filters.push((format!("{col} IS NOT NULL"), Vec::new())),
136 }
137 } else if self.deleted == Deleted::Only {
138 filters.push(("1 = 0".to_string(), Vec::new()));
139 }
140 filters
141 }
142
143 pub fn where_eq(mut self, col: &str, val: impl ToColumn) -> Self {
145 self.filters.push((
146 format!("{} = __placeholder__", col),
147 vec![val.to_column()],
148 ));
149 self
150 }
151
152 pub fn filter(mut self, expr: &str, params: Vec<Value>) -> Self {
158 self.filters.push((expr.to_owned(), params));
159 self
160 }
161
162 pub fn order_by(mut self, col: &str, order: Order) -> Self {
164 self.order = Some((col.to_owned(), order));
165 self
166 }
167
168 pub fn limit(mut self, n: u64) -> Self {
170 self.limit = Some(n);
171 self
172 }
173
174 pub fn offset(mut self, n: u64) -> Self {
176 self.offset = Some(n);
177 self
178 }
179
180 pub async fn fetch_all(self) -> Result<Vec<T>, DbError> {
184 let backend = self.pool.backend();
185 let filters = self.scoped_filters();
186 let (sql, params) = build_select::<T>(
187 filters, self.order, self.limit, self.offset, "*", backend,
188 );
189 let rows = self.pool.query_rows(&sql, ¶ms).await?;
190 rows.iter().map(|r| T::from_row(r)).collect()
191 }
192
193 pub async fn fetch_one(self) -> Result<Option<T>, DbError> {
195 let backend = self.pool.backend();
196 let filters = self.scoped_filters();
197 let (sql, params) = build_select::<T>(
198 filters, self.order, Some(1), self.offset, "*", backend,
199 );
200 let rows = self.pool.query_rows(&sql, ¶ms).await?;
201 match rows.into_iter().next() {
202 Some(row) => Ok(Some(T::from_row(&row)?)),
203 None => Ok(None),
204 }
205 }
206
207 pub async fn count(self) -> Result<i64, DbError> {
210 let backend = self.pool.backend();
211 let filters = self.scoped_filters();
212 let (sql, params) = build_select::<T>(
213 filters, None, self.limit, self.offset, "COUNT(*)", backend,
214 );
215 let rows = self.pool.query_rows(&sql, ¶ms).await?;
216 extract_count(rows)
217 }
218
219 pub async fn delete(self) -> Result<(), DbError> {
223 let Some(col) = T::soft_delete_column() else {
224 return self.force_delete().await.map(|_| ());
225 };
226 let backend = self.pool.backend();
227 let mut filters = self.filters.clone();
228 filters.extend(live_condition::<T>().map(|c| (c, Vec::new())));
229 let (where_clause, where_params, _) = build_where(filters, 1, backend);
230 let mut params = vec![Timestamp::now().to_value(backend)];
231 params.extend(where_params);
232 let sql = format!("UPDATE {} SET {} = {}{}", T::table_name(), col, timestamp_placeholder(backend, 1), where_clause);
233 self.pool.execute(&sql, ¶ms).await?;
234 Ok(())
235 }
236
237 pub async fn force_delete(self) -> Result<u64, DbError> {
242 let backend = self.pool.backend();
243 let (where_clause, params, _) = build_where(self.scoped_filters(), 0, backend);
244 let sql = format!("DELETE FROM {}{}", T::table_name(), where_clause);
245 self.pool.execute(&sql, ¶ms).await
246 }
247
248 pub async fn restore(self) -> Result<u64, DbError> {
251 let Some(col) = T::soft_delete_column() else {
252 return Err(DbError::new(format!(
253 "restore: {} has no soft-delete column (add #[soft_delete] and a deleted_at field)",
254 T::table_name()
255 )));
256 };
257 let backend = self.pool.backend();
258 let mut filters = self.filters.clone();
259 filters.push((format!("{col} IS NOT NULL"), Vec::new()));
260 let (where_clause, params, _) = build_where(filters, 0, backend);
261 let sql = format!("UPDATE {} SET {} = NULL{}", T::table_name(), col, where_clause);
262 self.pool.execute(&sql, ¶ms).await
263 }
264
265 pub async fn update(self, col: &str, val: impl ToColumn) -> Result<(), DbError> {
268 let backend = self.pool.backend();
269 let (mut sets, mut params) = match val.to_column() {
270 Value::Null => (vec![format!("{col} = NULL")], Vec::new()),
273 value => (vec![format!("{} = {}", col, placeholder(backend, 1))], vec![value]),
274 };
275 if let Some(updated) = T::updated_at_column().filter(|c| *c != col) {
276 params.push(Timestamp::now().to_value(backend));
277 sets.push(format!("{} = {}", updated, timestamp_placeholder(backend, params.len())));
278 }
279 if let Some(version) = T::version_column().filter(|c| *c != col) {
280 sets.push(format!("{version} = {version} + 1"));
281 }
282 let (where_clause, where_params, _) = build_where(self.scoped_filters(), params.len(), backend);
283 params.extend(where_params);
284 let sql = format!("UPDATE {} SET {}{}", T::table_name(), sets.join(", "), where_clause);
285 self.pool.execute(&sql, ¶ms).await?;
286 Ok(())
287 }
288
289 pub async fn paginate(self, page: u64, per_page: u64) -> Result<Page<T>, DbError> {
299 let page = page.max(1);
300 let per_page = per_page.max(1);
301 let offset = (page - 1) * per_page;
302
303 let total_items = self.clone().count().await? as u64;
304 let items = self.limit(per_page).offset(offset).fetch_all().await?;
305
306 Ok(Page::new(items, page, per_page, total_items))
307 }
308
309 pub async fn paginate_after(self, cursor: Option<&str>, per_page: u64) -> Result<CursorPage<T>, DbError> {
326 let per_page = per_page.max(1);
327 let pk_col = T::primary_key_name();
328
329 let mut builder = self;
330 if let Some(cursor_str) = cursor {
331 let cursor_val: i64 = cursor_str
332 .parse()
333 .map_err(|_| DbError::new(format!("invalid cursor '{}': expected an integer primary key", cursor_str)))?;
334 builder = builder.filter(&format!("{} > __placeholder__", pk_col), vec![Value::Int(cursor_val)]);
335 }
336
337 let mut rows = builder.order_by(pk_col, Order::Asc).limit(per_page + 1).fetch_all().await?;
338
339 let has_more = rows.len() as u64 > per_page;
340 if has_more {
341 rows.truncate(per_page as usize);
342 }
343
344 let next_cursor = if has_more {
345 rows.last().map(|item| match item.primary_key_value() {
346 Value::Int(n) => n.to_string(),
347 Value::Text(s) => s,
348 other => format!("{:?}", other),
349 })
350 } else {
351 None
352 };
353
354 Ok(CursorPage { items: rows, next_cursor })
355 }
356}
357
358fn build_select<T: Model>(
361 filters: Vec<(String, Vec<Value>)>,
362 order: Option<(String, Order)>,
363 limit: Option<u64>,
364 offset: Option<u64>,
365 projection: &str,
366 backend: Backend,
367) -> (String, Vec<Value>) {
368 let (where_clause, params, _) = build_where(filters, 0, backend);
369 let mut sql = format!("SELECT {} FROM {}{}", projection, T::table_name(), where_clause);
370 if let Some((col, ord)) = order {
371 sql.push_str(&format!(" ORDER BY {} {}", col, ord.as_sql()));
372 }
373 if let Some(n) = limit {
374 sql.push_str(&format!(" LIMIT {}", n));
375 }
376 if let Some(n) = offset {
377 sql.push_str(&format!(" OFFSET {}", n));
378 }
379 (sql, params)
380}
381
382#[doc(hidden)]
384pub fn build_where(
385 filters: Vec<(String, Vec<Value>)>,
386 start_idx: usize,
387 backend: Backend,
388) -> (String, Vec<Value>, usize) {
389 let mut all_params: Vec<Value> = Vec::new();
390 let mut conditions: Vec<String> = Vec::new();
391 let mut idx = start_idx;
392
393 for (mut fragment, params) in filters {
394 for param in params {
395 idx += 1;
396 if fragment.contains("__placeholder__") {
397 fragment = fragment.replacen("__placeholder__", &placeholder(backend, idx), 1);
398 } else {
399 fragment = fragment.replacen("?", &placeholder(backend, idx), 1);
403 }
404 all_params.push(param);
405 }
406 conditions.push(fragment);
407 }
408
409 let where_clause = if conditions.is_empty() {
410 String::new()
411 } else {
412 format!(" WHERE {}", conditions.join(" AND "))
413 };
414
415 (where_clause, all_params, idx)
416}