#![cfg(not(target_arch = "wasm32"))]
use bytes::Bytes;
use std::time::Duration;
use surrealmx::{AolMode, Database, DatabaseOptions, FsyncMode, PersistenceOptions, SnapshotMode};
use tempfile::TempDir;
#[test]
fn test_aol_synchronous_basic() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::SynchronousOnCommit)
.with_snapshot_mode(SnapshotMode::Never)
.with_fsync_mode(FsyncMode::EveryAppend);
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
{
let mut tx = db.transaction(true);
tx.set("key1", "value1").unwrap();
tx.set("key2", "value2").unwrap();
tx.commit().unwrap();
}
{
let mut tx = db.transaction(true);
tx.set("key3", "value3").unwrap();
tx.del("key1").unwrap(); tx.commit().unwrap();
}
{
let mut tx = db.transaction(false);
assert_eq!(tx.get("key1").unwrap(), None); assert_eq!(tx.get("key2").unwrap(), Some(Bytes::from("value2")));
assert_eq!(tx.get("key3").unwrap(), Some(Bytes::from("value3")));
tx.cancel().unwrap();
}
let aol_path = temp_path.join("aol.bin");
let snapshot_path = temp_path.join("snapshot.bin");
assert!(aol_path.exists(), "AOL file should exist");
assert!(!snapshot_path.exists(), "Snapshot file should not exist in AOL-only mode");
std::thread::sleep(Duration::from_millis(250));
let aol_metadata = std::fs::metadata(&aol_path).unwrap();
assert!(aol_metadata.len() > 0, "AOL file should not be empty");
}
#[test]
fn test_aol_asynchronous_basic() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::AsynchronousAfterCommit)
.with_snapshot_mode(SnapshotMode::Never)
.with_fsync_mode(FsyncMode::Never);
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
{
let mut tx = db.transaction(true);
tx.set("key1", "value1").unwrap();
tx.set("key2", "value2").unwrap();
tx.commit().unwrap();
}
{
let mut tx = db.transaction(true);
tx.set("key3", "value3").unwrap();
tx.del("key1").unwrap(); tx.commit().unwrap();
}
{
let mut tx = db.transaction(false);
assert_eq!(tx.get("key1").unwrap(), None); assert_eq!(tx.get("key2").unwrap(), Some(Bytes::from("value2")));
assert_eq!(tx.get("key3").unwrap(), Some(Bytes::from("value3")));
tx.cancel().unwrap();
}
let aol_path = temp_path.join("aol.bin");
let snapshot_path = temp_path.join("snapshot.bin");
assert!(aol_path.exists(), "AOL file should exist");
assert!(!snapshot_path.exists(), "Snapshot file should not exist in AOL-only mode");
std::thread::sleep(Duration::from_millis(250));
let aol_metadata = std::fs::metadata(&aol_path).unwrap();
assert!(aol_metadata.len() > 0, "AOL file should not be empty");
}
#[test]
fn test_aol_recovery() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::SynchronousOnCommit)
.with_snapshot_mode(SnapshotMode::Never)
.with_fsync_mode(FsyncMode::EveryAppend);
{
let db = Database::new_with_persistence(db_opts.clone(), persistence_opts.clone()).unwrap();
let mut tx = db.transaction(true);
tx.set("recover_key1".to_string(), "recover_value1".to_string()).unwrap();
tx.set("recover_key2".to_string(), "recover_value2".to_string()).unwrap();
tx.commit().unwrap();
let mut tx = db.transaction(true);
tx.set("recover_key1".to_string(), "updated_value1".to_string()).unwrap();
tx.set("recover_key3".to_string(), "recover_value3".to_string()).unwrap();
tx.commit().unwrap();
let mut tx = db.transaction(true);
tx.del("recover_key2".to_string()).unwrap();
tx.commit().unwrap();
}
{
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
let mut tx = db.transaction(false);
assert_eq!(tx.get("recover_key1").unwrap(), Some(Bytes::from("updated_value1")));
assert_eq!(tx.get("recover_key2").unwrap(), None); assert_eq!(tx.get("recover_key3").unwrap(), Some(Bytes::from("recover_value3")));
tx.cancel().unwrap();
}
}
#[test]
fn test_aol_fsync_modes() {
{
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::SynchronousOnCommit)
.with_fsync_mode(FsyncMode::EveryAppend);
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
let mut tx = db.transaction(true);
tx.set(&b"key_1"[..], "fsync_every_append").unwrap();
tx.commit().unwrap(); }
{
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::SynchronousOnCommit)
.with_fsync_mode(FsyncMode::Interval(Duration::from_millis(100)));
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
let mut tx = db.transaction(true);
tx.set(&b"key_1"[..], "fsync_interval").unwrap();
tx.commit().unwrap();
std::thread::sleep(Duration::from_millis(200));
}
{
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::SynchronousOnCommit)
.with_fsync_mode(FsyncMode::Never);
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
let mut tx = db.transaction(true);
tx.set(&b"key_1"[..], "fsync_never").unwrap();
tx.commit().unwrap(); }
}
#[test]
fn test_snapshot_manual_creation() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::Never)
.with_snapshot_mode(SnapshotMode::Never);
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
{
let mut tx = db.transaction(true);
tx.set("snap_key1".to_string(), "snap_value1".to_string()).unwrap();
tx.set("snap_key2".to_string(), "snap_value2".to_string()).unwrap();
tx.commit().unwrap();
}
if let Some(persistence) = db.persistence() {
persistence.snapshot().unwrap();
}
let snapshot_path = temp_path.join("snapshot.bin");
let aol_path = temp_path.join("aol.bin");
assert!(snapshot_path.exists(), "Snapshot file should exist");
assert!(!aol_path.exists(), "AOL file should not exist in snapshot-only mode");
let snapshot_metadata = std::fs::metadata(&snapshot_path).unwrap();
assert!(snapshot_metadata.len() > 0, "Snapshot file should not be empty");
}
#[test]
fn test_snapshot_only_persistence_basic() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::Never)
.with_snapshot_mode(SnapshotMode::Interval(Duration::from_secs(60)));
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
{
let mut tx = db.transaction(true);
tx.put("key1".to_string(), "value1".to_string()).unwrap();
tx.put("key2".to_string(), "value2".to_string()).unwrap();
tx.commit().unwrap();
}
{
let mut tx = db.transaction(false);
assert_eq!(tx.get("key1").unwrap(), Some(Bytes::from("value1")));
assert_eq!(tx.get("key2").unwrap(), Some(Bytes::from("value2")));
tx.cancel().unwrap();
}
if let Some(persistence) = db.persistence() {
persistence.snapshot().unwrap();
}
let snapshot_path = temp_path.join("snapshot.bin");
let aol_path = temp_path.join("aol.bin");
assert!(snapshot_path.exists(), "Snapshot file should exist");
assert!(!aol_path.exists(), "AOL file should not exist in snapshot-only mode");
let snapshot_metadata = std::fs::metadata(&snapshot_path).unwrap();
assert!(snapshot_metadata.len() > 0, "Snapshot file should not be empty");
println!("Successfully created snapshot file with {} bytes", snapshot_metadata.len());
}
#[test]
fn test_snapshot_basic() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::Never)
.with_snapshot_mode(SnapshotMode::Never);
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
{
let mut tx = db.transaction(true);
for i in 0..100 {
tx.set(format!("key_{i}"), format!("value_{i}_with_some_data")).unwrap();
}
tx.commit().unwrap();
}
if let Some(persistence) = db.persistence() {
persistence.snapshot().unwrap();
}
let snapshot_path = temp_path.join("snapshot.bin");
assert!(snapshot_path.exists(), "Snapshot file should exist");
let metadata = std::fs::metadata(&snapshot_path).unwrap();
assert!(metadata.len() > 0, "Snapshot file should not be empty");
println!("Snapshot size = {} bytes", metadata.len());
}
#[test]
fn test_snapshot_recovery() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::Never)
.with_snapshot_mode(SnapshotMode::Never);
{
let db = Database::new_with_persistence(db_opts.clone(), persistence_opts.clone()).unwrap();
{
let mut tx = db.transaction(true);
tx.set("snapshot_key1".to_string(), "snapshot_value1".to_string()).unwrap();
tx.set("snapshot_key2".to_string(), "snapshot_value2".to_string()).unwrap();
tx.commit().unwrap();
}
{
let mut tx = db.transaction(true);
tx.set("snapshot_key1".to_string(), "updated_snapshot_value1".to_string()).unwrap();
tx.set("snapshot_key3".to_string(), "snapshot_value3".to_string()).unwrap();
tx.commit().unwrap();
}
{
let mut tx = db.transaction(true);
tx.del("snapshot_key2".to_string()).unwrap();
tx.commit().unwrap();
}
if let Some(persistence) = db.persistence() {
persistence.snapshot().unwrap();
}
}
{
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
let mut tx = db.transaction(false);
assert_eq!(
tx.get("snapshot_key1").unwrap().as_deref(),
Some(b"updated_snapshot_value1" as &[u8])
);
assert_eq!(tx.get("snapshot_key2").unwrap(), None); assert_eq!(tx.get("snapshot_key3").unwrap().as_deref(), Some(b"snapshot_value3" as &[u8]));
tx.cancel().unwrap();
}
}
#[test]
fn test_snapshot_interval() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::Never)
.with_snapshot_mode(SnapshotMode::Interval(Duration::from_millis(100)));
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
{
let mut tx = db.transaction(true);
tx.set("interval_key1".to_string(), "interval_value1".to_string()).unwrap();
tx.commit().unwrap();
}
std::thread::sleep(Duration::from_millis(200));
{
let mut tx = db.transaction(true);
tx.set("interval_key2".to_string(), "interval_value2".to_string()).unwrap();
tx.commit().unwrap();
}
std::thread::sleep(Duration::from_millis(200));
let snapshot_path = temp_path.join("snapshot.bin");
assert!(snapshot_path.exists(), "Snapshot file should exist with interval snapshots");
}
#[test]
fn test_combined_aol_and_snapshot() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::SynchronousOnCommit)
.with_snapshot_mode(SnapshotMode::Never) .with_fsync_mode(FsyncMode::EveryAppend);
{
let db = Database::new_with_persistence(db_opts.clone(), persistence_opts.clone()).unwrap();
{
let mut tx = db.transaction(true);
tx.set("combined_key1".to_string(), "combined_value1".to_string()).unwrap();
tx.set("combined_key2".to_string(), "combined_value2".to_string()).unwrap();
tx.commit().unwrap();
}
if let Some(persistence) = db.persistence() {
persistence.snapshot().unwrap();
}
{
let mut tx = db.transaction(true);
tx.set("combined_key3".to_string(), "combined_value3".to_string()).unwrap();
tx.set("combined_key1".to_string(), "updated_combined_value1".to_string()).unwrap();
tx.commit().unwrap();
}
}
let snapshot_path = temp_path.join("snapshot.bin");
let aol_path = temp_path.join("aol.bin");
assert!(snapshot_path.exists(), "Snapshot file should exist");
assert!(aol_path.exists(), "AOL file should exist");
{
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
let mut tx = db.transaction(false);
assert_eq!(
tx.get("combined_key1").unwrap().as_deref(),
Some(b"updated_combined_value1" as &[u8])
);
assert_eq!(tx.get("combined_key2").unwrap().as_deref(), Some(b"combined_value2" as &[u8]));
assert_eq!(tx.get("combined_key3").unwrap().as_deref(), Some(b"combined_value3" as &[u8]));
tx.cancel().unwrap();
}
}
#[test]
fn test_aol_snapshot_with_truncation() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::SynchronousOnCommit)
.with_snapshot_mode(SnapshotMode::Never)
.with_fsync_mode(FsyncMode::EveryAppend);
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
{
let mut tx = db.transaction(true);
for i in 0..10 {
tx.set(format!("key_{}", i), format!("value_{}", i)).unwrap();
}
tx.commit().unwrap();
}
let aol_path = temp_path.join("aol.bin");
let aol_size_before = std::fs::metadata(&aol_path).unwrap().len();
assert!(aol_size_before > 0, "AOL should have content before snapshot");
if let Some(persistence) = db.persistence() {
persistence.snapshot().unwrap();
}
let aol_size_after = std::fs::metadata(&aol_path).unwrap().len();
assert!(aol_size_after < aol_size_before, "AOL should be truncated after snapshot");
{
let mut tx = db.transaction(true);
tx.set("post_snapshot_key".to_string(), "post_snapshot_value".to_string()).unwrap();
tx.commit().unwrap();
}
let snapshot_path = temp_path.join("snapshot.bin");
assert!(snapshot_path.exists(), "Snapshot file should exist");
{
let mut tx = db.transaction(false);
for i in 0..10 {
assert_eq!(
tx.get(format!("key_{}", i)).unwrap(),
Some(Bytes::from(format!("value_{}", i)))
);
}
assert_eq!(tx.get("post_snapshot_key").unwrap(), Some(Bytes::from("post_snapshot_value")));
tx.cancel().unwrap();
}
}
#[test]
fn test_combined_recovery_complex() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::SynchronousOnCommit)
.with_snapshot_mode(SnapshotMode::Never)
.with_fsync_mode(FsyncMode::EveryAppend);
{
let db = Database::new_with_persistence(db_opts.clone(), persistence_opts.clone()).unwrap();
{
let mut tx = db.transaction(true);
tx.set("phase1_key1".to_string(), "phase1_value1".to_string()).unwrap();
tx.set("phase1_key2".to_string(), "phase1_value2".to_string()).unwrap();
tx.commit().unwrap();
}
{
let mut tx = db.transaction(true);
tx.set("phase1_key1".to_string(), "updated_phase1_value1".to_string()).unwrap();
tx.del("phase1_key2".to_string()).unwrap();
tx.set("phase2_key1".to_string(), "phase2_value1".to_string()).unwrap();
tx.commit().unwrap();
}
if let Some(persistence) = db.persistence() {
persistence.snapshot().unwrap();
}
{
let mut tx = db.transaction(true);
tx.set("phase3_key1".to_string(), "phase3_value1".to_string()).unwrap();
tx.set("phase1_key1".to_string(), "final_phase1_value1".to_string()).unwrap();
tx.commit().unwrap();
}
}
{
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
let mut tx = db.transaction(false);
assert_eq!(
tx.get("phase1_key1").unwrap().as_deref(),
Some(b"final_phase1_value1" as &[u8])
);
assert_eq!(tx.get("phase1_key2").unwrap(), None); assert_eq!(tx.get("phase2_key1").unwrap(), Some(Bytes::from("phase2_value1")));
assert_eq!(tx.get("phase3_key1").unwrap(), Some(Bytes::from("phase3_value1")));
tx.cancel().unwrap();
}
}
#[test]
fn test_custom_file_paths() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let aol_dir = temp_path.join("logs");
let snapshot_dir = temp_path.join("snapshots");
std::fs::create_dir_all(&aol_dir).unwrap();
std::fs::create_dir_all(&snapshot_dir).unwrap();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::SynchronousOnCommit)
.with_snapshot_mode(SnapshotMode::Never)
.with_aol_path(aol_dir.join("custom.aol"))
.with_snapshot_path(snapshot_dir.join("custom.snapshot"));
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
{
let mut tx = db.transaction(true);
tx.set("custom_key".to_string(), "custom_value".to_string()).unwrap();
tx.commit().unwrap();
}
if let Some(persistence) = db.persistence() {
persistence.snapshot().unwrap();
}
let custom_aol_path = aol_dir.join("custom.aol");
let custom_snapshot_path = snapshot_dir.join("custom.snapshot");
assert!(custom_aol_path.exists(), "Custom AOL file should exist");
assert!(custom_snapshot_path.exists(), "Custom snapshot file should exist");
}
#[test]
fn test_persistence_options_builder() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::AsynchronousAfterCommit)
.with_snapshot_mode(SnapshotMode::Interval(Duration::from_secs(1)))
.with_fsync_mode(FsyncMode::Interval(Duration::from_millis(500)));
assert_eq!(persistence_opts.aol_mode, AolMode::AsynchronousAfterCommit);
assert_eq!(persistence_opts.snapshot_mode, SnapshotMode::Interval(Duration::from_secs(1)));
assert_eq!(persistence_opts.fsync_mode, FsyncMode::Interval(Duration::from_millis(500)));
let db_opts = DatabaseOptions::default();
let _db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
}
#[test]
fn test_readonly_operations_no_persistence() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let db_opts = DatabaseOptions::default();
let persistence_opts = PersistenceOptions::new(temp_path)
.with_aol_mode(AolMode::SynchronousOnCommit)
.with_snapshot_mode(SnapshotMode::Never);
let db = Database::new_with_persistence(db_opts, persistence_opts).unwrap();
{
let mut tx = db.transaction(false);
assert_eq!(tx.get("non_existent_key").unwrap(), None);
assert!(!tx.exists("non_existent_key").unwrap());
tx.cancel().unwrap();
}
let aol_path = temp_path.join("aol.bin");
let snapshot_path = temp_path.join("snapshot.bin");
if aol_path.exists() {
let metadata = std::fs::metadata(&aol_path).unwrap();
assert_eq!(metadata.len(), 0, "AOL file should be empty after read-only operations");
}
assert!(!snapshot_path.exists(), "Snapshot file should not exist after read-only operations");
}