1use std::marker::PhantomData;
2
3use super::column::{CondKind, Condition, IntoColumn};
4use super::paginate::{CursorPage, SimplePage};
5use super::{Db, DbValue, Dialect, Executor, FromDb, Model, Paginated, ToDbValue, now, quote, sql};
6use crate::Result;
7use anyhow::anyhow;
8
9const OPERATORS: &[&str] = &["=", "!=", "<>", "<", "<=", ">", ">=", "like", "not like"];
10
11#[derive(Clone, Copy, PartialEq)]
12enum Trashed {
13 Without,
14 With,
15 Only,
16}
17
18const LARGE_IN: usize = 1000;
20
21pub trait Number: FromDb + sealed::Sealed {
23 #[doc(hidden)]
24 const SQL_TYPE: &'static str;
25}
26
27impl Number for i64 {
28 const SQL_TYPE: &'static str = "BIGINT";
29}
30
31impl Number for f64 {
32 const SQL_TYPE: &'static str = "DOUBLE PRECISION";
33}
34
35mod sealed {
36 pub trait Sealed {}
37 impl Sealed for i64 {}
38 impl Sealed for f64 {}
39}
40
41#[derive(Clone)]
43enum Filter {
44 Sql(String),
46 Like { column: String, not: bool },
48 JsonIn { kind: &'static str, column: String },
50 Group { any: bool, filters: Vec<Filter> },
52 Not(Box<Filter>),
54 Exists {
56 not: bool,
57 table: &'static str,
58 correlation: String,
59 filters: Vec<Filter>,
60 },
61 InQuery {
63 column: String,
64 table: &'static str,
65 sub_column: String,
66 filters: Vec<Filter>,
67 },
68 Search { sqlite: String, postgres: String },
71}
72
73#[derive(Clone)]
75enum Order {
76 Sql(String),
78 Relevance { sqlite: String, postgres: String },
81}
82
83impl Order {
84 fn render(&self, dialect: Dialect) -> &str {
85 match (self, dialect) {
86 (Order::Sql(sql), _) => sql,
87 (Order::Relevance { sqlite, .. }, Dialect::Sqlite) => sqlite,
88 (Order::Relevance { postgres, .. }, Dialect::Postgres) => postgres,
89 }
90 }
91}
92
93impl Filter {
94 fn render(&self, dialect: Dialect) -> String {
95 match self {
96 Filter::Sql(sql) => sql.clone(),
97 Filter::Like { column, not } => {
98 let op = match (dialect, not) {
99 (Dialect::Postgres, false) => "ILIKE",
100 (Dialect::Postgres, true) => "NOT ILIKE",
101 (_, false) => "LIKE",
102 (_, true) => "NOT LIKE",
103 };
104 format!("{column} {op} ?")
105 }
106 Filter::JsonIn { kind, column } => json_in(kind, column, dialect),
107 Filter::Group { any, filters } => {
108 if filters.is_empty() {
109 return if *any { "0 = 1".into() } else { "1 = 1".into() };
111 }
112 let joiner = if *any { " OR " } else { " AND " };
113 let parts: Vec<String> = filters.iter().map(|f| f.render(dialect)).collect();
114 format!("({})", parts.join(joiner))
115 }
116 Filter::Not(filter) => format!("NOT ({})", filter.render(dialect)),
117 Filter::Exists {
118 not,
119 table,
120 correlation,
121 filters,
122 } => {
123 let mut parts = vec![correlation.clone()];
124 parts.extend(filters.iter().map(|f| f.render(dialect)));
125 format!(
126 "{}EXISTS (SELECT 1 FROM {} WHERE {})",
127 if *not { "NOT " } else { "" },
128 quote(table),
129 parts.join(" AND ")
130 )
131 }
132 Filter::InQuery {
133 column,
134 table,
135 sub_column,
136 filters,
137 } => {
138 let condition = if filters.is_empty() {
139 String::new()
140 } else {
141 let parts: Vec<String> = filters.iter().map(|f| f.render(dialect)).collect();
142 format!(" WHERE {}", parts.join(" AND "))
143 };
144 format!(
145 "{column} IN (SELECT {sub_column} FROM {}{condition})",
146 quote(table)
147 )
148 }
149 Filter::Search { sqlite, postgres } => match dialect {
150 Dialect::Sqlite => sqlite.clone(),
151 Dialect::Postgres => postgres.clone(),
152 },
153 }
154 }
155}
156
157fn large_list(values: &[DbValue]) -> Option<(&'static str, serde_json::Value)> {
159 if values.len() <= LARGE_IN {
160 return None;
161 }
162 if let Some(ints) = values
163 .iter()
164 .map(|v| match v {
165 DbValue::Integer(n) => Some(serde_json::Value::from(*n)),
166 _ => None,
167 })
168 .collect::<Option<Vec<_>>>()
169 {
170 return Some(("int", ints.into()));
171 }
172 values
173 .iter()
174 .map(|v| match v {
175 DbValue::Text(s) => Some(serde_json::Value::from(s.clone())),
176 _ => None,
177 })
178 .collect::<Option<Vec<_>>>()
179 .map(|texts| ("text", texts.into()))
180}
181
182fn json_in(kind: &str, column: &str, dialect: Dialect) -> String {
183 match (dialect, kind) {
184 (Dialect::Sqlite, _) => format!("{column} IN (SELECT value FROM json_each(?))"),
185 (Dialect::Postgres, "int") => format!(
186 "{column} IN (SELECT CAST(x AS BIGINT) FROM jsonb_array_elements_text(CAST(? AS JSONB)) AS t(x))"
187 ),
188 (Dialect::Postgres, _) => format!(
189 "{column} IN (SELECT x FROM jsonb_array_elements_text(CAST(? AS JSONB)) AS t(x))"
190 ),
191 }
192}
193
194pub struct Query<M> {
214 filters: Vec<Filter>,
215 binds: Vec<DbValue>,
216 group: Vec<String>,
217 having: Vec<String>,
218 having_binds: Vec<DbValue>,
219 order: Vec<Order>,
220 order_binds: Vec<DbValue>,
222 limit: Option<u64>,
223 offset: Option<u64>,
224 lock: Option<&'static str>,
225 trashed: Trashed,
226 error: Option<String>,
227 model: PhantomData<fn() -> M>,
228}
229
230impl<M> Clone for Query<M> {
231 fn clone(&self) -> Self {
232 Self {
233 filters: self.filters.clone(),
234 binds: self.binds.clone(),
235 group: self.group.clone(),
236 having: self.having.clone(),
237 having_binds: self.having_binds.clone(),
238 order: self.order.clone(),
239 order_binds: self.order_binds.clone(),
240 limit: self.limit,
241 offset: self.offset,
242 lock: self.lock,
243 trashed: self.trashed,
244 error: self.error.clone(),
245 model: PhantomData,
246 }
247 }
248}
249
250impl<M: Model> Query<M> {
251 pub(crate) fn new() -> Self {
252 Self {
253 filters: Vec::new(),
254 binds: Vec::new(),
255 group: Vec::new(),
256 having: Vec::new(),
257 having_binds: Vec::new(),
258 order: Vec::new(),
259 order_binds: Vec::new(),
260 limit: None,
261 offset: None,
262 lock: None,
263 trashed: Trashed::Without,
264 error: None,
265 model: PhantomData,
266 }
267 }
268
269 fn column(&mut self, column: &str) -> Option<String> {
270 let plain = !column.is_empty()
271 && column
272 .chars()
273 .all(|c| c.is_ascii_alphanumeric() || c == '_');
274 if M::COLUMNS.contains(&column) || (M::SELECT_ALL && plain) {
275 Some(quote(column))
276 } else {
277 self.error
278 .get_or_insert_with(|| format!("`{}` has no column `{column}`", M::TABLE));
279 None
280 }
281 }
282
283 pub fn where_eq(self, column: &str, value: impl ToDbValue) -> Self {
285 self.where_op(column, "=", value)
286 }
287
288 pub fn where_(self, condition: Condition<M>) -> Self {
291 match condition.kind {
292 CondKind::Op(column, op, value) => self.where_op(column, op, value),
293 CondKind::Null(column, true) => self.where_null(column),
294 CondKind::Null(column, false) => self.where_not_null(column),
295 CondKind::In(column, values) => self.where_in(column, values),
296 }
297 }
298
299 pub fn where_op(mut self, column: &str, op: &str, value: impl ToDbValue) -> Self {
303 let op = op.to_ascii_lowercase();
304 if !OPERATORS.contains(&op.as_str()) {
305 self.error
306 .get_or_insert_with(|| format!("unsupported operator `{op}`"));
307 return self;
308 }
309 if let Some(column) = self.column(column) {
310 self.filters.push(match op.as_str() {
311 "like" => Filter::Like { column, not: false },
312 "not like" => Filter::Like { column, not: true },
313 _ => Filter::Sql(format!("{column} {} ?", op.to_uppercase())),
314 });
315 self.binds.push(value.to_db_value());
316 }
317 self
318 }
319
320 pub fn where_like(self, column: &str, pattern: impl ToDbValue) -> Self {
322 self.where_op(column, "like", pattern)
323 }
324
325 pub fn where_null(mut self, column: &str) -> Self {
327 if let Some(column) = self.column(column) {
328 self.filters.push(Filter::Sql(format!("{column} IS NULL")));
329 }
330 self
331 }
332
333 pub fn where_not_null(mut self, column: &str) -> Self {
335 if let Some(column) = self.column(column) {
336 self.filters
337 .push(Filter::Sql(format!("{column} IS NOT NULL")));
338 }
339 self
340 }
341
342 pub fn where_in<V: ToDbValue>(self, column: &str, values: impl IntoIterator<Item = V>) -> Self {
344 self.in_list(column, values, false)
345 }
346
347 pub fn where_not_in<V: ToDbValue>(
349 self,
350 column: &str,
351 values: impl IntoIterator<Item = V>,
352 ) -> Self {
353 self.in_list(column, values, true)
354 }
355
356 fn in_list<V: ToDbValue>(
357 mut self,
358 column: &str,
359 values: impl IntoIterator<Item = V>,
360 not: bool,
361 ) -> Self {
362 let values: Vec<DbValue> = values.into_iter().map(|v| v.to_db_value()).collect();
363 if let Some(column) = self.column(column) {
364 let filter = if values.is_empty() {
365 Filter::Sql("0 = 1".into())
367 } else if let Some((kind, list)) = large_list(&values) {
368 self.binds.push(DbValue::Json(list));
370 Filter::JsonIn { kind, column }
371 } else {
372 let marks = vec!["?"; values.len()].join(", ");
373 self.binds.extend(values);
374 Filter::Sql(format!("{column} IN ({marks})"))
375 };
376 self.filters.push(if not {
377 Filter::Not(Box::new(filter))
378 } else {
379 filter
380 });
381 }
382 self
383 }
384
385 pub fn where_between(
387 mut self,
388 column: &str,
389 low: impl ToDbValue,
390 high: impl ToDbValue,
391 ) -> Self {
392 if let Some(column) = self.column(column) {
393 self.filters
394 .push(Filter::Sql(format!("{column} BETWEEN ? AND ?")));
395 self.binds.push(low.to_db_value());
396 self.binds.push(high.to_db_value());
397 }
398 self
399 }
400
401 pub fn where_in_query<N: Model>(
416 mut self,
417 column: &str,
418 mut sub: Query<N>,
419 sub_column: &str,
420 ) -> Self {
421 let sub_column = sub.column(sub_column);
422 let column = self.column(column);
423 if let Some(error) = sub.error.take() {
424 self.error.get_or_insert(error);
425 }
426 if let (Some(column), Some(sub_column)) = (column, sub_column) {
427 let trashed = sub.trashed_filter();
428 let mut filters = sub.filters;
429 filters.extend(trashed);
430 self.filters.push(Filter::InQuery {
431 column,
432 table: N::TABLE,
433 sub_column,
434 filters,
435 });
436 self.binds.extend(sub.binds);
437 }
438 self
439 }
440
441 pub fn where_not_in_query<N: Model>(
444 self,
445 column: &str,
446 sub: Query<N>,
447 sub_column: &str,
448 ) -> Self {
449 let before = self.filters.len();
450 let mut query = self.where_in_query(column, sub, sub_column);
451 if query.filters.len() > before
452 && let Some(last) = query.filters.pop()
453 {
454 query.filters.push(Filter::Not(Box::new(last)));
455 }
456 query
457 }
458
459 pub fn where_has<N: Model>(self, children: Query<N>, foreign_key: &str) -> Self {
476 self.related(children, foreign_key, false)
477 }
478
479 pub fn where_doesnt_have<N: Model>(self, children: Query<N>, foreign_key: &str) -> Self {
481 self.related(children, foreign_key, true)
482 }
483
484 fn related<N: Model>(mut self, mut children: Query<N>, foreign_key: &str, not: bool) -> Self {
485 let foreign_key = children.column(foreign_key);
486 if let Some(error) = children.error.take() {
487 self.error.get_or_insert(error);
488 }
489 if let Some(foreign_key) = foreign_key {
490 let trashed = children.trashed_filter();
491 let mut filters = children.filters;
492 filters.extend(trashed);
493 self.filters.push(Filter::Exists {
494 not,
495 table: N::TABLE,
496 correlation: format!(
497 "{}.{foreign_key} = {}.\"id\"",
498 quote(N::TABLE),
499 quote(M::TABLE)
500 ),
501 filters,
502 });
503 self.binds.extend(children.binds);
504 }
505 self
506 }
507
508 pub fn where_any(self, group: impl FnOnce(Self) -> Self) -> Self {
511 self.group(true, group)
512 }
513
514 pub fn where_all(self, group: impl FnOnce(Self) -> Self) -> Self {
517 self.group(false, group)
518 }
519
520 fn group(mut self, any: bool, group: impl FnOnce(Self) -> Self) -> Self {
521 let mut inner = group(Self::new());
522 if let Some(error) = inner.error.take() {
523 self.error.get_or_insert(error);
524 }
525 self.filters.push(Filter::Group {
526 any,
527 filters: inner.filters,
528 });
529 self.binds.extend(inner.binds);
530 self
531 }
532
533 pub fn when(self, condition: bool, add: impl FnOnce(Self) -> Self) -> Self {
536 if condition { add(self) } else { self }
537 }
538
539 pub fn search(self, words: &str) -> Self {
568 self.where_search(words).order_by_relevance(words)
569 }
570
571 pub fn where_search(mut self, words: &str) -> Self {
575 if let Some(problem) = super::search::unsearchable::<M>() {
576 self.error.get_or_insert(problem);
577 return self;
578 }
579 if let Some(terms) = super::search::bound(words) {
580 self.filters.push(Filter::Search {
581 sqlite: super::search::filter_sql::<M>(Dialect::Sqlite),
582 postgres: super::search::filter_sql::<M>(Dialect::Postgres),
583 });
584 self.binds.push(DbValue::Text(terms));
585 }
586 self
587 }
588
589 pub fn order_by_relevance(mut self, words: &str) -> Self {
592 if let Some(problem) = super::search::unsearchable::<M>() {
593 self.error.get_or_insert(problem);
594 return self;
595 }
596 if let Some(terms) = super::search::bound(words) {
597 self.order.push(Order::Relevance {
598 sqlite: super::search::rank_sql::<M>(Dialect::Sqlite),
599 postgres: super::search::rank_sql::<M>(Dialect::Postgres),
600 });
601 self.order_binds.push(DbValue::Text(terms));
602 }
603 self
604 }
605
606 pub fn order_by(mut self, column: impl IntoColumn<M>) -> Self {
608 if let Some(column) = self.column(column.column_name()) {
609 self.order.push(Order::Sql(format!("{column} ASC")));
610 }
611 self
612 }
613
614 pub fn order_by_desc(mut self, column: impl IntoColumn<M>) -> Self {
616 if let Some(column) = self.column(column.column_name()) {
617 self.order.push(Order::Sql(format!("{column} DESC")));
618 }
619 self
620 }
621
622 pub fn latest(self) -> Self {
624 let column = if M::COLUMNS.contains(&"created_at") {
625 "created_at"
626 } else {
627 "id"
628 };
629 self.order_by_desc(column).order_by_desc("id")
630 }
631
632 pub fn limit(mut self, limit: u64) -> Self {
634 self.limit = Some(limit.min(i64::MAX as u64));
635 self
636 }
637
638 pub fn offset(mut self, offset: u64) -> Self {
640 self.offset = Some(offset.min(i64::MAX as u64));
641 self
642 }
643
644 pub fn where_raw<V: ToDbValue>(
660 mut self,
661 sql: &str,
662 values: impl IntoIterator<Item = V>,
663 ) -> Self {
664 self.filters.push(Filter::Sql(format!("({sql})")));
665 self.binds
666 .extend(values.into_iter().map(|v| v.to_db_value()));
667 self
668 }
669
670 pub fn order_by_raw(mut self, sql: &str) -> Self {
673 self.order.push(Order::Sql(sql.to_owned()));
674 self
675 }
676
677 pub fn group_by(mut self, column: &str) -> Self {
680 if let Some(column) = self.column(column) {
681 self.group.push(column);
682 }
683 self
684 }
685
686 pub fn group_by_raw(mut self, sql: &str) -> Self {
689 self.group.push(sql.to_owned());
690 self
691 }
692
693 pub fn having_raw<V: ToDbValue>(
696 mut self,
697 sql: &str,
698 values: impl IntoIterator<Item = V>,
699 ) -> Self {
700 self.having.push(format!("({sql})"));
701 self.having_binds
702 .extend(values.into_iter().map(|v| v.to_db_value()));
703 self
704 }
705
706 pub fn lock_for_update(mut self) -> Self {
711 self.lock = Some("FOR UPDATE");
712 self
713 }
714
715 pub fn shared_lock(mut self) -> Self {
717 self.lock = Some("FOR SHARE");
718 self
719 }
720
721 pub fn none(mut self) -> Self {
723 self.filters.push(Filter::Sql("1 = 0".into()));
724 self
725 }
726
727 pub fn with_trashed(mut self) -> Self {
729 self.trashed = Trashed::With;
730 self
731 }
732
733 pub fn only_trashed(mut self) -> Self {
735 self.trashed = Trashed::Only;
736 self
737 }
738
739 fn check(&self) -> Result {
740 match &self.error {
741 Some(error) => Err(anyhow!("invalid query: {error}").into()),
742 None => Ok(()),
743 }
744 }
745
746 fn trashed_filter(&self) -> Option<Filter> {
748 if !M::SOFT_DELETES {
749 return None;
750 }
751 match self.trashed {
752 Trashed::Without => Some(Filter::Sql("\"deleted_at\" IS NULL".into())),
753 Trashed::Only => Some(Filter::Sql("\"deleted_at\" IS NOT NULL".into())),
754 Trashed::With => None,
755 }
756 }
757
758 fn where_sql(&self, dialect: Dialect) -> String {
759 let mut filters: Vec<String> = self.filters.iter().map(|f| f.render(dialect)).collect();
760 filters.extend(self.trashed_filter().map(|f| f.render(dialect)));
761 if filters.is_empty() {
762 String::new()
763 } else {
764 format!(" WHERE {}", filters.join(" AND "))
765 }
766 }
767
768 fn select_sql(&self, dialect: Dialect) -> String {
769 let columns: Vec<String> = if M::SELECT_ALL {
770 vec!["*".to_owned()]
771 } else {
772 M::COLUMNS.iter().map(|c| quote(c)).collect()
773 };
774 self.select_columns_sql(dialect, &columns.join(", "))
775 }
776
777 fn select_columns_sql(&self, dialect: Dialect, columns: &str) -> String {
778 let mut sql = format!(
779 "SELECT {columns} FROM {}{}",
780 quote(M::TABLE),
781 self.where_sql(dialect)
782 );
783 sql.push_str(&self.group_sql());
784 if !self.order.is_empty() {
785 let terms: Vec<&str> = self.order.iter().map(|o| o.render(dialect)).collect();
786 sql.push_str(&format!(" ORDER BY {}", terms.join(", ")));
787 }
788 match (self.limit, self.offset) {
789 (Some(limit), Some(offset)) => sql.push_str(&format!(" LIMIT {limit} OFFSET {offset}")),
790 (Some(limit), None) => sql.push_str(&format!(" LIMIT {limit}")),
791 (None, Some(offset)) if dialect == Dialect::Sqlite => {
793 sql.push_str(&format!(" LIMIT -1 OFFSET {offset}"))
794 }
795 (None, Some(offset)) => sql.push_str(&format!(" OFFSET {offset}")),
796 (None, None) => {}
797 }
798 if let (Some(lock), Dialect::Postgres) = (self.lock, dialect) {
799 sql.push(' ');
800 sql.push_str(lock);
801 }
802 sql
803 }
804
805 fn group_sql(&self) -> String {
806 let mut sql = String::new();
807 if !self.group.is_empty() {
808 sql.push_str(&format!(" GROUP BY {}", self.group.join(", ")));
809 }
810 if !self.having.is_empty() {
811 sql.push_str(&format!(" HAVING {}", self.having.join(" AND ")));
812 }
813 sql
814 }
815
816 fn all_binds(&self) -> Vec<DbValue> {
819 let mut binds = self.binds.clone();
820 binds.extend(self.having_binds.iter().cloned());
821 binds
822 }
823
824 fn select_binds(&self) -> Vec<DbValue> {
826 let mut binds = self.all_binds();
827 binds.extend(self.order_binds.iter().cloned());
828 binds
829 }
830
831 fn clear_order(&mut self) {
832 self.order.clear();
833 self.order_binds.clear();
834 }
835
836 pub(crate) fn check_column(mut self, column: &str) -> Self {
838 let _ = self.column(column);
839 self
840 }
841
842 pub fn to_sql(&self, dialect: Dialect) -> Result<(String, Vec<DbValue>)> {
844 self.check()?;
845 Ok((self.select_sql(dialect), self.select_binds()))
846 }
847
848 pub async fn select_as<'c, T: super::FromRow, E: Executor<'c>>(
865 self,
866 db: E,
867 columns: &str,
868 ) -> Result<Vec<T>> {
869 self.check()?;
870 let db = db.into_conn();
871 let statement = self.select_columns_sql(db.dialect(), columns);
872 Ok(sql(statement)
873 .bind_all(self.select_binds())
874 .fetch_as(db)
875 .await?)
876 }
877
878 pub(crate) async fn buckets<'c, E: Executor<'c>>(
882 mut self,
883 db: E,
884 bucket: &(dyn Fn(Dialect) -> String + Send + Sync),
885 aggregate: &str,
886 ) -> Result<Vec<(String, Option<f64>)>> {
887 self.check()?;
888 self.clear_order();
889 self.group = vec!["1".to_owned()];
890 self.having.clear();
891 self.having_binds.clear();
892 self.limit = None;
893 self.offset = None;
894 self.lock = None;
895 let db = db.into_conn();
896 let dialect = db.dialect();
897 let statement = self.select_columns_sql(
898 dialect,
899 &format!("{}, CAST({aggregate} AS DOUBLE PRECISION)", bucket(dialect)),
900 );
901 Ok(sql(statement)
902 .bind_all(self.all_binds())
903 .fetch_as(db)
904 .await?)
905 }
906
907 async fn aggregate<'c, T: FromDb, E: Executor<'c>>(
909 self,
910 db: E,
911 expression: String,
912 ) -> Result<T> {
913 self.check()?;
914 let db = db.into_conn();
915 let statement = format!(
916 "SELECT {expression} FROM {}{}",
917 quote(M::TABLE),
918 self.where_sql(db.dialect())
919 );
920 Ok(sql(statement).bind_all(self.binds).scalar(db).await?)
921 }
922
923 pub async fn sum<'c, T: Number, E: Executor<'c>>(mut self, db: E, column: &str) -> Result<T> {
926 let Some(column) = self.column(column) else {
927 return Err(self.check().unwrap_err());
928 };
929 let expression = format!("CAST(COALESCE(SUM({column}), 0) AS {})", T::SQL_TYPE);
930 self.aggregate(db, expression).await
931 }
932
933 pub async fn avg<'c, E: Executor<'c>>(mut self, db: E, column: &str) -> Result<Option<f64>> {
935 let Some(column) = self.column(column) else {
936 return Err(self.check().unwrap_err());
937 };
938 self.aggregate(db, format!("CAST(AVG({column}) AS DOUBLE PRECISION)"))
939 .await
940 }
941
942 pub async fn min<'c, T: FromDb, E: Executor<'c>>(
944 mut self,
945 db: E,
946 column: &str,
947 ) -> Result<Option<T>>
948 where
949 Option<T>: FromDb,
950 {
951 let Some(column) = self.column(column) else {
952 return Err(self.check().unwrap_err());
953 };
954 self.aggregate(db, format!("MIN({column})")).await
955 }
956
957 pub async fn max<'c, T: FromDb, E: Executor<'c>>(
959 mut self,
960 db: E,
961 column: &str,
962 ) -> Result<Option<T>>
963 where
964 Option<T>: FromDb,
965 {
966 let Some(column) = self.column(column) else {
967 return Err(self.check().unwrap_err());
968 };
969 self.aggregate(db, format!("MAX({column})")).await
970 }
971
972 pub async fn pluck<'c, T: FromDb, E: Executor<'c>>(
975 mut self,
976 db: E,
977 column: impl IntoColumn<M>,
978 ) -> Result<Vec<T>> {
979 let Some(column) = self.column(column.column_name()) else {
980 return Err(self.check().unwrap_err());
981 };
982 self.check()?;
983 let db = db.into_conn();
984 let statement = self.select_columns_sql(db.dialect(), &column);
985 Ok(sql(statement)
986 .bind_all(self.select_binds())
987 .scalars(db)
988 .await?)
989 }
990
991 pub async fn update<'c, E: Executor<'c>>(
1004 mut self,
1005 db: E,
1006 values: &[(&str, &(dyn ToDbValue + Sync))],
1007 ) -> Result<u64> {
1008 let mut sets = Vec::new();
1009 let mut binds = Vec::new();
1010 for (column, value) in values {
1011 if *column == "id" {
1012 self.error
1013 .get_or_insert_with(|| "update can't change `id`".into());
1014 }
1015 if let Some(quoted) = self.column(column) {
1016 sets.push(format!("{quoted} = ?"));
1017 binds.push(value.to_db_value());
1018 }
1019 }
1020 if M::COLUMNS.contains(&"updated_at") && !values.iter().any(|(c, _)| *c == "updated_at") {
1021 sets.push(format!("{} = ?", quote("updated_at")));
1022 binds.push(now().to_db_value());
1023 }
1024 if sets.is_empty() {
1025 self.check()?;
1026 return Ok(0);
1027 }
1028 self.set_rows(db, sets.join(", "), binds).await
1029 }
1030
1031 pub async fn increment<'c, E: Executor<'c>>(
1035 mut self,
1036 db: E,
1037 column: &str,
1038 by: i64,
1039 ) -> Result<u64> {
1040 let Some(quoted) = self.column(column) else {
1041 return Err(self.check().unwrap_err());
1042 };
1043 let mut sets = format!("{quoted} = {quoted} + ?");
1044 let mut binds = vec![DbValue::Integer(by)];
1045 if M::COLUMNS.contains(&"updated_at") {
1046 sets.push_str(&format!(", {} = ?", quote("updated_at")));
1047 binds.push(now().to_db_value());
1048 }
1049 self.set_rows(db, sets, binds).await
1050 }
1051
1052 async fn set_rows<'c, E: Executor<'c>>(
1053 self,
1054 db: E,
1055 sets: String,
1056 binds: Vec<DbValue>,
1057 ) -> Result<u64> {
1058 self.check()?;
1059 let db = db.into_conn();
1060 let statement = format!(
1061 "UPDATE {} SET {sets}{}",
1062 quote(M::TABLE),
1063 self.where_sql(db.dialect())
1064 );
1065 Ok(sql(statement)
1066 .bind_all(binds)
1067 .bind_all(self.binds)
1068 .execute(db)
1069 .await?)
1070 }
1071
1072 pub async fn first_or_404<'c, E: Executor<'c>>(self, db: E) -> Result<M> {
1074 self.first(db).await?.ok_or(crate::Error::NotFound)
1075 }
1076
1077 #[allow(clippy::manual_async_fn)] pub fn first_or_create<'a>(
1085 self,
1086 db: &'a Db,
1087 make: impl FnOnce() -> M + Send + 'a,
1088 ) -> impl Future<Output = Result<M>> + Send + 'a {
1089 async move {
1090 if let Some(found) = self.clone().first(db).await? {
1091 return Ok(found);
1092 }
1093 match M::create(db, make()).await {
1094 Ok(created) => Ok(created),
1095 Err(err) if err.is_unique_violation() => self.first(db).await?.ok_or(err),
1096 Err(err) => Err(err),
1097 }
1098 }
1099 }
1100
1101 pub async fn chunk<F, Fut>(self, db: &Db, size: u64, mut each: F) -> Result<u64>
1105 where
1106 F: FnMut(Vec<M>) -> Fut,
1107 Fut: std::future::Future<Output = Result>,
1108 {
1109 let mut last: Option<M::Key> = None;
1110 let mut seen = 0_u64;
1111 loop {
1112 let mut page = self.clone();
1113 page.clear_order();
1114 page.limit = None;
1115 page.offset = None;
1116 if let Some(last) = last.take() {
1117 page = page.where_op("id", ">", last);
1118 }
1119 let rows = page.order_by("id").limit(size.max(1)).get(db).await?;
1120 let Some(tail) = rows.last() else { break };
1121 last = Some(tail.id());
1122 let full = rows.len() as u64 == size.max(1);
1123 seen += rows.len() as u64;
1124 each(rows).await?;
1125 if !full {
1126 break;
1127 }
1128 }
1129 Ok(seen)
1130 }
1131
1132 pub async fn get<'c, E: Executor<'c>>(self, db: E) -> Result<Vec<M>> {
1134 self.check()?;
1135 let db = db.into_conn();
1136 let rows = sql(self.select_sql(db.dialect()))
1137 .bind_all(self.select_binds())
1138 .fetch_all(db)
1139 .await?;
1140 Ok(rows
1141 .iter()
1142 .map(M::from_row)
1143 .collect::<std::result::Result<_, _>>()?)
1144 }
1145
1146 pub async fn first<'c, E: Executor<'c>>(self, db: E) -> Result<Option<M>> {
1148 Ok(self.limit(1).get(db).await?.into_iter().next())
1149 }
1150
1151 pub async fn count<'c, E: Executor<'c>>(self, db: E) -> Result<u64> {
1153 self.check()?;
1154 let db = db.into_conn();
1155 let statement = if self.group.is_empty() {
1156 format!(
1157 "SELECT COUNT(*) FROM {}{}",
1158 quote(M::TABLE),
1159 self.where_sql(db.dialect())
1160 )
1161 } else {
1162 format!(
1163 "SELECT COUNT(*) FROM (SELECT 1 AS one FROM {}{}{}) AS groups",
1164 quote(M::TABLE),
1165 self.where_sql(db.dialect()),
1166 self.group_sql()
1167 )
1168 };
1169 let count: i64 = sql(statement).bind_all(self.all_binds()).scalar(db).await?;
1170 Ok(count as u64)
1171 }
1172
1173 pub async fn exists<'c, E: Executor<'c>>(self, db: E) -> Result<bool> {
1175 Ok(self.count(db).await? > 0)
1176 }
1177
1178 pub async fn paginate(self, db: &Db, page: u32, per_page: u32) -> Result<Paginated<M>> {
1181 let page = page.max(1);
1182 let per_page = per_page.clamp(1, 1000);
1183 let total = self.clone().count(db).await?;
1184 let items = self
1185 .limit(u64::from(per_page))
1186 .offset(u64::from(page - 1) * u64::from(per_page))
1187 .get(db)
1188 .await?;
1189 Ok(Paginated::new(items, page, per_page, total))
1190 }
1191
1192 pub async fn simple_paginate(self, db: &Db, page: u32, per_page: u32) -> Result<SimplePage<M>> {
1195 let page = page.max(1);
1196 let per_page = per_page.clamp(1, 1000);
1197 let mut items = self
1198 .limit(u64::from(per_page) + 1)
1199 .offset(u64::from(page - 1) * u64::from(per_page))
1200 .get(db)
1201 .await?;
1202 let has_next = items.len() > per_page as usize;
1203 items.truncate(per_page as usize);
1204 Ok(SimplePage {
1205 items,
1206 page,
1207 per_page,
1208 has_prev: page > 1,
1209 has_next,
1210 })
1211 }
1212
1213 pub async fn cursor_paginate(
1230 mut self,
1231 db: &Db,
1232 cursor: Option<&str>,
1233 per_page: u32,
1234 ) -> Result<CursorPage<M>> {
1235 let per_page = per_page.clamp(1, 1000);
1236 if let Some(cursor) = cursor {
1237 let Ok(after) = cursor.parse::<M::Key>() else {
1238 return Err(crate::Error::BadRequest("invalid cursor".into()));
1239 };
1240 self = self.where_op("id", "<", after);
1241 }
1242 self.clear_order();
1243 let mut items = self
1244 .order_by_desc("id")
1245 .limit(u64::from(per_page) + 1)
1246 .get(db)
1247 .await?;
1248 let more = items.len() > per_page as usize;
1249 items.truncate(per_page as usize);
1250 let next_cursor = more
1251 .then(|| items.last().map(|last| last.id().to_string()))
1252 .flatten();
1253 Ok(CursorPage {
1254 items,
1255 per_page,
1256 next_cursor,
1257 })
1258 }
1259
1260 pub async fn first_or_new(self, db: &Db, make: impl FnOnce() -> M + Send) -> Result<M> {
1262 Ok(match self.first(db).await? {
1263 Some(found) => found,
1264 None => make(),
1265 })
1266 }
1267
1268 #[allow(clippy::manual_async_fn)] pub fn update_or_create<'a>(
1287 self,
1288 db: &'a Db,
1289 make: impl FnOnce() -> M + Send + 'a,
1290 change: impl FnOnce(&mut M) + Send + 'a,
1291 ) -> impl Future<Output = Result<M>> + Send + 'a {
1292 async move {
1293 let mut model = match self.first(db).await? {
1294 Some(found) => found,
1295 None => make(),
1296 };
1297 change(&mut model);
1298 model.save(db).await?;
1299 Ok(model)
1300 }
1301 }
1302
1303 pub async fn delete<'c, E: Executor<'c>>(self, db: E) -> Result<u64> {
1305 if M::SOFT_DELETES {
1306 self.check()?;
1307 let db = db.into_conn();
1308 return Ok(sql(format!(
1309 "UPDATE {} SET \"deleted_at\" = ?{}",
1310 quote(M::TABLE),
1311 self.where_sql(db.dialect())
1312 ))
1313 .bind(now())
1314 .bind_all(self.binds)
1315 .execute(db)
1316 .await?);
1317 }
1318 self.force_delete(db).await
1319 }
1320
1321 pub async fn force_delete<'c, E: Executor<'c>>(self, db: E) -> Result<u64> {
1323 self.check()?;
1324 let db = db.into_conn();
1325 Ok(sql(format!(
1326 "DELETE FROM {}{}",
1327 quote(M::TABLE),
1328 self.where_sql(db.dialect())
1329 ))
1330 .bind_all(self.binds)
1331 .execute(db)
1332 .await?)
1333 }
1334}