use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use stoolap::api::Database;
#[test]
fn test_dirty_read_reproduction() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE dirty_test (id INTEGER PRIMARY KEY, value INTEGER)",
(),
)
.expect("Failed to create table");
db.execute("INSERT INTO dirty_test VALUES (1, 100)", ())
.expect("Failed to insert initial data");
let initial: i64 = db
.query_one("SELECT value FROM dirty_test WHERE id = 1", ())
.expect("Failed to query initial value");
assert_eq!(initial, 100, "Initial value should be 100");
let dirty_read_count = Arc::new(AtomicI32::new(0));
let total_iterations = 10;
for iteration in 0..total_iterations {
let db_writer = db.clone();
let db_reader = db.clone();
let dirty_count = Arc::clone(&dirty_read_count);
let writer_started = Arc::new(std::sync::Barrier::new(2));
let writer_started_clone = Arc::clone(&writer_started);
let writer_handle = thread::spawn(move || {
db_writer.execute("BEGIN", ()).expect("BEGIN failed");
db_writer
.execute("UPDATE dirty_test SET value = 999 WHERE id = 1", ())
.expect("UPDATE failed");
writer_started_clone.wait();
thread::sleep(Duration::from_micros(500));
db_writer.execute("ROLLBACK", ()).expect("ROLLBACK failed");
});
let reader_handle = thread::spawn(move || {
writer_started.wait();
thread::sleep(Duration::from_micros(100));
let value: Result<i64, _> =
db_reader.query_one("SELECT value FROM dirty_test WHERE id = 1", ());
if let Ok(v) = value {
if v == 999 {
dirty_count.fetch_add(1, Ordering::SeqCst);
eprintln!(
"DIRTY READ in iteration {}: Read uncommitted value 999!",
iteration
);
}
}
});
writer_handle.join().expect("Writer thread panicked");
reader_handle.join().expect("Reader thread panicked");
}
let dirty_reads = dirty_read_count.load(Ordering::SeqCst);
println!(
"\nDirty read test completed: {}/{} dirty reads detected",
dirty_reads, total_iterations
);
let final_value: i64 = db
.query_one("SELECT value FROM dirty_test WHERE id = 1", ())
.expect("Failed to query final value");
assert_eq!(
final_value, 100,
"Final value should be 100 after rollbacks"
);
assert_eq!(
dirty_reads, 0,
"Dirty reads detected! {} out of {} iterations had dirty reads",
dirty_reads, total_iterations
);
}
#[test]
fn test_dirty_read_simple() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE simple_dirty (id INTEGER PRIMARY KEY, value INTEGER)",
(),
)
.expect("Failed to create table");
db.execute("INSERT INTO simple_dirty VALUES (1, 100)", ())
.expect("Failed to insert");
let mut tx = db.begin().expect("Failed to begin transaction");
tx.execute("UPDATE simple_dirty SET value = 999 WHERE id = 1", ())
.expect("Failed to update");
let value: i64 = db
.query_one("SELECT value FROM simple_dirty WHERE id = 1", ())
.expect("Failed to query");
println!("Value read outside transaction: {}", value);
if value == 999 {
eprintln!("DIRTY READ: Saw uncommitted value 999!");
}
tx.rollback().expect("Failed to rollback");
let final_value: i64 = db
.query_one("SELECT value FROM simple_dirty WHERE id = 1", ())
.expect("Failed to query final");
assert_eq!(final_value, 100, "Value should be 100 after rollback");
assert_eq!(
value, 100,
"DIRTY READ DETECTED: Read uncommitted value {} instead of 100",
value
);
}
#[test]
fn test_shared_executor_issue() {
let db = Database::open_in_memory().expect("Failed to create database");
db.execute(
"CREATE TABLE shared_test (id INTEGER PRIMARY KEY, value INTEGER)",
(),
)
.expect("Failed to create table");
db.execute("INSERT INTO shared_test VALUES (1, 100)", ())
.expect("Failed to insert");
let db2 = db.clone();
db.execute("BEGIN", ()).expect("BEGIN failed");
db.execute("UPDATE shared_test SET value = 999 WHERE id = 1", ())
.expect("UPDATE failed");
let value: i64 = db2
.query_one("SELECT value FROM shared_test WHERE id = 1", ())
.expect("Failed to query");
println!(
"db2 read value: {} (expected 100 if isolated, 999 if shared)",
value
);
db.execute("ROLLBACK", ()).expect("ROLLBACK failed");
if value == 100 {
println!("CORRECT: db2 sees committed value 100, not uncommitted 999");
} else {
eprintln!(
"BUG: db2 read uncommitted value {} from db's transaction",
value
);
}
assert_eq!(
value, 100,
"db2 should see committed value 100, not uncommitted value from db's transaction"
);
}