use std::thread;
use std::time::Duration;
use tensorlogic_adapters::{
CacheConfig, CacheKey, DomainInfo, PredicateInfo, QueryCache, SymbolTable, SymbolTableCache,
};
fn main() {
println!("=== Example 25: Query Result Caching ===\n");
println!("📦 Scenario 1: Basic Cache Operations");
println!("─────────────────────────────────────");
scenario_basic_caching();
println!();
println!("⏰ Scenario 2: TTL-Based Expiration");
println!("───────────────────────────────────");
scenario_ttl_expiration();
println!();
println!("🔄 Scenario 3: LRU Eviction");
println!("───────────────────────────");
scenario_lru_eviction();
println!();
println!("⚙️ Scenario 4: Cache Configurations");
println!("────────────────────────────────────");
scenario_cache_configs();
println!();
println!("🔍 Scenario 5: Symbol Table Caching");
println!("────────────────────────────────────");
scenario_symbol_table_cache();
println!();
println!("⚡ Scenario 6: Performance Comparison");
println!("─────────────────────────────────────");
scenario_performance();
println!();
println!("✅ All query caching scenarios completed!");
}
fn scenario_basic_caching() {
let mut cache: QueryCache<Vec<String>> = QueryCache::with_config(CacheConfig::default());
println!("Creating cache with default config:");
println!(" - Max entries: 1000");
println!(" - Default TTL: 5 minutes");
println!(" - LRU eviction: enabled");
println!();
let key1 = CacheKey::PredicateByName("knows".to_string());
let key2 = CacheKey::PredicatesByArity(2);
let key3 = CacheKey::AllDomainNames;
cache.insert(
key1.clone(),
vec!["Person".to_string(), "Person".to_string()],
);
cache.insert(key2.clone(), vec!["knows".to_string(), "likes".to_string()]);
cache.insert(
key3.clone(),
vec!["Person".to_string(), "Organization".to_string()],
);
println!("Added 3 entries to cache");
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key2).is_some());
assert!(cache.get(&key3).is_some());
println!("✓ All 3 entries retrieved successfully (cache hits)");
let key4 = CacheKey::PredicateByName("unknown".to_string());
assert!(cache.get(&key4).is_none());
println!("✓ Cache miss for non-existent key");
let stats = cache.stats();
println!("\nCache statistics:");
println!(" - Total entries: {}", cache.len());
println!(" - Hits: {}", stats.hits);
println!(" - Misses: {}", stats.misses);
println!(" - Hit rate: {:.2}%", stats.hit_rate() * 100.0);
}
fn scenario_ttl_expiration() {
let config = CacheConfig {
max_entries: 100,
default_ttl: Some(Duration::from_millis(100)),
enable_lru: true,
enable_stats: true,
};
let mut cache: QueryCache<String> = QueryCache::with_config(config);
println!("Creating cache with 100ms TTL");
println!();
let key = CacheKey::PredicateByName("temporary".to_string());
cache.insert(key.clone(), "This will expire soon".to_string());
println!("Added entry to cache");
assert!(cache.get(&key).is_some());
println!("✓ Entry retrieved immediately (before expiration)");
println!("⏳ Waiting 150ms for expiration...");
thread::sleep(Duration::from_millis(150));
assert!(cache.get(&key).is_none());
println!("✓ Entry expired and cannot be retrieved");
let stats = cache.stats();
println!("\nCache statistics after expiration:");
println!(" - Total entries: {}", cache.len());
println!(" - Expirations: {}", stats.expirations);
}
fn scenario_lru_eviction() {
let config = CacheConfig {
max_entries: 3, default_ttl: None,
enable_lru: true,
enable_stats: true,
};
let mut cache: QueryCache<usize> = QueryCache::with_config(config);
println!("Creating cache with max 3 entries (no TTL)");
println!();
for i in 0..3 {
let key = CacheKey::Custom(format!("key{}", i));
cache.insert(key, i);
println!("Added entry {} to cache", i);
}
for i in 0..3 {
let key = CacheKey::Custom(format!("key{}", i));
assert!(cache.get(&key).is_some());
}
println!("✓ All 3 entries present in cache");
println!("\nAdding 4th entry (should evict least recently used)...");
let key4 = CacheKey::Custom("key3".to_string());
cache.insert(key4, 3);
let key0 = CacheKey::Custom("key0".to_string());
assert!(cache.get(&key0).is_none());
println!("✓ Entry 0 evicted (was least recently used)");
for i in 1..=3 {
let key = CacheKey::Custom(format!("key{}", i));
assert!(cache.get(&key).is_some());
}
println!("✓ Entries 1-3 still present");
let stats = cache.stats();
println!("\nCache statistics after eviction:");
println!(" - Total entries: {}", cache.len());
println!(" - Evictions: {}", stats.evictions);
}
fn scenario_cache_configs() {
println!("Testing different cache configurations:");
println!();
let small_config = CacheConfig::small();
println!("Small cache:");
println!(" - Max entries: {}", small_config.max_entries);
println!(
" - TTL: {:?}",
small_config.default_ttl.map(|d| format!("{:?}", d))
);
let large_config = CacheConfig::large();
println!("\nLarge cache:");
println!(" - Max entries: {}", large_config.max_entries);
println!(
" - TTL: {:?}",
large_config.default_ttl.map(|d| format!("{:?}", d))
);
let no_ttl_config = CacheConfig::no_ttl();
println!("\nNo-TTL cache:");
println!(" - Max entries: {}", no_ttl_config.max_entries);
println!(" - TTL: None (entries never expire by time)");
println!("\n✓ All configurations created successfully");
}
fn scenario_symbol_table_cache() {
let mut table = SymbolTable::new();
table
.add_domain(DomainInfo::new("Person", 100))
.expect("unwrap");
table
.add_domain(DomainInfo::new("Organization", 50))
.expect("unwrap");
table
.add_domain(DomainInfo::new("Event", 200))
.expect("unwrap");
table
.add_predicate(PredicateInfo::new(
"knows",
vec!["Person".to_string(), "Person".to_string()],
))
.expect("unwrap");
table
.add_predicate(PredicateInfo::new(
"works_at",
vec!["Person".to_string(), "Organization".to_string()],
))
.expect("unwrap");
table
.add_predicate(PredicateInfo::new(
"attends",
vec!["Person".to_string(), "Event".to_string()],
))
.expect("unwrap");
table
.add_predicate(PredicateInfo::new("age", vec!["Person".to_string()]))
.expect("unwrap");
println!("Created symbol table:");
println!(" - 3 domains: Person, Organization, Event");
println!(" - 4 predicates: knows, works_at, attends, age");
println!();
let mut cache = SymbolTableCache::new();
println!("Performing cached queries:");
println!();
let binary_predicates = cache.get_predicates_by_arity(&table, 2);
println!("Query 1: Binary predicates (arity=2)");
println!(" Found {} predicates", binary_predicates.len());
for pred in &binary_predicates {
println!(" - {}", pred.name);
}
let person_predicates = cache.get_predicates_by_domain(&table, "Person");
println!("\nQuery 2: Predicates using 'Person' domain");
println!(" Found {} predicates", person_predicates.len());
for pred in &person_predicates {
println!(" - {}", pred.name);
}
let domain_names = cache.get_domain_names(&table);
println!("\nQuery 3: All domain names");
println!(" Found {} domains: {:?}", domain_names.len(), domain_names);
let person_usage = cache.get_domain_usage_count(&table, "Person");
println!("\nQuery 4: Usage count for 'Person' domain");
println!(" Used in {} predicates", person_usage);
println!();
println!("Repeating queries (should hit cache):");
let _ = cache.get_predicates_by_arity(&table, 2);
let _ = cache.get_predicates_by_domain(&table, "Person");
let _ = cache.get_domain_names(&table);
let _ = cache.get_domain_usage_count(&table, "Person");
println!("✓ All queries completed");
let stats = cache.combined_stats();
println!("\nCache statistics:");
println!(" - Hits: {}", stats.hits);
println!(" - Misses: {}", stats.misses);
println!(" - Hit rate: {:.2}%", stats.hit_rate() * 100.0);
println!(" - Miss rate: {:.2}%", stats.miss_rate() * 100.0);
}
fn scenario_performance() {
use std::time::Instant;
println!("Measuring cache performance benefits:");
println!();
let mut table = SymbolTable::new();
for i in 0..100 {
table
.add_domain(DomainInfo::new(format!("Domain{}", i), 100))
.expect("unwrap");
}
for i in 0..500 {
let arity = (i % 4) + 1; let domains: Vec<String> = (0..arity)
.map(|j| format!("Domain{}", (i + j) % 100))
.collect();
table
.add_predicate(PredicateInfo::new(format!("pred{}", i), domains))
.expect("unwrap");
}
println!("Created large symbol table:");
println!(" - 100 domains");
println!(" - 500 predicates");
println!();
let start = Instant::now();
for _ in 0..100 {
let _ = table
.predicates
.values()
.filter(|p| p.arg_domains.len() == 2)
.map(|p| p.name.clone())
.collect::<Vec<_>>();
}
let no_cache_time = start.elapsed();
println!("Without cache (100 queries): {:?}", no_cache_time);
let mut cache = SymbolTableCache::with_config(CacheConfig::large());
let start = Instant::now();
for _ in 0..100 {
let _ = cache.get_predicates_by_arity(&table, 2);
}
let with_cache_time = start.elapsed();
println!("With cache (100 queries): {:?}", with_cache_time);
let speedup = no_cache_time.as_micros() as f64 / with_cache_time.as_micros() as f64;
println!("\n⚡ Speedup: {:.2}x faster with caching", speedup);
let stats = cache.combined_stats();
println!("\nFinal cache statistics:");
println!(" - Hit rate: {:.2}%", stats.hit_rate() * 100.0);
println!(" - Total hits: {}", stats.hits);
println!(" - Total misses: {}", stats.misses);
}