prax_query/
lazy.rs

1//! Lazy loading utilities for relations.
2//!
3//! This module provides lazy loading wrappers that defer loading of related
4//! data until it is actually accessed, improving performance when relations
5//! are not always needed.
6//!
7//! # Example
8//!
9//! ```rust,ignore
10//! use prax_query::lazy::Lazy;
11//!
12//! struct User {
13//!     id: i64,
14//!     name: String,
15//!     // Posts are lazily loaded
16//!     posts: Lazy<Vec<Post>>,
17//! }
18//!
19//! // Posts are not loaded until accessed
20//! let posts = user.posts.load(&engine).await?;
21//! ```
22
23use std::cell::UnsafeCell;
24use std::fmt;
25use std::future::Future;
26use std::sync::atomic::{AtomicU8, Ordering};
27
28/// State of a lazy value.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[repr(u8)]
31enum LazyState {
32    /// Not yet loaded.
33    Unloaded = 0,
34    /// Currently loading.
35    Loading = 1,
36    /// Loaded successfully.
37    Loaded = 2,
38    /// Failed to load.
39    Failed = 3,
40}
41
42impl From<u8> for LazyState {
43    fn from(v: u8) -> Self {
44        match v {
45            0 => Self::Unloaded,
46            1 => Self::Loading,
47            2 => Self::Loaded,
48            3 => Self::Failed,
49            _ => Self::Unloaded,
50        }
51    }
52}
53
54/// A lazily-loaded value.
55///
56/// The value is not loaded until explicitly requested, allowing
57/// for deferred loading of expensive relations.
58pub struct Lazy<T> {
59    state: AtomicU8,
60    value: UnsafeCell<Option<T>>,
61}
62
63// SAFETY: Lazy uses atomic operations for state and only allows
64// mutable access when state transitions are valid.
65unsafe impl<T: Send> Send for Lazy<T> {}
66unsafe impl<T: Sync> Sync for Lazy<T> {}
67
68impl<T> Lazy<T> {
69    /// Create a new unloaded lazy value.
70    pub const fn new() -> Self {
71        Self {
72            state: AtomicU8::new(LazyState::Unloaded as u8),
73            value: UnsafeCell::new(None),
74        }
75    }
76
77    /// Create a lazy value that is already loaded.
78    pub fn loaded(value: T) -> Self {
79        Self {
80            state: AtomicU8::new(LazyState::Loaded as u8),
81            value: UnsafeCell::new(Some(value)),
82        }
83    }
84
85    /// Check if the value has been loaded.
86    #[inline]
87    pub fn is_loaded(&self) -> bool {
88        LazyState::from(self.state.load(Ordering::Acquire)) == LazyState::Loaded
89    }
90
91    /// Check if the value is currently loading.
92    #[inline]
93    pub fn is_loading(&self) -> bool {
94        LazyState::from(self.state.load(Ordering::Acquire)) == LazyState::Loading
95    }
96
97    /// Get the value if it has been loaded.
98    pub fn get(&self) -> Option<&T> {
99        if self.is_loaded() {
100            // SAFETY: We only read when state is Loaded, which means
101            // the value has been written and won't change.
102            unsafe { (*self.value.get()).as_ref() }
103        } else {
104            None
105        }
106    }
107
108    /// Get a mutable reference to the value if loaded.
109    pub fn get_mut(&mut self) -> Option<&mut T> {
110        if self.is_loaded() {
111            self.value.get_mut().as_mut()
112        } else {
113            None
114        }
115    }
116
117    /// Set the value directly.
118    pub fn set(&self, value: T) {
119        // SAFETY: We're transitioning to Loaded state
120        unsafe {
121            *self.value.get() = Some(value);
122        }
123        self.state.store(LazyState::Loaded as u8, Ordering::Release);
124    }
125
126    /// Take the value, leaving the lazy unloaded.
127    pub fn take(&mut self) -> Option<T> {
128        if self.is_loaded() {
129            self.state
130                .store(LazyState::Unloaded as u8, Ordering::Release);
131            self.value.get_mut().take()
132        } else {
133            None
134        }
135    }
136
137    /// Reset to unloaded state.
138    pub fn reset(&mut self) {
139        self.state
140            .store(LazyState::Unloaded as u8, Ordering::Release);
141        *self.value.get_mut() = None;
142    }
143
144    /// Load the value using the provided async loader function.
145    ///
146    /// If already loaded, returns the cached value.
147    /// If loading fails, the error is returned but the state remains unloaded.
148    pub async fn load_with<F, Fut, E>(&self, loader: F) -> Result<&T, E>
149    where
150        F: FnOnce() -> Fut,
151        Fut: Future<Output = Result<T, E>>,
152    {
153        // Fast path: already loaded
154        if self.is_loaded() {
155            // SAFETY: State is Loaded, value is immutable
156            return Ok(unsafe { (*self.value.get()).as_ref().unwrap() });
157        }
158
159        // Try to transition to loading state
160        let prev = self.state.compare_exchange(
161            LazyState::Unloaded as u8,
162            LazyState::Loading as u8,
163            Ordering::AcqRel,
164            Ordering::Acquire,
165        );
166
167        match prev {
168            Ok(_) => {
169                // We own the loading transition
170                match loader().await {
171                    Ok(value) => {
172                        // SAFETY: We're the only writer (we hold Loading state)
173                        unsafe {
174                            *self.value.get() = Some(value);
175                        }
176                        self.state.store(LazyState::Loaded as u8, Ordering::Release);
177                        // SAFETY: We just stored the value
178                        Ok(unsafe { (*self.value.get()).as_ref().unwrap() })
179                    }
180                    Err(e) => {
181                        self.state
182                            .store(LazyState::Unloaded as u8, Ordering::Release);
183                        Err(e)
184                    }
185                }
186            }
187            Err(current) => {
188                // Someone else is loading or already loaded
189                match LazyState::from(current) {
190                    LazyState::Loaded => {
191                        // SAFETY: State is Loaded
192                        Ok(unsafe { (*self.value.get()).as_ref().unwrap() })
193                    }
194                    LazyState::Loading => {
195                        // Wait for the other loader (spin with yield)
196                        loop {
197                            tokio::task::yield_now().await;
198                            let state = LazyState::from(self.state.load(Ordering::Acquire));
199                            match state {
200                                LazyState::Loaded => {
201                                    return Ok(unsafe { (*self.value.get()).as_ref().unwrap() });
202                                }
203                                LazyState::Unloaded | LazyState::Failed => {
204                                    // Other loader failed, try again
205                                    return Box::pin(self.load_with(loader)).await;
206                                }
207                                LazyState::Loading => continue,
208                            }
209                        }
210                    }
211                    _ => {
212                        // Retry loading
213                        Box::pin(self.load_with(loader)).await
214                    }
215                }
216            }
217        }
218    }
219}
220
221impl<T: Default> Default for Lazy<T> {
222    fn default() -> Self {
223        Self::new()
224    }
225}
226
227impl<T: Clone> Clone for Lazy<T> {
228    fn clone(&self) -> Self {
229        if self.is_loaded() {
230            Self::loaded(self.get().unwrap().clone())
231        } else {
232            Self::new()
233        }
234    }
235}
236
237impl<T: fmt::Debug> fmt::Debug for Lazy<T> {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        let state = LazyState::from(self.state.load(Ordering::Acquire));
240        match state {
241            LazyState::Loaded => {
242                if let Some(value) = self.get() {
243                    f.debug_struct("Lazy")
244                        .field("state", &"Loaded")
245                        .field("value", value)
246                        .finish()
247                } else {
248                    f.debug_struct("Lazy").field("state", &"Loaded").finish()
249                }
250            }
251            _ => f.debug_struct("Lazy").field("state", &state).finish(),
252        }
253    }
254}
255
256/// A relation that can be lazily loaded.
257///
258/// This is similar to `Lazy` but includes the loader configuration.
259pub struct LazyRelation<T, L> {
260    /// The lazy value.
261    pub value: Lazy<T>,
262    /// The loader (e.g., query parameters).
263    pub loader: L,
264}
265
266impl<T, L> LazyRelation<T, L> {
267    /// Create a new lazy relation.
268    pub fn new(loader: L) -> Self {
269        Self {
270            value: Lazy::new(),
271            loader,
272        }
273    }
274
275    /// Create a lazy relation with a pre-loaded value.
276    pub fn loaded(value: T, loader: L) -> Self {
277        Self {
278            value: Lazy::loaded(value),
279            loader,
280        }
281    }
282
283    /// Check if the relation has been loaded.
284    #[inline]
285    pub fn is_loaded(&self) -> bool {
286        self.value.is_loaded()
287    }
288
289    /// Get the loaded value if available.
290    pub fn get(&self) -> Option<&T> {
291        self.value.get()
292    }
293}
294
295impl<T: Clone, L: Clone> Clone for LazyRelation<T, L> {
296    fn clone(&self) -> Self {
297        Self {
298            value: self.value.clone(),
299            loader: self.loader.clone(),
300        }
301    }
302}
303
304impl<T: fmt::Debug, L: fmt::Debug> fmt::Debug for LazyRelation<T, L> {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        f.debug_struct("LazyRelation")
307            .field("value", &self.value)
308            .field("loader", &self.loader)
309            .finish()
310    }
311}
312
313/// Configuration for a one-to-many relation loader.
314#[derive(Debug, Clone)]
315pub struct OneToManyLoader {
316    /// The foreign key column in the related table.
317    pub foreign_key: String,
318    /// The local key value.
319    pub local_key_value: crate::filter::FilterValue,
320    /// The related table name.
321    pub table: String,
322}
323
324impl OneToManyLoader {
325    /// Create a new one-to-many loader.
326    pub fn new(
327        table: impl Into<String>,
328        foreign_key: impl Into<String>,
329        local_key_value: impl Into<crate::filter::FilterValue>,
330    ) -> Self {
331        Self {
332            table: table.into(),
333            foreign_key: foreign_key.into(),
334            local_key_value: local_key_value.into(),
335        }
336    }
337}
338
339/// Configuration for a many-to-one relation loader.
340#[derive(Debug, Clone)]
341pub struct ManyToOneLoader {
342    /// The foreign key value.
343    pub foreign_key_value: crate::filter::FilterValue,
344    /// The related table name.
345    pub table: String,
346    /// The primary key column in the related table.
347    pub primary_key: String,
348}
349
350impl ManyToOneLoader {
351    /// Create a new many-to-one loader.
352    pub fn new(
353        table: impl Into<String>,
354        primary_key: impl Into<String>,
355        foreign_key_value: impl Into<crate::filter::FilterValue>,
356    ) -> Self {
357        Self {
358            table: table.into(),
359            primary_key: primary_key.into(),
360            foreign_key_value: foreign_key_value.into(),
361        }
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368
369    #[test]
370    fn test_lazy_new() {
371        let lazy: Lazy<i32> = Lazy::new();
372        assert!(!lazy.is_loaded());
373        assert!(lazy.get().is_none());
374    }
375
376    #[test]
377    fn test_lazy_loaded() {
378        let lazy = Lazy::loaded(42);
379        assert!(lazy.is_loaded());
380        assert_eq!(lazy.get(), Some(&42));
381    }
382
383    #[test]
384    fn test_lazy_set() {
385        let lazy: Lazy<i32> = Lazy::new();
386        lazy.set(42);
387        assert!(lazy.is_loaded());
388        assert_eq!(lazy.get(), Some(&42));
389    }
390
391    #[test]
392    fn test_lazy_take() {
393        let mut lazy = Lazy::loaded(42);
394        let value = lazy.take();
395        assert_eq!(value, Some(42));
396        assert!(!lazy.is_loaded());
397    }
398
399    #[test]
400    fn test_lazy_reset() {
401        let mut lazy = Lazy::loaded(42);
402        lazy.reset();
403        assert!(!lazy.is_loaded());
404        assert!(lazy.get().is_none());
405    }
406
407    #[test]
408    fn test_lazy_clone() {
409        let lazy = Lazy::loaded(42);
410        let cloned = lazy.clone();
411        assert!(cloned.is_loaded());
412        assert_eq!(cloned.get(), Some(&42));
413    }
414
415    #[test]
416    fn test_lazy_clone_unloaded() {
417        let lazy: Lazy<i32> = Lazy::new();
418        let cloned = lazy.clone();
419        assert!(!cloned.is_loaded());
420    }
421
422    #[tokio::test]
423    async fn test_lazy_load_with() {
424        let lazy: Lazy<i32> = Lazy::new();
425
426        let result = lazy.load_with(|| async { Ok::<_, &str>(42) }).await;
427
428        assert!(result.is_ok());
429        assert_eq!(result.unwrap(), &42);
430        assert!(lazy.is_loaded());
431    }
432
433    #[tokio::test]
434    async fn test_lazy_load_cached() {
435        let lazy = Lazy::loaded(42);
436
437        // Should return cached value
438        let result = lazy.load_with(|| async { Ok::<_, &str>(100) }).await;
439
440        assert!(result.is_ok());
441        assert_eq!(result.unwrap(), &42); // Original value, not 100
442    }
443
444    #[test]
445    fn test_lazy_relation() {
446        let relation: LazyRelation<Vec<i32>, OneToManyLoader> =
447            LazyRelation::new(OneToManyLoader::new("posts", "user_id", 1i64));
448
449        assert!(!relation.is_loaded());
450        assert!(relation.get().is_none());
451    }
452}