rust_query/
lazy.rs

1use std::{cell::OnceCell, fmt::Debug, ops::Deref};
2
3#[cfg(doc)]
4use crate::FromExpr;
5use crate::{IntoExpr, Table, TableRow, Transaction, value::SecretFromSql};
6
7/// [Lazy] can be used to read any column of a table row and its parents.
8/// Columns are loaded on demand, one row at at time.
9///
10/// As an example, if you have two tables `Post` and `User`:
11/// ```
12/// # #[rust_query::migration::schema(Schema)]
13/// # pub mod vN {
14/// #     pub struct Post {
15/// #         pub author: User,
16/// #     }
17/// #     pub struct User {
18/// #         pub name: String,
19/// #     }
20/// # }
21/// # use rust_query::Lazy;
22/// # use v0::*;
23/// fn foo(post: Lazy<Post>) {
24///     let user = &post.author; // If the `post` row was not retrieved yet, then it is retrieved now to read the `user` column.
25///     let user_id = user.table_row(); // This doesn't access the database because the `user` id was already read from the `post` row.
26///     let user_name = &user.name; // If the `user` row was not retrieved yet, then it is retrieved now to read the `name` column.
27/// }
28/// ```
29///
30/// Note that [Lazy] borrows the transaction immutably.
31/// This means that it is not possible to keep a [Lazy] value when doing inserts or updates.
32/// Here are some alternatives to solve this problem:
33/// - [Copy]/[Clone] the columns that you need from the [Lazy] value before doing inserts and or updates.
34/// - Another option is to use [Lazy::table_row] to retrieve an owned [TableRow].
35/// - If you need many columns in a struct, then consider [derive@crate::FromExpr].
36pub struct Lazy<'transaction, T: Table> {
37    pub(crate) id: TableRow<T>,
38    pub(crate) lazy: OnceCell<Box<T::Lazy<'transaction>>>,
39    pub(crate) txn: &'transaction Transaction<T::Schema>,
40}
41
42impl<T: Table> Debug for Lazy<'_, T> {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        f.debug_tuple("Lazy").field(&self.id).finish()
45    }
46}
47
48impl<'transaction, T: Table> Lazy<'transaction, T> {
49    /// Get an owned [TableRow] out of this [Lazy] value.
50    ///
51    /// If you don't care about deleting the row then you probably want to
52    /// immediately use [TableRow::into_expr](crate::FromExpr::from_expr) on the returned [TableRow].
53    pub fn table_row(&self) -> TableRow<T> {
54        self.id
55    }
56}
57
58impl<'transaction, T: Table> Clone for Lazy<'transaction, T> {
59    fn clone(&self) -> Self {
60        Self {
61            id: self.id,
62            lazy: OnceCell::new(),
63            txn: self.txn,
64        }
65    }
66}
67
68impl<'transaction, T: Table> Deref for Lazy<'transaction, T> {
69    type Target = T::Lazy<'transaction>;
70
71    fn deref(&self) -> &Self::Target {
72        self.lazy
73            .get_or_init(|| Box::new(T::get_lazy(self.txn, self.id)))
74    }
75}
76
77impl<'transaction, T: Table> SecretFromSql for Lazy<'transaction, T> {
78    fn from_sql(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
79        Ok(Self {
80            id: TableRow::from_sql(value)?,
81            lazy: OnceCell::new(),
82            txn: Transaction::new_ref(),
83        })
84    }
85}
86
87impl<'column, T: Table> IntoExpr<'column, T::Schema> for Lazy<'_, T> {
88    type Typ = T;
89
90    fn into_expr(self) -> crate::Expr<'column, T::Schema, Self::Typ> {
91        self.id.into_expr()
92    }
93}