use ngdb::{DatabaseConfig, Ref, Result, Storable, ngdb};
#[ngdb("users")]
struct User {
id: u64,
name: String,
email: String,
}
impl Storable for User {
type Key = u64;
fn key(&self) -> Self::Key {
self.id
}
}
#[ngdb("posts")]
struct Post {
id: u64,
title: String,
content: String,
author: Ref<User>,
}
impl Storable for Post {
type Key = u64;
fn key(&self) -> Self::Key {
self.id
}
}
#[ngdb("comments")]
struct Comment {
id: u64,
text: String,
author: Ref<User>,
post: Ref<Post>,
}
impl Storable for Comment {
type Key = u64;
fn key(&self) -> Self::Key {
self.id
}
}
fn main() -> Result<()> {
let db = DatabaseConfig::new("./data/nested_refs_example")
.create_if_missing(true)
.add_column_family("users")
.add_column_family("posts")
.add_column_family("comments")
.open()?;
let alice = User {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
};
alice.save(&db)?;
let post = Post {
id: 1,
title: "Introduction to NGDB".to_string(),
content: "NGDB is a high-performance RocksDB wrapper...".to_string(),
author: Ref::from_value(alice.clone()),
};
post.save(&db)?;
Comment {
id: 1,
text: "Great article!".to_string(),
author: Ref::new(1),
post: Ref::new(1),
}
.save(&db)?;
Comment {
id: 2,
text: "Very helpful, thanks!".to_string(),
author: Ref::new(1),
post: Ref::new(1),
}
.save(&db)?;
Comment {
id: 3,
text: "Looking forward to more.".to_string(),
author: Ref::new(1),
post: Ref::new(1),
}
.save(&db)?;
println!("=== Example 1: Manual reference resolution (no mut needed!) ===\n");
let comments = Comment::collection(&db)?;
let comment1 = comments.get(&1)?.unwrap();
println!("Comment: '{}'", comment1.text);
let author = comment1.author.get(&db)?;
println!("Author: {} ({})", author.name, author.email);
drop(author);
let post = comment1.post.get(&db)?;
println!("Post: '{}'", post.title);
let post_author = post.author.get(&db)?;
println!("Post Author: {}", post_author.name);
println!("\n=== Example 2: Multiple accesses use cached value ===\n");
let comment2 = comments.get(&2)?.unwrap();
println!("Is author resolved? {}", comment2.author.is_resolved());
let author = comment2.author.get(&db)?;
println!("First access: {}", author.name);
drop(author);
println!("Is author resolved? {}", comment2.author.is_resolved());
let author_name = comment2.author.get(&db)?.name.clone();
let author_email = comment2.author.get(&db)?.email.clone();
println!("Cached access: {} <{}>", author_name, author_email);
println!("\n=== Example 3: get_with_refs() resolves everything upfront ===\n");
let resolved = comments.get_with_refs(&3, &db)?.unwrap();
println!("Comment: '{}'", resolved.text);
println!(
"Author: {} ({})",
resolved.author.get(&db)?.name,
resolved.author.get(&db)?.email
);
println!("Post: '{}'", resolved.post.get(&db)?.title);
println!(
"Post Author: {}",
resolved.post.get(&db)?.author.get(&db)?.name
);
println!("\n=== Example 4: Batch retrieval ===\n");
let ids = vec![1, 2, 3];
let all_comments = comments.get_many_with_refs(&ids, &db)?;
println!("All comments (batch loaded with references resolved):");
for comment in all_comments.into_iter().flatten() {
println!(" '{}' by {}", comment.text, comment.author.get(&db)?.name);
}
println!("\n=== Example 5: Mutable access ===\n");
let comment = comments.get(&1)?.unwrap();
let mut author = comment.author.get_mut(&db)?;
let old_name = author.name.clone();
author.name = "Alice Smith".to_string();
println!(
"Changed author name from '{}' to '{}'",
old_name, author.name
);
println!("\n✅ All examples completed successfully!");
println!("Key takeaways:");
println!(" • No 'mut' needed on parent structs");
println!(" • First .get() resolves from DB, subsequent calls use cache");
println!(" • No more get_unchecked() - just use .get() everywhere");
println!(" • Cleaner, more intuitive API!");
Ok(())
}