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 where_in<I>(mut self, col: &str, values: I) -> Self
154 where
155 I: IntoIterator,
156 I::Item: ToColumn,
157 {
158 let values: Vec<Value> = values.into_iter().map(|v| v.to_column()).collect();
159 if values.is_empty() {
160 self.filters.push(("1 = 0".to_string(), Vec::new()));
161 } else {
162 let marks = vec!["__placeholder__"; values.len()].join(", ");
163 self.filters.push((format!("{col} IN ({marks})"), values));
164 }
165 self
166 }
167
168 pub fn where_null(mut self, col: &str) -> Self {
170 self.filters.push((format!("{col} IS NULL"), Vec::new()));
171 self
172 }
173
174 pub fn where_not_null(mut self, col: &str) -> Self {
176 self.filters.push((format!("{col} IS NOT NULL"), Vec::new()));
177 self
178 }
179
180 pub fn scope(self, scope: impl FnOnce(Self) -> Self) -> Self {
208 scope(self)
209 }
210
211 pub fn with<R: Model + Clone + 'static>(self, relation: super::Relation<T, R>) -> super::EagerQuery<'a, T>
221 where
222 T: 'static,
223 {
224 super::EagerQuery::new(self).with(relation)
225 }
226
227 pub(crate) fn pool(&self) -> &'a DbPool {
229 self.pool
230 }
231
232 pub fn filter(mut self, expr: &str, params: Vec<Value>) -> Self {
238 self.filters.push((expr.to_owned(), params));
239 self
240 }
241
242 pub fn order_by(mut self, col: &str, order: Order) -> Self {
244 self.order = Some((col.to_owned(), order));
245 self
246 }
247
248 pub fn limit(mut self, n: u64) -> Self {
250 self.limit = Some(n);
251 self
252 }
253
254 pub fn offset(mut self, n: u64) -> Self {
256 self.offset = Some(n);
257 self
258 }
259
260 pub async fn fetch_all(self) -> Result<Vec<T>, DbError> {
264 let backend = self.pool.backend();
265 let filters = self.scoped_filters();
266 let (sql, params) = build_select::<T>(
267 filters, self.order, self.limit, self.offset, "*", backend,
268 );
269 let rows = self.pool.query_rows(&sql, ¶ms).await?;
270 rows.iter().map(|r| T::from_row(r)).collect()
271 }
272
273 pub async fn fetch_one(self) -> Result<Option<T>, DbError> {
275 let backend = self.pool.backend();
276 let filters = self.scoped_filters();
277 let (sql, params) = build_select::<T>(
278 filters, self.order, Some(1), self.offset, "*", backend,
279 );
280 let rows = self.pool.query_rows(&sql, ¶ms).await?;
281 match rows.into_iter().next() {
282 Some(row) => Ok(Some(T::from_row(&row)?)),
283 None => Ok(None),
284 }
285 }
286
287 pub async fn count(self) -> Result<i64, DbError> {
290 let backend = self.pool.backend();
291 let filters = self.scoped_filters();
292 let (sql, params) = build_select::<T>(
293 filters, None, self.limit, self.offset, "COUNT(*)", backend,
294 );
295 let rows = self.pool.query_rows(&sql, ¶ms).await?;
296 extract_count(rows)
297 }
298
299 pub async fn delete(self) -> Result<(), DbError> {
303 let Some(col) = T::soft_delete_column() else {
304 return self.force_delete().await.map(|_| ());
305 };
306 let backend = self.pool.backend();
307 let mut filters = self.filters.clone();
308 filters.extend(live_condition::<T>().map(|c| (c, Vec::new())));
309 let (where_clause, where_params, _) = build_where(filters, 1, backend);
310 let mut params = vec![Timestamp::now().to_value(backend)];
311 params.extend(where_params);
312 let sql = format!("UPDATE {} SET {} = {}{}", T::table_name(), col, timestamp_placeholder(backend, 1), where_clause);
313 self.pool.execute(&sql, ¶ms).await?;
314 Ok(())
315 }
316
317 pub async fn force_delete(self) -> Result<u64, DbError> {
322 let backend = self.pool.backend();
323 let (where_clause, params, _) = build_where(self.scoped_filters(), 0, backend);
324 let sql = format!("DELETE FROM {}{}", T::table_name(), where_clause);
325 self.pool.execute(&sql, ¶ms).await
326 }
327
328 pub async fn restore(self) -> Result<u64, DbError> {
331 let Some(col) = T::soft_delete_column() else {
332 return Err(DbError::new(format!(
333 "restore: {} has no soft-delete column (add #[soft_delete] and a deleted_at field)",
334 T::table_name()
335 )));
336 };
337 let backend = self.pool.backend();
338 let mut filters = self.filters.clone();
339 filters.push((format!("{col} IS NOT NULL"), Vec::new()));
340 let (where_clause, params, _) = build_where(filters, 0, backend);
341 let sql = format!("UPDATE {} SET {} = NULL{}", T::table_name(), col, where_clause);
342 self.pool.execute(&sql, ¶ms).await
343 }
344
345 pub async fn update(self, col: &str, val: impl ToColumn) -> Result<(), DbError> {
348 let backend = self.pool.backend();
349 let (mut sets, mut params) = match val.to_column() {
350 Value::Null => (vec![format!("{col} = NULL")], Vec::new()),
353 value => (vec![format!("{} = {}", col, placeholder(backend, 1))], vec![value]),
354 };
355 if let Some(updated) = T::updated_at_column().filter(|c| *c != col) {
356 params.push(Timestamp::now().to_value(backend));
357 sets.push(format!("{} = {}", updated, timestamp_placeholder(backend, params.len())));
358 }
359 if let Some(version) = T::version_column().filter(|c| *c != col) {
360 sets.push(format!("{version} = {version} + 1"));
361 }
362 let (where_clause, where_params, _) = build_where(self.scoped_filters(), params.len(), backend);
363 params.extend(where_params);
364 let sql = format!("UPDATE {} SET {}{}", T::table_name(), sets.join(", "), where_clause);
365 self.pool.execute(&sql, ¶ms).await?;
366 Ok(())
367 }
368
369 pub async fn paginate(self, page: u64, per_page: u64) -> Result<Page<T>, DbError> {
379 let page = page.max(1);
380 let per_page = per_page.max(1);
381 let offset = (page - 1) * per_page;
382
383 let total_items = self.clone().count().await? as u64;
384 let items = self.limit(per_page).offset(offset).fetch_all().await?;
385
386 Ok(Page::new(items, page, per_page, total_items))
387 }
388
389 pub async fn paginate_after(self, cursor: Option<&str>, per_page: u64) -> Result<CursorPage<T>, DbError> {
406 let per_page = per_page.max(1);
407 let pk_col = T::primary_key_name();
408
409 let mut builder = self;
410 if let Some(cursor_str) = cursor {
411 let cursor_val: i64 = cursor_str
412 .parse()
413 .map_err(|_| DbError::new(format!("invalid cursor '{}': expected an integer primary key", cursor_str)))?;
414 builder = builder.filter(&format!("{} > __placeholder__", pk_col), vec![Value::Int(cursor_val)]);
415 }
416
417 let mut rows = builder.order_by(pk_col, Order::Asc).limit(per_page + 1).fetch_all().await?;
418
419 let has_more = rows.len() as u64 > per_page;
420 if has_more {
421 rows.truncate(per_page as usize);
422 }
423
424 let next_cursor = if has_more {
425 rows.last().map(|item| match item.primary_key_value() {
426 Value::Int(n) => n.to_string(),
427 Value::Text(s) => s,
428 other => format!("{:?}", other),
429 })
430 } else {
431 None
432 };
433
434 Ok(CursorPage { items: rows, next_cursor })
435 }
436}
437
438fn build_select<T: Model>(
441 filters: Vec<(String, Vec<Value>)>,
442 order: Option<(String, Order)>,
443 limit: Option<u64>,
444 offset: Option<u64>,
445 projection: &str,
446 backend: Backend,
447) -> (String, Vec<Value>) {
448 let (where_clause, params, _) = build_where(filters, 0, backend);
449 let mut sql = format!("SELECT {} FROM {}{}", projection, T::table_name(), where_clause);
450 if let Some((col, ord)) = order {
451 sql.push_str(&format!(" ORDER BY {} {}", col, ord.as_sql()));
452 }
453 if let Some(n) = limit {
454 sql.push_str(&format!(" LIMIT {}", n));
455 }
456 if let Some(n) = offset {
457 sql.push_str(&format!(" OFFSET {}", n));
458 }
459 (sql, params)
460}
461
462#[doc(hidden)]
464pub fn build_where(
465 filters: Vec<(String, Vec<Value>)>,
466 start_idx: usize,
467 backend: Backend,
468) -> (String, Vec<Value>, usize) {
469 let mut all_params: Vec<Value> = Vec::new();
470 let mut conditions: Vec<String> = Vec::new();
471 let mut idx = start_idx;
472
473 for (mut fragment, params) in filters {
474 for param in params {
475 idx += 1;
476 if fragment.contains("__placeholder__") {
477 fragment = fragment.replacen("__placeholder__", &placeholder(backend, idx), 1);
478 } else {
479 fragment = fragment.replacen("?", &placeholder(backend, idx), 1);
483 }
484 all_params.push(param);
485 }
486 conditions.push(fragment);
487 }
488
489 let where_clause = if conditions.is_empty() {
490 String::new()
491 } else {
492 format!(" WHERE {}", conditions.join(" AND "))
493 };
494
495 (where_clause, all_params, idx)
496}