use async_trait::async_trait;
use sqlx::{Pool, Postgres};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::error::{ModelError, ModelResult};
use crate::model::Model;
#[async_trait]
pub trait RelationshipLoader<T>: Send + Sync {
async fn load(&self, pool: &Pool<Postgres>) -> ModelResult<T>;
async fn reload(&self, pool: &Pool<Postgres>) -> ModelResult<T>;
}
pub struct CachedRelationshipLoader<T> {
loader_fn: Arc<
dyn Fn(&Pool<Postgres>) -> Pin<Box<dyn Future<Output = ModelResult<T>> + Send>>
+ Send
+ Sync,
>,
cache: Arc<RwLock<Option<T>>>,
loaded: Arc<RwLock<bool>>,
}
use std::future::Future;
use std::pin::Pin;
impl<T> CachedRelationshipLoader<T>
where
T: Send + Sync,
{
pub fn new<F, Fut>(loader: F) -> Self
where
F: Fn(&Pool<Postgres>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ModelResult<T>> + Send + 'static,
{
Self {
loader_fn: Arc::new(move |pool| Box::pin(loader(pool))),
cache: Arc::new(RwLock::new(None)),
loaded: Arc::new(RwLock::new(false)),
}
}
}
#[async_trait]
impl<T> RelationshipLoader<T> for CachedRelationshipLoader<T>
where
T: Send + Sync + Clone,
{
async fn load(&self, pool: &Pool<Postgres>) -> ModelResult<T> {
{
let loaded = self.loaded.read().await;
if *loaded {
let cache = self.cache.read().await;
if let Some(ref value) = *cache {
return Ok(value.clone());
}
}
}
let result = (self.loader_fn)(pool).await?;
{
let mut cache = self.cache.write().await;
*cache = Some(result.clone());
}
{
let mut loaded = self.loaded.write().await;
*loaded = true;
}
Ok(result)
}
async fn reload(&self, pool: &Pool<Postgres>) -> ModelResult<T> {
{
let mut cache = self.cache.write().await;
*cache = None;
}
{
let mut loaded = self.loaded.write().await;
*loaded = false;
}
self.load(pool).await
}
}
#[derive(Debug, Clone)]
pub struct AccessPattern {
pub access_count: usize,
pub should_auto_load: bool,
pub last_accessed: std::time::Instant,
}
impl Default for AccessPattern {
fn default() -> Self {
Self {
access_count: 0,
should_auto_load: false,
last_accessed: std::time::Instant::now(),
}
}
}
pub struct Lazy<T> {
loader: Box<dyn RelationshipLoader<T>>,
loaded: bool,
value: Option<T>,
access_pattern: Arc<RwLock<AccessPattern>>,
}
impl<T> Lazy<T>
where
T: Clone + Send + Sync + 'static,
{
pub fn new<L>(loader: L) -> Self
where
L: RelationshipLoader<T> + 'static,
{
Self {
loader: Box::new(loader),
loaded: false,
value: None,
access_pattern: Arc::new(RwLock::new(AccessPattern::default())),
}
}
pub fn loaded(value: T) -> Self {
Self {
loader: Box::new(NoOpLoader::new(value.clone())),
loaded: true,
value: Some(value),
access_pattern: Arc::new(RwLock::new(AccessPattern::default())),
}
}
}
impl<T> Lazy<T>
where
T: Send + Sync,
{
pub async fn get(&mut self, pool: &Pool<Postgres>) -> ModelResult<&T> {
{
let mut pattern = self.access_pattern.write().await;
pattern.access_count += 1;
pattern.last_accessed = std::time::Instant::now();
if pattern.access_count >= 3 {
pattern.should_auto_load = true;
}
}
if !self.loaded {
self.load(pool).await?;
}
self.value.as_ref().ok_or_else(|| {
ModelError::Database("Lazy relationship value not available".to_string())
})
}
pub async fn load(&mut self, pool: &Pool<Postgres>) -> ModelResult<&T> {
let value = self.loader.load(pool).await?;
self.value = Some(value);
self.loaded = true;
self.value.as_ref().ok_or_else(|| {
ModelError::Database("Failed to store lazy relationship value".to_string())
})
}
pub async fn reload(&mut self, pool: &Pool<Postgres>) -> ModelResult<&T> {
let value = self.loader.reload(pool).await?;
self.value = Some(value);
self.loaded = true;
self.value.as_ref().ok_or_else(|| {
ModelError::Database("Failed to store reloaded relationship value".to_string())
})
}
}
impl<T> Lazy<T> {
pub fn is_loaded(&self) -> bool {
self.loaded
}
pub fn take(&mut self) -> Option<T> {
self.loaded = false;
self.value.take()
}
pub fn set(&mut self, value: T) {
self.value = Some(value);
self.loaded = true;
}
pub fn clear(&mut self) {
self.value = None;
self.loaded = false;
}
pub async fn get_access_pattern(&self) -> AccessPattern {
self.access_pattern.read().await.clone()
}
pub async fn should_auto_load(&self) -> bool {
self.access_pattern.read().await.should_auto_load
}
pub async fn enable_auto_load(&self) {
let mut pattern = self.access_pattern.write().await;
pattern.should_auto_load = true;
}
pub async fn disable_auto_load(&self) {
let mut pattern = self.access_pattern.write().await;
pattern.should_auto_load = false;
}
}
struct NoOpLoader<T> {
value: T,
}
impl<T> NoOpLoader<T> {
fn new(value: T) -> Self {
Self { value }
}
}
#[async_trait]
impl<T> RelationshipLoader<T> for NoOpLoader<T>
where
T: Send + Sync + Clone,
{
async fn load(&self, _pool: &Pool<Postgres>) -> ModelResult<T> {
Ok(self.value.clone())
}
async fn reload(&self, _pool: &Pool<Postgres>) -> ModelResult<T> {
Ok(self.value.clone())
}
}
pub struct RelationshipCache {
cache: Arc<RwLock<HashMap<String, HashMap<String, HashMap<String, serde_json::Value>>>>>,
}
impl RelationshipCache {
pub fn new() -> Self {
Self {
cache: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn store(
&self,
model_type: &str,
model_id: &str,
relation: &str,
data: serde_json::Value,
) {
let mut cache = self.cache.write().await;
cache
.entry(model_type.to_string())
.or_insert_with(HashMap::new)
.entry(model_id.to_string())
.or_insert_with(HashMap::new)
.insert(relation.to_string(), data);
}
pub async fn get(
&self,
model_type: &str,
model_id: &str,
relation: &str,
) -> Option<serde_json::Value> {
let cache = self.cache.read().await;
cache.get(model_type)?.get(model_id)?.get(relation).cloned()
}
pub async fn contains(&self, model_type: &str, model_id: &str, relation: &str) -> bool {
let cache = self.cache.read().await;
cache
.get(model_type)
.and_then(|models| models.get(model_id))
.and_then(|relations| relations.get(relation))
.is_some()
}
pub async fn clear_model(&self, model_type: &str, model_id: &str) {
let mut cache = self.cache.write().await;
if let Some(models) = cache.get_mut(model_type) {
models.remove(model_id);
}
}
pub async fn clear_model_type(&self, model_type: &str) {
let mut cache = self.cache.write().await;
cache.remove(model_type);
}
pub async fn clear_all(&self) {
let mut cache = self.cache.write().await;
cache.clear();
}
pub async fn stats(&self) -> CacheStats {
let cache = self.cache.read().await;
let model_types = cache.len();
let total_models = cache.values().map(|m| m.len()).sum();
let total_relationships = cache
.values()
.flat_map(|models| models.values())
.map(|relations| relations.len())
.sum();
CacheStats {
model_types,
total_models,
total_relationships,
}
}
}
impl Default for RelationshipCache {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CacheStats {
pub model_types: usize,
pub total_models: usize,
pub total_relationships: usize,
}
static RELATIONSHIP_CACHE: tokio::sync::OnceCell<RelationshipCache> =
tokio::sync::OnceCell::const_new();
pub async fn get_relationship_cache() -> &'static RelationshipCache {
RELATIONSHIP_CACHE
.get_or_init(|| async { RelationshipCache::new() })
.await
}
pub type LazyHasOne<T> = Lazy<Option<T>>;
pub type LazyHasMany<T> = Lazy<Vec<T>>;
pub type LazyBelongsTo<T> = Lazy<T>;
pub trait LazyRelationshipBuilder<Parent, Related>
where
Parent: Model + Send + Sync + Clone + 'static,
Related: Model + Send + Sync + Clone + 'static,
{
fn lazy_has_one(parent: &Parent, foreign_key: String) -> LazyHasOne<Related> {
let parent_id = parent
.primary_key()
.map(|pk| pk.to_string())
.unwrap_or_default();
let loader = CachedRelationshipLoader::new(move |_pool| {
let _foreign_key = foreign_key.clone();
let _parent_id = parent_id.clone();
async move {
Ok(None)
}
});
Lazy::new(loader)
}
fn lazy_has_many(parent: &Parent, foreign_key: String) -> LazyHasMany<Related> {
let parent_id = parent
.primary_key()
.map(|pk| pk.to_string())
.unwrap_or_default();
let loader = CachedRelationshipLoader::new(move |_pool| {
let _foreign_key = foreign_key.clone();
let _parent_id = parent_id.clone();
async move {
Ok(Vec::<Related>::new())
}
});
Lazy::new(loader)
}
fn lazy_belongs_to(_child: &Related, _parent_id_field: String) -> LazyBelongsTo<Parent> {
let parent_id = "placeholder_id".to_string(); let loader = CachedRelationshipLoader::new(move |_pool| {
let _parent_id = parent_id.clone();
async move {
Err(crate::error::ModelError::Database(
"Placeholder implementation".to_string(),
))
}
});
Lazy::new(loader)
}
}