Skip to main content

renox_core/db/
query.rs

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
18/// `where_in` lists longer than this are sent as one JSON array.
19const LARGE_IN: usize = 1000;
20
21/// A number type `Query::sum` can return.
22pub 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/// One condition of a query, rendered per database.
42#[derive(Clone)]
43enum Filter {
44    /// SQL with `?` placeholders.
45    Sql(String),
46    /// `column LIKE ?` (ILIKE on PostgreSQL); `not` for NOT LIKE.
47    Like { column: String, not: bool },
48    /// `column IN (…)` for a long list sent as one JSON array.
49    JsonIn { kind: &'static str, column: String },
50    /// Conditions joined with OR (`any`) or AND, in parentheses.
51    Group { any: bool, filters: Vec<Filter> },
52    /// `NOT (…)`.
53    Not(Box<Filter>),
54    /// `[NOT] EXISTS (SELECT 1 FROM table WHERE correlation AND …)`.
55    Exists {
56        not: bool,
57        table: &'static str,
58        correlation: String,
59        filters: Vec<Filter>,
60    },
61    /// `column IN (SELECT sub_column FROM table WHERE …)`.
62    InQuery {
63        column: String,
64        table: &'static str,
65        sub_column: String,
66        filters: Vec<Filter>,
67    },
68    /// A full-text search on a model's index, already rendered per
69    /// database (`search::filter_sql`), with one `?`.
70    Search { sqlite: String, postgres: String },
71}
72
73/// One `ORDER BY` term.
74#[derive(Clone)]
75enum Order {
76    /// SQL without values.
77    Sql(String),
78    /// Full-text relevance (`search::rank_sql`) per database, with one `?`
79    /// in `order_binds`.
80    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                    // Nothing to match for `any`, nothing to restrict for AND.
110                    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
157/// A long list of integers or of strings, as a JSON array.
158fn 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
194/// A query on a model's table, built with chained filters.
195///
196/// ```
197/// # #[derive(Model, serde::Serialize, Default)]
198/// # #[model(table = "products")]
199/// # struct Product { id: i64, name: String, price: i64, category: Option<String>, user_id: i64 }
200/// # use renox::prelude::*;
201/// # async fn demo(db: Db, page: u32) -> Result {
202/// let products = Product::query()
203///     .where_eq("category", "coffee")
204///     .where_op("price", "<", 25_000)
205///     .order_by("name")
206///     .paginate(&db, page, 20)
207///     .await?;
208/// # let _ = products; Ok(()) }
209/// ```
210///
211/// Column names are checked against the model; an unknown column or operator
212/// makes the query return an error instead of running.
213pub 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    /// Values of the `ORDER BY` terms, bound after the WHERE and HAVING ones.
221    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    /// Filters on `column = value`.
284    pub fn where_eq(self, column: &str, value: impl ToDbValue) -> Self {
285        self.where_op(column, "=", value)
286    }
287
288    /// Filters on a typed [`Condition`] made by a [`Col`](super::Col), like
289    /// `Product::PRICE.lt(20_000)`.
290    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    /// Filters with a comparison: `=`, `!=`, `<>`, `<`, `<=`, `>`, `>=`, `like`, `not like`.
300    /// `like` ignores ASCII case on both databases (`ILIKE` on PostgreSQL, as
301    /// SQLite's `LIKE` already does).
302    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    /// Filters on `column LIKE pattern`, ignoring ASCII case (`%` and `_` are wildcards).
321    pub fn where_like(self, column: &str, pattern: impl ToDbValue) -> Self {
322        self.where_op(column, "like", pattern)
323    }
324
325    /// Filters on `column IS NULL`.
326    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    /// Filters on `column IS NOT NULL`.
334    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    /// Filters on `column IN (…)`; an empty list matches no rows.
343    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    /// Filters on `column NOT IN (…)`; an empty list matches every row.
348    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                // Nothing is in an empty list.
366                Filter::Sql("0 = 1".into())
367            } else if let Some((kind, list)) = large_list(&values) {
368                // Over the databases' bind limits: one JSON array instead.
369                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    /// `low <= column <= high`.
386    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    /// Rows whose `column` is among `sub_column`'s values in another model's
402    /// query, e.g. products in active categories:
403    ///
404    /// ```
405    /// # use renox::prelude::*;
406    /// # #[derive(Model, serde::Serialize, Default)] struct Product { id: i64, category_id: i64 }
407    /// # #[derive(Model, serde::Serialize, Default)] struct Category { id: i64, active: bool }
408    /// # async fn demo(db: Db) -> Result {
409    /// let products = Product::query()
410    ///     .where_in_query("category_id", Category::where_eq("active", true), "id")
411    ///     .get(&db)
412    ///     .await?;
413    /// # let _ = products; Ok(()) }
414    /// ```
415    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    /// Like `where_in_query`, keeping the rows whose `column` is *not* among
442    /// the sub-query's values.
443    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    /// Rows that have at least one related row in `children`, joined by the
460    /// children's `foreign_key` column to this model's `id` (`EXISTS`):
461    /// products with a 5-star review.
462    ///
463    /// ```
464    /// # use renox::prelude::*;
465    /// # #[derive(Model, serde::Serialize, Default)] struct Product { id: i64, name: String }
466    /// # #[derive(Model, serde::Serialize, Default)] struct Review { id: i64, product_id: i64, stars: i64 }
467    /// # async fn demo(db: Db) -> Result {
468    /// let loved = Product::query()
469    ///     .where_has(Review::where_eq("stars", 5), "product_id")
470    ///     .get(&db)
471    ///     .await?;
472    /// let unreviewed = Product::query().where_doesnt_have(Review::query(), "product_id").count(&db).await?;
473    /// # let _ = (loved, unreviewed); Ok(()) }
474    /// ```
475    pub fn where_has<N: Model>(self, children: Query<N>, foreign_key: &str) -> Self {
476        self.related(children, foreign_key, false)
477    }
478
479    /// Rows without any related row in `children` (`NOT EXISTS`).
480    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    /// Any of the conditions `group` adds must hold (`OR`), in parentheses:
509    /// `.where_any(|q| q.where_eq("status", "new").where_op("total", ">", 100))`.
510    pub fn where_any(self, group: impl FnOnce(Self) -> Self) -> Self {
511        self.group(true, group)
512    }
513
514    /// All of the conditions `group` adds must hold, in parentheses; useful
515    /// inside `where_any`: `.where_any(|q| q.where_eq("a", 1).where_all(|q| …))`.
516    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    /// Applies `add` only when `condition` holds, e.g. an optional search:
534    /// `.when(!q.is_empty(), |query| query.where_like("name", format!("%{q}%")))`.
535    pub fn when(self, condition: bool, add: impl FnOnce(Self) -> Self) -> Self {
536        if condition { add(self) } else { self }
537    }
538
539    /// The rows matching a full-text search, best matches first (as
540    /// [`where_search`](Self::where_search) then
541    /// [`order_by_relevance`](Self::order_by_relevance)); more `order_by`
542    /// calls break ties. The model needs `#[model(search = "…")]` and its
543    /// index: see [`renox::db::search`](super::search). A text without any
544    /// word changes nothing.
545    ///
546    /// ```
547    /// # use renox::prelude::*;
548    /// #[derive(Model, serde::Serialize, Default)]
549    /// #[model(table = "posts", search = "title, body", soft_deletes)]
550    /// struct Post {
551    ///     id: i64,
552    ///     title: String,
553    ///     body: String,
554    ///     author_id: i64,
555    ///     deleted_at: Option<DateTime>,
556    /// }
557    ///
558    /// # async fn demo(db: Db, q: String) -> Result {
559    /// let page = Post::query()
560    ///     .where_eq("author_id", 7)
561    ///     .search(&q)
562    ///     .order_by_desc("id")
563    ///     .paginate(&db, 1, 20)
564    ///     .await?;
565    /// # let _ = page; Ok(()) }
566    /// ```
567    pub fn search(self, words: &str) -> Self {
568        self.where_search(words).order_by_relevance(words)
569    }
570
571    /// Keeps the rows matching a full-text search (every word, as a word
572    /// or the start of one), without ordering them. User input is safe
573    /// here: only its words reach the database, bound as a value.
574    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    /// Sorts by how well rows match a full-text search, best first (rows
590    /// that don't match last); call `order_by` after it to break ties.
591    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    /// Sorts by `column`, ascending; call again to add tie-breakers.
607    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    /// Sorts by `column`, descending; call again to add tie-breakers.
615    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    /// Newest first, by `created_at` when the model has it, otherwise by `id`.
623    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    /// At most `limit` rows (a limit past `i64::MAX` means no limit).
633    pub fn limit(mut self, limit: u64) -> Self {
634        self.limit = Some(limit.min(i64::MAX as u64));
635        self
636    }
637
638    /// Skips the first `offset` rows.
639    pub fn offset(mut self, offset: u64) -> Self {
640        self.offset = Some(offset.min(i64::MAX as u64));
641        self
642    }
643
644    /// A condition in SQL, for what the builder doesn't cover (dates, JSON,
645    /// full-text search, …), with `?` for each value. Column names are yours
646    /// to quote; never build `sql` from user input.
647    ///
648    /// ```
649    /// # use renox::prelude::*;
650    /// # #[derive(Model, serde::Serialize, Default)] struct Order { id: i64, total: i64, created_at: Option<DateTime> }
651    /// # async fn demo(db: Db) -> Result {
652    /// let today = Order::query()
653    ///     .where_raw("DATE(created_at) = DATE(?)", [renox::db::now()])
654    ///     .order_by_raw("total DESC, id")
655    ///     .get(&db)
656    ///     .await?;
657    /// # let _ = today; Ok(()) }
658    /// ```
659    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    /// An `ORDER BY` term in SQL, e.g. `"total DESC, id"` (no values; never
671    /// from user input).
672    pub fn order_by_raw(mut self, sql: &str) -> Self {
673        self.order.push(Order::Sql(sql.to_owned()));
674        self
675    }
676
677    /// Groups rows by `column`, for `select_as` and `count`:
678    /// `.group_by("user_id").select_as::<(i64, i64), _>(&db, "user_id, COUNT(*)")`.
679    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    /// Groups rows by a SQL expression (written by the app, never from a
687    /// request): `.group_by_raw("CAST(\"price\" / 100 AS INTEGER)")`.
688    pub fn group_by_raw(mut self, sql: &str) -> Self {
689        self.group.push(sql.to_owned());
690        self
691    }
692
693    /// A condition on the groups, in SQL with `?` for each value:
694    /// `.having_raw("COUNT(*) > ?", [2])`.
695    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    /// Locks the matching rows until the transaction ends (`FOR UPDATE`),
707    /// e.g. to read a balance and write it back safely. Use it on `&mut tx`.
708    /// PostgreSQL only: on SQLite a write transaction already holds the whole
709    /// database, so start it with `db.begin_immediate()` instead.
710    pub fn lock_for_update(mut self) -> Self {
711        self.lock = Some("FOR UPDATE");
712        self
713    }
714
715    /// Like `lock_for_update`, but others may still read-lock (`FOR SHARE`).
716    pub fn shared_lock(mut self) -> Self {
717        self.lock = Some("FOR SHARE");
718        self
719    }
720
721    /// Matches no rows at all, e.g. a default scope when no tenant is set.
722    pub fn none(mut self) -> Self {
723        self.filters.push(Filter::Sql("1 = 0".into()));
724        self
725    }
726
727    /// Include soft-deleted rows.
728    pub fn with_trashed(mut self) -> Self {
729        self.trashed = Trashed::With;
730        self
731    }
732
733    /// Only soft-deleted rows.
734    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    /// The soft-delete condition, if the model has soft deletes.
747    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            // SQLite needs a LIMIT before OFFSET; -1 means none.
792            (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    /// The query's values in the order its SQL uses them (without the
817    /// `ORDER BY` ones: see `select_binds`).
818    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    /// The values of a SELECT with its `ORDER BY` (`select_columns_sql`).
825    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    /// Records an error if `column` isn't one of the model's.
837    pub(crate) fn check_column(mut self, column: &str) -> Self {
838        let _ = self.column(column);
839        self
840    }
841
842    /// The SELECT this query runs and its values, e.g. to log or debug it.
843    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    /// Selects `columns` (SQL, e.g. `"user_id, COUNT(*) AS orders"`) of the
849    /// matching rows, with `group_by`/`having_raw`, read into a
850    /// `#[derive(FromRow)]` struct or a tuple:
851    ///
852    /// ```
853    /// # use renox::prelude::*;
854    /// # #[derive(Model, serde::Serialize, Default)] struct Order { id: i64, user_id: i64, total: i64, status: String }
855    /// # async fn demo(db: Db) -> Result {
856    /// let big_spenders: Vec<(i64, i64)> = Order::where_eq("status", "paid")
857    ///     .group_by("user_id")
858    ///     .having_raw("SUM(total) > ?", [1_000_000])
859    ///     .order_by_raw("2 DESC")
860    ///     .select_as(&db, "user_id, CAST(SUM(total) AS BIGINT)")
861    ///     .await?;
862    /// # let _ = big_spenders; Ok(()) }
863    /// ```
864    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    /// `bucket` and an aggregate per bucket (`chart::Trend`): the query's
879    /// conditions, grouped by the first column; its order, limit, groups
880    /// and lock don't apply.
881    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    /// One aggregate over the matching rows (order and limit don't apply).
908    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    /// The sum of `column`, 0 without rows: `sum::<i64>(…)` for whole
924    /// numbers (money in its smallest unit), `sum::<f64>(…)` for measures.
925    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    /// The average of `column`, or `None` without rows.
934    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    /// The smallest value of `column`, or `None` without rows.
943    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    /// The largest value of `column`, or `None` without rows.
958    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    /// One column of the matching rows, in the query's order:
973    /// `Product::query().order_by("name").pluck::<String, _>(&db, "name")`.
974    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    /// Sets columns on every matching row (and `updated_at` when the model
992    /// has it); returns how many changed.
993    ///
994    /// ```
995    /// # use renox::prelude::*;
996    /// # #[derive(Model, serde::Serialize, Default)] struct Order { id: i64, status: String, paid_at: Option<DateTime> }
997    /// # async fn demo(db: Db) -> Result {
998    /// Order::where_eq("status", "pending")
999    ///     .update(&db, &[("status", &"paid"), ("paid_at", &renox::db::now())])
1000    ///     .await?;
1001    /// # Ok(()) }
1002    /// ```
1003    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    /// Adds `by` to `column` on every matching row (negative to subtract),
1032    /// in the database, so concurrent changes aren't lost:
1033    /// `Product::where_eq("id", id).increment(&db, "stock", -1)`.
1034    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    /// Like `first`, but no row becomes a 404 response.
1073    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    /// The first matching row, or `make()` saved as a new one. If another
1078    /// request creates it at the same moment (a unique index stops the
1079    /// second insert), the row it created is returned.
1080    //
1081    // Not an `async fn`: written that way, a handler awaiting it failed
1082    // axum's `Send` check (rustc issue #100013; see it/send_handlers.rs).
1083    #[allow(clippy::manual_async_fn)] // the `+ Send` in the signature is the point
1084    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    /// Runs `each` on the matching rows `size` at a time, in id order, so a
1102    /// large table never sits in memory at once. (The query's own order and
1103    /// limit don't apply.)
1104    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    /// Runs the query and returns every matching row.
1133    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    /// The first matching row (in the query's order), or `None`.
1147    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    /// How many rows match (with `group_by`: how many groups).
1152    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    /// Whether any row matches.
1174    pub async fn exists<'c, E: Executor<'c>>(self, db: E) -> Result<bool> {
1175        Ok(self.count(db).await? > 0)
1176    }
1177
1178    /// One page of results plus the numbers needed to render page links.
1179    /// `page` starts at 1; `per_page` is capped at 1000.
1180    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    /// One page without counting the rows (one query instead of two): for
1193    /// "previous / next" links on large tables. `page` starts at 1.
1194    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    /// The `per_page` newest rows after `cursor` (by id; the query's own
1214    /// order doesn't apply), for APIs and infinite scroll on big tables:
1215    /// unlike page numbers, rows added meanwhile don't shift the pages.
1216    /// "Newest" is the key's order: creation order for `i64`, ULIDs and
1217    /// UUID v7s; a `String` key pages in text order.
1218    ///
1219    /// ```
1220    /// # use renox::prelude::*;
1221    /// # #[derive(Model, serde::Serialize, Default)] struct Event { id: i64, name: String }
1222    /// #[derive(serde::Deserialize)]
1223    /// struct Params { cursor: Option<String> }
1224    ///
1225    /// async fn events(State(db): State<Db>, Query(p): Query<Params>) -> Result<Json<renox::db::CursorPage<Event>>> {
1226    ///     Ok(Json(Event::query().cursor_paginate(&db, p.cursor.as_deref(), 50).await?))
1227    /// }
1228    /// ```
1229    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    /// The first matching row, or `make()` unsaved (`firstOrNew`).
1261    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    /// Changes the first matching row with `change`, or creates `make()`
1269    /// with `change` applied (`updateOrCreate`); returns it saved.
1270    ///
1271    /// ```
1272    /// # use renox::prelude::*;
1273    /// # #[derive(Model, serde::Serialize, Default)] struct Setting { id: i64, user_id: i64, key: String, value: String }
1274    /// # async fn demo(db: Db) -> Result {
1275    /// let theme = Setting::where_eq("user_id", 7)
1276    ///     .where_eq("key", "theme")
1277    ///     .update_or_create(
1278    ///         &db,
1279    ///         || Setting { user_id: 7, key: "theme".into(), ..Default::default() },
1280    ///         |s| s.value = "dark".into(),
1281    ///     )
1282    ///     .await?;
1283    /// # let _ = theme; Ok(()) }
1284    /// ```
1285    #[allow(clippy::manual_async_fn)] // the `+ Send` in the signature is the point
1286    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    /// Deletes every matching row (soft-deletes them for models with soft deletes).
1304    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    /// Removes every matching row, even for models with soft deletes.
1322    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}