use rust_ef::db_context::{DbContext, DbContextOptionsBuilder};
use rust_ef::prelude::*;
use rust_ef_sqlite::DbContextOptionsBuilderExt as _;
#[derive(Debug, Clone, EntityType)]
#[table("lazy_blogs")]
struct LazyBlog {
#[primary_key]
#[auto_increment]
blog_id: i32,
#[required]
url: String,
#[navigation]
posts: HasMany<LazyPost>,
}
#[derive(Debug, Clone, EntityType)]
#[table("lazy_posts")]
struct LazyPost {
#[primary_key]
#[auto_increment]
post_id: i32,
#[required]
title: String,
#[foreign_key(LazyBlog)]
blog_id: i32,
#[navigation]
blog: BelongsTo<LazyBlog>,
}
fn build_ctx(lazy: bool) -> DbContext {
let mut builder = DbContextOptionsBuilder::new();
builder.use_sqlite_in_memory();
builder.use_lazy_loading(lazy);
let options = builder.build();
DbContext::from_options(&options).expect("DbContext")
}
async fn seed_data(ctx: &mut DbContext) {
ctx.set::<LazyBlog>();
ctx.set::<LazyPost>();
ctx.ensure_created().await.unwrap();
ctx.set::<LazyBlog>().add(LazyBlog {
blog_id: 0,
url: "https://lazy.example".into(),
posts: HasMany::new(),
});
ctx.save_changes().await.unwrap();
let blog_id = ctx.set::<LazyBlog>().query().to_list().await.unwrap()[0].blog_id;
ctx.set::<LazyPost>().add(LazyPost {
post_id: 0,
title: "Post A".into(),
blog_id,
blog: BelongsTo::new(),
});
ctx.set::<LazyPost>().add(LazyPost {
post_id: 0,
title: "Post B".into(),
blog_id,
blog: BelongsTo::new(),
});
ctx.save_changes().await.unwrap();
}
#[tokio::test]
async fn test_lazy_loading_disabled_by_default() {
let mut ctx = build_ctx(false);
seed_data(&mut ctx).await;
let blogs = ctx.set::<LazyBlog>().query().to_list().await.unwrap();
assert_eq!(blogs.len(), 1);
assert!(!blogs[0].posts.is_loaded());
assert_eq!(blogs[0].posts.len(), 0);
}
#[tokio::test]
async fn test_lazy_loading_disabled_load_is_noop() {
let mut ctx = build_ctx(false);
seed_data(&mut ctx).await;
let mut blogs = ctx.set::<LazyBlog>().query().to_list().await.unwrap();
assert!(!blogs[0].posts.is_loaded());
blogs[0].posts.load().await.unwrap();
assert!(!blogs[0].posts.is_loaded());
assert_eq!(blogs[0].posts.len(), 0);
}
#[tokio::test]
async fn test_hasmany_lazy_load() {
let mut ctx = build_ctx(true);
seed_data(&mut ctx).await;
let mut blogs = ctx.set::<LazyBlog>().query().to_list().await.unwrap();
assert_eq!(blogs.len(), 1);
assert!(!blogs[0].posts.is_loaded());
assert_eq!(blogs[0].posts.len(), 0);
blogs[0].posts.load().await.unwrap();
assert!(blogs[0].posts.is_loaded());
assert_eq!(blogs[0].posts.len(), 2);
let titles: Vec<&str> = blogs[0]
.posts
.items()
.iter()
.map(|p| p.title.as_str())
.collect();
assert!(titles.contains(&"Post A"));
assert!(titles.contains(&"Post B"));
}
#[tokio::test]
async fn test_belongs_to_lazy_load() {
let mut ctx = build_ctx(true);
seed_data(&mut ctx).await;
let mut posts = ctx.set::<LazyPost>().query().to_list().await.unwrap();
assert_eq!(posts.len(), 2);
assert!(!posts[0].blog.is_loaded());
assert!(posts[0].blog.get().is_none());
posts[0].blog.load().await.unwrap();
assert!(posts[0].blog.is_loaded());
let blog = posts[0].blog.get().expect("blog loaded");
assert_eq!(blog.url, "https://lazy.example");
}
#[tokio::test]
async fn test_double_load_is_noop() {
let mut ctx = build_ctx(true);
seed_data(&mut ctx).await;
let mut blogs = ctx.set::<LazyBlog>().query().to_list().await.unwrap();
blogs[0].posts.load().await.unwrap();
let count_after_first = blogs[0].posts.len();
assert!(blogs[0].posts.is_loaded());
blogs[0].posts.load().await.unwrap();
assert!(blogs[0].posts.is_loaded());
assert_eq!(blogs[0].posts.len(), count_after_first);
}
#[tokio::test]
async fn test_include_takes_precedence_over_lazy() {
let mut ctx = build_ctx(true);
seed_data(&mut ctx).await;
let blogs = linq!(ctx.set::<LazyBlog>(); include b.posts)
.to_list()
.await
.unwrap();
assert_eq!(blogs.len(), 1);
assert!(blogs[0].posts.is_loaded());
assert_eq!(blogs[0].posts.len(), 2);
}
#[tokio::test]
async fn test_nested_lazy_load_attaches_child_contexts() {
let mut ctx = build_ctx(true);
seed_data(&mut ctx).await;
let mut blogs = ctx.set::<LazyBlog>().query().to_list().await.unwrap();
blogs[0].posts.load().await.unwrap();
let posts = blogs[0].posts.items_mut();
for post in posts.iter_mut() {
assert!(!post.blog.is_loaded());
}
}