fmt, fixes and additions
This commit is contained in:
@@ -15,9 +15,9 @@ rand = "0.8.5"
|
||||
criterion = "0.5.1"
|
||||
tempfile = "3.8.0"
|
||||
|
||||
[[bench]]
|
||||
name = "ourdb_benchmarks"
|
||||
harness = false
|
||||
# [[bench]]
|
||||
# name = "ourdb_benchmarks"
|
||||
# harness = false
|
||||
|
||||
[[example]]
|
||||
name = "basic_usage"
|
||||
|
@@ -6,18 +6,18 @@ fn main() -> Result<(), ourdb::Error> {
|
||||
// Create a temporary directory for the database
|
||||
let db_path = std::env::temp_dir().join("ourdb_advanced_example");
|
||||
std::fs::create_dir_all(&db_path)?;
|
||||
|
||||
|
||||
println!("Creating database at: {}", db_path.display());
|
||||
|
||||
|
||||
// Demonstrate key-value mode (non-incremental)
|
||||
key_value_mode_example(&db_path)?;
|
||||
|
||||
|
||||
// Demonstrate incremental mode
|
||||
incremental_mode_example(&db_path)?;
|
||||
|
||||
|
||||
// Demonstrate performance benchmarking
|
||||
performance_benchmark(&db_path)?;
|
||||
|
||||
|
||||
// Clean up (optional)
|
||||
if std::env::var("KEEP_DB").is_err() {
|
||||
std::fs::remove_dir_all(&db_path)?;
|
||||
@@ -25,16 +25,16 @@ fn main() -> Result<(), ourdb::Error> {
|
||||
} else {
|
||||
println!("Database kept at: {}", db_path.display());
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn key_value_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
|
||||
println!("\n=== Key-Value Mode Example ===");
|
||||
|
||||
|
||||
let db_path = base_path.join("key_value");
|
||||
std::fs::create_dir_all(&db_path)?;
|
||||
|
||||
|
||||
// Create a new database with key-value mode (non-incremental)
|
||||
let config = OurDBConfig {
|
||||
path: db_path,
|
||||
@@ -43,52 +43,62 @@ fn key_value_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
|
||||
keysize: Some(2), // Small key size for demonstration
|
||||
reset: None, // Don't reset existing database
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config)?;
|
||||
|
||||
|
||||
// In key-value mode, we must provide IDs explicitly
|
||||
let custom_ids = [100, 200, 300, 400, 500];
|
||||
|
||||
|
||||
// Store data with custom IDs
|
||||
for (i, &id) in custom_ids.iter().enumerate() {
|
||||
let data = format!("Record with custom ID {}", id);
|
||||
db.set(OurDBSetArgs { id: Some(id), data: data.as_bytes() })?;
|
||||
println!("Stored record {} with custom ID: {}", i+1, id);
|
||||
db.set(OurDBSetArgs {
|
||||
id: Some(id),
|
||||
data: data.as_bytes(),
|
||||
})?;
|
||||
println!("Stored record {} with custom ID: {}", i + 1, id);
|
||||
}
|
||||
|
||||
|
||||
// Retrieve data by custom IDs
|
||||
for &id in &custom_ids {
|
||||
let retrieved = db.get(id)?;
|
||||
println!("Retrieved ID {}: {}", id, String::from_utf8_lossy(&retrieved));
|
||||
println!(
|
||||
"Retrieved ID {}: {}",
|
||||
id,
|
||||
String::from_utf8_lossy(&retrieved)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Update and track history
|
||||
let id_to_update = custom_ids[2]; // ID 300
|
||||
for i in 1..=3 {
|
||||
let updated_data = format!("Updated record {} (version {})", id_to_update, i);
|
||||
db.set(OurDBSetArgs { id: Some(id_to_update), data: updated_data.as_bytes() })?;
|
||||
db.set(OurDBSetArgs {
|
||||
id: Some(id_to_update),
|
||||
data: updated_data.as_bytes(),
|
||||
})?;
|
||||
println!("Updated ID {} (version {})", id_to_update, i);
|
||||
}
|
||||
|
||||
|
||||
// Get history for the updated record
|
||||
let history = db.get_history(id_to_update, 5)?;
|
||||
println!("History for ID {} (most recent first):", id_to_update);
|
||||
for (i, entry) in history.iter().enumerate() {
|
||||
println!(" Version {}: {}", i, String::from_utf8_lossy(entry));
|
||||
}
|
||||
|
||||
|
||||
db.close()?;
|
||||
println!("Key-value mode example completed");
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
|
||||
println!("\n=== Incremental Mode Example ===");
|
||||
|
||||
|
||||
let db_path = base_path.join("incremental");
|
||||
std::fs::create_dir_all(&db_path)?;
|
||||
|
||||
|
||||
// Create a new database with incremental mode
|
||||
let config = OurDBConfig {
|
||||
path: db_path,
|
||||
@@ -97,42 +107,49 @@ fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
|
||||
keysize: Some(3), // 3-byte keys
|
||||
reset: None, // Don't reset existing database
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config)?;
|
||||
|
||||
|
||||
// In incremental mode, IDs are auto-generated
|
||||
let mut assigned_ids = Vec::new();
|
||||
|
||||
|
||||
// Store multiple records and collect assigned IDs
|
||||
for i in 1..=5 {
|
||||
let data = format!("Auto-increment record {}", i);
|
||||
let id = db.set(OurDBSetArgs { id: None, data: data.as_bytes() })?;
|
||||
let id = db.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: data.as_bytes(),
|
||||
})?;
|
||||
assigned_ids.push(id);
|
||||
println!("Stored record {} with auto-assigned ID: {}", i, id);
|
||||
}
|
||||
|
||||
|
||||
// Check next ID
|
||||
let next_id = db.get_next_id()?;
|
||||
println!("Next ID to be assigned: {}", next_id);
|
||||
|
||||
|
||||
// Retrieve all records
|
||||
for &id in &assigned_ids {
|
||||
let retrieved = db.get(id)?;
|
||||
println!("Retrieved ID {}: {}", id, String::from_utf8_lossy(&retrieved));
|
||||
println!(
|
||||
"Retrieved ID {}: {}",
|
||||
id,
|
||||
String::from_utf8_lossy(&retrieved)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
db.close()?;
|
||||
println!("Incremental mode example completed");
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> {
|
||||
println!("\n=== Performance Benchmark ===");
|
||||
|
||||
|
||||
let db_path = base_path.join("benchmark");
|
||||
std::fs::create_dir_all(&db_path)?;
|
||||
|
||||
|
||||
// Create a new database
|
||||
let config = OurDBConfig {
|
||||
path: db_path,
|
||||
@@ -141,62 +158,74 @@ fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> {
|
||||
keysize: Some(4), // 4-byte keys
|
||||
reset: None, // Don't reset existing database
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config)?;
|
||||
|
||||
|
||||
// Number of operations for the benchmark
|
||||
let num_operations = 1000;
|
||||
let data_size = 100; // bytes per record
|
||||
|
||||
|
||||
// Prepare test data
|
||||
let test_data = vec![b'A'; data_size];
|
||||
|
||||
|
||||
// Benchmark write operations
|
||||
println!("Benchmarking {} write operations...", num_operations);
|
||||
let start = Instant::now();
|
||||
|
||||
|
||||
let mut ids = Vec::with_capacity(num_operations);
|
||||
for _ in 0..num_operations {
|
||||
let id = db.set(OurDBSetArgs { id: None, data: &test_data })?;
|
||||
let id = db.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: &test_data,
|
||||
})?;
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
|
||||
let write_duration = start.elapsed();
|
||||
let writes_per_second = num_operations as f64 / write_duration.as_secs_f64();
|
||||
println!("Write performance: {:.2} ops/sec ({:.2} ms/op)",
|
||||
writes_per_second,
|
||||
write_duration.as_secs_f64() * 1000.0 / num_operations as f64);
|
||||
|
||||
println!(
|
||||
"Write performance: {:.2} ops/sec ({:.2} ms/op)",
|
||||
writes_per_second,
|
||||
write_duration.as_secs_f64() * 1000.0 / num_operations as f64
|
||||
);
|
||||
|
||||
// Benchmark read operations
|
||||
println!("Benchmarking {} read operations...", num_operations);
|
||||
let start = Instant::now();
|
||||
|
||||
|
||||
for &id in &ids {
|
||||
let _ = db.get(id)?;
|
||||
}
|
||||
|
||||
|
||||
let read_duration = start.elapsed();
|
||||
let reads_per_second = num_operations as f64 / read_duration.as_secs_f64();
|
||||
println!("Read performance: {:.2} ops/sec ({:.2} ms/op)",
|
||||
reads_per_second,
|
||||
read_duration.as_secs_f64() * 1000.0 / num_operations as f64);
|
||||
|
||||
println!(
|
||||
"Read performance: {:.2} ops/sec ({:.2} ms/op)",
|
||||
reads_per_second,
|
||||
read_duration.as_secs_f64() * 1000.0 / num_operations as f64
|
||||
);
|
||||
|
||||
// Benchmark update operations
|
||||
println!("Benchmarking {} update operations...", num_operations);
|
||||
let start = Instant::now();
|
||||
|
||||
|
||||
for &id in &ids {
|
||||
db.set(OurDBSetArgs { id: Some(id), data: &test_data })?;
|
||||
db.set(OurDBSetArgs {
|
||||
id: Some(id),
|
||||
data: &test_data,
|
||||
})?;
|
||||
}
|
||||
|
||||
|
||||
let update_duration = start.elapsed();
|
||||
let updates_per_second = num_operations as f64 / update_duration.as_secs_f64();
|
||||
println!("Update performance: {:.2} ops/sec ({:.2} ms/op)",
|
||||
updates_per_second,
|
||||
update_duration.as_secs_f64() * 1000.0 / num_operations as f64);
|
||||
|
||||
println!(
|
||||
"Update performance: {:.2} ops/sec ({:.2} ms/op)",
|
||||
updates_per_second,
|
||||
update_duration.as_secs_f64() * 1000.0 / num_operations as f64
|
||||
);
|
||||
|
||||
db.close()?;
|
||||
println!("Performance benchmark completed");
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -4,9 +4,9 @@ fn main() -> Result<(), ourdb::Error> {
|
||||
// Create a temporary directory for the database
|
||||
let db_path = std::env::temp_dir().join("ourdb_example");
|
||||
std::fs::create_dir_all(&db_path)?;
|
||||
|
||||
|
||||
println!("Creating database at: {}", db_path.display());
|
||||
|
||||
|
||||
// Create a new database with incremental mode enabled
|
||||
let config = OurDBConfig {
|
||||
path: db_path.clone(),
|
||||
@@ -15,51 +15,68 @@ fn main() -> Result<(), ourdb::Error> {
|
||||
keysize: None, // Use default (4 bytes)
|
||||
reset: None, // Don't reset existing database
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config)?;
|
||||
|
||||
|
||||
// Store some data with auto-generated IDs
|
||||
let data1 = b"First record";
|
||||
let id1 = db.set(OurDBSetArgs { id: None, data: data1 })?;
|
||||
let id1 = db.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: data1,
|
||||
})?;
|
||||
println!("Stored first record with ID: {}", id1);
|
||||
|
||||
|
||||
let data2 = b"Second record";
|
||||
let id2 = db.set(OurDBSetArgs { id: None, data: data2 })?;
|
||||
let id2 = db.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: data2,
|
||||
})?;
|
||||
println!("Stored second record with ID: {}", id2);
|
||||
|
||||
|
||||
// Retrieve and print the data
|
||||
let retrieved1 = db.get(id1)?;
|
||||
println!("Retrieved ID {}: {}", id1, String::from_utf8_lossy(&retrieved1));
|
||||
|
||||
println!(
|
||||
"Retrieved ID {}: {}",
|
||||
id1,
|
||||
String::from_utf8_lossy(&retrieved1)
|
||||
);
|
||||
|
||||
let retrieved2 = db.get(id2)?;
|
||||
println!("Retrieved ID {}: {}", id2, String::from_utf8_lossy(&retrieved2));
|
||||
|
||||
println!(
|
||||
"Retrieved ID {}: {}",
|
||||
id2,
|
||||
String::from_utf8_lossy(&retrieved2)
|
||||
);
|
||||
|
||||
// Update a record to demonstrate history tracking
|
||||
let updated_data = b"Updated first record";
|
||||
db.set(OurDBSetArgs { id: Some(id1), data: updated_data })?;
|
||||
db.set(OurDBSetArgs {
|
||||
id: Some(id1),
|
||||
data: updated_data,
|
||||
})?;
|
||||
println!("Updated record with ID: {}", id1);
|
||||
|
||||
|
||||
// Get history for the updated record
|
||||
let history = db.get_history(id1, 2)?;
|
||||
println!("History for ID {}:", id1);
|
||||
for (i, entry) in history.iter().enumerate() {
|
||||
println!(" Version {}: {}", i, String::from_utf8_lossy(entry));
|
||||
}
|
||||
|
||||
|
||||
// Delete a record
|
||||
db.delete(id2)?;
|
||||
println!("Deleted record with ID: {}", id2);
|
||||
|
||||
|
||||
// Verify deletion
|
||||
match db.get(id2) {
|
||||
Ok(_) => println!("Record still exists (unexpected)"),
|
||||
Err(e) => println!("Verified deletion: {}", e),
|
||||
}
|
||||
|
||||
|
||||
// Close the database
|
||||
db.close()?;
|
||||
println!("Database closed successfully");
|
||||
|
||||
|
||||
// Clean up (optional)
|
||||
if std::env::var("KEEP_DB").is_err() {
|
||||
std::fs::remove_dir_all(&db_path)?;
|
||||
@@ -67,6 +84,6 @@ fn main() -> Result<(), ourdb::Error> {
|
||||
} else {
|
||||
println!("Database kept at: {}", db_path.display());
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -4,12 +4,12 @@ use std::time::Instant;
|
||||
fn main() -> Result<(), ourdb::Error> {
|
||||
// Parse command-line arguments
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
|
||||
// Default values
|
||||
let mut incremental_mode = true;
|
||||
let mut keysize: u8 = 4;
|
||||
let mut num_operations = 10000;
|
||||
|
||||
|
||||
// Parse arguments
|
||||
for i in 1..args.len() {
|
||||
if args[i] == "--no-incremental" {
|
||||
@@ -20,13 +20,13 @@ fn main() -> Result<(), ourdb::Error> {
|
||||
num_operations = args[i + 1].parse().unwrap_or(10000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create a temporary directory for the database
|
||||
let db_path = std::env::temp_dir().join("ourdb_benchmark");
|
||||
std::fs::create_dir_all(&db_path)?;
|
||||
|
||||
|
||||
println!("Database path: {}", db_path.display());
|
||||
|
||||
|
||||
// Create a new database
|
||||
let config = OurDBConfig {
|
||||
path: db_path.clone(),
|
||||
@@ -35,73 +35,90 @@ fn main() -> Result<(), ourdb::Error> {
|
||||
keysize: Some(keysize),
|
||||
reset: Some(true), // Reset the database for benchmarking
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config)?;
|
||||
|
||||
|
||||
// Prepare test data (100 bytes per record)
|
||||
let test_data = vec![b'A'; 100];
|
||||
|
||||
|
||||
// Benchmark write operations
|
||||
println!("Benchmarking {} write operations (incremental: {}, keysize: {})...",
|
||||
num_operations, incremental_mode, keysize);
|
||||
|
||||
println!(
|
||||
"Benchmarking {} write operations (incremental: {}, keysize: {})...",
|
||||
num_operations, incremental_mode, keysize
|
||||
);
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
|
||||
let mut ids = Vec::with_capacity(num_operations);
|
||||
for _ in 0..num_operations {
|
||||
let id = if incremental_mode {
|
||||
db.set(OurDBSetArgs { id: None, data: &test_data })?
|
||||
db.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: &test_data,
|
||||
})?
|
||||
} else {
|
||||
// In non-incremental mode, we need to provide IDs
|
||||
let id = ids.len() as u32 + 1;
|
||||
db.set(OurDBSetArgs { id: Some(id), data: &test_data })?;
|
||||
db.set(OurDBSetArgs {
|
||||
id: Some(id),
|
||||
data: &test_data,
|
||||
})?;
|
||||
id
|
||||
};
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
|
||||
let write_duration = start.elapsed();
|
||||
let writes_per_second = num_operations as f64 / write_duration.as_secs_f64();
|
||||
|
||||
println!("Write performance: {:.2} ops/sec ({:.2} ms/op)",
|
||||
writes_per_second,
|
||||
write_duration.as_secs_f64() * 1000.0 / num_operations as f64);
|
||||
|
||||
|
||||
println!(
|
||||
"Write performance: {:.2} ops/sec ({:.2} ms/op)",
|
||||
writes_per_second,
|
||||
write_duration.as_secs_f64() * 1000.0 / num_operations as f64
|
||||
);
|
||||
|
||||
// Benchmark read operations
|
||||
println!("Benchmarking {} read operations...", num_operations);
|
||||
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
|
||||
for &id in &ids {
|
||||
let _ = db.get(id)?;
|
||||
}
|
||||
|
||||
|
||||
let read_duration = start.elapsed();
|
||||
let reads_per_second = num_operations as f64 / read_duration.as_secs_f64();
|
||||
|
||||
println!("Read performance: {:.2} ops/sec ({:.2} ms/op)",
|
||||
reads_per_second,
|
||||
read_duration.as_secs_f64() * 1000.0 / num_operations as f64);
|
||||
|
||||
|
||||
println!(
|
||||
"Read performance: {:.2} ops/sec ({:.2} ms/op)",
|
||||
reads_per_second,
|
||||
read_duration.as_secs_f64() * 1000.0 / num_operations as f64
|
||||
);
|
||||
|
||||
// Benchmark update operations
|
||||
println!("Benchmarking {} update operations...", num_operations);
|
||||
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
|
||||
for &id in &ids {
|
||||
db.set(OurDBSetArgs { id: Some(id), data: &test_data })?;
|
||||
db.set(OurDBSetArgs {
|
||||
id: Some(id),
|
||||
data: &test_data,
|
||||
})?;
|
||||
}
|
||||
|
||||
|
||||
let update_duration = start.elapsed();
|
||||
let updates_per_second = num_operations as f64 / update_duration.as_secs_f64();
|
||||
|
||||
println!("Update performance: {:.2} ops/sec ({:.2} ms/op)",
|
||||
updates_per_second,
|
||||
update_duration.as_secs_f64() * 1000.0 / num_operations as f64);
|
||||
|
||||
|
||||
println!(
|
||||
"Update performance: {:.2} ops/sec ({:.2} ms/op)",
|
||||
updates_per_second,
|
||||
update_duration.as_secs_f64() * 1000.0 / num_operations as f64
|
||||
);
|
||||
|
||||
// Clean up
|
||||
db.close()?;
|
||||
std::fs::remove_dir_all(&db_path)?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -13,9 +13,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.as_secs();
|
||||
let db_path = temp_dir().join(format!("ourdb_example_{}", timestamp));
|
||||
std::fs::create_dir_all(&db_path)?;
|
||||
|
||||
|
||||
println!("Creating database at: {}", db_path.display());
|
||||
|
||||
|
||||
// Create a new OurDB instance
|
||||
let config = OurDBConfig {
|
||||
path: db_path.clone(),
|
||||
@@ -24,51 +24,60 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
keysize: None,
|
||||
reset: Some(false),
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config)?;
|
||||
println!("Database created successfully");
|
||||
|
||||
|
||||
// Store some data
|
||||
let test_data = b"Hello, OurDB!";
|
||||
let id = db.set(OurDBSetArgs { id: None, data: test_data })?;
|
||||
let id = db.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: test_data,
|
||||
})?;
|
||||
println!("\nStored data with ID: {}", id);
|
||||
|
||||
|
||||
// Retrieve the data
|
||||
let retrieved = db.get(id)?;
|
||||
println!("Retrieved data: {}", String::from_utf8_lossy(&retrieved));
|
||||
|
||||
|
||||
// Update the data
|
||||
let updated_data = b"Updated data in OurDB!";
|
||||
db.set(OurDBSetArgs { id: Some(id), data: updated_data })?;
|
||||
db.set(OurDBSetArgs {
|
||||
id: Some(id),
|
||||
data: updated_data,
|
||||
})?;
|
||||
println!("\nUpdated data with ID: {}", id);
|
||||
|
||||
|
||||
// Retrieve the updated data
|
||||
let retrieved = db.get(id)?;
|
||||
println!("Retrieved updated data: {}", String::from_utf8_lossy(&retrieved));
|
||||
|
||||
println!(
|
||||
"Retrieved updated data: {}",
|
||||
String::from_utf8_lossy(&retrieved)
|
||||
);
|
||||
|
||||
// Get history
|
||||
let history = db.get_history(id, 2)?;
|
||||
println!("\nHistory for ID {}:", id);
|
||||
for (i, data) in history.iter().enumerate() {
|
||||
println!(" Version {}: {}", i + 1, String::from_utf8_lossy(data));
|
||||
}
|
||||
|
||||
|
||||
// Delete the data
|
||||
db.delete(id)?;
|
||||
println!("\nDeleted data with ID: {}", id);
|
||||
|
||||
|
||||
// Try to retrieve the deleted data (should fail)
|
||||
match db.get(id) {
|
||||
Ok(_) => println!("Data still exists (unexpected)"),
|
||||
Err(e) => println!("Verified deletion: {}", e),
|
||||
}
|
||||
|
||||
|
||||
println!("\nExample completed successfully!");
|
||||
|
||||
|
||||
// Clean up
|
||||
db.close()?;
|
||||
std::fs::remove_dir_all(&db_path)?;
|
||||
println!("Cleaned up database directory");
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
|
||||
use crc32fast::Hasher;
|
||||
|
||||
use crate::error::Error;
|
||||
@@ -27,11 +26,8 @@ impl OurDB {
|
||||
}
|
||||
|
||||
// Open the file fresh
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&path)?;
|
||||
|
||||
let file = OpenOptions::new().read(true).write(true).open(&path)?;
|
||||
|
||||
self.file = Some(file);
|
||||
self.file_nr = file_nr;
|
||||
|
||||
@@ -42,10 +38,10 @@ impl OurDB {
|
||||
pub(crate) fn create_new_db_file(&mut self, file_nr: u16) -> Result<(), Error> {
|
||||
let new_file_path = self.path.join(format!("{}.db", file_nr));
|
||||
let mut file = File::create(&new_file_path)?;
|
||||
|
||||
|
||||
// Write a single byte to make all positions start from 1
|
||||
file.write_all(&[0u8])?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -54,17 +50,17 @@ impl OurDB {
|
||||
// For keysize 2, 3, or 4, we can only use file_nr 0
|
||||
if self.lookup.keysize() <= 4 {
|
||||
let path = self.path.join("0.db");
|
||||
|
||||
|
||||
if !path.exists() {
|
||||
self.create_new_db_file(0)?;
|
||||
}
|
||||
|
||||
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
|
||||
// For keysize 6, we can use multiple files
|
||||
let path = self.path.join(format!("{}.db", self.last_used_file_nr));
|
||||
|
||||
|
||||
if !path.exists() {
|
||||
self.create_new_db_file(self.last_used_file_nr)?;
|
||||
return Ok(self.last_used_file_nr);
|
||||
@@ -80,30 +76,36 @@ impl OurDB {
|
||||
}
|
||||
|
||||
/// Stores data at the specified ID with history tracking
|
||||
pub(crate) fn set_(&mut self, id: u32, old_location: Location, data: &[u8]) -> Result<(), Error> {
|
||||
pub(crate) fn set_(
|
||||
&mut self,
|
||||
id: u32,
|
||||
old_location: Location,
|
||||
data: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
// Validate data size - maximum is u16::MAX (65535 bytes or ~64KB)
|
||||
if data.len() > u16::MAX as usize {
|
||||
return Err(Error::InvalidOperation(
|
||||
format!("Data size exceeds maximum allowed size of {} bytes", u16::MAX)
|
||||
));
|
||||
return Err(Error::InvalidOperation(format!(
|
||||
"Data size exceeds maximum allowed size of {} bytes",
|
||||
u16::MAX
|
||||
)));
|
||||
}
|
||||
|
||||
// Get file number to use
|
||||
let file_nr = self.get_file_nr()?;
|
||||
|
||||
|
||||
// Select the file
|
||||
self.db_file_select(file_nr)?;
|
||||
|
||||
// Get current file position for lookup
|
||||
let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?;
|
||||
let file = self
|
||||
.file
|
||||
.as_mut()
|
||||
.ok_or_else(|| Error::Other("No file open".to_string()))?;
|
||||
file.seek(SeekFrom::End(0))?;
|
||||
let position = file.stream_position()? as u32;
|
||||
|
||||
// Create new location
|
||||
let new_location = Location {
|
||||
file_nr,
|
||||
position,
|
||||
};
|
||||
let new_location = Location { file_nr, position };
|
||||
|
||||
// Calculate CRC of data
|
||||
let crc = calculate_crc(data);
|
||||
@@ -144,13 +146,19 @@ impl OurDB {
|
||||
/// Retrieves data at the specified location
|
||||
pub(crate) fn get_(&mut self, location: Location) -> Result<Vec<u8>, Error> {
|
||||
if location.position == 0 {
|
||||
return Err(Error::NotFound(format!("Record not found, location: {:?}", location)));
|
||||
return Err(Error::NotFound(format!(
|
||||
"Record not found, location: {:?}",
|
||||
location
|
||||
)));
|
||||
}
|
||||
|
||||
// Select the file
|
||||
self.db_file_select(location.file_nr)?;
|
||||
|
||||
let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?;
|
||||
let file = self
|
||||
.file
|
||||
.as_mut()
|
||||
.ok_or_else(|| Error::Other("No file open".to_string()))?;
|
||||
|
||||
// Read header
|
||||
file.seek(SeekFrom::Start(location.position as u64))?;
|
||||
@@ -161,10 +169,10 @@ impl OurDB {
|
||||
let size = u16::from(header[0]) | (u16::from(header[1]) << 8);
|
||||
|
||||
// Parse CRC (4 bytes)
|
||||
let stored_crc = u32::from(header[2])
|
||||
| (u32::from(header[3]) << 8)
|
||||
| (u32::from(header[4]) << 16)
|
||||
| (u32::from(header[5]) << 24);
|
||||
let stored_crc = u32::from(header[2])
|
||||
| (u32::from(header[3]) << 8)
|
||||
| (u32::from(header[4]) << 16)
|
||||
| (u32::from(header[5]) << 24);
|
||||
|
||||
// Read data
|
||||
let mut data = vec![0u8; size as usize];
|
||||
@@ -173,7 +181,9 @@ impl OurDB {
|
||||
// Verify CRC
|
||||
let calculated_crc = calculate_crc(&data);
|
||||
if calculated_crc != stored_crc {
|
||||
return Err(Error::DataCorruption("CRC mismatch: data corruption detected".to_string()));
|
||||
return Err(Error::DataCorruption(
|
||||
"CRC mismatch: data corruption detected".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
@@ -188,7 +198,10 @@ impl OurDB {
|
||||
// Select the file
|
||||
self.db_file_select(location.file_nr)?;
|
||||
|
||||
let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?;
|
||||
let file = self
|
||||
.file
|
||||
.as_mut()
|
||||
.ok_or_else(|| Error::Other("No file open".to_string()))?;
|
||||
|
||||
// Skip size and CRC (6 bytes)
|
||||
file.seek(SeekFrom::Start(location.position as u64 + 6))?;
|
||||
@@ -210,7 +223,10 @@ impl OurDB {
|
||||
// Select the file
|
||||
self.db_file_select(location.file_nr)?;
|
||||
|
||||
let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?;
|
||||
let file = self
|
||||
.file
|
||||
.as_mut()
|
||||
.ok_or_else(|| Error::Other("No file open".to_string()))?;
|
||||
|
||||
// Read size first
|
||||
file.seek(SeekFrom::Start(location.position as u64))?;
|
||||
@@ -240,7 +256,7 @@ impl OurDB {
|
||||
for entry in fs::read_dir(&self.path)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
|
||||
if path.is_file() && path.extension().map_or(false, |ext| ext == "db") {
|
||||
if let Some(stem) = path.file_stem() {
|
||||
if let Ok(file_nr) = stem.to_string_lossy().parse::<u16>() {
|
||||
@@ -254,42 +270,42 @@ impl OurDB {
|
||||
for file_nr in file_numbers {
|
||||
let src_path = self.path.join(format!("{}.db", file_nr));
|
||||
let temp_file_path = temp_path.join(format!("{}.db", file_nr));
|
||||
|
||||
|
||||
// Create new file
|
||||
let mut temp_file = File::create(&temp_file_path)?;
|
||||
temp_file.write_all(&[0u8])?; // Initialize with a byte
|
||||
|
||||
|
||||
// Open source file
|
||||
let mut src_file = File::open(&src_path)?;
|
||||
|
||||
|
||||
// Read and process records
|
||||
let mut buffer = vec![0u8; 1024]; // Read in chunks
|
||||
let mut _position = 0;
|
||||
|
||||
|
||||
while let Ok(bytes_read) = src_file.read(&mut buffer) {
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Process the chunk
|
||||
// This is a simplified version - in a real implementation,
|
||||
// you would need to handle records that span chunk boundaries
|
||||
|
||||
|
||||
_position += bytes_read;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Implement proper record copying and position updating
|
||||
// This would involve:
|
||||
// 1. Reading each record from the source file
|
||||
// 2. If not deleted (all zeros), copy to temp file
|
||||
// 3. Update lookup table with new positions
|
||||
}
|
||||
|
||||
|
||||
// TODO: Replace original files with temp files
|
||||
|
||||
|
||||
// Clean up
|
||||
fs::remove_dir_all(&temp_path)?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -304,7 +320,7 @@ fn calculate_crc(data: &[u8]) -> u32 {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
||||
use crate::{OurDB, OurDBConfig, OurDBSetArgs};
|
||||
use std::env::temp_dir;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
@@ -320,26 +336,30 @@ mod tests {
|
||||
#[test]
|
||||
fn test_backend_operations() {
|
||||
let temp_dir = get_temp_dir();
|
||||
|
||||
|
||||
let config = OurDBConfig {
|
||||
path: temp_dir.clone(),
|
||||
incremental_mode: false,
|
||||
file_size: None,
|
||||
keysize: None,
|
||||
reset: None, // Don't reset existing database
|
||||
reset: None, // Don't reset existing database
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config).unwrap();
|
||||
|
||||
|
||||
// Test set and get
|
||||
let test_data = b"Test data for backend operations";
|
||||
let id = 1;
|
||||
|
||||
db.set(OurDBSetArgs { id: Some(id), data: test_data }).unwrap();
|
||||
|
||||
|
||||
db.set(OurDBSetArgs {
|
||||
id: Some(id),
|
||||
data: test_data,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let retrieved = db.get(id).unwrap();
|
||||
assert_eq!(retrieved, test_data);
|
||||
|
||||
|
||||
// Clean up
|
||||
db.destroy().unwrap();
|
||||
}
|
||||
|
@@ -6,23 +6,23 @@ pub enum Error {
|
||||
/// IO errors from file operations
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
|
||||
/// Data corruption errors
|
||||
#[error("Data corruption: {0}")]
|
||||
DataCorruption(String),
|
||||
|
||||
|
||||
/// Invalid operation errors
|
||||
#[error("Invalid operation: {0}")]
|
||||
InvalidOperation(String),
|
||||
|
||||
|
||||
/// Lookup table errors
|
||||
#[error("Lookup error: {0}")]
|
||||
LookupError(String),
|
||||
|
||||
|
||||
/// Record not found errors
|
||||
#[error("Record not found: {0}")]
|
||||
NotFound(String),
|
||||
|
||||
|
||||
/// Other errors
|
||||
#[error("Error: {0}")]
|
||||
Other(String),
|
||||
|
@@ -1,7 +1,7 @@
|
||||
mod backend;
|
||||
mod error;
|
||||
mod location;
|
||||
mod lookup;
|
||||
mod backend;
|
||||
|
||||
pub use error::Error;
|
||||
pub use location::Location;
|
||||
@@ -62,7 +62,7 @@ impl OurDB {
|
||||
if config.reset.unwrap_or(false) && config.path.exists() {
|
||||
std::fs::remove_dir_all(&config.path)?;
|
||||
}
|
||||
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
std::fs::create_dir_all(&config.path)?;
|
||||
|
||||
@@ -96,11 +96,11 @@ impl OurDB {
|
||||
}
|
||||
|
||||
/// Sets a value in the database
|
||||
///
|
||||
///
|
||||
/// In incremental mode:
|
||||
/// - If ID is provided, it updates an existing record
|
||||
/// - If ID is not provided, it creates a new record with auto-generated ID
|
||||
///
|
||||
///
|
||||
/// In key-value mode:
|
||||
/// - ID must be provided
|
||||
pub fn set(&mut self, args: OurDBSetArgs) -> Result<u32, Error> {
|
||||
@@ -110,7 +110,7 @@ impl OurDB {
|
||||
let location = self.lookup.get(id)?;
|
||||
if location.position == 0 {
|
||||
return Err(Error::InvalidOperation(
|
||||
"Cannot set ID for insertions when incremental mode is enabled".to_string()
|
||||
"Cannot set ID for insertions when incremental mode is enabled".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -124,10 +124,12 @@ impl OurDB {
|
||||
}
|
||||
} else {
|
||||
// Using key-value mode
|
||||
let id = args.id.ok_or_else(|| Error::InvalidOperation(
|
||||
"ID must be provided when incremental is disabled".to_string()
|
||||
))?;
|
||||
|
||||
let id = args.id.ok_or_else(|| {
|
||||
Error::InvalidOperation(
|
||||
"ID must be provided when incremental is disabled".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let location = self.lookup.get(id)?;
|
||||
self.set_(id, location, args.data)?;
|
||||
Ok(id)
|
||||
@@ -141,7 +143,7 @@ impl OurDB {
|
||||
}
|
||||
|
||||
/// Retrieves a list of previous values for the specified key
|
||||
///
|
||||
///
|
||||
/// The depth parameter controls how many historical values to retrieve (maximum)
|
||||
pub fn get_history(&mut self, id: u32, depth: u8) -> Result<Vec<Vec<u8>>, Error> {
|
||||
let mut result = Vec::new();
|
||||
@@ -179,7 +181,9 @@ impl OurDB {
|
||||
/// Returns the next ID which will be used when storing in incremental mode
|
||||
pub fn get_next_id(&mut self) -> Result<u32, Error> {
|
||||
if !self.incremental_mode {
|
||||
return Err(Error::InvalidOperation("Incremental mode is not enabled".to_string()));
|
||||
return Err(Error::InvalidOperation(
|
||||
"Incremental mode is not enabled".to_string(),
|
||||
));
|
||||
}
|
||||
self.lookup.get_next_id()
|
||||
}
|
||||
@@ -212,7 +216,8 @@ impl OurDB {
|
||||
}
|
||||
|
||||
fn save(&mut self) -> Result<(), Error> {
|
||||
self.lookup.export_sparse(&self.lookup_dump_path().to_string_lossy())?;
|
||||
self.lookup
|
||||
.export_sparse(&self.lookup_dump_path().to_string_lossy())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -238,41 +243,50 @@ mod tests {
|
||||
#[test]
|
||||
fn test_basic_operations() {
|
||||
let temp_dir = get_temp_dir();
|
||||
|
||||
|
||||
let config = OurDBConfig {
|
||||
path: temp_dir.clone(),
|
||||
incremental_mode: true,
|
||||
file_size: None,
|
||||
keysize: None,
|
||||
reset: None, // Don't reset existing database
|
||||
reset: None, // Don't reset existing database
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config).unwrap();
|
||||
|
||||
|
||||
// Test set and get
|
||||
let test_data = b"Hello, OurDB!";
|
||||
let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap();
|
||||
|
||||
let id = db
|
||||
.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: test_data,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let retrieved = db.get(id).unwrap();
|
||||
assert_eq!(retrieved, test_data);
|
||||
|
||||
|
||||
// Test update
|
||||
let updated_data = b"Updated data";
|
||||
db.set(OurDBSetArgs { id: Some(id), data: updated_data }).unwrap();
|
||||
|
||||
db.set(OurDBSetArgs {
|
||||
id: Some(id),
|
||||
data: updated_data,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let retrieved = db.get(id).unwrap();
|
||||
assert_eq!(retrieved, updated_data);
|
||||
|
||||
|
||||
// Test history
|
||||
let history = db.get_history(id, 2).unwrap();
|
||||
assert_eq!(history.len(), 2);
|
||||
assert_eq!(history[0], updated_data);
|
||||
assert_eq!(history[1], test_data);
|
||||
|
||||
|
||||
// Test delete
|
||||
db.delete(id).unwrap();
|
||||
assert!(db.get(id).is_err());
|
||||
|
||||
|
||||
// Clean up
|
||||
db.destroy().unwrap();
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
use crate::error::Error;
|
||||
|
||||
/// Location represents a physical position in a database file
|
||||
///
|
||||
///
|
||||
/// It consists of a file number and a position within that file.
|
||||
/// This allows OurDB to span multiple files for large datasets.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
@@ -14,7 +14,7 @@ pub struct Location {
|
||||
|
||||
impl Location {
|
||||
/// Creates a new Location from bytes based on keysize
|
||||
///
|
||||
///
|
||||
/// - keysize = 2: Only position (2 bytes), file_nr = 0
|
||||
/// - keysize = 3: Only position (3 bytes), file_nr = 0
|
||||
/// - keysize = 4: Only position (4 bytes), file_nr = 0
|
||||
@@ -22,13 +22,18 @@ impl Location {
|
||||
pub fn from_bytes(bytes: &[u8], keysize: u8) -> Result<Self, Error> {
|
||||
// Validate keysize
|
||||
if ![2, 3, 4, 6].contains(&keysize) {
|
||||
return Err(Error::InvalidOperation(format!("Invalid keysize: {}", keysize)));
|
||||
return Err(Error::InvalidOperation(format!(
|
||||
"Invalid keysize: {}",
|
||||
keysize
|
||||
)));
|
||||
}
|
||||
|
||||
// Create padded bytes
|
||||
let mut padded = vec![0u8; keysize as usize];
|
||||
if bytes.len() > keysize as usize {
|
||||
return Err(Error::InvalidOperation("Input bytes exceed keysize".to_string()));
|
||||
return Err(Error::InvalidOperation(
|
||||
"Input bytes exceed keysize".to_string(),
|
||||
));
|
||||
}
|
||||
let start_idx = keysize as usize - bytes.len();
|
||||
|
||||
@@ -49,34 +54,39 @@ impl Location {
|
||||
// Verify limits
|
||||
if location.position > 0xFFFF {
|
||||
return Err(Error::InvalidOperation(
|
||||
"Position exceeds max value for keysize=2 (max 65535)".to_string()
|
||||
"Position exceeds max value for keysize=2 (max 65535)".to_string(),
|
||||
));
|
||||
}
|
||||
},
|
||||
}
|
||||
3 => {
|
||||
// Only position, 3 bytes big endian
|
||||
location.position = u32::from(padded[0]) << 16 | u32::from(padded[1]) << 8 | u32::from(padded[2]);
|
||||
location.position =
|
||||
u32::from(padded[0]) << 16 | u32::from(padded[1]) << 8 | u32::from(padded[2]);
|
||||
location.file_nr = 0;
|
||||
|
||||
// Verify limits
|
||||
if location.position > 0xFFFFFF {
|
||||
return Err(Error::InvalidOperation(
|
||||
"Position exceeds max value for keysize=3 (max 16777215)".to_string()
|
||||
"Position exceeds max value for keysize=3 (max 16777215)".to_string(),
|
||||
));
|
||||
}
|
||||
},
|
||||
}
|
||||
4 => {
|
||||
// Only position, 4 bytes big endian
|
||||
location.position = u32::from(padded[0]) << 24 | u32::from(padded[1]) << 16
|
||||
| u32::from(padded[2]) << 8 | u32::from(padded[3]);
|
||||
location.position = u32::from(padded[0]) << 24
|
||||
| u32::from(padded[1]) << 16
|
||||
| u32::from(padded[2]) << 8
|
||||
| u32::from(padded[3]);
|
||||
location.file_nr = 0;
|
||||
},
|
||||
}
|
||||
6 => {
|
||||
// 2 bytes file_nr + 4 bytes position, all big endian
|
||||
location.file_nr = u16::from(padded[0]) << 8 | u16::from(padded[1]);
|
||||
location.position = u32::from(padded[2]) << 24 | u32::from(padded[3]) << 16
|
||||
| u32::from(padded[4]) << 8 | u32::from(padded[5]);
|
||||
},
|
||||
location.position = u32::from(padded[2]) << 24
|
||||
| u32::from(padded[3]) << 16
|
||||
| u32::from(padded[4]) << 8
|
||||
| u32::from(padded[5]);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
@@ -84,26 +94,26 @@ impl Location {
|
||||
}
|
||||
|
||||
/// Converts the location to bytes (always 6 bytes)
|
||||
///
|
||||
///
|
||||
/// Format: [file_nr (2 bytes)][position (4 bytes)]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(6);
|
||||
|
||||
|
||||
// Put file_nr first (2 bytes)
|
||||
bytes.push((self.file_nr >> 8) as u8);
|
||||
bytes.push(self.file_nr as u8);
|
||||
|
||||
|
||||
// Put position next (4 bytes)
|
||||
bytes.push((self.position >> 24) as u8);
|
||||
bytes.push((self.position >> 16) as u8);
|
||||
bytes.push((self.position >> 8) as u8);
|
||||
bytes.push(self.position as u8);
|
||||
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Converts the location to a u64 value
|
||||
///
|
||||
///
|
||||
/// The file_nr is stored in the most significant bits
|
||||
pub fn to_u64(&self) -> u64 {
|
||||
(u64::from(self.file_nr) << 32) | u64::from(self.position)
|
||||
|
@@ -16,7 +16,7 @@ pub struct LookupConfig {
|
||||
/// - 2: For databases with < 65,536 records (single file)
|
||||
/// - 3: For databases with < 16,777,216 records (single file)
|
||||
/// - 4: For databases with < 4,294,967,296 records (single file)
|
||||
/// - 6: For large databases requiring multiple files
|
||||
/// - 6: For large databases requiring multiple files
|
||||
pub keysize: u8,
|
||||
/// Path for disk-based lookup
|
||||
pub lookuppath: String,
|
||||
@@ -46,7 +46,10 @@ impl LookupTable {
|
||||
pub fn new(config: LookupConfig) -> Result<Self, Error> {
|
||||
// Verify keysize is valid
|
||||
if ![2, 3, 4, 6].contains(&config.keysize) {
|
||||
return Err(Error::InvalidOperation(format!("Invalid keysize: {}", config.keysize)));
|
||||
return Err(Error::InvalidOperation(format!(
|
||||
"Invalid keysize: {}",
|
||||
config.keysize
|
||||
)));
|
||||
}
|
||||
|
||||
let incremental = if config.incremental_mode {
|
||||
@@ -90,7 +93,7 @@ impl LookupTable {
|
||||
if !self.lookuppath.is_empty() {
|
||||
// Disk-based lookup
|
||||
let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME);
|
||||
|
||||
|
||||
// Check file size first
|
||||
let file_size = fs::metadata(&data_path)?.len();
|
||||
let start_pos = id as u64 * entry_size as u64;
|
||||
@@ -98,7 +101,9 @@ impl LookupTable {
|
||||
if start_pos + entry_size as u64 > file_size {
|
||||
return Err(Error::LookupError(format!(
|
||||
"Invalid read for get in lut: {}: {} would exceed file size {}",
|
||||
self.lookuppath, start_pos + entry_size as u64, file_size
|
||||
self.lookuppath,
|
||||
start_pos + entry_size as u64,
|
||||
file_size
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -108,14 +113,14 @@ impl LookupTable {
|
||||
|
||||
let mut data = vec![0u8; entry_size];
|
||||
let bytes_read = file.read(&mut data)?;
|
||||
|
||||
|
||||
if bytes_read < entry_size {
|
||||
return Err(Error::LookupError(format!(
|
||||
"Incomplete read: expected {} bytes but got {}",
|
||||
entry_size, bytes_read
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
return Location::from_bytes(&data, self.keysize);
|
||||
}
|
||||
|
||||
@@ -126,7 +131,7 @@ impl LookupTable {
|
||||
|
||||
let start = (id * self.keysize as u32) as usize;
|
||||
let end = start + entry_size;
|
||||
|
||||
|
||||
Location::from_bytes(&self.data[start..end], self.keysize)
|
||||
}
|
||||
|
||||
@@ -142,7 +147,7 @@ impl LookupTable {
|
||||
|
||||
if id > incremental {
|
||||
return Err(Error::InvalidOperation(
|
||||
"Cannot set ID for insertions when incremental mode is enabled".to_string()
|
||||
"Cannot set ID for insertions when incremental mode is enabled".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -151,53 +156,64 @@ impl LookupTable {
|
||||
let location_bytes = match self.keysize {
|
||||
2 => {
|
||||
if location.file_nr != 0 {
|
||||
return Err(Error::InvalidOperation("file_nr must be 0 for keysize=2".to_string()));
|
||||
return Err(Error::InvalidOperation(
|
||||
"file_nr must be 0 for keysize=2".to_string(),
|
||||
));
|
||||
}
|
||||
if location.position > 0xFFFF {
|
||||
return Err(Error::InvalidOperation(
|
||||
"position exceeds max value for keysize=2 (max 65535)".to_string()
|
||||
"position exceeds max value for keysize=2 (max 65535)".to_string(),
|
||||
));
|
||||
}
|
||||
vec![(location.position >> 8) as u8, location.position as u8]
|
||||
},
|
||||
}
|
||||
3 => {
|
||||
if location.file_nr != 0 {
|
||||
return Err(Error::InvalidOperation("file_nr must be 0 for keysize=3".to_string()));
|
||||
return Err(Error::InvalidOperation(
|
||||
"file_nr must be 0 for keysize=3".to_string(),
|
||||
));
|
||||
}
|
||||
if location.position > 0xFFFFFF {
|
||||
return Err(Error::InvalidOperation(
|
||||
"position exceeds max value for keysize=3 (max 16777215)".to_string()
|
||||
"position exceeds max value for keysize=3 (max 16777215)".to_string(),
|
||||
));
|
||||
}
|
||||
vec![
|
||||
(location.position >> 16) as u8,
|
||||
(location.position >> 8) as u8,
|
||||
location.position as u8
|
||||
location.position as u8,
|
||||
]
|
||||
},
|
||||
}
|
||||
4 => {
|
||||
if location.file_nr != 0 {
|
||||
return Err(Error::InvalidOperation("file_nr must be 0 for keysize=4".to_string()));
|
||||
return Err(Error::InvalidOperation(
|
||||
"file_nr must be 0 for keysize=4".to_string(),
|
||||
));
|
||||
}
|
||||
vec![
|
||||
(location.position >> 24) as u8,
|
||||
(location.position >> 16) as u8,
|
||||
(location.position >> 8) as u8,
|
||||
location.position as u8
|
||||
location.position as u8,
|
||||
]
|
||||
},
|
||||
}
|
||||
6 => {
|
||||
// Full location with file_nr and position
|
||||
location.to_bytes()
|
||||
},
|
||||
_ => return Err(Error::InvalidOperation(format!("Invalid keysize: {}", self.keysize))),
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::InvalidOperation(format!(
|
||||
"Invalid keysize: {}",
|
||||
self.keysize
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
if !self.lookuppath.is_empty() {
|
||||
// Disk-based lookup
|
||||
let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME);
|
||||
let mut file = OpenOptions::new().write(true).open(data_path)?;
|
||||
|
||||
|
||||
let start_pos = id as u64 * entry_size as u64;
|
||||
file.seek(SeekFrom::Start(start_pos))?;
|
||||
file.write_all(&location_bytes)?;
|
||||
@@ -207,7 +223,7 @@ impl LookupTable {
|
||||
if start + entry_size > self.data.len() {
|
||||
return Err(Error::LookupError("Index out of bounds".to_string()));
|
||||
}
|
||||
|
||||
|
||||
for (i, &byte) in location_bytes.iter().enumerate() {
|
||||
self.data[start + i] = byte;
|
||||
}
|
||||
@@ -224,9 +240,9 @@ impl LookupTable {
|
||||
|
||||
/// Gets the next available ID in incremental mode
|
||||
pub fn get_next_id(&self) -> Result<u32, Error> {
|
||||
let incremental = self.incremental.ok_or_else(||
|
||||
let incremental = self.incremental.ok_or_else(|| {
|
||||
Error::InvalidOperation("Lookup table not in incremental mode".to_string())
|
||||
)?;
|
||||
})?;
|
||||
|
||||
let table_size = if !self.lookuppath.is_empty() {
|
||||
let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME);
|
||||
@@ -244,9 +260,9 @@ impl LookupTable {
|
||||
|
||||
/// Increments the index in incremental mode
|
||||
pub fn increment_index(&mut self) -> Result<(), Error> {
|
||||
let mut incremental = self.incremental.ok_or_else(||
|
||||
let mut incremental = self.incremental.ok_or_else(|| {
|
||||
Error::InvalidOperation("Lookup table not in incremental mode".to_string())
|
||||
)?;
|
||||
})?;
|
||||
|
||||
incremental += 1;
|
||||
self.incremental = Some(incremental);
|
||||
@@ -299,10 +315,10 @@ impl LookupTable {
|
||||
|
||||
for id in 0..max_entries {
|
||||
file.seek(SeekFrom::Start(id * entry_size as u64))?;
|
||||
|
||||
|
||||
let mut buffer = vec![0u8; entry_size];
|
||||
let bytes_read = file.read(&mut buffer)?;
|
||||
|
||||
|
||||
if bytes_read < entry_size {
|
||||
break;
|
||||
}
|
||||
@@ -317,11 +333,11 @@ impl LookupTable {
|
||||
} else {
|
||||
// For memory-based lookup
|
||||
let max_entries = self.data.len() / entry_size;
|
||||
|
||||
|
||||
for id in 0..max_entries {
|
||||
let start = id * entry_size;
|
||||
let entry = &self.data[start..start + entry_size];
|
||||
|
||||
|
||||
// Check if entry is non-zero
|
||||
if entry.iter().any(|&b| b != 0) {
|
||||
// Write ID (4 bytes) + entry
|
||||
@@ -344,7 +360,7 @@ impl LookupTable {
|
||||
|
||||
if data.len() % record_size != 0 {
|
||||
return Err(Error::DataCorruption(
|
||||
"Invalid sparse data format: size mismatch".to_string()
|
||||
"Invalid sparse data format: size mismatch".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -359,10 +375,10 @@ impl LookupTable {
|
||||
|
||||
// Extract entry
|
||||
let entry = &data[chunk_start + 4..chunk_start + record_size];
|
||||
|
||||
|
||||
// Create location from entry
|
||||
let location = Location::from_bytes(entry, self.keysize)?;
|
||||
|
||||
|
||||
// Set the entry
|
||||
self.set(id, location)?;
|
||||
}
|
||||
@@ -380,13 +396,13 @@ impl LookupTable {
|
||||
let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME);
|
||||
let mut file = File::open(&data_path)?;
|
||||
let file_size = fs::metadata(&data_path)?.len();
|
||||
|
||||
|
||||
let mut buffer = vec![0u8; entry_size];
|
||||
let mut pos = 0u32;
|
||||
|
||||
while (pos as u64 * entry_size as u64) < file_size {
|
||||
file.seek(SeekFrom::Start(pos as u64 * entry_size as u64))?;
|
||||
|
||||
|
||||
let bytes_read = file.read(&mut buffer)?;
|
||||
if bytes_read == 0 || bytes_read < entry_size {
|
||||
break;
|
||||
@@ -396,7 +412,7 @@ impl LookupTable {
|
||||
if location.position != 0 || location.file_nr != 0 {
|
||||
last_id = pos;
|
||||
}
|
||||
|
||||
|
||||
pos += 1;
|
||||
}
|
||||
} else {
|
||||
@@ -422,7 +438,7 @@ fn get_incremental_info(config: &LookupConfig) -> Result<u32, Error> {
|
||||
|
||||
if !config.lookuppath.is_empty() {
|
||||
let inc_path = Path::new(&config.lookuppath).join(INCREMENTAL_FILE_NAME);
|
||||
|
||||
|
||||
if !inc_path.exists() {
|
||||
// Create a separate file for storing the incremental value
|
||||
fs::write(&inc_path, "1")?;
|
||||
@@ -437,7 +453,7 @@ fn get_incremental_info(config: &LookupConfig) -> Result<u32, Error> {
|
||||
1
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Ok(incremental)
|
||||
} else {
|
||||
// For memory-based lookup, start with 1
|
||||
@@ -447,9 +463,9 @@ fn get_incremental_info(config: &LookupConfig) -> Result<u32, Error> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
use super::*;
|
||||
use std::env::temp_dir;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
fn get_temp_dir() -> PathBuf {
|
||||
@@ -468,25 +484,25 @@ mod tests {
|
||||
lookuppath: String::new(),
|
||||
incremental_mode: true,
|
||||
};
|
||||
|
||||
|
||||
let mut lookup = LookupTable::new(config).unwrap();
|
||||
|
||||
|
||||
// Test set and get
|
||||
let location = Location {
|
||||
file_nr: 0,
|
||||
position: 12345,
|
||||
};
|
||||
|
||||
|
||||
lookup.set(1, location).unwrap();
|
||||
let retrieved = lookup.get(1).unwrap();
|
||||
|
||||
|
||||
assert_eq!(retrieved.file_nr, location.file_nr);
|
||||
assert_eq!(retrieved.position, location.position);
|
||||
|
||||
|
||||
// Test incremental mode
|
||||
let next_id = lookup.get_next_id().unwrap();
|
||||
assert_eq!(next_id, 2);
|
||||
|
||||
|
||||
lookup.increment_index().unwrap();
|
||||
let next_id = lookup.get_next_id().unwrap();
|
||||
assert_eq!(next_id, 3);
|
||||
@@ -496,28 +512,28 @@ mod tests {
|
||||
fn test_disk_lookup() {
|
||||
let temp_dir = get_temp_dir();
|
||||
fs::create_dir_all(&temp_dir).unwrap();
|
||||
|
||||
|
||||
let config = LookupConfig {
|
||||
size: 1000,
|
||||
keysize: 4,
|
||||
lookuppath: temp_dir.to_string_lossy().to_string(),
|
||||
incremental_mode: true,
|
||||
};
|
||||
|
||||
|
||||
let mut lookup = LookupTable::new(config).unwrap();
|
||||
|
||||
|
||||
// Test set and get
|
||||
let location = Location {
|
||||
file_nr: 0,
|
||||
position: 12345,
|
||||
};
|
||||
|
||||
|
||||
lookup.set(1, location).unwrap();
|
||||
let retrieved = lookup.get(1).unwrap();
|
||||
|
||||
|
||||
assert_eq!(retrieved.file_nr, location.file_nr);
|
||||
assert_eq!(retrieved.position, location.position);
|
||||
|
||||
|
||||
// Clean up
|
||||
fs::remove_dir_all(temp_dir).unwrap();
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
use ourdb::{OurDB, OurDBConfig, OurDBSetArgs};
|
||||
use rand;
|
||||
use std::env::temp_dir;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use rand;
|
||||
|
||||
// Helper function to create a unique temporary directory for tests
|
||||
fn get_temp_dir() -> PathBuf {
|
||||
@@ -13,56 +13,64 @@ fn get_temp_dir() -> PathBuf {
|
||||
.as_nanos();
|
||||
let random_part = rand::random::<u32>();
|
||||
let dir = temp_dir().join(format!("ourdb_test_{}_{}", timestamp, random_part));
|
||||
|
||||
|
||||
// Ensure the directory exists and is empty
|
||||
if dir.exists() {
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
}
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
|
||||
|
||||
dir
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_operations() {
|
||||
let temp_dir = get_temp_dir();
|
||||
|
||||
|
||||
// Create a new database with incremental mode
|
||||
let config = OurDBConfig {
|
||||
path: temp_dir.clone(),
|
||||
incremental_mode: true,
|
||||
file_size: None,
|
||||
keysize: None,
|
||||
reset: None
|
||||
reset: None,
|
||||
};
|
||||
|
||||
|
||||
|
||||
let mut db = OurDB::new(config).unwrap();
|
||||
|
||||
|
||||
// Test set and get
|
||||
let test_data = b"Hello, OurDB!";
|
||||
let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap();
|
||||
|
||||
let id = db
|
||||
.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: test_data,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let retrieved = db.get(id).unwrap();
|
||||
assert_eq!(retrieved, test_data);
|
||||
|
||||
|
||||
// Test update
|
||||
let updated_data = b"Updated data";
|
||||
db.set(OurDBSetArgs { id: Some(id), data: updated_data }).unwrap();
|
||||
|
||||
db.set(OurDBSetArgs {
|
||||
id: Some(id),
|
||||
data: updated_data,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let retrieved = db.get(id).unwrap();
|
||||
assert_eq!(retrieved, updated_data);
|
||||
|
||||
|
||||
// Test history
|
||||
let history = db.get_history(id, 2).unwrap();
|
||||
assert_eq!(history.len(), 2);
|
||||
assert_eq!(history[0], updated_data);
|
||||
assert_eq!(history[1], test_data);
|
||||
|
||||
|
||||
// Test delete
|
||||
db.delete(id).unwrap();
|
||||
assert!(db.get(id).is_err());
|
||||
|
||||
|
||||
// Clean up
|
||||
db.destroy().unwrap();
|
||||
}
|
||||
@@ -70,30 +78,33 @@ fn test_basic_operations() {
|
||||
#[test]
|
||||
fn test_key_value_mode() {
|
||||
let temp_dir = get_temp_dir();
|
||||
|
||||
|
||||
|
||||
// Create a new database with key-value mode
|
||||
let config = OurDBConfig {
|
||||
path: temp_dir.clone(),
|
||||
incremental_mode: false,
|
||||
file_size: None,
|
||||
keysize: None,
|
||||
reset: None
|
||||
reset: None,
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config).unwrap();
|
||||
|
||||
|
||||
// Test set with explicit ID
|
||||
let test_data = b"Key-value data";
|
||||
let id = 42;
|
||||
db.set(OurDBSetArgs { id: Some(id), data: test_data }).unwrap();
|
||||
|
||||
db.set(OurDBSetArgs {
|
||||
id: Some(id),
|
||||
data: test_data,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let retrieved = db.get(id).unwrap();
|
||||
assert_eq!(retrieved, test_data);
|
||||
|
||||
|
||||
// Verify next_id fails in key-value mode
|
||||
assert!(db.get_next_id().is_err());
|
||||
|
||||
|
||||
// Clean up
|
||||
db.destroy().unwrap();
|
||||
}
|
||||
@@ -101,33 +112,42 @@ fn test_key_value_mode() {
|
||||
#[test]
|
||||
fn test_incremental_mode() {
|
||||
let temp_dir = get_temp_dir();
|
||||
|
||||
|
||||
// Create a new database with incremental mode
|
||||
let config = OurDBConfig {
|
||||
path: temp_dir.clone(),
|
||||
incremental_mode: true,
|
||||
file_size: None,
|
||||
keysize: None,
|
||||
reset: None
|
||||
reset: None,
|
||||
};
|
||||
|
||||
|
||||
|
||||
let mut db = OurDB::new(config).unwrap();
|
||||
|
||||
|
||||
// Test auto-increment IDs
|
||||
let data1 = b"First record";
|
||||
let id1 = db.set(OurDBSetArgs { id: None, data: data1 }).unwrap();
|
||||
|
||||
let id1 = db
|
||||
.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: data1,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let data2 = b"Second record";
|
||||
let id2 = db.set(OurDBSetArgs { id: None, data: data2 }).unwrap();
|
||||
|
||||
let id2 = db
|
||||
.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: data2,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// IDs should be sequential
|
||||
assert_eq!(id2, id1 + 1);
|
||||
|
||||
|
||||
// Verify get_next_id works
|
||||
let next_id = db.get_next_id().unwrap();
|
||||
assert_eq!(next_id, id2 + 1);
|
||||
|
||||
|
||||
// Clean up
|
||||
db.destroy().unwrap();
|
||||
}
|
||||
@@ -135,8 +155,7 @@ fn test_incremental_mode() {
|
||||
#[test]
|
||||
fn test_persistence() {
|
||||
let temp_dir = get_temp_dir();
|
||||
|
||||
|
||||
|
||||
// Create data in a new database
|
||||
{
|
||||
let config = OurDBConfig {
|
||||
@@ -144,21 +163,26 @@ fn test_persistence() {
|
||||
incremental_mode: true,
|
||||
file_size: None,
|
||||
keysize: None,
|
||||
reset: None
|
||||
reset: None,
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config).unwrap();
|
||||
|
||||
|
||||
let test_data = b"Persistent data";
|
||||
let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap();
|
||||
|
||||
let id = db
|
||||
.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: test_data,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Explicitly close the database
|
||||
db.close().unwrap();
|
||||
|
||||
|
||||
// ID should be 1 in a new database
|
||||
assert_eq!(id, 1);
|
||||
}
|
||||
|
||||
|
||||
// Reopen the database and verify data persists
|
||||
{
|
||||
let config = OurDBConfig {
|
||||
@@ -166,19 +190,19 @@ fn test_persistence() {
|
||||
incremental_mode: true,
|
||||
file_size: None,
|
||||
keysize: None,
|
||||
reset: None
|
||||
reset: None,
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config).unwrap();
|
||||
|
||||
|
||||
// Verify data is still there
|
||||
let retrieved = db.get(1).unwrap();
|
||||
assert_eq!(retrieved, b"Persistent data");
|
||||
|
||||
|
||||
// Verify incremental counter persisted
|
||||
let next_id = db.get_next_id().unwrap();
|
||||
assert_eq!(next_id, 2);
|
||||
|
||||
|
||||
// Clean up
|
||||
db.destroy().unwrap();
|
||||
}
|
||||
@@ -188,28 +212,33 @@ fn test_persistence() {
|
||||
fn test_different_keysizes() {
|
||||
for keysize in [2, 3, 4, 6].iter() {
|
||||
let temp_dir = get_temp_dir();
|
||||
|
||||
|
||||
// Ensure the directory exists
|
||||
std::fs::create_dir_all(&temp_dir).unwrap();
|
||||
|
||||
|
||||
// Create a new database with specified keysize
|
||||
let config = OurDBConfig {
|
||||
path: temp_dir.clone(),
|
||||
incremental_mode: true,
|
||||
file_size: None,
|
||||
keysize: Some(*keysize),
|
||||
reset: None
|
||||
reset: None,
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config).unwrap();
|
||||
|
||||
|
||||
// Test basic operations
|
||||
let test_data = b"Keysize test data";
|
||||
let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap();
|
||||
|
||||
let id = db
|
||||
.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: test_data,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let retrieved = db.get(id).unwrap();
|
||||
assert_eq!(retrieved, test_data);
|
||||
|
||||
|
||||
// Clean up
|
||||
db.destroy().unwrap();
|
||||
}
|
||||
@@ -218,28 +247,33 @@ fn test_different_keysizes() {
|
||||
#[test]
|
||||
fn test_large_data() {
|
||||
let temp_dir = get_temp_dir();
|
||||
|
||||
|
||||
// Create a new database
|
||||
let config = OurDBConfig {
|
||||
path: temp_dir.clone(),
|
||||
incremental_mode: true,
|
||||
file_size: None,
|
||||
keysize: None,
|
||||
reset: None
|
||||
reset: None,
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config).unwrap();
|
||||
|
||||
|
||||
// Create a large data set (60KB - within the 64KB limit)
|
||||
let large_data = vec![b'X'; 60 * 1024];
|
||||
|
||||
|
||||
// Store and retrieve large data
|
||||
let id = db.set(OurDBSetArgs { id: None, data: &large_data }).unwrap();
|
||||
let id = db
|
||||
.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: &large_data,
|
||||
})
|
||||
.unwrap();
|
||||
let retrieved = db.get(id).unwrap();
|
||||
|
||||
|
||||
assert_eq!(retrieved.len(), large_data.len());
|
||||
assert_eq!(retrieved, large_data);
|
||||
|
||||
|
||||
// Clean up
|
||||
db.destroy().unwrap();
|
||||
}
|
||||
@@ -247,27 +281,33 @@ fn test_large_data() {
|
||||
#[test]
|
||||
fn test_exceed_size_limit() {
|
||||
let temp_dir = get_temp_dir();
|
||||
|
||||
|
||||
// Create a new database
|
||||
let config = OurDBConfig {
|
||||
path: temp_dir.clone(),
|
||||
incremental_mode: true,
|
||||
file_size: None,
|
||||
keysize: None,
|
||||
reset: None
|
||||
reset: None,
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config).unwrap();
|
||||
|
||||
|
||||
// Create data larger than the 64KB limit (70KB)
|
||||
let oversized_data = vec![b'X'; 70 * 1024];
|
||||
|
||||
|
||||
// Attempt to store data that exceeds the size limit
|
||||
let result = db.set(OurDBSetArgs { id: None, data: &oversized_data });
|
||||
|
||||
let result = db.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: &oversized_data,
|
||||
});
|
||||
|
||||
// Verify that an error is returned
|
||||
assert!(result.is_err(), "Expected an error when storing data larger than 64KB");
|
||||
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Expected an error when storing data larger than 64KB"
|
||||
);
|
||||
|
||||
// Clean up
|
||||
db.destroy().unwrap();
|
||||
}
|
||||
@@ -275,46 +315,55 @@ fn test_exceed_size_limit() {
|
||||
#[test]
|
||||
fn test_multiple_files() {
|
||||
let temp_dir = get_temp_dir();
|
||||
|
||||
|
||||
|
||||
// Create a new database with small file size to force multiple files
|
||||
let config = OurDBConfig {
|
||||
path: temp_dir.clone(),
|
||||
incremental_mode: true,
|
||||
file_size: Some(1024), // Very small file size (1KB)
|
||||
keysize: Some(6), // 6-byte keysize for multiple files
|
||||
reset: None
|
||||
reset: None,
|
||||
};
|
||||
|
||||
|
||||
let mut db = OurDB::new(config).unwrap();
|
||||
|
||||
|
||||
// Store enough data to span multiple files
|
||||
let data_size = 500; // bytes per record
|
||||
let test_data = vec![b'A'; data_size];
|
||||
|
||||
|
||||
let mut ids = Vec::new();
|
||||
for _ in 0..10 {
|
||||
let id = db.set(OurDBSetArgs { id: None, data: &test_data }).unwrap();
|
||||
let id = db
|
||||
.set(OurDBSetArgs {
|
||||
id: None,
|
||||
data: &test_data,
|
||||
})
|
||||
.unwrap();
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
|
||||
// Verify all data can be retrieved
|
||||
for &id in &ids {
|
||||
let retrieved = db.get(id).unwrap();
|
||||
assert_eq!(retrieved.len(), data_size);
|
||||
}
|
||||
|
||||
|
||||
// Verify multiple files were created
|
||||
let files = fs::read_dir(&temp_dir).unwrap()
|
||||
let files = fs::read_dir(&temp_dir)
|
||||
.unwrap()
|
||||
.filter_map(Result::ok)
|
||||
.filter(|entry| {
|
||||
let path = entry.path();
|
||||
path.is_file() && path.extension().map_or(false, |ext| ext == "db")
|
||||
})
|
||||
.count();
|
||||
|
||||
assert!(files > 1, "Expected multiple database files, found {}", files);
|
||||
|
||||
|
||||
assert!(
|
||||
files > 1,
|
||||
"Expected multiple database files, found {}",
|
||||
files
|
||||
);
|
||||
|
||||
// Clean up
|
||||
db.destroy().unwrap();
|
||||
}
|
||||
|
Reference in New Issue
Block a user