use crate::common::{do_flush, maybe_setup_tracing, TempDatabase}; use std::cell::RefCell; use std::ops::Deref; use std::rc::Rc; use std::sync::{Arc, Mutex}; use turso_core::{Connection, LimboError, Result, StepResult}; #[allow(clippy::arc_with_non_send_sync)] #[test] fn test_wal_checkpoint_result() -> Result<()> { maybe_setup_tracing(); let tmp_db = TempDatabase::new("test_wal.db", false); let conn = tmp_db.connect_limbo(); conn.execute("CREATE TABLE t1 (id text);")?; let res = execute_and_get_strings(&conn, "pragma journal_mode;")?; assert_eq!(res, vec!["wal"]); conn.execute("insert into t1(id) values (1), (2);")?; do_flush(&conn, &tmp_db).unwrap(); conn.execute("select * from t1;")?; do_flush(&conn, &tmp_db).unwrap(); // checkpoint result should return > 0 num pages now as database has data let res = execute_and_get_ints(&conn, "pragma wal_checkpoint;")?; println!("'pragma wal_checkpoint;' returns: {res:?}"); assert_eq!(res.len(), 3); assert_eq!(res[0], 0); // checkpoint successfully assert!(res[1] > 0); // num pages in wal assert!(res[2] > 0); // num pages checkpointed successfully Ok(()) } #[test] #[ignore = "ignored for now because it's flaky"] fn test_wal_1_writer_1_reader() -> Result<()> { maybe_setup_tracing(); let tmp_db = Arc::new(Mutex::new(TempDatabase::new("test_wal.db", false))); let db = tmp_db.lock().unwrap().limbo_database(false); { let conn = db.connect().unwrap(); match conn.query("CREATE TABLE t (id)")? { Some(ref mut rows) => loop { match rows.step().unwrap() { StepResult::Row => {} StepResult::IO => { rows.run_once().unwrap(); } StepResult::Interrupt => break, StepResult::Done => break, StepResult::Busy => unreachable!(), } }, None => todo!(), } do_flush(&conn, tmp_db.lock().unwrap().deref()).unwrap(); } let rows = Arc::new(std::sync::Mutex::new(0)); let rows_ = rows.clone(); const ROWS_WRITE: usize = 1000; let tmp_db_w = db.clone(); let writer_thread = std::thread::spawn(move || { let conn = tmp_db_w.connect().unwrap(); for i in 0..ROWS_WRITE { conn.execute(format!("INSERT INTO t values({i})").as_str()) .unwrap(); let mut rows = rows_.lock().unwrap(); *rows += 1; } }); let rows_ = rows.clone(); let reader_thread = std::thread::spawn(move || { let conn = db.connect().unwrap(); loop { let rows = *rows_.lock().unwrap(); let mut i = 0; match conn.query("SELECT * FROM t") { Ok(Some(ref mut rows)) => loop { match rows.step().unwrap() { StepResult::Row => { let row = rows.row().unwrap(); let id = row.get::(0).unwrap(); assert_eq!(id, i); i += 1; } StepResult::IO => { rows.run_once().unwrap(); } StepResult::Interrupt => break, StepResult::Done => break, StepResult::Busy => unreachable!(), } }, Ok(None) => {} Err(err) => { eprintln!("{err}"); } } if rows == ROWS_WRITE { break; } } }); writer_thread.join().unwrap(); reader_thread.join().unwrap(); Ok(()) } /// Execute a statement and get strings result pub(crate) fn execute_and_get_strings(conn: &Arc, sql: &str) -> Result> { let statement = conn.prepare(sql)?; let stmt = Rc::new(RefCell::new(statement)); let mut result = Vec::new(); let mut stmt = stmt.borrow_mut(); while let Ok(step_result) = stmt.step() { match step_result { StepResult::Row => { let row = stmt.row().unwrap(); for el in row.get_values() { result.push(format!("{el}")); } } StepResult::Done => break, StepResult::Interrupt => break, StepResult::IO => stmt.run_once()?, StepResult::Busy => stmt.run_once()?, } } Ok(result) } /// Execute a statement and get integers pub(crate) fn execute_and_get_ints(conn: &Arc, sql: &str) -> Result> { let statement = conn.prepare(sql)?; let stmt = Rc::new(RefCell::new(statement)); let mut result = Vec::new(); let mut stmt = stmt.borrow_mut(); while let Ok(step_result) = stmt.step() { match step_result { StepResult::Row => { let row = stmt.row().unwrap(); for value in row.get_values() { let out = match value { turso_core::Value::Integer(i) => i, _ => { return Err(LimboError::ConversionError(format!( "cannot convert {value} to int" ))) } }; result.push(*out); } } StepResult::Done => break, StepResult::Interrupt => break, StepResult::IO => stmt.run_once()?, StepResult::Busy => stmt.run_once()?, } } Ok(result) }