butane_core/
fkey.rs

1//! Implementation of foreign key relationships between models.
2#![deny(missing_docs)]
3use std::borrow::Cow;
4use std::fmt::Debug;
5use std::sync::OnceLock;
6
7#[cfg(feature = "fake")]
8use fake::{Dummy, Faker};
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11use crate::util::get_or_init_once_lock;
12#[cfg(feature = "async")]
13use crate::{util::get_or_init_once_lock_async, ConnectionMethodsAsync};
14use crate::{
15    AsPrimaryKey, ConnectionMethods, DataObject, Error, FieldType, FromSql, Result, SqlType,
16    SqlVal, SqlValRef, ToSql,
17};
18
19/// Used to implement a relationship between models.
20///
21/// Initialize using `From` or `from_pk`
22///
23/// See [`ForeignKeyOpsSync`] and [`ForeignKeyOpsAsync`] for operations requiring a live database connection.
24///
25/// # Examples
26/// ```ignore
27/// #[model]
28/// struct Blog {
29///   ...
30/// }
31/// #[model]
32/// struct Post {
33///   blog: ForeignKey<Blog>,
34///   ...
35/// }
36#[derive(Clone, Debug)]
37pub struct ForeignKey<T>
38where
39    T: DataObject,
40{
41    // At least one must be initialized (enforced internally by this
42    // type), but both need not be
43    // Using OnceLock instead of OnceCell because of Sync requirements when working with async.
44    val: OnceLock<Box<T>>,
45    valpk: OnceLock<SqlVal>,
46}
47impl<T: DataObject> ForeignKey<T> {
48    /// Create a value from a reference to the primary key of the value
49    pub fn from_pk(pk: T::PKType) -> Self {
50        let ret = Self::new_raw();
51        ret.valpk.set(pk.into_sql()).unwrap();
52        ret
53    }
54    /// Returns a reference to the value. It must have already been loaded. If not, returns Error::ValueNotLoaded
55    pub fn get(&self) -> Result<&T> {
56        self.val
57            .get()
58            .map(|v| v.as_ref())
59            .ok_or(Error::ValueNotLoaded)
60    }
61
62    /// Returns a reference to the primary key of the value.
63    pub fn pk(&self) -> T::PKType {
64        match self.val.get() {
65            Some(v) => v.pk().clone(),
66            None => match self.valpk.get() {
67                Some(pk) => T::PKType::from_sql_ref(pk.as_ref()).unwrap(),
68                None => panic!("Invalid foreign key state"),
69            },
70        }
71    }
72
73    fn new_raw() -> Self {
74        ForeignKey {
75            val: OnceLock::new(),
76            valpk: OnceLock::new(),
77        }
78    }
79
80    fn ensure_valpk(&self) -> &SqlVal {
81        match self.valpk.get() {
82            Some(sqlval) => return sqlval,
83            None => match self.val.get() {
84                Some(val) => self.valpk.set(val.pk().to_sql()).unwrap(),
85                None => panic!("Invalid foreign key state"),
86            },
87        }
88        self.valpk.get().unwrap()
89    }
90}
91
92/// [`ForeignKey`] operations which require a `Connection`.
93#[allow(async_fn_in_trait)] // Not intended to be implemented outside Butane
94#[maybe_async_cfg::maybe(
95    idents(ConnectionMethods(sync = "ConnectionMethods"),),
96    sync(),
97    async(feature = "async")
98)]
99pub trait ForeignKeyOps<T: DataObject> {
100    /// Loads the value referred to by this foreign key from the
101    /// database if necessary and returns a reference to it.
102    async fn load<'a>(&'a self, conn: &impl ConnectionMethods) -> Result<&'a T>
103    where
104        T: 'a;
105}
106
107#[cfg(feature = "async")]
108impl<T: DataObject> ForeignKeyOpsAsync<T> for ForeignKey<T> {
109    async fn load<'a>(&'a self, conn: &impl ConnectionMethodsAsync) -> Result<&'a T>
110    where
111        T: 'a,
112    {
113        use crate::DataObjectOpsAsync;
114        get_or_init_once_lock_async(&self.val, || async {
115            let pk = self.valpk.get().unwrap();
116            T::get(conn, T::PKType::from_sql_ref(pk.as_ref())?)
117                .await
118                .map(Box::new)
119        })
120        .await
121        .map(|v| v.as_ref())
122    }
123}
124
125impl<T: DataObject> ForeignKeyOpsSync<T> for ForeignKey<T> {
126    fn load<'a>(&'a self, conn: &impl ConnectionMethods) -> Result<&'a T>
127    where
128        T: 'a,
129    {
130        use crate::DataObjectOpsSync;
131        get_or_init_once_lock(&self.val, || {
132            let pk = self.valpk.get().unwrap();
133            T::get(conn, T::PKType::from_sql_ref(pk.as_ref())?).map(Box::new)
134        })
135        .map(|v| v.as_ref())
136    }
137}
138
139impl<T: DataObject> From<T> for ForeignKey<T> {
140    fn from(obj: T) -> Self {
141        let ret = Self::new_raw();
142        ret.val.set(Box::new(obj)).ok();
143        ret
144    }
145}
146impl<T: DataObject> From<&T> for ForeignKey<T> {
147    fn from(obj: &T) -> Self {
148        Self::from_pk(obj.pk().clone())
149    }
150}
151
152impl<T> AsPrimaryKey<T> for ForeignKey<T>
153where
154    T: DataObject,
155{
156    fn as_pk(&self) -> Cow<T::PKType> {
157        Cow::Owned(self.pk())
158    }
159}
160
161impl<T: DataObject> Eq for ForeignKey<T> {}
162
163impl<T> ToSql for ForeignKey<T>
164where
165    T: DataObject,
166{
167    fn to_sql(&self) -> SqlVal {
168        self.ensure_valpk().clone()
169    }
170    fn to_sql_ref(&self) -> SqlValRef<'_> {
171        self.ensure_valpk().as_ref()
172    }
173    fn into_sql(self) -> SqlVal {
174        self.ensure_valpk();
175        self.valpk.into_inner().unwrap()
176    }
177}
178impl<T> FieldType for ForeignKey<T>
179where
180    T: DataObject,
181{
182    const SQLTYPE: SqlType = <T as DataObject>::PKType::SQLTYPE;
183    type RefType = <<T as DataObject>::PKType as FieldType>::RefType;
184}
185impl<T> FromSql for ForeignKey<T>
186where
187    T: DataObject,
188{
189    fn from_sql_ref(valref: SqlValRef) -> Result<Self> {
190        Ok(ForeignKey {
191            valpk: SqlVal::from(valref).into(),
192            val: OnceLock::new(),
193        })
194    }
195}
196impl<T, U> PartialEq<U> for ForeignKey<T>
197where
198    U: AsPrimaryKey<T>,
199    T: DataObject,
200{
201    fn eq(&self, other: &U) -> bool {
202        match self.val.get() {
203            Some(t) => t.pk().eq(&other.as_pk()),
204            None => match self.valpk.get() {
205                Some(valpk) => valpk.eq(&other.as_pk().to_sql()),
206                None => panic!("Invalid foreign key state"),
207            },
208        }
209    }
210}
211
212impl<T> Serialize for ForeignKey<T>
213where
214    T: DataObject,
215    T::PKType: Serialize,
216{
217    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
218    where
219        S: Serializer,
220    {
221        self.pk().serialize(serializer)
222    }
223}
224
225impl<'de, T> Deserialize<'de> for ForeignKey<T>
226where
227    T: DataObject,
228    T::PKType: Deserialize<'de>,
229{
230    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
231    where
232        D: Deserializer<'de>,
233    {
234        Ok(Self::from_pk(T::PKType::deserialize(deserializer)?))
235    }
236}
237
238#[cfg(feature = "fake")]
239/// Fake data support is currently limited to empty ForeignKey relationships.
240impl<T: DataObject> Dummy<Faker> for ForeignKey<T> {
241    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &Faker, _rng: &mut R) -> Self {
242        Self::new_raw()
243    }
244}