use feoxdb::{FeoxStore, Result};
use std::fs;
use std::thread;
use std::time::Duration;
fn main() -> Result<()> {
println!("{}", "β".repeat(72));
println!("FeOxDB Persistence Test");
println!("{}", "β".repeat(72));
#[cfg(unix)]
let db_path = "/tmp/feox_persistence_test.db".to_string();
#[cfg(windows)]
let db_path = {
let temp_dir = std::env::temp_dir();
temp_dir
.join("feox_persistence_test.db")
.to_string_lossy()
.to_string()
};
#[cfg(not(any(unix, windows)))]
let db_path = "feox_persistence_test.db".to_string();
let _ = fs::remove_file(&db_path);
println!("\n>>> Phase 1: Writing test data");
println!("Database path: {}", &db_path);
let test_data = vec![
("tiny", b"x".to_vec()),
("small", b"Hello, World!".to_vec()),
("medium", vec![b'M'; 100]),
("large", vec![b'L'; 1024]),
("xlarge", vec![b'X'; 10 * 1024]), (
"json",
br#"{"name":"Test","values":[1,2,3,4,5],"nested":{"deep":true}}"#.to_vec(),
),
("binary", (0u8..=255).collect::<Vec<u8>>()),
("unicode", "Hello δΈη π Hej ΠΌΠΈΡ".as_bytes().to_vec()),
("counter", 42i64.to_le_bytes().to_vec()),
];
{
let store = FeoxStore::new(Some(db_path.clone()))?;
for (key, value) in &test_data {
let key_bytes = format!("test:{}", key).into_bytes();
store.insert(&key_bytes, value)?;
println!(" β Inserted '{}' ({} bytes)", key, value.len());
}
for i in 0..10 {
let key = format!("range:{:03}", i);
let value = format!("value_{}", i);
store.insert(key.as_bytes(), value.as_bytes())?;
}
println!(" β Inserted 10 range query test keys");
println!("\n>>> Flushing to disk...");
store.flush().unwrap();
thread::sleep(Duration::from_secs(5));
println!(">>> Flush complete");
println!("\n>>> Verifying immediate read...");
for (key, expected_value) in &test_data {
let key_bytes = format!("test:{}", key).into_bytes();
let value = store.get(&key_bytes)?;
assert_eq!(value, *expected_value, "Value mismatch for key: {}", key);
println!(" β Verified '{}'", key);
}
println!("\n>>> Store statistics before closing:");
println!(" Total keys: {}", store.len());
println!(" Memory usage: {} bytes", store.memory_usage());
}
println!("\n{}", "β".repeat(72));
println!(">>> Phase 2: Reloading database and adding more data");
println!("{}", "β".repeat(72));
let phase2_data = vec![
("phase2_small", b"Phase 2 small data".to_vec()),
("phase2_medium", vec![b'P'; 500]),
("phase2_large", vec![b'2'; 5 * 1024]), (
"phase2_json",
br#"{"phase":2,"test":"persistence","array":[10,20,30]}"#.to_vec(),
),
("phase2_binary", (0u8..100).collect::<Vec<u8>>()),
];
{
println!("\n>>> Opening existing database...");
let store = FeoxStore::new(Some(db_path.clone()))?;
thread::sleep(Duration::from_secs(1));
println!(">>> Verifying all Phase 1 data persisted correctly...");
for (key, expected_value) in &test_data {
let key_bytes = format!("test:{}", key).into_bytes();
if !store.contains_key(&key_bytes) {
panic!("Key '{}' not found after reload!", key);
}
let value = store.get(&key_bytes)?;
assert_eq!(
value, *expected_value,
"Value mismatch for key '{}' after reload",
key
);
let display_value = if *key == "xlarge" {
format!("{} bytes of 'X'", value.len())
} else if *key == "binary" {
format!("{} bytes (binary)", value.len())
} else if value.len() > 50 {
format!("{} bytes", value.len())
} else {
String::from_utf8_lossy(&value).to_string()
};
println!(" β {} = {}", key, display_value);
}
println!("\n>>> Verifying range query data...");
let range_results = store.range_query(b"range:000", b"range:009", 100)?;
assert_eq!(range_results.len(), 10, "Expected 10 range results");
println!(" β Range query returned {} results", range_results.len());
for (i, (key, value)) in range_results.iter().enumerate() {
let expected_key = format!("range:{:03}", i);
let expected_value = format!("value_{}", i);
assert_eq!(key, expected_key.as_bytes());
assert_eq!(value, expected_value.as_bytes());
}
println!(" β All range values verified");
println!("\n>>> Testing atomic counter persistence...");
let counter_value = store.atomic_increment(b"test:counter", 8)?;
assert_eq!(counter_value, 50); println!(" β Counter incremented from 42 to 50");
println!("\n>>> Adding new data in Phase 2...");
for (key, value) in &phase2_data {
let key_bytes = format!("test:{}", key).into_bytes();
store.insert(&key_bytes, value)?;
println!(
" β Inserted {} ({} bytes)",
key.replace("phase2_", ""),
value.len()
);
}
for i in 10..20 {
let key = format!("range:{:03}", i);
let value = format!("phase2_value_{}", i);
store.insert(key.as_bytes(), value.as_bytes())?;
}
println!(" β Inserted 10 more range query test keys");
println!("\n>>> Flushing Phase 2 data to disk...");
store.flush().unwrap();
thread::sleep(Duration::from_secs(5));
println!(">>> Flush complete");
println!("\n>>> Verifying Phase 2 data immediate read...");
for (key, expected_value) in &phase2_data {
let key_bytes = format!("test:{}", key).into_bytes();
let value = store.get(&key_bytes)?;
assert_eq!(
value, *expected_value,
"Phase 2 value mismatch for key: {}",
key
);
println!(" β Verified {}", key.replace("phase2_", ""));
}
println!("\n>>> Store statistics after Phase 2:");
println!(" Total keys: {}", store.len());
println!(" Memory usage: {} bytes", store.memory_usage());
}
println!("\n{}", "β".repeat(72));
println!(">>> Phase 3: Final verification and cleanup");
println!("{}", "β".repeat(72));
{
println!("\n>>> Opening database for final verification...");
let store = FeoxStore::new(Some(db_path.clone()))?;
thread::sleep(Duration::from_secs(1));
println!(">>> Verifying ALL data from Phase 1 and Phase 2...");
println!("\n>>> Phase 1 data:");
for (key, expected_value) in &test_data {
let key_bytes = format!("test:{}", key).into_bytes();
if !store.contains_key(&key_bytes) {
panic!("Phase 1 key '{}' not found in Phase 3!", key);
}
let value = store.get(&key_bytes)?;
if *key == "counter" {
let counter = i64::from_le_bytes(value.clone().try_into().unwrap());
assert_eq!(counter, 50, "Counter should be 50 after Phase 2 increment");
} else {
assert_eq!(
value, *expected_value,
"Phase 1 value mismatch in Phase 3 for key: {}",
key
);
}
let display = if *key == "xlarge" || value.len() > 30 {
format!("{} bytes", value.len())
} else {
String::from_utf8_lossy(&value).to_string()
};
println!(" β {} = {}", key, display);
}
println!("\n>>> Phase 2 data:");
for (key, expected_value) in &phase2_data {
let key_bytes = format!("test:{}", key).into_bytes();
if !store.contains_key(&key_bytes) {
panic!("Phase 2 key '{}' not found in Phase 3!", key);
}
let value = store.get(&key_bytes)?;
assert_eq!(
value, *expected_value,
"Phase 2 value mismatch in Phase 3 for key: {}",
key
);
let display = if value.len() > 30 {
format!("{} bytes", value.len())
} else {
String::from_utf8_lossy(&value).to_string()
};
println!(" β {} = {}", key.replace("phase2_", ""), display);
}
println!("\n>>> Verifying all range query data (20 keys total)...");
let range_results = store.range_query(b"range:000", b"range:999", 100)?;
assert_eq!(
range_results.len(),
20,
"Expected 20 range results (10 from each phase)"
);
println!(" β Range query returned {} results", range_results.len());
let counter_value = store.get(b"test:counter")?;
let counter = i64::from_le_bytes(counter_value.try_into().unwrap());
assert_eq!(counter, 50);
println!(" β Counter value is {}", counter);
println!("\n>>> Final store statistics:");
println!(" Total keys: {}", store.len());
println!(" Memory usage: {} bytes", store.memory_usage());
println!("\n>>> Testing deletion and free space management...");
store.delete(b"test:tiny")?;
store.delete(b"test:phase2_small")?;
println!(" β Deleted keys from both phases");
assert!(!store.contains_key(b"test:tiny"));
assert!(!store.contains_key(b"test:phase2_small"));
assert!(store.contains_key(b"test:medium"));
assert!(store.contains_key(b"test:phase2_medium"));
println!(" β Deletion verified");
}
println!("\n>>> Cleaning up test database...");
fs::remove_file(db_path).map_err(feoxdb::FeoxError::IoError)?;
println!(" β Test database removed");
println!("\n{}", "β".repeat(72));
println!("β
All persistence tests passed!");
println!("{}", "β".repeat(72));
Ok(())
}