elif_orm/relationships/
loader.rs

1//! Relationship Loading Utilities - Handles the loading and caching of relationships
2
3use async_trait::async_trait;
4use sqlx::{Pool, Postgres};
5use std::collections::HashMap;
6use std::sync::Arc;
7use tokio::sync::RwLock;
8
9use crate::error::{ModelError, ModelResult};
10use crate::model::Model;
11
12/// Trait for loading relationships dynamically
13#[async_trait]
14pub trait RelationshipLoader<T>: Send + Sync {
15    /// Load the relationship from the database
16    async fn load(&self, pool: &Pool<Postgres>) -> ModelResult<T>;
17
18    /// Reload the relationship, bypassing cache
19    async fn reload(&self, pool: &Pool<Postgres>) -> ModelResult<T>;
20}
21
22/// Cached relationship loader that implements lazy loading with caching
23pub struct CachedRelationshipLoader<T> {
24    /// The loader function
25    loader_fn: Arc<
26        dyn Fn(&Pool<Postgres>) -> Pin<Box<dyn Future<Output = ModelResult<T>> + Send>>
27            + Send
28            + Sync,
29    >,
30    /// Cached value
31    cache: Arc<RwLock<Option<T>>>,
32    /// Whether the relationship has been loaded
33    loaded: Arc<RwLock<bool>>,
34}
35
36use std::future::Future;
37use std::pin::Pin;
38
39impl<T> CachedRelationshipLoader<T>
40where
41    T: Send + Sync,
42{
43    /// Create a new cached loader with a loading function
44    pub fn new<F, Fut>(loader: F) -> Self
45    where
46        F: Fn(&Pool<Postgres>) -> Fut + Send + Sync + 'static,
47        Fut: Future<Output = ModelResult<T>> + Send + 'static,
48    {
49        Self {
50            loader_fn: Arc::new(move |pool| Box::pin(loader(pool))),
51            cache: Arc::new(RwLock::new(None)),
52            loaded: Arc::new(RwLock::new(false)),
53        }
54    }
55}
56
57#[async_trait]
58impl<T> RelationshipLoader<T> for CachedRelationshipLoader<T>
59where
60    T: Send + Sync + Clone,
61{
62    async fn load(&self, pool: &Pool<Postgres>) -> ModelResult<T> {
63        // Check if already loaded
64        {
65            let loaded = self.loaded.read().await;
66            if *loaded {
67                let cache = self.cache.read().await;
68                if let Some(ref value) = *cache {
69                    return Ok(value.clone());
70                }
71            }
72        }
73
74        // Load the relationship
75        let result = (self.loader_fn)(pool).await?;
76
77        // Cache the result
78        {
79            let mut cache = self.cache.write().await;
80            *cache = Some(result.clone());
81        }
82        {
83            let mut loaded = self.loaded.write().await;
84            *loaded = true;
85        }
86
87        Ok(result)
88    }
89
90    async fn reload(&self, pool: &Pool<Postgres>) -> ModelResult<T> {
91        // Clear cache and reload
92        {
93            let mut cache = self.cache.write().await;
94            *cache = None;
95        }
96        {
97            let mut loaded = self.loaded.write().await;
98            *loaded = false;
99        }
100
101        self.load(pool).await
102    }
103}
104
105/// Access pattern tracking for auto-loading optimization
106#[derive(Debug, Clone)]
107pub struct AccessPattern {
108    /// Number of times this relationship has been accessed
109    pub access_count: usize,
110    /// Whether this relationship should be auto-loaded based on patterns
111    pub should_auto_load: bool,
112    /// Last access timestamp (for eviction policies)
113    pub last_accessed: std::time::Instant,
114}
115
116impl Default for AccessPattern {
117    fn default() -> Self {
118        Self {
119            access_count: 0,
120            should_auto_load: false,
121            last_accessed: std::time::Instant::now(),
122        }
123    }
124}
125
126/// Lazy loading wrapper for relationships
127pub struct Lazy<T> {
128    loader: Box<dyn RelationshipLoader<T>>,
129    loaded: bool,
130    value: Option<T>,
131    /// Access pattern tracking for optimization
132    access_pattern: Arc<RwLock<AccessPattern>>,
133}
134
135impl<T> Lazy<T>
136where
137    T: Clone + Send + Sync + 'static,
138{
139    /// Create a new lazy relationship
140    pub fn new<L>(loader: L) -> Self
141    where
142        L: RelationshipLoader<T> + 'static,
143    {
144        Self {
145            loader: Box::new(loader),
146            loaded: false,
147            value: None,
148            access_pattern: Arc::new(RwLock::new(AccessPattern::default())),
149        }
150    }
151
152    /// Create a lazy relationship with a pre-loaded value
153    pub fn loaded(value: T) -> Self {
154        Self {
155            loader: Box::new(NoOpLoader::new(value.clone())),
156            loaded: true,
157            value: Some(value),
158            access_pattern: Arc::new(RwLock::new(AccessPattern::default())),
159        }
160    }
161}
162
163impl<T> Lazy<T>
164where
165    T: Send + Sync,
166{
167    /// Get the loaded value, loading if necessary
168    pub async fn get(&mut self, pool: &Pool<Postgres>) -> ModelResult<&T> {
169        // Track access pattern
170        {
171            let mut pattern = self.access_pattern.write().await;
172            pattern.access_count += 1;
173            pattern.last_accessed = std::time::Instant::now();
174
175            // Auto-enable after multiple accesses
176            if pattern.access_count >= 3 {
177                pattern.should_auto_load = true;
178            }
179        }
180
181        if !self.loaded {
182            self.load(pool).await?;
183        }
184
185        self.value.as_ref().ok_or_else(|| {
186            ModelError::Database("Lazy relationship value not available".to_string())
187        })
188    }
189
190    /// Load the relationship
191    pub async fn load(&mut self, pool: &Pool<Postgres>) -> ModelResult<&T> {
192        let value = self.loader.load(pool).await?;
193        self.value = Some(value);
194        self.loaded = true;
195
196        self.value.as_ref().ok_or_else(|| {
197            ModelError::Database("Failed to store lazy relationship value".to_string())
198        })
199    }
200
201    /// Reload the relationship, bypassing cache
202    pub async fn reload(&mut self, pool: &Pool<Postgres>) -> ModelResult<&T> {
203        let value = self.loader.reload(pool).await?;
204        self.value = Some(value);
205        self.loaded = true;
206
207        self.value.as_ref().ok_or_else(|| {
208            ModelError::Database("Failed to store reloaded relationship value".to_string())
209        })
210    }
211}
212
213impl<T> Lazy<T> {
214    /// Check if the relationship has been loaded
215    pub fn is_loaded(&self) -> bool {
216        self.loaded
217    }
218
219    /// Take the loaded value, leaving None in its place
220    pub fn take(&mut self) -> Option<T> {
221        self.loaded = false;
222        self.value.take()
223    }
224
225    /// Set a pre-loaded value
226    pub fn set(&mut self, value: T) {
227        self.value = Some(value);
228        self.loaded = true;
229    }
230
231    /// Clear the cached value
232    pub fn clear(&mut self) {
233        self.value = None;
234        self.loaded = false;
235    }
236
237    /// Get access pattern statistics
238    pub async fn get_access_pattern(&self) -> AccessPattern {
239        self.access_pattern.read().await.clone()
240    }
241
242    /// Check if this relationship should be auto-loaded based on access patterns
243    pub async fn should_auto_load(&self) -> bool {
244        self.access_pattern.read().await.should_auto_load
245    }
246
247    /// Force enable auto-loading for this relationship
248    pub async fn enable_auto_load(&self) {
249        let mut pattern = self.access_pattern.write().await;
250        pattern.should_auto_load = true;
251    }
252
253    /// Disable auto-loading for this relationship
254    pub async fn disable_auto_load(&self) {
255        let mut pattern = self.access_pattern.write().await;
256        pattern.should_auto_load = false;
257    }
258}
259
260/// No-op loader for pre-loaded values
261struct NoOpLoader<T> {
262    value: T,
263}
264
265impl<T> NoOpLoader<T> {
266    fn new(value: T) -> Self {
267        Self { value }
268    }
269}
270
271#[async_trait]
272impl<T> RelationshipLoader<T> for NoOpLoader<T>
273where
274    T: Send + Sync + Clone,
275{
276    async fn load(&self, _pool: &Pool<Postgres>) -> ModelResult<T> {
277        Ok(self.value.clone())
278    }
279
280    async fn reload(&self, _pool: &Pool<Postgres>) -> ModelResult<T> {
281        Ok(self.value.clone())
282    }
283}
284
285/// Relationship cache for managing loaded relationships across models
286pub struct RelationshipCache {
287    /// Cache storage organized by model type, model id, and relationship name
288    cache: Arc<RwLock<HashMap<String, HashMap<String, HashMap<String, serde_json::Value>>>>>,
289}
290
291impl RelationshipCache {
292    /// Create a new relationship cache
293    pub fn new() -> Self {
294        Self {
295            cache: Arc::new(RwLock::new(HashMap::new())),
296        }
297    }
298
299    /// Store a relationship in the cache
300    pub async fn store(
301        &self,
302        model_type: &str,
303        model_id: &str,
304        relation: &str,
305        data: serde_json::Value,
306    ) {
307        let mut cache = self.cache.write().await;
308
309        cache
310            .entry(model_type.to_string())
311            .or_insert_with(HashMap::new)
312            .entry(model_id.to_string())
313            .or_insert_with(HashMap::new)
314            .insert(relation.to_string(), data);
315    }
316
317    /// Retrieve a relationship from the cache
318    pub async fn get(
319        &self,
320        model_type: &str,
321        model_id: &str,
322        relation: &str,
323    ) -> Option<serde_json::Value> {
324        let cache = self.cache.read().await;
325
326        cache.get(model_type)?.get(model_id)?.get(relation).cloned()
327    }
328
329    /// Check if a relationship is cached
330    pub async fn contains(&self, model_type: &str, model_id: &str, relation: &str) -> bool {
331        let cache = self.cache.read().await;
332
333        cache
334            .get(model_type)
335            .and_then(|models| models.get(model_id))
336            .and_then(|relations| relations.get(relation))
337            .is_some()
338    }
339
340    /// Clear all cached relationships for a model instance
341    pub async fn clear_model(&self, model_type: &str, model_id: &str) {
342        let mut cache = self.cache.write().await;
343
344        if let Some(models) = cache.get_mut(model_type) {
345            models.remove(model_id);
346        }
347    }
348
349    /// Clear all cached relationships for a model type
350    pub async fn clear_model_type(&self, model_type: &str) {
351        let mut cache = self.cache.write().await;
352        cache.remove(model_type);
353    }
354
355    /// Clear all cached relationships
356    pub async fn clear_all(&self) {
357        let mut cache = self.cache.write().await;
358        cache.clear();
359    }
360
361    /// Get cache statistics
362    pub async fn stats(&self) -> CacheStats {
363        let cache = self.cache.read().await;
364
365        let model_types = cache.len();
366        let total_models = cache.values().map(|m| m.len()).sum();
367        let total_relationships = cache
368            .values()
369            .flat_map(|models| models.values())
370            .map(|relations| relations.len())
371            .sum();
372
373        CacheStats {
374            model_types,
375            total_models,
376            total_relationships,
377        }
378    }
379}
380
381impl Default for RelationshipCache {
382    fn default() -> Self {
383        Self::new()
384    }
385}
386
387/// Cache statistics
388#[derive(Debug, Clone)]
389pub struct CacheStats {
390    pub model_types: usize,
391    pub total_models: usize,
392    pub total_relationships: usize,
393}
394
395/// Global relationship cache instance
396static RELATIONSHIP_CACHE: tokio::sync::OnceCell<RelationshipCache> =
397    tokio::sync::OnceCell::const_new();
398
399/// Get the global relationship cache
400pub async fn get_relationship_cache() -> &'static RelationshipCache {
401    RELATIONSHIP_CACHE
402        .get_or_init(|| async { RelationshipCache::new() })
403        .await
404}
405
406/// Lazy HasOne relationship - wraps a single optional related model
407pub type LazyHasOne<T> = Lazy<Option<T>>;
408
409/// Lazy HasMany relationship - wraps a collection of related models  
410pub type LazyHasMany<T> = Lazy<Vec<T>>;
411
412/// Lazy BelongsTo relationship - wraps a single related model
413pub type LazyBelongsTo<T> = Lazy<T>;
414
415/// Helper trait to create lazy relationship loaders
416pub trait LazyRelationshipBuilder<Parent, Related>
417where
418    Parent: Model + Send + Sync + Clone + 'static,
419    Related: Model + Send + Sync + Clone + 'static,
420{
421    /// Create a lazy HasOne loader for a relationship
422    fn lazy_has_one(parent: &Parent, foreign_key: String) -> LazyHasOne<Related> {
423        let parent_id = parent
424            .primary_key()
425            .map(|pk| pk.to_string())
426            .unwrap_or_default();
427        let loader = CachedRelationshipLoader::new(move |_pool| {
428            let _foreign_key = foreign_key.clone();
429            let _parent_id = parent_id.clone();
430            async move {
431                // This would typically execute a query like:
432                // SELECT * FROM related_table WHERE foreign_key_field = parent_id LIMIT 1
433                // For now, we return None as a placeholder
434                Ok(None)
435            }
436        });
437        Lazy::new(loader)
438    }
439
440    /// Create a lazy HasMany loader for a relationship
441    fn lazy_has_many(parent: &Parent, foreign_key: String) -> LazyHasMany<Related> {
442        let parent_id = parent
443            .primary_key()
444            .map(|pk| pk.to_string())
445            .unwrap_or_default();
446        let loader = CachedRelationshipLoader::new(move |_pool| {
447            let _foreign_key = foreign_key.clone();
448            let _parent_id = parent_id.clone();
449            async move {
450                // This would typically execute a query like:
451                // SELECT * FROM related_table WHERE foreign_key_field = parent_id
452                // For now, we return an empty Vec as a placeholder
453                Ok(Vec::<Related>::new())
454            }
455        });
456        Lazy::new(loader)
457    }
458
459    /// Create a lazy BelongsTo loader for a relationship
460    fn lazy_belongs_to(_child: &Related, _parent_id_field: String) -> LazyBelongsTo<Parent> {
461        let parent_id = "placeholder_id".to_string(); // Would extract from child model
462        let loader = CachedRelationshipLoader::new(move |_pool| {
463            let _parent_id = parent_id.clone();
464            async move {
465                // This would typically execute a query like:
466                // SELECT * FROM parent_table WHERE id = parent_id LIMIT 1
467                // For now, this is a placeholder implementation
468                Err(crate::error::ModelError::Database(
469                    "Placeholder implementation".to_string(),
470                ))
471            }
472        });
473        Lazy::new(loader)
474    }
475}