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