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::atomic::{AtomicU8, Ordering};
28use std::sync::Arc;
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.store(LazyState::Unloaded as u8, Ordering::Release);
132            self.value.get_mut().take()
133        } else {
134            None
135        }
136    }
137
138    /// Reset to unloaded state.
139    pub fn reset(&mut self) {
140        self.state.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.store(LazyState::Unloaded as u8, Ordering::Release);
182                        Err(e)
183                    }
184                }
185            }
186            Err(current) => {
187                // Someone else is loading or already loaded
188                match LazyState::from(current) {
189                    LazyState::Loaded => {
190                        // SAFETY: State is Loaded
191                        Ok(unsafe { (*self.value.get()).as_ref().unwrap() })
192                    }
193                    LazyState::Loading => {
194                        // Wait for the other loader (spin with yield)
195                        loop {
196                            tokio::task::yield_now().await;
197                            let state = LazyState::from(self.state.load(Ordering::Acquire));
198                            match state {
199                                LazyState::Loaded => {
200                                    return Ok(unsafe { (*self.value.get()).as_ref().unwrap() });
201                                }
202                                LazyState::Unloaded | LazyState::Failed => {
203                                    // Other loader failed, try again
204                                    return Box::pin(self.load_with(loader)).await;
205                                }
206                                LazyState::Loading => continue,
207                            }
208                        }
209                    }
210                    _ => {
211                        // Retry loading
212                        Box::pin(self.load_with(loader)).await
213                    }
214                }
215            }
216        }
217    }
218}
219
220impl<T: Default> Default for Lazy<T> {
221    fn default() -> Self {
222        Self::new()
223    }
224}
225
226impl<T: Clone> Clone for Lazy<T> {
227    fn clone(&self) -> Self {
228        if self.is_loaded() {
229            Self::loaded(self.get().unwrap().clone())
230        } else {
231            Self::new()
232        }
233    }
234}
235
236impl<T: fmt::Debug> fmt::Debug for Lazy<T> {
237    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238        let state = LazyState::from(self.state.load(Ordering::Acquire));
239        match state {
240            LazyState::Loaded => {
241                if let Some(value) = self.get() {
242                    f.debug_struct("Lazy")
243                        .field("state", &"Loaded")
244                        .field("value", value)
245                        .finish()
246                } else {
247                    f.debug_struct("Lazy")
248                        .field("state", &"Loaded")
249                        .finish()
250                }
251            }
252            _ => f.debug_struct("Lazy").field("state", &state).finish(),
253        }
254    }
255}
256
257/// A relation that can be lazily loaded.
258///
259/// This is similar to `Lazy` but includes the loader configuration.
260pub struct LazyRelation<T, L> {
261    /// The lazy value.
262    pub value: Lazy<T>,
263    /// The loader (e.g., query parameters).
264    pub loader: L,
265}
266
267impl<T, L> LazyRelation<T, L> {
268    /// Create a new lazy relation.
269    pub fn new(loader: L) -> Self {
270        Self {
271            value: Lazy::new(),
272            loader,
273        }
274    }
275
276    /// Create a lazy relation with a pre-loaded value.
277    pub fn loaded(value: T, loader: L) -> Self {
278        Self {
279            value: Lazy::loaded(value),
280            loader,
281        }
282    }
283
284    /// Check if the relation has been loaded.
285    #[inline]
286    pub fn is_loaded(&self) -> bool {
287        self.value.is_loaded()
288    }
289
290    /// Get the loaded value if available.
291    pub fn get(&self) -> Option<&T> {
292        self.value.get()
293    }
294}
295
296impl<T: Clone, L: Clone> Clone for LazyRelation<T, L> {
297    fn clone(&self) -> Self {
298        Self {
299            value: self.value.clone(),
300            loader: self.loader.clone(),
301        }
302    }
303}
304
305impl<T: fmt::Debug, L: fmt::Debug> fmt::Debug for LazyRelation<T, L> {
306    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307        f.debug_struct("LazyRelation")
308            .field("value", &self.value)
309            .field("loader", &self.loader)
310            .finish()
311    }
312}
313
314/// Configuration for a one-to-many relation loader.
315#[derive(Debug, Clone)]
316pub struct OneToManyLoader {
317    /// The foreign key column in the related table.
318    pub foreign_key: String,
319    /// The local key value.
320    pub local_key_value: crate::filter::FilterValue,
321    /// The related table name.
322    pub table: String,
323}
324
325impl OneToManyLoader {
326    /// Create a new one-to-many loader.
327    pub fn new(
328        table: impl Into<String>,
329        foreign_key: impl Into<String>,
330        local_key_value: impl Into<crate::filter::FilterValue>,
331    ) -> Self {
332        Self {
333            table: table.into(),
334            foreign_key: foreign_key.into(),
335            local_key_value: local_key_value.into(),
336        }
337    }
338}
339
340/// Configuration for a many-to-one relation loader.
341#[derive(Debug, Clone)]
342pub struct ManyToOneLoader {
343    /// The foreign key value.
344    pub foreign_key_value: crate::filter::FilterValue,
345    /// The related table name.
346    pub table: String,
347    /// The primary key column in the related table.
348    pub primary_key: String,
349}
350
351impl ManyToOneLoader {
352    /// Create a new many-to-one loader.
353    pub fn new(
354        table: impl Into<String>,
355        primary_key: impl Into<String>,
356        foreign_key_value: impl Into<crate::filter::FilterValue>,
357    ) -> Self {
358        Self {
359            table: table.into(),
360            primary_key: primary_key.into(),
361            foreign_key_value: foreign_key_value.into(),
362        }
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369
370    #[test]
371    fn test_lazy_new() {
372        let lazy: Lazy<i32> = Lazy::new();
373        assert!(!lazy.is_loaded());
374        assert!(lazy.get().is_none());
375    }
376
377    #[test]
378    fn test_lazy_loaded() {
379        let lazy = Lazy::loaded(42);
380        assert!(lazy.is_loaded());
381        assert_eq!(lazy.get(), Some(&42));
382    }
383
384    #[test]
385    fn test_lazy_set() {
386        let lazy: Lazy<i32> = Lazy::new();
387        lazy.set(42);
388        assert!(lazy.is_loaded());
389        assert_eq!(lazy.get(), Some(&42));
390    }
391
392    #[test]
393    fn test_lazy_take() {
394        let mut lazy = Lazy::loaded(42);
395        let value = lazy.take();
396        assert_eq!(value, Some(42));
397        assert!(!lazy.is_loaded());
398    }
399
400    #[test]
401    fn test_lazy_reset() {
402        let mut lazy = Lazy::loaded(42);
403        lazy.reset();
404        assert!(!lazy.is_loaded());
405        assert!(lazy.get().is_none());
406    }
407
408    #[test]
409    fn test_lazy_clone() {
410        let lazy = Lazy::loaded(42);
411        let cloned = lazy.clone();
412        assert!(cloned.is_loaded());
413        assert_eq!(cloned.get(), Some(&42));
414    }
415
416    #[test]
417    fn test_lazy_clone_unloaded() {
418        let lazy: Lazy<i32> = Lazy::new();
419        let cloned = lazy.clone();
420        assert!(!cloned.is_loaded());
421    }
422
423    #[tokio::test]
424    async fn test_lazy_load_with() {
425        let lazy: Lazy<i32> = Lazy::new();
426
427        let result = lazy
428            .load_with(|| async { Ok::<_, &str>(42) })
429            .await;
430
431        assert!(result.is_ok());
432        assert_eq!(result.unwrap(), &42);
433        assert!(lazy.is_loaded());
434    }
435
436    #[tokio::test]
437    async fn test_lazy_load_cached() {
438        let lazy = Lazy::loaded(42);
439
440        // Should return cached value
441        let result = lazy
442            .load_with(|| async { Ok::<_, &str>(100) })
443            .await;
444
445        assert!(result.is_ok());
446        assert_eq!(result.unwrap(), &42); // Original value, not 100
447    }
448
449    #[test]
450    fn test_lazy_relation() {
451        let relation: LazyRelation<Vec<i32>, OneToManyLoader> = LazyRelation::new(
452            OneToManyLoader::new("posts", "user_id", 1i64)
453        );
454
455        assert!(!relation.is_loaded());
456        assert!(relation.get().is_none());
457    }
458}
459