From c51de732c82efff745247e18b8ed1dcec347ebe8 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Fri, 13 Dec 2024 07:51:47 -0500 Subject: [PATCH 01/19] - added Arbitrary and ArbitraryOf traits for mroe centralized generation - implemented random generation for tables and structured queries --- simulator/main.rs | 425 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 388 insertions(+), 37 deletions(-) diff --git a/simulator/main.rs b/simulator/main.rs index 3c71bfef5..714c0cd15 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -2,6 +2,7 @@ use limbo_core::{Connection, Database, File, OpenFlags, PlatformIO, Result, RowR use rand::prelude::*; use rand_chacha::ChaCha8Rng; use std::cell::RefCell; +use std::fmt::Display; use std::rc::Rc; use std::sync::Arc; use tempfile::TempDir; @@ -36,12 +37,32 @@ struct SimulatorOpts { page_size: usize, } +trait Arbitrary { + fn arbitrary(rng: &mut R) -> Self; +} + +trait ArbitraryOf { + fn arbitrary_of(rng: &mut R, t: &T) -> Self; +} + struct Table { rows: Vec>, name: String, columns: Vec, } +impl Arbitrary for Table { + fn arbitrary(rng: &mut R) -> Self { + let name = gen_random_name(rng); + let columns = gen_columns(rng); + Table { + rows: Vec::new(), + name, + columns, + } + } +} + #[derive(Clone)] struct Column { name: String, @@ -50,6 +71,19 @@ struct Column { unique: bool, } +impl Arbitrary for Column { + fn arbitrary(rng: &mut R) -> Self { + let name = gen_random_name(rng); + let column_type = ColumnType::arbitrary(rng); + Column { + name, + column_type, + primary: false, + unique: false, + } + } +} + #[derive(Clone)] enum ColumnType { Integer, @@ -58,7 +92,19 @@ enum ColumnType { Blob, } -#[derive(Debug, PartialEq)] +impl Arbitrary for ColumnType { + fn arbitrary(rng: &mut R) -> Self { + match rng.gen_range(0..4) { + 0 => ColumnType::Integer, + 1 => ColumnType::Float, + 2 => ColumnType::Text, + 3 => ColumnType::Blob, + _ => unreachable!(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] enum Value { Null, Integer(i64), @@ -67,6 +113,312 @@ enum Value { Blob(Vec), } +impl ArbitraryOf> for Value { + fn arbitrary_of(rng: &mut R, t: &Vec<&Value>) -> Self { + if t.is_empty() { + return Value::Null; + } + + let index = rng.gen_range(0..t.len()); + t[index].clone() + } +} + +impl ArbitraryOf for Value { + fn arbitrary_of(rng: &mut R, t: &ColumnType) -> Self { + match t { + ColumnType::Integer => Value::Integer(rng.gen_range(i64::MIN..i64::MAX)), + ColumnType::Float => Value::Float(rng.gen_range(-1e10..1e10)), + ColumnType::Text => Value::Text(gen_random_text(rng)), + ColumnType::Blob => Value::Blob(gen_random_text(rng).as_bytes().to_vec()), + } + } +} + +struct LTValue(Value); + + +impl ArbitraryOf> for LTValue { + fn arbitrary_of(rng: &mut R, t: &Vec<&Value>) -> Self { + if t.is_empty() { + return LTValue(Value::Null); + } + + let index = rng.gen_range(0..t.len()); + LTValue::arbitrary_of(rng, t[index]) + } +} + +impl ArbitraryOf for LTValue { + fn arbitrary_of(rng: &mut R, t: &Value) -> Self { + match t { + Value::Integer(i) => LTValue(Value::Integer(rng.gen_range(i64::MIN..*i - 1))), + Value::Float(f) => LTValue(Value::Float(rng.gen_range(-1e10..*f - 1.0))), + Value::Text(t) => { + // Either shorten the string, or make at least one character smaller and mutate the rest + let mut t = t.clone(); + if rng.gen_bool(0.01) { + t.pop(); + LTValue(Value::Text(t)) + } else { + let index = rng.gen_range(0..t.len()); + let mut t = t.into_bytes(); + t[index] -= 1; + // Mutate the rest of the string + for i in (index+1)..t.len() { + t[i] = rng.gen_range(0..=255); + } + LTValue(Value::Text(String::from_utf8(t).unwrap())) + } + } + Value::Blob(b) => todo!(), + _ => unreachable!(), + } + } +} + + +struct GTValue(Value); + +impl ArbitraryOf> for GTValue { + fn arbitrary_of(rng: &mut R, t: &Vec<&Value>) -> Self { + if t.is_empty() { + return GTValue(Value::Null); + } + + let index = rng.gen_range(0..t.len()); + GTValue::arbitrary_of(rng, t[index]) + } +} + +impl ArbitraryOf for GTValue { + fn arbitrary_of(rng: &mut R, t: &Value) -> Self { + match t { + Value::Integer(i) => GTValue(Value::Integer(rng.gen_range(*i..i64::MAX))), + Value::Float(f) => GTValue(Value::Float(rng.gen_range(*f..1e10))), + Value::Text(t) => { + // Either lengthen the string, or make at least one character smaller and mutate the rest + let mut t = t.clone(); + if rng.gen_bool(0.01) { + t.push(rng.gen_range(0..=255) as u8 as char); + GTValue(Value::Text(t)) + } else { + let index = rng.gen_range(0..t.len()); + let mut t = t.into_bytes(); + t[index] += 1; + // Mutate the rest of the string + for i in (index+1)..t.len() { + t[i] = rng.gen_range(0..=255); + } + GTValue(Value::Text(String::from_utf8(t).unwrap())) + } + } + Value::Blob(b) => todo!(), + _ => unreachable!(), + } + } +} + + +enum Predicate { + And(Vec), + Or(Vec), + Eq(String, Value), + Gt(String, Value), + Lt(String, Value), +} + +enum Query { + Create { table: Table }, + Select { table: String, guard: Predicate }, + Insert { table: String, values: Vec }, + Delete { table: String, guard: Predicate }, +} + +impl ArbitraryOf for Query { + fn arbitrary_of(rng: &mut R, t: &Table) -> Self { + match rng.gen_range(0..=200) { + 0 => Query::Create { + table: Table::arbitrary(rng), + }, + 1..=100 => Query::Select { + table: t.name.clone(), + guard: Predicate::arbitrary_of(rng, t), + }, + 101..=200 => Query::Insert { + table: t.name.clone(), + values: t + .columns + .iter() + .map(|c| Value::arbitrary_of(rng, &c.column_type)) + .collect(), + }, + 201..=300 => Query::Delete { + table: t.name.clone(), + guard: Predicate::arbitrary_of(rng, t), + }, + _ => unreachable!(), + } + } +} + +struct CompoundPredicate(Predicate); +struct SimplePredicate(Predicate); + +impl ArbitraryOf<(&Table, bool)> for SimplePredicate { + fn arbitrary_of(rng: &mut R, (t, b): &(&Table, bool)) -> Self { + // Pick a random column + let column_index = rng.gen_range(0..t.columns.len()); + let column = &t.columns[column_index]; + let column_values = t.rows.iter().map(|r| &r[column_index]).collect::>(); + // Pick an operator + let operator = match rng.gen_range(0..3) { + 0 => { + if *b { + Predicate::Eq(column.name.clone(), Value::arbitrary_of(rng, &column_values)) + } else { + Predicate::Eq(column.name.clone(), Value::arbitrary_of(rng, &column.column_type)) + } + } + 1 => Predicate::Gt(column.name.clone(), + match b { + true => GTValue::arbitrary_of(rng, &column_values).0, + false => LTValue::arbitrary_of(rng, &column_values).0, + }), + 2 => Predicate::Lt(column.name.clone(), + match b { + true => LTValue::arbitrary_of(rng, &column_values).0, + false => GTValue::arbitrary_of(rng, &column_values).0, + }), + _ => unreachable!(), + }; + + SimplePredicate(operator) + } +} + + + +impl ArbitraryOf<(&Table, bool)> for CompoundPredicate { + fn arbitrary_of(rng: &mut R, (t, b): &(&Table, bool)) -> Self { + // Decide if you want to create an AND or an OR + CompoundPredicate(if rng.gen_bool(0.7) { + // An AND for true requires each of its children to be true + // An AND for false requires at least one of its children to be false + if *b { + Predicate::And( + (0..rng.gen_range(1..=3)) + .map(|_| SimplePredicate::arbitrary_of(rng, &(*t, true)).0) + .collect(), + ) + } else { + // Create a vector of random booleans + let mut booleans = (0..rng.gen_range(1..=3)) + .map(|_| rng.gen_bool(0.5)) + .collect::>(); + + let len = booleans.len(); + + // Make sure at least one of them is false + if booleans.iter().all(|b| *b) { + booleans[rng.gen_range(0..len)] = false; + } + + Predicate::And( + booleans + .iter() + .map(|b| SimplePredicate::arbitrary_of(rng, &(*t, *b)).0) + .collect(), + ) + } + } else { + // An OR for true requires at least one of its children to be true + // An OR for false requires each of its children to be false + if *b { + // Create a vector of random booleans + let mut booleans = (0..rng.gen_range(1..=3)) + .map(|_| rng.gen_bool(0.5)) + .collect::>(); + let len = booleans.len(); + // Make sure at least one of them is true + if booleans.iter().all(|b| !*b) { + booleans[rng.gen_range(0..len)] = true; + } + + Predicate::Or( + booleans + .iter() + .map(|b| SimplePredicate::arbitrary_of(rng, &(*t, *b)).0) + .collect(), + ) + } else { + Predicate::Or( + (0..rng.gen_range(1..=3)) + .map(|_| SimplePredicate::arbitrary_of(rng, &(*t, false)).0) + .collect(), + ) + } + }) + } +} + +impl ArbitraryOf
for Predicate { + fn arbitrary_of(rng: &mut R, t: &Table) -> Self { + let b= rng.gen_bool(0.5); + CompoundPredicate::arbitrary_of(rng, &(t, b)).0 + } +} + +impl Display for Predicate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Predicate::And(predicates) => { + write!(f, "(")?; + for (i, p) in predicates.iter().enumerate() { + if i != 0 { + write!(f, " AND ")?; + } + write!(f, "{}", p)?; + } + write!(f, ")") + } + Predicate::Or(predicates) => { + write!(f, "(")?; + for (i, p) in predicates.iter().enumerate() { + if i != 0 { + write!(f, " OR ")?; + } + write!(f, "{}", p)?; + } + write!(f, ")") + } + Predicate::Eq(name, value) => write!(f, "{} = {}", name, value), + Predicate::Gt(name, value) => write!(f, "{} > {}", name, value), + Predicate::Lt(name, value) => write!(f, "{} < {}", name, value), + } + } +} + +impl Display for Query { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Query::Create { table } => write!(f, "{}", table.to_create_str()), + Query::Select { table, guard } => write!(f, "SELECT * FROM {} WHERE {}", table, guard), + Query::Insert { table, values } => { + write!(f, "INSERT INTO {} VALUES (", table)?; + for (i, v) in values.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + write!(f, "{}", v)?; + } + write!(f, ")") + } + Query::Delete { table, guard } => write!(f, "DELETE FROM {} WHERE {}", table, guard), + } + } +} + #[allow(clippy::arc_with_non_send_sync)] fn main() { let _ = env_logger::try_init(); @@ -160,17 +512,21 @@ fn process_connection(env: &mut SimulatorEnv, conn: &mut Rc) -> Resu } else if env.tables.is_empty() { maybe_add_table(env, conn)?; } else { - let roll = env.rng.gen_range(0..100); - if roll < env.opts.read_percent { - // read - do_select(env, conn)?; - } else if roll < env.opts.read_percent + env.opts.write_percent { - // write - do_write(env, conn)?; - } else { - // delete - // TODO - } + let query = Query::arbitrary_of(&mut env.rng, &env.tables[0]); + log::info!("running query '{}'", query); + let rows = get_all_rows(env, conn, query.to_string().as_str())?; + log::debug!("{:?}", rows); + // let roll = env.rng.gen_range(0..100); + // if roll < env.opts.read_percent { + // // read + // do_select(env, conn)?; + // } else if roll < env.opts.read_percent + env.opts.write_percent { + // // write + // do_write(env, conn)?; + // } else { + // // delete + // // TODO + // } } Ok(()) } @@ -201,12 +557,7 @@ fn do_write(env: &mut SimulatorEnv, conn: &mut Rc) -> Result<()> { // gen insert query for column in &columns { - let value = match column.column_type { - ColumnType::Integer => Value::Integer(env.rng.gen_range(i64::MIN..i64::MAX)), - ColumnType::Float => Value::Float(env.rng.gen_range(-1e10..1e10)), - ColumnType::Text => Value::Text(gen_random_text(env)), - ColumnType::Blob => Value::Blob(gen_random_text(env).as_bytes().to_vec()), - }; + let value = Value::arbitrary_of(&mut env.rng, &column.column_type); query.push_str(value.to_string().as_str()); query.push(','); @@ -237,8 +588,8 @@ fn maybe_add_table(env: &mut SimulatorEnv, conn: &mut Rc) -> Result< if env.tables.len() < env.opts.max_tables { let table = Table { rows: Vec::new(), - name: gen_random_name(env), - columns: gen_columns(env), + name: gen_random_name(&mut env.rng), + columns: gen_columns(&mut env.rng), }; let rows = get_all_rows(env, conn, table.to_create_str().as_str())?; log::debug!("{:?}", rows); @@ -266,32 +617,32 @@ fn maybe_add_table(env: &mut SimulatorEnv, conn: &mut Rc) -> Result< Ok(()) } -fn gen_random_name(env: &mut SimulatorEnv) -> String { - let name = readable_name_custom("_", &mut env.rng); +fn gen_random_name(rng: &mut T) -> String { + let name = readable_name_custom("_", rng); name.replace("-", "_") } -fn gen_random_text(env: &mut SimulatorEnv) -> String { - let big_text = env.rng.gen_ratio(1, 1000); +fn gen_random_text(rng: &mut T) -> String { + let big_text = rng.gen_ratio(1, 1000); if big_text { let max_size: u64 = 2 * 1024 * 1024 * 1024; - let size = env.rng.gen_range(1024..max_size); + let size = rng.gen_range(1024..max_size); let mut name = String::new(); for i in 0..size { name.push(((i % 26) as u8 + b'A') as char); } name } else { - let name = readable_name_custom("_", &mut env.rng); + let name = readable_name_custom("_", rng); name.replace("-", "_") } } -fn gen_columns(env: &mut SimulatorEnv) -> Vec { - let mut column_range = env.rng.gen_range(1..128); +fn gen_columns(rng: &mut T) -> Vec { + let mut column_range = rng.gen_range(1..128); let mut columns = Vec::new(); while column_range > 0 { - let column_type = match env.rng.gen_range(0..4) { + let column_type = match rng.gen_range(0..4) { 0 => ColumnType::Integer, 1 => ColumnType::Float, 2 => ColumnType::Text, @@ -299,7 +650,7 @@ fn gen_columns(env: &mut SimulatorEnv) -> Vec { _ => unreachable!(), }; let column = Column { - name: gen_random_name(env), + name: gen_random_name(rng), column_type, primary: false, unique: false, @@ -565,14 +916,14 @@ impl Table { } } -impl Value { - pub fn to_string(&self) -> String { +impl Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Value::Null => "NULL".to_string(), - Value::Integer(i) => i.to_string(), - Value::Float(f) => f.to_string(), - Value::Text(t) => format!("'{}'", t.clone()), - Value::Blob(vec) => to_sqlite_blob(vec), + Value::Null => write!(f, "NULL"), + Value::Integer(i) => write!(f, "{}", i), + Value::Float(fl) => write!(f, "{}", fl), + Value::Text(t) => write!(f, "'{}'", t), + Value::Blob(b) => write!(f, "{}", to_sqlite_blob(b)), } } } From 6029fc6303a17e6236fa2470e0f170d46c229722 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Fri, 13 Dec 2024 07:56:41 -0500 Subject: [PATCH 02/19] fix formatting errors --- simulator/main.rs | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/simulator/main.rs b/simulator/main.rs index 714c0cd15..5edfab452 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -137,7 +137,6 @@ impl ArbitraryOf for Value { struct LTValue(Value); - impl ArbitraryOf> for LTValue { fn arbitrary_of(rng: &mut R, t: &Vec<&Value>) -> Self { if t.is_empty() { @@ -165,7 +164,7 @@ impl ArbitraryOf for LTValue { let mut t = t.into_bytes(); t[index] -= 1; // Mutate the rest of the string - for i in (index+1)..t.len() { + for i in (index + 1)..t.len() { t[i] = rng.gen_range(0..=255); } LTValue(Value::Text(String::from_utf8(t).unwrap())) @@ -177,7 +176,6 @@ impl ArbitraryOf for LTValue { } } - struct GTValue(Value); impl ArbitraryOf> for GTValue { @@ -207,7 +205,7 @@ impl ArbitraryOf for GTValue { let mut t = t.into_bytes(); t[index] += 1; // Mutate the rest of the string - for i in (index+1)..t.len() { + for i in (index + 1)..t.len() { t[i] = rng.gen_range(0..=255); } GTValue(Value::Text(String::from_utf8(t).unwrap())) @@ -219,7 +217,6 @@ impl ArbitraryOf for GTValue { } } - enum Predicate { And(Vec), Or(Vec), @@ -275,21 +272,31 @@ impl ArbitraryOf<(&Table, bool)> for SimplePredicate { let operator = match rng.gen_range(0..3) { 0 => { if *b { - Predicate::Eq(column.name.clone(), Value::arbitrary_of(rng, &column_values)) + Predicate::Eq( + column.name.clone(), + Value::arbitrary_of(rng, &column_values), + ) } else { - Predicate::Eq(column.name.clone(), Value::arbitrary_of(rng, &column.column_type)) + Predicate::Eq( + column.name.clone(), + Value::arbitrary_of(rng, &column.column_type), + ) } } - 1 => Predicate::Gt(column.name.clone(), + 1 => Predicate::Gt( + column.name.clone(), match b { true => GTValue::arbitrary_of(rng, &column_values).0, false => LTValue::arbitrary_of(rng, &column_values).0, - }), - 2 => Predicate::Lt(column.name.clone(), + }, + ), + 2 => Predicate::Lt( + column.name.clone(), match b { true => LTValue::arbitrary_of(rng, &column_values).0, false => GTValue::arbitrary_of(rng, &column_values).0, - }), + }, + ), _ => unreachable!(), }; @@ -297,8 +304,6 @@ impl ArbitraryOf<(&Table, bool)> for SimplePredicate { } } - - impl ArbitraryOf<(&Table, bool)> for CompoundPredicate { fn arbitrary_of(rng: &mut R, (t, b): &(&Table, bool)) -> Self { // Decide if you want to create an AND or an OR @@ -364,7 +369,7 @@ impl ArbitraryOf<(&Table, bool)> for CompoundPredicate { impl ArbitraryOf
for Predicate { fn arbitrary_of(rng: &mut R, t: &Table) -> Self { - let b= rng.gen_bool(0.5); + let b = rng.gen_bool(0.5); CompoundPredicate::arbitrary_of(rng, &(t, b)).0 } } From 2d712d2b358e8860ed0715c02b0990dc65d9e785 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Fri, 13 Dec 2024 15:59:16 -0500 Subject: [PATCH 03/19] update simulator to randomly pick an action and check its postconditions --- simulator/main.rs | 237 ++++++++++++++++++++++++++-------------------- 1 file changed, 136 insertions(+), 101 deletions(-) diff --git a/simulator/main.rs b/simulator/main.rs index 5edfab452..02a15b9a0 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -3,6 +3,7 @@ use rand::prelude::*; use rand_chacha::ChaCha8Rng; use std::cell::RefCell; use std::fmt::Display; +use std::ops::Deref; use std::rc::Rc; use std::sync::Arc; use tempfile::TempDir; @@ -53,8 +54,10 @@ struct Table { impl Arbitrary for Table { fn arbitrary(rng: &mut R) -> Self { - let name = gen_random_name(rng); - let columns = gen_columns(rng); + let name = Name::arbitrary(rng).0; + let columns = (1..rng.gen_range(1..128)) + .map(|_| Column::arbitrary(rng)) + .collect(); Table { rows: Vec::new(), name, @@ -73,7 +76,7 @@ struct Column { impl Arbitrary for Column { fn arbitrary(rng: &mut R) -> Self { - let name = gen_random_name(rng); + let name = Name::arbitrary(rng).0; let column_type = ColumnType::arbitrary(rng); Column { name, @@ -374,28 +377,48 @@ impl ArbitraryOf
for Predicate { } } +impl ArbitraryOf<(&str, &Value)> for Predicate { + fn arbitrary_of(rng: &mut R, (c, t): &(&str, &Value)) -> Self { + match rng.gen_range(0..3) { + 0 => Predicate::Eq(c.to_string(), (*t).clone()), + 1 => Predicate::Gt(c.to_string(), LTValue::arbitrary_of(rng, *t).0), + 2 => Predicate::Lt(c.to_string(), LTValue::arbitrary_of(rng, *t).0), + _ => unreachable!(), + } + } +} + impl Display for Predicate { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Predicate::And(predicates) => { - write!(f, "(")?; - for (i, p) in predicates.iter().enumerate() { - if i != 0 { - write!(f, " AND ")?; + if predicates.is_empty() { + // todo: Make this TRUE when the bug is fixed + write!(f, "1 = 1") + } else { + write!(f, "(")?; + for (i, p) in predicates.iter().enumerate() { + if i != 0 { + write!(f, " AND ")?; + } + write!(f, "{}", p)?; } - write!(f, "{}", p)?; + write!(f, ")") } - write!(f, ")") } Predicate::Or(predicates) => { - write!(f, "(")?; - for (i, p) in predicates.iter().enumerate() { - if i != 0 { - write!(f, " OR ")?; + if predicates.is_empty() { + write!(f, "FALSE") + } else { + write!(f, "(")?; + for (i, p) in predicates.iter().enumerate() { + if i != 0 { + write!(f, " OR ")?; + } + write!(f, "{}", p)?; } - write!(f, "{}", p)?; + write!(f, ")") } - write!(f, ")") } Predicate::Eq(name, value) => write!(f, "{} = {}", name, value), Predicate::Gt(name, value) => write!(f, "{} > {}", name, value), @@ -509,73 +532,94 @@ fn main() { env.io.print_stats(); } -fn process_connection(env: &mut SimulatorEnv, conn: &mut Rc) -> Result<()> { - let management = env.rng.gen_ratio(1, 100); - if management { - // for now create table only - maybe_add_table(env, conn)?; - } else if env.tables.is_empty() { - maybe_add_table(env, conn)?; - } else { - let query = Query::arbitrary_of(&mut env.rng, &env.tables[0]); - log::info!("running query '{}'", query); - let rows = get_all_rows(env, conn, query.to_string().as_str())?; - log::debug!("{:?}", rows); - // let roll = env.rng.gen_range(0..100); - // if roll < env.opts.read_percent { - // // read - // do_select(env, conn)?; - // } else if roll < env.opts.read_percent + env.opts.write_percent { - // // write - // do_write(env, conn)?; - // } else { - // // delete - // // TODO - // } - } - Ok(()) -} - -fn do_select(env: &mut SimulatorEnv, conn: &mut Rc) -> Result<()> { +fn property_insert_select(env: &mut SimulatorEnv, conn: &mut Rc) { + // Get a random table let table = env.rng.gen_range(0..env.tables.len()); - let table_name = { - let table = &env.tables[table]; - table.name.clone() - }; - let rows = get_all_rows(env, conn, format!("SELECT * FROM {}", table_name).as_str())?; - let table = &env.tables[table]; - compare_equal_rows(&table.rows, &rows); - Ok(()) -} + // let table = &env.tables[table]; -fn do_write(env: &mut SimulatorEnv, conn: &mut Rc) -> Result<()> { - let mut query = String::new(); - let table = env.rng.gen_range(0..env.tables.len()); - { - let table = &env.tables[table]; - query.push_str(format!("INSERT INTO {} VALUES (", table.name).as_str()); - } + // Pick a random column + let column_index = env.rng.gen_range(0..env.tables[table].columns.len()); + let column = &env.tables[table].columns[column_index].clone(); - let columns = env.tables[table].columns.clone(); + let mut rng = env.rng.clone(); + + // Generate a random value of the column type + let value = Value::arbitrary_of(&mut rng, &column.column_type); + + // Create a whole new row let mut row = Vec::new(); - - // gen insert query - for column in &columns { - let value = Value::arbitrary_of(&mut env.rng, &column.column_type); - - query.push_str(value.to_string().as_str()); - query.push(','); - row.push(value); + for (i, column) in env.tables[table].columns.iter().enumerate() { + if i == column_index { + row.push(value.clone()); + } else { + let value = Value::arbitrary_of(&mut rng, &column.column_type); + row.push(value); + } } - let table = &mut env.tables[table]; - table.rows.push(row); + // Insert the row + let query = Query::Insert { + table: env.tables[table].name.clone(), + values: row.clone(), + }; + let _ = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); + // Shadow operation on the table + env.tables[table].rows.push(row.clone()); - query.pop(); - query.push_str(");"); + // Create a query that selects the row + let query = Query::Select { + table: env.tables[table].name.clone(), + guard: Predicate::Eq(column.name.clone(), value), + }; - let _ = get_all_rows(env, conn, query.as_str())?; + // Get all rows + let rows = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); + + // Check that the row is there + assert!(rows.iter().any(|r| r == &row)); +} + +fn property_select_all(env: &mut SimulatorEnv, conn: &mut Rc) { + // Get a random table + let table = env.rng.gen_range(0..env.tables.len()); + + // Create a query that selects all rows + let query = Query::Select { + table: env.tables[table].name.clone(), + guard: Predicate::And(Vec::new()), + }; + + // Get all rows + let rows = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); + + // Check that all rows are there + assert_eq!(rows.len(), env.tables[table].rows.len()); + for row in &env.tables[table].rows { + assert!(rows.iter().any(|r| r == row)); + } +} + +fn process_connection(env: &mut SimulatorEnv, conn: &mut Rc) -> Result<()> { + if env.tables.is_empty() { + maybe_add_table(env, conn)?; + } + + match env.rng.gen_range(0..2) { + // Randomly insert a value and check that the select result contains it. + 0 => property_insert_select(env, conn), + // Check that the current state of the in-memory table is the same as the one in the + // database. + 1 => property_select_all(env, conn), + // Perform a random query, update the in-memory table with the result. + 2 => { + let table_index = env.rng.gen_range(0..env.tables.len()); + let query = Query::arbitrary_of(&mut env.rng, &env.tables[table_index]); + let rows = get_all_rows(env, conn, query.to_string().as_str())?; + env.tables[table_index].rows = rows; + } + _ => unreachable!(), + } Ok(()) } @@ -593,8 +637,10 @@ fn maybe_add_table(env: &mut SimulatorEnv, conn: &mut Rc) -> Result< if env.tables.len() < env.opts.max_tables { let table = Table { rows: Vec::new(), - name: gen_random_name(&mut env.rng), - columns: gen_columns(&mut env.rng), + name: Name::arbitrary(&mut env.rng).0, + columns: (1..env.rng.gen_range(1..128)) + .map(|_| Column::arbitrary(&mut env.rng)) + .collect(), }; let rows = get_all_rows(env, conn, table.to_create_str().as_str())?; log::debug!("{:?}", rows); @@ -622,9 +668,21 @@ fn maybe_add_table(env: &mut SimulatorEnv, conn: &mut Rc) -> Result< Ok(()) } -fn gen_random_name(rng: &mut T) -> String { - let name = readable_name_custom("_", rng); - name.replace("-", "_") +struct Name(String); + +impl Arbitrary for Name { + fn arbitrary(rng: &mut R) -> Self { + let name = readable_name_custom("_", rng); + Name(name.replace("-", "_")) + } +} + +impl Deref for Name { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } } fn gen_random_text(rng: &mut T) -> String { @@ -643,29 +701,6 @@ fn gen_random_text(rng: &mut T) -> String { } } -fn gen_columns(rng: &mut T) -> Vec { - let mut column_range = rng.gen_range(1..128); - let mut columns = Vec::new(); - while column_range > 0 { - let column_type = match rng.gen_range(0..4) { - 0 => ColumnType::Integer, - 1 => ColumnType::Float, - 2 => ColumnType::Text, - 3 => ColumnType::Blob, - _ => unreachable!(), - }; - let column = Column { - name: gen_random_name(rng), - column_type, - primary: false, - unique: false, - }; - columns.push(column); - column_range -= 1; - } - columns -} - fn get_all_rows( env: &mut SimulatorEnv, conn: &mut Rc, From ab556032f5c5be33956597a2cadde974c6c51846 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Fri, 13 Dec 2024 16:07:56 -0500 Subject: [PATCH 04/19] change the names of guard to predicate, arbitrary_of to arbitrary_from, ArbitraryOf to ArbitraryFrom --- simulator/main.rs | 102 +++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/simulator/main.rs b/simulator/main.rs index 02a15b9a0..99b760271 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -42,8 +42,8 @@ trait Arbitrary { fn arbitrary(rng: &mut R) -> Self; } -trait ArbitraryOf { - fn arbitrary_of(rng: &mut R, t: &T) -> Self; +trait ArbitraryFrom { + fn arbitrary_from(rng: &mut R, t: &T) -> Self; } struct Table { @@ -116,8 +116,8 @@ enum Value { Blob(Vec), } -impl ArbitraryOf> for Value { - fn arbitrary_of(rng: &mut R, t: &Vec<&Value>) -> Self { +impl ArbitraryFrom> for Value { + fn arbitrary_from(rng: &mut R, t: &Vec<&Value>) -> Self { if t.is_empty() { return Value::Null; } @@ -127,8 +127,8 @@ impl ArbitraryOf> for Value { } } -impl ArbitraryOf for Value { - fn arbitrary_of(rng: &mut R, t: &ColumnType) -> Self { +impl ArbitraryFrom for Value { + fn arbitrary_from(rng: &mut R, t: &ColumnType) -> Self { match t { ColumnType::Integer => Value::Integer(rng.gen_range(i64::MIN..i64::MAX)), ColumnType::Float => Value::Float(rng.gen_range(-1e10..1e10)), @@ -140,19 +140,19 @@ impl ArbitraryOf for Value { struct LTValue(Value); -impl ArbitraryOf> for LTValue { - fn arbitrary_of(rng: &mut R, t: &Vec<&Value>) -> Self { +impl ArbitraryFrom> for LTValue { + fn arbitrary_from(rng: &mut R, t: &Vec<&Value>) -> Self { if t.is_empty() { return LTValue(Value::Null); } let index = rng.gen_range(0..t.len()); - LTValue::arbitrary_of(rng, t[index]) + LTValue::arbitrary_from(rng, t[index]) } } -impl ArbitraryOf for LTValue { - fn arbitrary_of(rng: &mut R, t: &Value) -> Self { +impl ArbitraryFrom for LTValue { + fn arbitrary_from(rng: &mut R, t: &Value) -> Self { match t { Value::Integer(i) => LTValue(Value::Integer(rng.gen_range(i64::MIN..*i - 1))), Value::Float(f) => LTValue(Value::Float(rng.gen_range(-1e10..*f - 1.0))), @@ -181,19 +181,19 @@ impl ArbitraryOf for LTValue { struct GTValue(Value); -impl ArbitraryOf> for GTValue { - fn arbitrary_of(rng: &mut R, t: &Vec<&Value>) -> Self { +impl ArbitraryFrom> for GTValue { + fn arbitrary_from(rng: &mut R, t: &Vec<&Value>) -> Self { if t.is_empty() { return GTValue(Value::Null); } let index = rng.gen_range(0..t.len()); - GTValue::arbitrary_of(rng, t[index]) + GTValue::arbitrary_from(rng, t[index]) } } -impl ArbitraryOf for GTValue { - fn arbitrary_of(rng: &mut R, t: &Value) -> Self { +impl ArbitraryFrom for GTValue { + fn arbitrary_from(rng: &mut R, t: &Value) -> Self { match t { Value::Integer(i) => GTValue(Value::Integer(rng.gen_range(*i..i64::MAX))), Value::Float(f) => GTValue(Value::Float(rng.gen_range(*f..1e10))), @@ -230,32 +230,32 @@ enum Predicate { enum Query { Create { table: Table }, - Select { table: String, guard: Predicate }, + Select { table: String, predicate: Predicate }, Insert { table: String, values: Vec }, - Delete { table: String, guard: Predicate }, + Delete { table: String, predicate: Predicate }, } -impl ArbitraryOf
for Query { - fn arbitrary_of(rng: &mut R, t: &Table) -> Self { +impl ArbitraryFrom
for Query { + fn arbitrary_from(rng: &mut R, t: &Table) -> Self { match rng.gen_range(0..=200) { 0 => Query::Create { table: Table::arbitrary(rng), }, 1..=100 => Query::Select { table: t.name.clone(), - guard: Predicate::arbitrary_of(rng, t), + predicate: Predicate::arbitrary_from(rng, t), }, 101..=200 => Query::Insert { table: t.name.clone(), values: t .columns .iter() - .map(|c| Value::arbitrary_of(rng, &c.column_type)) + .map(|c| Value::arbitrary_from(rng, &c.column_type)) .collect(), }, 201..=300 => Query::Delete { table: t.name.clone(), - guard: Predicate::arbitrary_of(rng, t), + predicate: Predicate::arbitrary_from(rng, t), }, _ => unreachable!(), } @@ -265,8 +265,8 @@ impl ArbitraryOf
for Query { struct CompoundPredicate(Predicate); struct SimplePredicate(Predicate); -impl ArbitraryOf<(&Table, bool)> for SimplePredicate { - fn arbitrary_of(rng: &mut R, (t, b): &(&Table, bool)) -> Self { +impl ArbitraryFrom<(&Table, bool)> for SimplePredicate { + fn arbitrary_from(rng: &mut R, (t, b): &(&Table, bool)) -> Self { // Pick a random column let column_index = rng.gen_range(0..t.columns.len()); let column = &t.columns[column_index]; @@ -277,27 +277,27 @@ impl ArbitraryOf<(&Table, bool)> for SimplePredicate { if *b { Predicate::Eq( column.name.clone(), - Value::arbitrary_of(rng, &column_values), + Value::arbitrary_from(rng, &column_values), ) } else { Predicate::Eq( column.name.clone(), - Value::arbitrary_of(rng, &column.column_type), + Value::arbitrary_from(rng, &column.column_type), ) } } 1 => Predicate::Gt( column.name.clone(), match b { - true => GTValue::arbitrary_of(rng, &column_values).0, - false => LTValue::arbitrary_of(rng, &column_values).0, + true => GTValue::arbitrary_from(rng, &column_values).0, + false => LTValue::arbitrary_from(rng, &column_values).0, }, ), 2 => Predicate::Lt( column.name.clone(), match b { - true => LTValue::arbitrary_of(rng, &column_values).0, - false => GTValue::arbitrary_of(rng, &column_values).0, + true => LTValue::arbitrary_from(rng, &column_values).0, + false => GTValue::arbitrary_from(rng, &column_values).0, }, ), _ => unreachable!(), @@ -307,8 +307,8 @@ impl ArbitraryOf<(&Table, bool)> for SimplePredicate { } } -impl ArbitraryOf<(&Table, bool)> for CompoundPredicate { - fn arbitrary_of(rng: &mut R, (t, b): &(&Table, bool)) -> Self { +impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate { + fn arbitrary_from(rng: &mut R, (t, b): &(&Table, bool)) -> Self { // Decide if you want to create an AND or an OR CompoundPredicate(if rng.gen_bool(0.7) { // An AND for true requires each of its children to be true @@ -316,7 +316,7 @@ impl ArbitraryOf<(&Table, bool)> for CompoundPredicate { if *b { Predicate::And( (0..rng.gen_range(1..=3)) - .map(|_| SimplePredicate::arbitrary_of(rng, &(*t, true)).0) + .map(|_| SimplePredicate::arbitrary_from(rng, &(*t, true)).0) .collect(), ) } else { @@ -335,7 +335,7 @@ impl ArbitraryOf<(&Table, bool)> for CompoundPredicate { Predicate::And( booleans .iter() - .map(|b| SimplePredicate::arbitrary_of(rng, &(*t, *b)).0) + .map(|b| SimplePredicate::arbitrary_from(rng, &(*t, *b)).0) .collect(), ) } @@ -356,13 +356,13 @@ impl ArbitraryOf<(&Table, bool)> for CompoundPredicate { Predicate::Or( booleans .iter() - .map(|b| SimplePredicate::arbitrary_of(rng, &(*t, *b)).0) + .map(|b| SimplePredicate::arbitrary_from(rng, &(*t, *b)).0) .collect(), ) } else { Predicate::Or( (0..rng.gen_range(1..=3)) - .map(|_| SimplePredicate::arbitrary_of(rng, &(*t, false)).0) + .map(|_| SimplePredicate::arbitrary_from(rng, &(*t, false)).0) .collect(), ) } @@ -370,19 +370,19 @@ impl ArbitraryOf<(&Table, bool)> for CompoundPredicate { } } -impl ArbitraryOf
for Predicate { - fn arbitrary_of(rng: &mut R, t: &Table) -> Self { +impl ArbitraryFrom
for Predicate { + fn arbitrary_from(rng: &mut R, t: &Table) -> Self { let b = rng.gen_bool(0.5); - CompoundPredicate::arbitrary_of(rng, &(t, b)).0 + CompoundPredicate::arbitrary_from(rng, &(t, b)).0 } } -impl ArbitraryOf<(&str, &Value)> for Predicate { - fn arbitrary_of(rng: &mut R, (c, t): &(&str, &Value)) -> Self { +impl ArbitraryFrom<(&str, &Value)> for Predicate { + fn arbitrary_from(rng: &mut R, (c, t): &(&str, &Value)) -> Self { match rng.gen_range(0..3) { 0 => Predicate::Eq(c.to_string(), (*t).clone()), - 1 => Predicate::Gt(c.to_string(), LTValue::arbitrary_of(rng, *t).0), - 2 => Predicate::Lt(c.to_string(), LTValue::arbitrary_of(rng, *t).0), + 1 => Predicate::Gt(c.to_string(), LTValue::arbitrary_from(rng, *t).0), + 2 => Predicate::Lt(c.to_string(), LTValue::arbitrary_from(rng, *t).0), _ => unreachable!(), } } @@ -431,7 +431,7 @@ impl Display for Query { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Query::Create { table } => write!(f, "{}", table.to_create_str()), - Query::Select { table, guard } => write!(f, "SELECT * FROM {} WHERE {}", table, guard), + Query::Select { table, predicate: guard } => write!(f, "SELECT * FROM {} WHERE {}", table, guard), Query::Insert { table, values } => { write!(f, "INSERT INTO {} VALUES (", table)?; for (i, v) in values.iter().enumerate() { @@ -442,7 +442,7 @@ impl Display for Query { } write!(f, ")") } - Query::Delete { table, guard } => write!(f, "DELETE FROM {} WHERE {}", table, guard), + Query::Delete { table, predicate: guard } => write!(f, "DELETE FROM {} WHERE {}", table, guard), } } } @@ -545,7 +545,7 @@ fn property_insert_select(env: &mut SimulatorEnv, conn: &mut Rc) { let mut rng = env.rng.clone(); // Generate a random value of the column type - let value = Value::arbitrary_of(&mut rng, &column.column_type); + let value = Value::arbitrary_from(&mut rng, &column.column_type); // Create a whole new row let mut row = Vec::new(); @@ -553,7 +553,7 @@ fn property_insert_select(env: &mut SimulatorEnv, conn: &mut Rc) { if i == column_index { row.push(value.clone()); } else { - let value = Value::arbitrary_of(&mut rng, &column.column_type); + let value = Value::arbitrary_from(&mut rng, &column.column_type); row.push(value); } } @@ -570,7 +570,7 @@ fn property_insert_select(env: &mut SimulatorEnv, conn: &mut Rc) { // Create a query that selects the row let query = Query::Select { table: env.tables[table].name.clone(), - guard: Predicate::Eq(column.name.clone(), value), + predicate: Predicate::Eq(column.name.clone(), value), }; // Get all rows @@ -587,7 +587,7 @@ fn property_select_all(env: &mut SimulatorEnv, conn: &mut Rc) { // Create a query that selects all rows let query = Query::Select { table: env.tables[table].name.clone(), - guard: Predicate::And(Vec::new()), + predicate: Predicate::And(Vec::new()), }; // Get all rows @@ -614,7 +614,7 @@ fn process_connection(env: &mut SimulatorEnv, conn: &mut Rc) -> Resu // Perform a random query, update the in-memory table with the result. 2 => { let table_index = env.rng.gen_range(0..env.tables.len()); - let query = Query::arbitrary_of(&mut env.rng, &env.tables[table_index]); + let query = Query::arbitrary_from(&mut env.rng, &env.tables[table_index]); let rows = get_all_rows(env, conn, query.to_string().as_str())?; env.tables[table_index].rows = rows; } From 8cb7086bfc3e8081048f7557cdbe3bb42b7a0daa Mon Sep 17 00:00:00 2001 From: alpaylan Date: Fri, 13 Dec 2024 16:08:50 -0500 Subject: [PATCH 05/19] formatting changes --- simulator/main.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/simulator/main.rs b/simulator/main.rs index 99b760271..d0544d3b6 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -431,7 +431,10 @@ impl Display for Query { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Query::Create { table } => write!(f, "{}", table.to_create_str()), - Query::Select { table, predicate: guard } => write!(f, "SELECT * FROM {} WHERE {}", table, guard), + Query::Select { + table, + predicate: guard, + } => write!(f, "SELECT * FROM {} WHERE {}", table, guard), Query::Insert { table, values } => { write!(f, "INSERT INTO {} VALUES (", table)?; for (i, v) in values.iter().enumerate() { @@ -442,7 +445,10 @@ impl Display for Query { } write!(f, ")") } - Query::Delete { table, predicate: guard } => write!(f, "DELETE FROM {} WHERE {}", table, guard), + Query::Delete { + table, + predicate: guard, + } => write!(f, "DELETE FROM {} WHERE {}", table, guard), } } } From 31fcdb8727da3828765aaf32d28b5aef747ca2c7 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Sat, 14 Dec 2024 12:44:23 -0500 Subject: [PATCH 06/19] add workload percentage back to the simulator, fix the smaller/larger UTF8 string generator --- simulator/main.rs | 254 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 197 insertions(+), 57 deletions(-) diff --git a/simulator/main.rs b/simulator/main.rs index 2373cb8ed..ed2fb14a7 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -25,6 +25,12 @@ enum SimConnection { Disconnected, } +#[derive(Debug, Copy, Clone)] +enum SimulatorMode { + Random, + Workload, +} + #[derive(Debug)] struct SimulatorOpts { ticks: usize, @@ -35,6 +41,7 @@ struct SimulatorOpts { read_percent: usize, write_percent: usize, delete_percent: usize, + mode: SimulatorMode, page_size: usize, } @@ -164,16 +171,32 @@ impl ArbitraryFrom for LTValue { LTValue(Value::Text(t)) } else { let index = rng.gen_range(0..t.len()); - let mut t = t.into_bytes(); + let mut t = t.chars().map(|c| c as u32).collect::>(); t[index] -= 1; // Mutate the rest of the string for i in (index + 1)..t.len() { t[i] = rng.gen_range(0..=255); } - LTValue(Value::Text(String::from_utf8(t).unwrap())) + let t = t.into_iter().map(|c| c as u8 as char).collect::(); + LTValue(Value::Text(t)) + } + } + Value::Blob(b) => { + // Either shorten the blob, or make at least one byte smaller and mutate the rest + let mut b = b.clone(); + if rng.gen_bool(0.01) { + b.pop(); + LTValue(Value::Blob(b)) + } else { + let index = rng.gen_range(0..b.len()); + b[index] -= 1; + // Mutate the rest of the blob + for i in (index + 1)..b.len() { + b[i] = rng.gen_range(0..=255); + } + LTValue(Value::Blob(b)) } } - Value::Blob(b) => todo!(), _ => unreachable!(), } } @@ -205,16 +228,32 @@ impl ArbitraryFrom for GTValue { GTValue(Value::Text(t)) } else { let index = rng.gen_range(0..t.len()); - let mut t = t.into_bytes(); + let mut t = t.chars().map(|c| c as u32).collect::>(); t[index] += 1; // Mutate the rest of the string for i in (index + 1)..t.len() { t[i] = rng.gen_range(0..=255); } - GTValue(Value::Text(String::from_utf8(t).unwrap())) + let t = t.into_iter().map(|c| c as u8 as char).collect::(); + GTValue(Value::Text(t)) + } + } + Value::Blob(b) => { + // Either lengthen the blob, or make at least one byte smaller and mutate the rest + let mut b = b.clone(); + if rng.gen_bool(0.01) { + b.push(rng.gen_range(0..=255)); + GTValue(Value::Blob(b)) + } else { + let index = rng.gen_range(0..b.len()); + b[index] += 1; + // Mutate the rest of the blob + for i in (index + 1)..b.len() { + b[i] = rng.gen_range(0..=255); + } + GTValue(Value::Blob(b)) } } - Value::Blob(b) => todo!(), _ => unreachable!(), } } @@ -229,34 +268,91 @@ enum Predicate { } enum Query { - Create { table: Table }, - Select { table: String, predicate: Predicate }, - Insert { table: String, values: Vec }, - Delete { table: String, predicate: Predicate }, + Create(Create), + Select(Select), + Insert(Insert), + Delete(Delete), +} + +struct Create { + table: Table, +} + +impl Arbitrary for Create { + fn arbitrary(rng: &mut R) -> Self { + Create { + table: Table::arbitrary(rng), + } + } +} + +struct Select { + table: String, + predicate: Predicate, +} + +impl ArbitraryFrom> for Select { + fn arbitrary_from(rng: &mut R, t: &Vec
) -> Self { + let table = rng.gen_range(0..t.len()); + Select { + table: t[table].name.clone(), + predicate: Predicate::arbitrary_from(rng, &t[table]), + } + } +} + +impl ArbitraryFrom> for Select { + fn arbitrary_from(rng: &mut R, t: &Vec<&Table>) -> Self { + let table = rng.gen_range(0..t.len()); + Select { + table: t[table].name.clone(), + predicate: Predicate::arbitrary_from(rng, t[table]), + } + } +} + +struct Insert { + table: String, + values: Vec, +} + +impl ArbitraryFrom
for Insert { + fn arbitrary_from(rng: &mut R, t: &Table) -> Self { + let values = t + .columns + .iter() + .map(|c| Value::arbitrary_from(rng, &c.column_type)) + .collect(); + Insert { + table: t.name.clone(), + values, + } + } +} + +struct Delete { + table: String, + predicate: Predicate, +} + +impl ArbitraryFrom
for Delete { + fn arbitrary_from(rng: &mut R, t: &Table) -> Self { + Delete { + table: t.name.clone(), + predicate: Predicate::arbitrary_from(rng, t), + } + } } impl ArbitraryFrom
for Query { fn arbitrary_from(rng: &mut R, t: &Table) -> Self { match rng.gen_range(0..=200) { - 0 => Query::Create { - table: Table::arbitrary(rng), - }, - 1..=100 => Query::Select { - table: t.name.clone(), - predicate: Predicate::arbitrary_from(rng, t), - }, - 101..=200 => Query::Insert { - table: t.name.clone(), - values: t - .columns - .iter() - .map(|c| Value::arbitrary_from(rng, &c.column_type)) - .collect(), - }, - 201..=300 => Query::Delete { - table: t.name.clone(), - predicate: Predicate::arbitrary_from(rng, t), - }, + 0 => Query::Create(Create::arbitrary(rng)), + 1..=100 => Query::Select(Select::arbitrary_from(rng, &vec![t])), + 101..=200 => Query::Insert(Insert::arbitrary_from(rng, t)), + // todo: This branch is currently never taken, as DELETE is not yet implemented. + // Change this when DELETE is implemented. + 201..=300 => Query::Delete(Delete::arbitrary_from(rng, t)), _ => unreachable!(), } } @@ -394,7 +490,7 @@ impl Display for Predicate { Predicate::And(predicates) => { if predicates.is_empty() { // todo: Make this TRUE when the bug is fixed - write!(f, "1 = 1") + write!(f, "TRUE") } else { write!(f, "(")?; for (i, p) in predicates.iter().enumerate() { @@ -430,12 +526,12 @@ impl Display for Predicate { impl Display for Query { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Query::Create { table } => write!(f, "{}", table.to_create_str()), - Query::Select { + Query::Create(Create { table }) => write!(f, "{}", table.to_create_str()), + Query::Select(Select { table, predicate: guard, - } => write!(f, "SELECT * FROM {} WHERE {}", table, guard), - Query::Insert { table, values } => { + }) => write!(f, "SELECT * FROM {} WHERE {}", table, guard), + Query::Insert(Insert { table, values }) => { write!(f, "INSERT INTO {} VALUES (", table)?; for (i, v) in values.iter().enumerate() { if i != 0 { @@ -445,10 +541,10 @@ impl Display for Query { } write!(f, ")") } - Query::Delete { + Query::Delete(Delete { table, predicate: guard, - } => write!(f, "DELETE FROM {} WHERE {}", table, guard), + }) => write!(f, "DELETE FROM {} WHERE {}", table, guard), } } } @@ -481,6 +577,7 @@ fn main() { read_percent, write_percent, delete_percent, + mode: SimulatorMode::Workload, page_size: 4096, // TODO: randomize this too }; let io = Arc::new(SimulatorIO::new(seed, opts.page_size).unwrap()); @@ -542,8 +639,6 @@ fn property_insert_select(env: &mut SimulatorEnv, conn: &mut Rc) { // Get a random table let table = env.rng.gen_range(0..env.tables.len()); - // let table = &env.tables[table]; - // Pick a random column let column_index = env.rng.gen_range(0..env.tables[table].columns.len()); let column = &env.tables[table].columns[column_index].clone(); @@ -565,19 +660,19 @@ fn property_insert_select(env: &mut SimulatorEnv, conn: &mut Rc) { } // Insert the row - let query = Query::Insert { + let query = Query::Insert(Insert { table: env.tables[table].name.clone(), values: row.clone(), - }; + }); let _ = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); // Shadow operation on the table env.tables[table].rows.push(row.clone()); // Create a query that selects the row - let query = Query::Select { + let query = Query::Select(Select { table: env.tables[table].name.clone(), predicate: Predicate::Eq(column.name.clone(), value), - }; + }); // Get all rows let rows = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); @@ -591,10 +686,10 @@ fn property_select_all(env: &mut SimulatorEnv, conn: &mut Rc) { let table = env.rng.gen_range(0..env.tables.len()); // Create a query that selects all rows - let query = Query::Select { + let query = Query::Select(Select { table: env.tables[table].name.clone(), predicate: Predicate::And(Vec::new()), - }; + }); // Get all rows let rows = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); @@ -611,20 +706,65 @@ fn process_connection(env: &mut SimulatorEnv, conn: &mut Rc) -> Resu maybe_add_table(env, conn)?; } - match env.rng.gen_range(0..2) { - // Randomly insert a value and check that the select result contains it. - 0 => property_insert_select(env, conn), - // Check that the current state of the in-memory table is the same as the one in the - // database. - 1 => property_select_all(env, conn), - // Perform a random query, update the in-memory table with the result. - 2 => { - let table_index = env.rng.gen_range(0..env.tables.len()); - let query = Query::arbitrary_from(&mut env.rng, &env.tables[table_index]); - let rows = get_all_rows(env, conn, query.to_string().as_str())?; - env.tables[table_index].rows = rows; + match env.opts.mode { + SimulatorMode::Random => { + match env.rng.gen_range(0..2) { + // Randomly insert a value and check that the select result contains it. + 0 => property_insert_select(env, conn), + // Check that the current state of the in-memory table is the same as the one in the + // database. + 1 => property_select_all(env, conn), + // Perform a random query, update the in-memory table with the result. + 2 => { + let table_index = env.rng.gen_range(0..env.tables.len()); + let query = Query::arbitrary_from(&mut env.rng, &env.tables[table_index]); + let rows = get_all_rows(env, conn, query.to_string().as_str())?; + env.tables[table_index].rows = rows; + } + _ => unreachable!(), + } + } + SimulatorMode::Workload => { + let picked = env.rng.gen_range(0..100); + + if env.rng.gen_ratio(1, 100) { + maybe_add_table(env, conn)?; + } + + if picked < env.opts.read_percent { + let query = Select::arbitrary_from(&mut env.rng, &env.tables); + + let _ = get_all_rows(env, conn, Query::Select(query).to_string().as_str())?; + } else if picked < env.opts.read_percent + env.opts.write_percent { + let table_index = env.rng.gen_range(0..env.tables.len()); + let column_index = env.rng.gen_range(0..env.tables[table_index].columns.len()); + let column = &env.tables[table_index].columns[column_index].clone(); + let mut rng = env.rng.clone(); + let value = Value::arbitrary_from(&mut rng, &column.column_type); + let mut row = Vec::new(); + for (i, column) in env.tables[table_index].columns.iter().enumerate() { + if i == column_index { + row.push(value.clone()); + } else { + let value = Value::arbitrary_from(&mut rng, &column.column_type); + row.push(value); + } + } + let query = Query::Insert(Insert { + table: env.tables[table_index].name.clone(), + values: row.clone(), + }); + let _ = get_all_rows(env, conn, query.to_string().as_str())?; + env.tables[table_index].rows.push(row.clone()); + } else { + let table_index = env.rng.gen_range(0..env.tables.len()); + let query = Query::Select(Select { + table: env.tables[table_index].name.clone(), + predicate: Predicate::And(Vec::new()), + }); + let _ = get_all_rows(env, conn, query.to_string().as_str())?; + } } - _ => unreachable!(), } Ok(()) From a1b2ab3f54dcd64518f7e052318dfa9650183413 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Sat, 14 Dec 2024 14:42:24 -0500 Subject: [PATCH 07/19] change names to more descriptive versions, fix the equal row comparison to check the ordering --- simulator/main.rs | 131 ++++++++++++++++++++++------------------------ 1 file changed, 64 insertions(+), 67 deletions(-) diff --git a/simulator/main.rs b/simulator/main.rs index ed2fb14a7..8a7f10d24 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -124,19 +124,19 @@ enum Value { } impl ArbitraryFrom> for Value { - fn arbitrary_from(rng: &mut R, t: &Vec<&Value>) -> Self { - if t.is_empty() { + fn arbitrary_from(rng: &mut R, values: &Vec<&Value>) -> Self { + if values.is_empty() { return Value::Null; } - let index = rng.gen_range(0..t.len()); - t[index].clone() + let index = rng.gen_range(0..values.len()); + values[index].clone() } } impl ArbitraryFrom for Value { - fn arbitrary_from(rng: &mut R, t: &ColumnType) -> Self { - match t { + fn arbitrary_from(rng: &mut R, column_type: &ColumnType) -> Self { + match column_type { ColumnType::Integer => Value::Integer(rng.gen_range(i64::MIN..i64::MAX)), ColumnType::Float => Value::Float(rng.gen_range(-1e10..1e10)), ColumnType::Text => Value::Text(gen_random_text(rng)), @@ -148,19 +148,19 @@ impl ArbitraryFrom for Value { struct LTValue(Value); impl ArbitraryFrom> for LTValue { - fn arbitrary_from(rng: &mut R, t: &Vec<&Value>) -> Self { - if t.is_empty() { + fn arbitrary_from(rng: &mut R, values: &Vec<&Value>) -> Self { + if values.is_empty() { return LTValue(Value::Null); } - let index = rng.gen_range(0..t.len()); - LTValue::arbitrary_from(rng, t[index]) + let index = rng.gen_range(0..values.len()); + LTValue::arbitrary_from(rng, values[index]) } } impl ArbitraryFrom for LTValue { - fn arbitrary_from(rng: &mut R, t: &Value) -> Self { - match t { + fn arbitrary_from(rng: &mut R, value: &Value) -> Self { + match value { Value::Integer(i) => LTValue(Value::Integer(rng.gen_range(i64::MIN..*i - 1))), Value::Float(f) => LTValue(Value::Float(rng.gen_range(-1e10..*f - 1.0))), Value::Text(t) => { @@ -205,19 +205,19 @@ impl ArbitraryFrom for LTValue { struct GTValue(Value); impl ArbitraryFrom> for GTValue { - fn arbitrary_from(rng: &mut R, t: &Vec<&Value>) -> Self { - if t.is_empty() { + fn arbitrary_from(rng: &mut R, values: &Vec<&Value>) -> Self { + if values.is_empty() { return GTValue(Value::Null); } - let index = rng.gen_range(0..t.len()); - GTValue::arbitrary_from(rng, t[index]) + let index = rng.gen_range(0..values.len()); + GTValue::arbitrary_from(rng, values[index]) } } impl ArbitraryFrom for GTValue { - fn arbitrary_from(rng: &mut R, t: &Value) -> Self { - match t { + fn arbitrary_from(rng: &mut R, value: &Value) -> Self { + match value { Value::Integer(i) => GTValue(Value::Integer(rng.gen_range(*i..i64::MAX))), Value::Float(f) => GTValue(Value::Float(rng.gen_range(*f..1e10))), Value::Text(t) => { @@ -260,13 +260,14 @@ impl ArbitraryFrom for GTValue { } enum Predicate { - And(Vec), - Or(Vec), - Eq(String, Value), - Gt(String, Value), - Lt(String, Value), + And(Vec), // p1 AND p2 AND p3... AND pn + Or(Vec), // p1 OR p2 OR p3... OR pn + Eq(String, Value), // column = Value + Gt(String, Value), // column > Value + Lt(String, Value), // column < Value } +// This type represents the potential queries on the database. enum Query { Create(Create), Select(Select), @@ -292,21 +293,21 @@ struct Select { } impl ArbitraryFrom> for Select { - fn arbitrary_from(rng: &mut R, t: &Vec
) -> Self { - let table = rng.gen_range(0..t.len()); + fn arbitrary_from(rng: &mut R, tables: &Vec
) -> Self { + let table = rng.gen_range(0..tables.len()); Select { - table: t[table].name.clone(), - predicate: Predicate::arbitrary_from(rng, &t[table]), + table: tables[table].name.clone(), + predicate: Predicate::arbitrary_from(rng, &tables[table]), } } } impl ArbitraryFrom> for Select { - fn arbitrary_from(rng: &mut R, t: &Vec<&Table>) -> Self { - let table = rng.gen_range(0..t.len()); + fn arbitrary_from(rng: &mut R, tables: &Vec<&Table>) -> Self { + let table = rng.gen_range(0..tables.len()); Select { - table: t[table].name.clone(), - predicate: Predicate::arbitrary_from(rng, t[table]), + table: tables[table].name.clone(), + predicate: Predicate::arbitrary_from(rng, tables[table]), } } } @@ -317,14 +318,14 @@ struct Insert { } impl ArbitraryFrom
for Insert { - fn arbitrary_from(rng: &mut R, t: &Table) -> Self { - let values = t + fn arbitrary_from(rng: &mut R, table: &Table) -> Self { + let values = table .columns .iter() .map(|c| Value::arbitrary_from(rng, &c.column_type)) .collect(); Insert { - table: t.name.clone(), + table: table.name.clone(), values, } } @@ -336,23 +337,23 @@ struct Delete { } impl ArbitraryFrom
for Delete { - fn arbitrary_from(rng: &mut R, t: &Table) -> Self { + fn arbitrary_from(rng: &mut R, table: &Table) -> Self { Delete { - table: t.name.clone(), - predicate: Predicate::arbitrary_from(rng, t), + table: table.name.clone(), + predicate: Predicate::arbitrary_from(rng, table), } } } impl ArbitraryFrom
for Query { - fn arbitrary_from(rng: &mut R, t: &Table) -> Self { + fn arbitrary_from(rng: &mut R, table: &Table) -> Self { match rng.gen_range(0..=200) { 0 => Query::Create(Create::arbitrary(rng)), - 1..=100 => Query::Select(Select::arbitrary_from(rng, &vec![t])), - 101..=200 => Query::Insert(Insert::arbitrary_from(rng, t)), + 1..=100 => Query::Select(Select::arbitrary_from(rng, &vec![table])), + 101..=200 => Query::Insert(Insert::arbitrary_from(rng, table)), // todo: This branch is currently never taken, as DELETE is not yet implemented. // Change this when DELETE is implemented. - 201..=300 => Query::Delete(Delete::arbitrary_from(rng, t)), + 201..=300 => Query::Delete(Delete::arbitrary_from(rng, table)), _ => unreachable!(), } } @@ -362,15 +363,15 @@ struct CompoundPredicate(Predicate); struct SimplePredicate(Predicate); impl ArbitraryFrom<(&Table, bool)> for SimplePredicate { - fn arbitrary_from(rng: &mut R, (t, b): &(&Table, bool)) -> Self { + fn arbitrary_from(rng: &mut R, (table, predicate_value): &(&Table, bool)) -> Self { // Pick a random column - let column_index = rng.gen_range(0..t.columns.len()); - let column = &t.columns[column_index]; - let column_values = t.rows.iter().map(|r| &r[column_index]).collect::>(); + let column_index = rng.gen_range(0..table.columns.len()); + let column = &table.columns[column_index]; + let column_values = table.rows.iter().map(|r| &r[column_index]).collect::>(); // Pick an operator let operator = match rng.gen_range(0..3) { 0 => { - if *b { + if *predicate_value { Predicate::Eq( column.name.clone(), Value::arbitrary_from(rng, &column_values), @@ -384,14 +385,14 @@ impl ArbitraryFrom<(&Table, bool)> for SimplePredicate { } 1 => Predicate::Gt( column.name.clone(), - match b { + match predicate_value { true => GTValue::arbitrary_from(rng, &column_values).0, false => LTValue::arbitrary_from(rng, &column_values).0, }, ), 2 => Predicate::Lt( column.name.clone(), - match b { + match predicate_value { true => LTValue::arbitrary_from(rng, &column_values).0, false => GTValue::arbitrary_from(rng, &column_values).0, }, @@ -404,15 +405,15 @@ impl ArbitraryFrom<(&Table, bool)> for SimplePredicate { } impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate { - fn arbitrary_from(rng: &mut R, (t, b): &(&Table, bool)) -> Self { + fn arbitrary_from(rng: &mut R, (table, predicate_value): &(&Table, bool)) -> Self { // Decide if you want to create an AND or an OR CompoundPredicate(if rng.gen_bool(0.7) { // An AND for true requires each of its children to be true // An AND for false requires at least one of its children to be false - if *b { + if *predicate_value { Predicate::And( (0..rng.gen_range(1..=3)) - .map(|_| SimplePredicate::arbitrary_from(rng, &(*t, true)).0) + .map(|_| SimplePredicate::arbitrary_from(rng, &(*table, true)).0) .collect(), ) } else { @@ -431,14 +432,14 @@ impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate { Predicate::And( booleans .iter() - .map(|b| SimplePredicate::arbitrary_from(rng, &(*t, *b)).0) + .map(|b| SimplePredicate::arbitrary_from(rng, &(*table, *b)).0) .collect(), ) } } else { // An OR for true requires at least one of its children to be true // An OR for false requires each of its children to be false - if *b { + if *predicate_value { // Create a vector of random booleans let mut booleans = (0..rng.gen_range(1..=3)) .map(|_| rng.gen_bool(0.5)) @@ -452,13 +453,13 @@ impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate { Predicate::Or( booleans .iter() - .map(|b| SimplePredicate::arbitrary_from(rng, &(*t, *b)).0) + .map(|b| SimplePredicate::arbitrary_from(rng, &(*table, *b)).0) .collect(), ) } else { Predicate::Or( (0..rng.gen_range(1..=3)) - .map(|_| SimplePredicate::arbitrary_from(rng, &(*t, false)).0) + .map(|_| SimplePredicate::arbitrary_from(rng, &(*table, false)).0) .collect(), ) } @@ -467,18 +468,18 @@ impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate { } impl ArbitraryFrom
for Predicate { - fn arbitrary_from(rng: &mut R, t: &Table) -> Self { + fn arbitrary_from(rng: &mut R, table: &Table) -> Self { let b = rng.gen_bool(0.5); - CompoundPredicate::arbitrary_from(rng, &(t, b)).0 + CompoundPredicate::arbitrary_from(rng, &(table, b)).0 } } impl ArbitraryFrom<(&str, &Value)> for Predicate { - fn arbitrary_from(rng: &mut R, (c, t): &(&str, &Value)) -> Self { + fn arbitrary_from(rng: &mut R, (column_name, value): &(&str, &Value)) -> Self { match rng.gen_range(0..3) { - 0 => Predicate::Eq(c.to_string(), (*t).clone()), - 1 => Predicate::Gt(c.to_string(), LTValue::arbitrary_from(rng, *t).0), - 2 => Predicate::Lt(c.to_string(), LTValue::arbitrary_from(rng, *t).0), + 0 => Predicate::Eq(column_name.to_string(), (*value).clone()), + 1 => Predicate::Gt(column_name.to_string(), LTValue::arbitrary_from(rng, *value).0), + 2 => Predicate::Lt(column_name.to_string(), LTValue::arbitrary_from(rng, *value).0), _ => unreachable!(), } } @@ -694,11 +695,8 @@ fn property_select_all(env: &mut SimulatorEnv, conn: &mut Rc) { // Get all rows let rows = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); - // Check that all rows are there - assert_eq!(rows.len(), env.tables[table].rows.len()); - for row in &env.tables[table].rows { - assert!(rows.iter().any(|r| r == row)); - } + // Make sure the rows are the same + compare_equal_rows(&rows, &env.tables[table].rows); } fn process_connection(env: &mut SimulatorEnv, conn: &mut Rc) -> Result<()> { @@ -733,7 +731,6 @@ fn process_connection(env: &mut SimulatorEnv, conn: &mut Rc) -> Resu if picked < env.opts.read_percent { let query = Select::arbitrary_from(&mut env.rng, &env.tables); - let _ = get_all_rows(env, conn, Query::Select(query).to_string().as_str())?; } else if picked < env.opts.read_percent + env.opts.write_percent { let table_index = env.rng.gen_range(0..env.tables.len()); From 8e094de6aae887abf2530cdefd802fbfd2f138b1 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Sat, 14 Dec 2024 14:57:32 -0500 Subject: [PATCH 08/19] fix random character generation --- simulator/main.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/simulator/main.rs b/simulator/main.rs index 8a7f10d24..30d3a0176 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -170,14 +170,14 @@ impl ArbitraryFrom for LTValue { t.pop(); LTValue(Value::Text(t)) } else { - let index = rng.gen_range(0..t.len()); let mut t = t.chars().map(|c| c as u32).collect::>(); + let index = rng.gen_range(0..t.len()); t[index] -= 1; // Mutate the rest of the string for i in (index + 1)..t.len() { - t[i] = rng.gen_range(0..=255); + t[i] = rng.gen_range('a' as u32..='z' as u32); } - let t = t.into_iter().map(|c| c as u8 as char).collect::(); + let t = t.into_iter().map(|c| char::from_u32(c).unwrap_or('z')).collect::(); LTValue(Value::Text(t)) } } @@ -227,14 +227,14 @@ impl ArbitraryFrom for GTValue { t.push(rng.gen_range(0..=255) as u8 as char); GTValue(Value::Text(t)) } else { - let index = rng.gen_range(0..t.len()); let mut t = t.chars().map(|c| c as u32).collect::>(); + let index = rng.gen_range(0..t.len()); t[index] += 1; // Mutate the rest of the string for i in (index + 1)..t.len() { - t[i] = rng.gen_range(0..=255); + t[i] = rng.gen_range('a' as u32..='z' as u32); } - let t = t.into_iter().map(|c| c as u8 as char).collect::(); + let t = t.into_iter().map(|c| char::from_u32(c).unwrap_or('a')).collect::(); GTValue(Value::Text(t)) } } From ec1c796650c1412b8b55ce5c53960f05b1b40403 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Sun, 15 Dec 2024 12:50:38 -0500 Subject: [PATCH 09/19] change the boolean name to a more descriptive version --- simulator/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simulator/main.rs b/simulator/main.rs index 30d3a0176..fcb6fc116 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -469,8 +469,8 @@ impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate { impl ArbitraryFrom
for Predicate { fn arbitrary_from(rng: &mut R, table: &Table) -> Self { - let b = rng.gen_bool(0.5); - CompoundPredicate::arbitrary_from(rng, &(table, b)).0 + let predicate_value = rng.gen_bool(0.5); + CompoundPredicate::arbitrary_from(rng, &(table, predicate_value)).0 } } From 0172c512ac01dff0480b58a19eb948da4b7d5ba4 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Sun, 15 Dec 2024 12:51:01 -0500 Subject: [PATCH 10/19] fix formatting --- simulator/main.rs | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/simulator/main.rs b/simulator/main.rs index fcb6fc116..48318f299 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -177,7 +177,10 @@ impl ArbitraryFrom for LTValue { for i in (index + 1)..t.len() { t[i] = rng.gen_range('a' as u32..='z' as u32); } - let t = t.into_iter().map(|c| char::from_u32(c).unwrap_or('z')).collect::(); + let t = t + .into_iter() + .map(|c| char::from_u32(c).unwrap_or('z')) + .collect::(); LTValue(Value::Text(t)) } } @@ -234,7 +237,10 @@ impl ArbitraryFrom for GTValue { for i in (index + 1)..t.len() { t[i] = rng.gen_range('a' as u32..='z' as u32); } - let t = t.into_iter().map(|c| char::from_u32(c).unwrap_or('a')).collect::(); + let t = t + .into_iter() + .map(|c| char::from_u32(c).unwrap_or('a')) + .collect::(); GTValue(Value::Text(t)) } } @@ -260,11 +266,11 @@ impl ArbitraryFrom for GTValue { } enum Predicate { - And(Vec), // p1 AND p2 AND p3... AND pn - Or(Vec), // p1 OR p2 OR p3... OR pn - Eq(String, Value), // column = Value - Gt(String, Value), // column > Value - Lt(String, Value), // column < Value + And(Vec), // p1 AND p2 AND p3... AND pn + Or(Vec), // p1 OR p2 OR p3... OR pn + Eq(String, Value), // column = Value + Gt(String, Value), // column > Value + Lt(String, Value), // column < Value } // This type represents the potential queries on the database. @@ -367,7 +373,11 @@ impl ArbitraryFrom<(&Table, bool)> for SimplePredicate { // Pick a random column let column_index = rng.gen_range(0..table.columns.len()); let column = &table.columns[column_index]; - let column_values = table.rows.iter().map(|r| &r[column_index]).collect::>(); + let column_values = table + .rows + .iter() + .map(|r| &r[column_index]) + .collect::>(); // Pick an operator let operator = match rng.gen_range(0..3) { 0 => { @@ -478,8 +488,14 @@ impl ArbitraryFrom<(&str, &Value)> for Predicate { fn arbitrary_from(rng: &mut R, (column_name, value): &(&str, &Value)) -> Self { match rng.gen_range(0..3) { 0 => Predicate::Eq(column_name.to_string(), (*value).clone()), - 1 => Predicate::Gt(column_name.to_string(), LTValue::arbitrary_from(rng, *value).0), - 2 => Predicate::Lt(column_name.to_string(), LTValue::arbitrary_from(rng, *value).0), + 1 => Predicate::Gt( + column_name.to_string(), + LTValue::arbitrary_from(rng, *value).0, + ), + 2 => Predicate::Lt( + column_name.to_string(), + LTValue::arbitrary_from(rng, *value).0, + ), _ => unreachable!(), } } From be18c6e8f04ac2c9b4c681816d86413428c04473 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Mon, 16 Dec 2024 10:49:05 -0500 Subject: [PATCH 11/19] break simulator into parts, add readme --- simulator/README.md | 2 + simulator/generation.rs | 32 ++ simulator/generation/query.rs | 213 +++++++++++ simulator/generation/table.rs | 196 ++++++++++ simulator/main.rs | 667 +--------------------------------- simulator/model.rs | 3 + simulator/model/query.rs | 111 ++++++ simulator/model/table.rs | 91 +++++ simulator/properties.rs | 78 ++++ 9 files changed, 733 insertions(+), 660 deletions(-) create mode 100644 simulator/README.md create mode 100644 simulator/generation.rs create mode 100644 simulator/generation/query.rs create mode 100644 simulator/generation/table.rs create mode 100644 simulator/model.rs create mode 100644 simulator/model/query.rs create mode 100644 simulator/model/table.rs create mode 100644 simulator/properties.rs diff --git a/simulator/README.md b/simulator/README.md new file mode 100644 index 000000000..cdaf20c6f --- /dev/null +++ b/simulator/README.md @@ -0,0 +1,2 @@ +# Simulator + diff --git a/simulator/generation.rs b/simulator/generation.rs new file mode 100644 index 000000000..73f84c11c --- /dev/null +++ b/simulator/generation.rs @@ -0,0 +1,32 @@ +use anarchist_readable_name_generator_lib::readable_name_custom; +use rand::Rng; + +pub mod table; +pub mod query; + +pub trait Arbitrary { + fn arbitrary(rng: &mut R) -> Self; +} + +pub trait ArbitraryFrom { + fn arbitrary_from(rng: &mut R, t: &T) -> Self; +} + + +fn gen_random_text(rng: &mut T) -> String { + let big_text = rng.gen_ratio(1, 1000); + if big_text { + let max_size: u64 = 2 * 1024 * 1024 * 1024; + let size = rng.gen_range(1024..max_size); + let mut name = String::new(); + for i in 0..size { + name.push(((i % 26) as u8 + b'A') as char); + } + name + } else { + let name = readable_name_custom("_", rng); + name.replace("-", "_") + } +} + + diff --git a/simulator/generation/query.rs b/simulator/generation/query.rs new file mode 100644 index 000000000..d8fc4ae16 --- /dev/null +++ b/simulator/generation/query.rs @@ -0,0 +1,213 @@ + +use crate::generation::{Arbitrary, ArbitraryFrom}; +use crate::generation::table::{LTValue, GTValue}; + +use crate::model::query::{Create, Delete, Insert, Predicate, Query, Select}; +use crate::model::table::{Table, Value}; +use rand::Rng; + + +impl Arbitrary for Create { + fn arbitrary(rng: &mut R) -> Self { + Create { + table: Table::arbitrary(rng), + } + } +} + + +impl ArbitraryFrom> for Select { + fn arbitrary_from(rng: &mut R, tables: &Vec
) -> Self { + let table = rng.gen_range(0..tables.len()); + Select { + table: tables[table].name.clone(), + predicate: Predicate::arbitrary_from(rng, &tables[table]), + } + } +} + +impl ArbitraryFrom> for Select { + fn arbitrary_from(rng: &mut R, tables: &Vec<&Table>) -> Self { + let table = rng.gen_range(0..tables.len()); + Select { + table: tables[table].name.clone(), + predicate: Predicate::arbitrary_from(rng, tables[table]), + } + } +} + + +impl ArbitraryFrom
for Insert { + fn arbitrary_from(rng: &mut R, table: &Table) -> Self { + let values = table + .columns + .iter() + .map(|c| Value::arbitrary_from(rng, &c.column_type)) + .collect(); + Insert { + table: table.name.clone(), + values, + } + } +} + + +impl ArbitraryFrom
for Delete { + fn arbitrary_from(rng: &mut R, table: &Table) -> Self { + Delete { + table: table.name.clone(), + predicate: Predicate::arbitrary_from(rng, table), + } + } +} + +impl ArbitraryFrom
for Query { + fn arbitrary_from(rng: &mut R, table: &Table) -> Self { + match rng.gen_range(0..=200) { + 0 => Query::Create(Create::arbitrary(rng)), + 1..=100 => Query::Select(Select::arbitrary_from(rng, &vec![table])), + 101..=200 => Query::Insert(Insert::arbitrary_from(rng, table)), + // todo: This branch is currently never taken, as DELETE is not yet implemented. + // Change this when DELETE is implemented. + 201..=300 => Query::Delete(Delete::arbitrary_from(rng, table)), + _ => unreachable!(), + } + } +} + + +struct CompoundPredicate(Predicate); +struct SimplePredicate(Predicate); + +impl ArbitraryFrom<(&Table, bool)> for SimplePredicate { + fn arbitrary_from(rng: &mut R, (table, predicate_value): &(&Table, bool)) -> Self { + // Pick a random column + let column_index = rng.gen_range(0..table.columns.len()); + let column = &table.columns[column_index]; + let column_values = table + .rows + .iter() + .map(|r| &r[column_index]) + .collect::>(); + // Pick an operator + let operator = match rng.gen_range(0..3) { + 0 => { + if *predicate_value { + Predicate::Eq( + column.name.clone(), + Value::arbitrary_from(rng, &column_values), + ) + } else { + Predicate::Eq( + column.name.clone(), + Value::arbitrary_from(rng, &column.column_type), + ) + } + } + 1 => Predicate::Gt( + column.name.clone(), + match predicate_value { + true => GTValue::arbitrary_from(rng, &column_values).0, + false => LTValue::arbitrary_from(rng, &column_values).0, + }, + ), + 2 => Predicate::Lt( + column.name.clone(), + match predicate_value { + true => LTValue::arbitrary_from(rng, &column_values).0, + false => GTValue::arbitrary_from(rng, &column_values).0, + }, + ), + _ => unreachable!(), + }; + + SimplePredicate(operator) + } +} + +impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate { + fn arbitrary_from(rng: &mut R, (table, predicate_value): &(&Table, bool)) -> Self { + // Decide if you want to create an AND or an OR + CompoundPredicate(if rng.gen_bool(0.7) { + // An AND for true requires each of its children to be true + // An AND for false requires at least one of its children to be false + if *predicate_value { + Predicate::And( + (0..rng.gen_range(1..=3)) + .map(|_| SimplePredicate::arbitrary_from(rng, &(*table, true)).0) + .collect(), + ) + } else { + // Create a vector of random booleans + let mut booleans = (0..rng.gen_range(1..=3)) + .map(|_| rng.gen_bool(0.5)) + .collect::>(); + + let len = booleans.len(); + + // Make sure at least one of them is false + if booleans.iter().all(|b| *b) { + booleans[rng.gen_range(0..len)] = false; + } + + Predicate::And( + booleans + .iter() + .map(|b| SimplePredicate::arbitrary_from(rng, &(*table, *b)).0) + .collect(), + ) + } + } else { + // An OR for true requires at least one of its children to be true + // An OR for false requires each of its children to be false + if *predicate_value { + // Create a vector of random booleans + let mut booleans = (0..rng.gen_range(1..=3)) + .map(|_| rng.gen_bool(0.5)) + .collect::>(); + let len = booleans.len(); + // Make sure at least one of them is true + if booleans.iter().all(|b| !*b) { + booleans[rng.gen_range(0..len)] = true; + } + + Predicate::Or( + booleans + .iter() + .map(|b| SimplePredicate::arbitrary_from(rng, &(*table, *b)).0) + .collect(), + ) + } else { + Predicate::Or( + (0..rng.gen_range(1..=3)) + .map(|_| SimplePredicate::arbitrary_from(rng, &(*table, false)).0) + .collect(), + ) + } + }) + } +} + +impl ArbitraryFrom
for Predicate { + fn arbitrary_from(rng: &mut R, table: &Table) -> Self { + let predicate_value = rng.gen_bool(0.5); + CompoundPredicate::arbitrary_from(rng, &(table, predicate_value)).0 + } +} + +impl ArbitraryFrom<(&str, &Value)> for Predicate { + fn arbitrary_from(rng: &mut R, (column_name, value): &(&str, &Value)) -> Self { + match rng.gen_range(0..3) { + 0 => Predicate::Eq(column_name.to_string(), (*value).clone()), + 1 => Predicate::Gt( + column_name.to_string(), + LTValue::arbitrary_from(rng, *value).0, + ), + 2 => Predicate::Lt( + column_name.to_string(), + LTValue::arbitrary_from(rng, *value).0, + ), + _ => unreachable!(), + } + } +} diff --git a/simulator/generation/table.rs b/simulator/generation/table.rs new file mode 100644 index 000000000..8b1d6d421 --- /dev/null +++ b/simulator/generation/table.rs @@ -0,0 +1,196 @@ + +use rand::Rng; + +use crate::generation::{Arbitrary, ArbitraryFrom, readable_name_custom, gen_random_text}; +use crate::model::table::{Column, ColumnType, Name, Table, Value}; + +impl Arbitrary for Name { + fn arbitrary(rng: &mut R) -> Self { + let name = readable_name_custom("_", rng); + Name(name.replace("-", "_")) + } +} + + +impl Arbitrary for Table { + fn arbitrary(rng: &mut R) -> Self { + let name = Name::arbitrary(rng).0; + let columns = (1..rng.gen_range(1..128)) + .map(|_| Column::arbitrary(rng)) + .collect(); + Table { + rows: Vec::new(), + name, + columns, + } + } +} + +impl Arbitrary for Column { + fn arbitrary(rng: &mut R) -> Self { + let name = Name::arbitrary(rng).0; + let column_type = ColumnType::arbitrary(rng); + Column { + name, + column_type, + primary: false, + unique: false, + } + } +} + + +impl Arbitrary for ColumnType { + fn arbitrary(rng: &mut R) -> Self { + match rng.gen_range(0..4) { + 0 => ColumnType::Integer, + 1 => ColumnType::Float, + 2 => ColumnType::Text, + 3 => ColumnType::Blob, + _ => unreachable!(), + } + } +} + +impl ArbitraryFrom> for Value { + fn arbitrary_from(rng: &mut R, values: &Vec<&Value>) -> Self { + if values.is_empty() { + return Value::Null; + } + + let index = rng.gen_range(0..values.len()); + values[index].clone() + } +} + +impl ArbitraryFrom for Value { + fn arbitrary_from(rng: &mut R, column_type: &ColumnType) -> Self { + match column_type { + ColumnType::Integer => Value::Integer(rng.gen_range(i64::MIN..i64::MAX)), + ColumnType::Float => Value::Float(rng.gen_range(-1e10..1e10)), + ColumnType::Text => Value::Text(gen_random_text(rng)), + ColumnType::Blob => Value::Blob(gen_random_text(rng).as_bytes().to_vec()), + } + } +} + +pub(crate) struct LTValue(pub(crate) Value); + +impl ArbitraryFrom> for LTValue { + fn arbitrary_from(rng: &mut R, values: &Vec<&Value>) -> Self { + if values.is_empty() { + return LTValue(Value::Null); + } + + let index = rng.gen_range(0..values.len()); + LTValue::arbitrary_from(rng, values[index]) + } +} + +impl ArbitraryFrom for LTValue { + fn arbitrary_from(rng: &mut R, value: &Value) -> Self { + match value { + Value::Integer(i) => LTValue(Value::Integer(rng.gen_range(i64::MIN..*i - 1))), + Value::Float(f) => LTValue(Value::Float(rng.gen_range(-1e10..*f - 1.0))), + Value::Text(t) => { + // Either shorten the string, or make at least one character smaller and mutate the rest + let mut t = t.clone(); + if rng.gen_bool(0.01) { + t.pop(); + LTValue(Value::Text(t)) + } else { + let mut t = t.chars().map(|c| c as u32).collect::>(); + let index = rng.gen_range(0..t.len()); + t[index] -= 1; + // Mutate the rest of the string + for i in (index + 1)..t.len() { + t[i] = rng.gen_range('a' as u32..='z' as u32); + } + let t = t + .into_iter() + .map(|c| char::from_u32(c).unwrap_or('z')) + .collect::(); + LTValue(Value::Text(t)) + } + } + Value::Blob(b) => { + // Either shorten the blob, or make at least one byte smaller and mutate the rest + let mut b = b.clone(); + if rng.gen_bool(0.01) { + b.pop(); + LTValue(Value::Blob(b)) + } else { + let index = rng.gen_range(0..b.len()); + b[index] -= 1; + // Mutate the rest of the blob + for i in (index + 1)..b.len() { + b[i] = rng.gen_range(0..=255); + } + LTValue(Value::Blob(b)) + } + } + _ => unreachable!(), + } + } +} + +pub(crate) struct GTValue(pub(crate) Value); + +impl ArbitraryFrom> for GTValue { + fn arbitrary_from(rng: &mut R, values: &Vec<&Value>) -> Self { + if values.is_empty() { + return GTValue(Value::Null); + } + + let index = rng.gen_range(0..values.len()); + GTValue::arbitrary_from(rng, values[index]) + } +} + +impl ArbitraryFrom for GTValue { + fn arbitrary_from(rng: &mut R, value: &Value) -> Self { + match value { + Value::Integer(i) => GTValue(Value::Integer(rng.gen_range(*i..i64::MAX))), + Value::Float(f) => GTValue(Value::Float(rng.gen_range(*f..1e10))), + Value::Text(t) => { + // Either lengthen the string, or make at least one character smaller and mutate the rest + let mut t = t.clone(); + if rng.gen_bool(0.01) { + t.push(rng.gen_range(0..=255) as u8 as char); + GTValue(Value::Text(t)) + } else { + let mut t = t.chars().map(|c| c as u32).collect::>(); + let index = rng.gen_range(0..t.len()); + t[index] += 1; + // Mutate the rest of the string + for i in (index + 1)..t.len() { + t[i] = rng.gen_range('a' as u32..='z' as u32); + } + let t = t + .into_iter() + .map(|c| char::from_u32(c).unwrap_or('a')) + .collect::(); + GTValue(Value::Text(t)) + } + } + Value::Blob(b) => { + // Either lengthen the blob, or make at least one byte smaller and mutate the rest + let mut b = b.clone(); + if rng.gen_bool(0.01) { + b.push(rng.gen_range(0..=255)); + GTValue(Value::Blob(b)) + } else { + let index = rng.gen_range(0..b.len()); + b[index] += 1; + // Mutate the rest of the blob + for i in (index + 1)..b.len() { + b[i] = rng.gen_range(0..=255); + } + GTValue(Value::Blob(b)) + } + } + _ => unreachable!(), + } + } +} + diff --git a/simulator/main.rs b/simulator/main.rs index 48318f299..bfc9b658f 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -1,14 +1,18 @@ +use generation::{Arbitrary, ArbitraryFrom}; use limbo_core::{Connection, Database, File, OpenFlags, PlatformIO, Result, RowResult, IO}; +use model::table::{Column, Name, Table, Value}; +use model::query::{Insert, Predicate, Query, Select}; +use properties::{property_insert_select, property_select_all}; use rand::prelude::*; use rand_chacha::ChaCha8Rng; use std::cell::RefCell; -use std::fmt::Display; -use std::ops::Deref; use std::rc::Rc; use std::sync::Arc; use tempfile::TempDir; -use anarchist_readable_name_generator_lib::readable_name_custom; +mod generation; +mod properties; +mod model; struct SimulatorEnv { opts: SimulatorOpts, @@ -45,526 +49,7 @@ struct SimulatorOpts { page_size: usize, } -trait Arbitrary { - fn arbitrary(rng: &mut R) -> Self; -} -trait ArbitraryFrom { - fn arbitrary_from(rng: &mut R, t: &T) -> Self; -} - -struct Table { - rows: Vec>, - name: String, - columns: Vec, -} - -impl Arbitrary for Table { - fn arbitrary(rng: &mut R) -> Self { - let name = Name::arbitrary(rng).0; - let columns = (1..rng.gen_range(1..128)) - .map(|_| Column::arbitrary(rng)) - .collect(); - Table { - rows: Vec::new(), - name, - columns, - } - } -} - -#[derive(Clone)] -struct Column { - name: String, - column_type: ColumnType, - primary: bool, - unique: bool, -} - -impl Arbitrary for Column { - fn arbitrary(rng: &mut R) -> Self { - let name = Name::arbitrary(rng).0; - let column_type = ColumnType::arbitrary(rng); - Column { - name, - column_type, - primary: false, - unique: false, - } - } -} - -#[derive(Clone)] -enum ColumnType { - Integer, - Float, - Text, - Blob, -} - -impl Arbitrary for ColumnType { - fn arbitrary(rng: &mut R) -> Self { - match rng.gen_range(0..4) { - 0 => ColumnType::Integer, - 1 => ColumnType::Float, - 2 => ColumnType::Text, - 3 => ColumnType::Blob, - _ => unreachable!(), - } - } -} - -#[derive(Clone, Debug, PartialEq)] -enum Value { - Null, - Integer(i64), - Float(f64), - Text(String), - Blob(Vec), -} - -impl ArbitraryFrom> for Value { - fn arbitrary_from(rng: &mut R, values: &Vec<&Value>) -> Self { - if values.is_empty() { - return Value::Null; - } - - let index = rng.gen_range(0..values.len()); - values[index].clone() - } -} - -impl ArbitraryFrom for Value { - fn arbitrary_from(rng: &mut R, column_type: &ColumnType) -> Self { - match column_type { - ColumnType::Integer => Value::Integer(rng.gen_range(i64::MIN..i64::MAX)), - ColumnType::Float => Value::Float(rng.gen_range(-1e10..1e10)), - ColumnType::Text => Value::Text(gen_random_text(rng)), - ColumnType::Blob => Value::Blob(gen_random_text(rng).as_bytes().to_vec()), - } - } -} - -struct LTValue(Value); - -impl ArbitraryFrom> for LTValue { - fn arbitrary_from(rng: &mut R, values: &Vec<&Value>) -> Self { - if values.is_empty() { - return LTValue(Value::Null); - } - - let index = rng.gen_range(0..values.len()); - LTValue::arbitrary_from(rng, values[index]) - } -} - -impl ArbitraryFrom for LTValue { - fn arbitrary_from(rng: &mut R, value: &Value) -> Self { - match value { - Value::Integer(i) => LTValue(Value::Integer(rng.gen_range(i64::MIN..*i - 1))), - Value::Float(f) => LTValue(Value::Float(rng.gen_range(-1e10..*f - 1.0))), - Value::Text(t) => { - // Either shorten the string, or make at least one character smaller and mutate the rest - let mut t = t.clone(); - if rng.gen_bool(0.01) { - t.pop(); - LTValue(Value::Text(t)) - } else { - let mut t = t.chars().map(|c| c as u32).collect::>(); - let index = rng.gen_range(0..t.len()); - t[index] -= 1; - // Mutate the rest of the string - for i in (index + 1)..t.len() { - t[i] = rng.gen_range('a' as u32..='z' as u32); - } - let t = t - .into_iter() - .map(|c| char::from_u32(c).unwrap_or('z')) - .collect::(); - LTValue(Value::Text(t)) - } - } - Value::Blob(b) => { - // Either shorten the blob, or make at least one byte smaller and mutate the rest - let mut b = b.clone(); - if rng.gen_bool(0.01) { - b.pop(); - LTValue(Value::Blob(b)) - } else { - let index = rng.gen_range(0..b.len()); - b[index] -= 1; - // Mutate the rest of the blob - for i in (index + 1)..b.len() { - b[i] = rng.gen_range(0..=255); - } - LTValue(Value::Blob(b)) - } - } - _ => unreachable!(), - } - } -} - -struct GTValue(Value); - -impl ArbitraryFrom> for GTValue { - fn arbitrary_from(rng: &mut R, values: &Vec<&Value>) -> Self { - if values.is_empty() { - return GTValue(Value::Null); - } - - let index = rng.gen_range(0..values.len()); - GTValue::arbitrary_from(rng, values[index]) - } -} - -impl ArbitraryFrom for GTValue { - fn arbitrary_from(rng: &mut R, value: &Value) -> Self { - match value { - Value::Integer(i) => GTValue(Value::Integer(rng.gen_range(*i..i64::MAX))), - Value::Float(f) => GTValue(Value::Float(rng.gen_range(*f..1e10))), - Value::Text(t) => { - // Either lengthen the string, or make at least one character smaller and mutate the rest - let mut t = t.clone(); - if rng.gen_bool(0.01) { - t.push(rng.gen_range(0..=255) as u8 as char); - GTValue(Value::Text(t)) - } else { - let mut t = t.chars().map(|c| c as u32).collect::>(); - let index = rng.gen_range(0..t.len()); - t[index] += 1; - // Mutate the rest of the string - for i in (index + 1)..t.len() { - t[i] = rng.gen_range('a' as u32..='z' as u32); - } - let t = t - .into_iter() - .map(|c| char::from_u32(c).unwrap_or('a')) - .collect::(); - GTValue(Value::Text(t)) - } - } - Value::Blob(b) => { - // Either lengthen the blob, or make at least one byte smaller and mutate the rest - let mut b = b.clone(); - if rng.gen_bool(0.01) { - b.push(rng.gen_range(0..=255)); - GTValue(Value::Blob(b)) - } else { - let index = rng.gen_range(0..b.len()); - b[index] += 1; - // Mutate the rest of the blob - for i in (index + 1)..b.len() { - b[i] = rng.gen_range(0..=255); - } - GTValue(Value::Blob(b)) - } - } - _ => unreachable!(), - } - } -} - -enum Predicate { - And(Vec), // p1 AND p2 AND p3... AND pn - Or(Vec), // p1 OR p2 OR p3... OR pn - Eq(String, Value), // column = Value - Gt(String, Value), // column > Value - Lt(String, Value), // column < Value -} - -// This type represents the potential queries on the database. -enum Query { - Create(Create), - Select(Select), - Insert(Insert), - Delete(Delete), -} - -struct Create { - table: Table, -} - -impl Arbitrary for Create { - fn arbitrary(rng: &mut R) -> Self { - Create { - table: Table::arbitrary(rng), - } - } -} - -struct Select { - table: String, - predicate: Predicate, -} - -impl ArbitraryFrom> for Select { - fn arbitrary_from(rng: &mut R, tables: &Vec
) -> Self { - let table = rng.gen_range(0..tables.len()); - Select { - table: tables[table].name.clone(), - predicate: Predicate::arbitrary_from(rng, &tables[table]), - } - } -} - -impl ArbitraryFrom> for Select { - fn arbitrary_from(rng: &mut R, tables: &Vec<&Table>) -> Self { - let table = rng.gen_range(0..tables.len()); - Select { - table: tables[table].name.clone(), - predicate: Predicate::arbitrary_from(rng, tables[table]), - } - } -} - -struct Insert { - table: String, - values: Vec, -} - -impl ArbitraryFrom
for Insert { - fn arbitrary_from(rng: &mut R, table: &Table) -> Self { - let values = table - .columns - .iter() - .map(|c| Value::arbitrary_from(rng, &c.column_type)) - .collect(); - Insert { - table: table.name.clone(), - values, - } - } -} - -struct Delete { - table: String, - predicate: Predicate, -} - -impl ArbitraryFrom
for Delete { - fn arbitrary_from(rng: &mut R, table: &Table) -> Self { - Delete { - table: table.name.clone(), - predicate: Predicate::arbitrary_from(rng, table), - } - } -} - -impl ArbitraryFrom
for Query { - fn arbitrary_from(rng: &mut R, table: &Table) -> Self { - match rng.gen_range(0..=200) { - 0 => Query::Create(Create::arbitrary(rng)), - 1..=100 => Query::Select(Select::arbitrary_from(rng, &vec![table])), - 101..=200 => Query::Insert(Insert::arbitrary_from(rng, table)), - // todo: This branch is currently never taken, as DELETE is not yet implemented. - // Change this when DELETE is implemented. - 201..=300 => Query::Delete(Delete::arbitrary_from(rng, table)), - _ => unreachable!(), - } - } -} - -struct CompoundPredicate(Predicate); -struct SimplePredicate(Predicate); - -impl ArbitraryFrom<(&Table, bool)> for SimplePredicate { - fn arbitrary_from(rng: &mut R, (table, predicate_value): &(&Table, bool)) -> Self { - // Pick a random column - let column_index = rng.gen_range(0..table.columns.len()); - let column = &table.columns[column_index]; - let column_values = table - .rows - .iter() - .map(|r| &r[column_index]) - .collect::>(); - // Pick an operator - let operator = match rng.gen_range(0..3) { - 0 => { - if *predicate_value { - Predicate::Eq( - column.name.clone(), - Value::arbitrary_from(rng, &column_values), - ) - } else { - Predicate::Eq( - column.name.clone(), - Value::arbitrary_from(rng, &column.column_type), - ) - } - } - 1 => Predicate::Gt( - column.name.clone(), - match predicate_value { - true => GTValue::arbitrary_from(rng, &column_values).0, - false => LTValue::arbitrary_from(rng, &column_values).0, - }, - ), - 2 => Predicate::Lt( - column.name.clone(), - match predicate_value { - true => LTValue::arbitrary_from(rng, &column_values).0, - false => GTValue::arbitrary_from(rng, &column_values).0, - }, - ), - _ => unreachable!(), - }; - - SimplePredicate(operator) - } -} - -impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate { - fn arbitrary_from(rng: &mut R, (table, predicate_value): &(&Table, bool)) -> Self { - // Decide if you want to create an AND or an OR - CompoundPredicate(if rng.gen_bool(0.7) { - // An AND for true requires each of its children to be true - // An AND for false requires at least one of its children to be false - if *predicate_value { - Predicate::And( - (0..rng.gen_range(1..=3)) - .map(|_| SimplePredicate::arbitrary_from(rng, &(*table, true)).0) - .collect(), - ) - } else { - // Create a vector of random booleans - let mut booleans = (0..rng.gen_range(1..=3)) - .map(|_| rng.gen_bool(0.5)) - .collect::>(); - - let len = booleans.len(); - - // Make sure at least one of them is false - if booleans.iter().all(|b| *b) { - booleans[rng.gen_range(0..len)] = false; - } - - Predicate::And( - booleans - .iter() - .map(|b| SimplePredicate::arbitrary_from(rng, &(*table, *b)).0) - .collect(), - ) - } - } else { - // An OR for true requires at least one of its children to be true - // An OR for false requires each of its children to be false - if *predicate_value { - // Create a vector of random booleans - let mut booleans = (0..rng.gen_range(1..=3)) - .map(|_| rng.gen_bool(0.5)) - .collect::>(); - let len = booleans.len(); - // Make sure at least one of them is true - if booleans.iter().all(|b| !*b) { - booleans[rng.gen_range(0..len)] = true; - } - - Predicate::Or( - booleans - .iter() - .map(|b| SimplePredicate::arbitrary_from(rng, &(*table, *b)).0) - .collect(), - ) - } else { - Predicate::Or( - (0..rng.gen_range(1..=3)) - .map(|_| SimplePredicate::arbitrary_from(rng, &(*table, false)).0) - .collect(), - ) - } - }) - } -} - -impl ArbitraryFrom
for Predicate { - fn arbitrary_from(rng: &mut R, table: &Table) -> Self { - let predicate_value = rng.gen_bool(0.5); - CompoundPredicate::arbitrary_from(rng, &(table, predicate_value)).0 - } -} - -impl ArbitraryFrom<(&str, &Value)> for Predicate { - fn arbitrary_from(rng: &mut R, (column_name, value): &(&str, &Value)) -> Self { - match rng.gen_range(0..3) { - 0 => Predicate::Eq(column_name.to_string(), (*value).clone()), - 1 => Predicate::Gt( - column_name.to_string(), - LTValue::arbitrary_from(rng, *value).0, - ), - 2 => Predicate::Lt( - column_name.to_string(), - LTValue::arbitrary_from(rng, *value).0, - ), - _ => unreachable!(), - } - } -} - -impl Display for Predicate { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Predicate::And(predicates) => { - if predicates.is_empty() { - // todo: Make this TRUE when the bug is fixed - write!(f, "TRUE") - } else { - write!(f, "(")?; - for (i, p) in predicates.iter().enumerate() { - if i != 0 { - write!(f, " AND ")?; - } - write!(f, "{}", p)?; - } - write!(f, ")") - } - } - Predicate::Or(predicates) => { - if predicates.is_empty() { - write!(f, "FALSE") - } else { - write!(f, "(")?; - for (i, p) in predicates.iter().enumerate() { - if i != 0 { - write!(f, " OR ")?; - } - write!(f, "{}", p)?; - } - write!(f, ")") - } - } - Predicate::Eq(name, value) => write!(f, "{} = {}", name, value), - Predicate::Gt(name, value) => write!(f, "{} > {}", name, value), - Predicate::Lt(name, value) => write!(f, "{} < {}", name, value), - } - } -} - -impl Display for Query { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Query::Create(Create { table }) => write!(f, "{}", table.to_create_str()), - Query::Select(Select { - table, - predicate: guard, - }) => write!(f, "SELECT * FROM {} WHERE {}", table, guard), - Query::Insert(Insert { table, values }) => { - write!(f, "INSERT INTO {} VALUES (", table)?; - for (i, v) in values.iter().enumerate() { - if i != 0 { - write!(f, ", ")?; - } - write!(f, "{}", v)?; - } - write!(f, ")") - } - Query::Delete(Delete { - table, - predicate: guard, - }) => write!(f, "DELETE FROM {} WHERE {}", table, guard), - } - } -} #[allow(clippy::arc_with_non_send_sync)] fn main() { @@ -652,68 +137,6 @@ fn main() { env.io.print_stats(); } -fn property_insert_select(env: &mut SimulatorEnv, conn: &mut Rc) { - // Get a random table - let table = env.rng.gen_range(0..env.tables.len()); - - // Pick a random column - let column_index = env.rng.gen_range(0..env.tables[table].columns.len()); - let column = &env.tables[table].columns[column_index].clone(); - - let mut rng = env.rng.clone(); - - // Generate a random value of the column type - let value = Value::arbitrary_from(&mut rng, &column.column_type); - - // Create a whole new row - let mut row = Vec::new(); - for (i, column) in env.tables[table].columns.iter().enumerate() { - if i == column_index { - row.push(value.clone()); - } else { - let value = Value::arbitrary_from(&mut rng, &column.column_type); - row.push(value); - } - } - - // Insert the row - let query = Query::Insert(Insert { - table: env.tables[table].name.clone(), - values: row.clone(), - }); - let _ = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); - // Shadow operation on the table - env.tables[table].rows.push(row.clone()); - - // Create a query that selects the row - let query = Query::Select(Select { - table: env.tables[table].name.clone(), - predicate: Predicate::Eq(column.name.clone(), value), - }); - - // Get all rows - let rows = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); - - // Check that the row is there - assert!(rows.iter().any(|r| r == &row)); -} - -fn property_select_all(env: &mut SimulatorEnv, conn: &mut Rc) { - // Get a random table - let table = env.rng.gen_range(0..env.tables.len()); - - // Create a query that selects all rows - let query = Query::Select(Select { - table: env.tables[table].name.clone(), - predicate: Predicate::And(Vec::new()), - }); - - // Get all rows - let rows = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); - - // Make sure the rows are the same - compare_equal_rows(&rows, &env.tables[table].rows); -} fn process_connection(env: &mut SimulatorEnv, conn: &mut Rc) -> Result<()> { if env.tables.is_empty() { @@ -827,38 +250,7 @@ fn maybe_add_table(env: &mut SimulatorEnv, conn: &mut Rc) -> Result< Ok(()) } -struct Name(String); -impl Arbitrary for Name { - fn arbitrary(rng: &mut R) -> Self { - let name = readable_name_custom("_", rng); - Name(name.replace("-", "_")) - } -} - -impl Deref for Name { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -fn gen_random_text(rng: &mut T) -> String { - let big_text = rng.gen_ratio(1, 1000); - if big_text { - let max_size: u64 = 2 * 1024 * 1024 * 1024; - let size = rng.gen_range(1024..max_size); - let mut name = String::new(); - for i in 0..size { - name.push(((i % 26) as u8 + b'A') as char); - } - name - } else { - let name = readable_name_custom("_", rng); - name.replace("-", "_") - } -} fn get_all_rows( env: &mut SimulatorEnv, @@ -1086,48 +478,3 @@ impl Drop for SimulatorFile { } } -impl ColumnType { - pub fn as_str(&self) -> &str { - match self { - ColumnType::Integer => "INTEGER", - ColumnType::Float => "FLOAT", - ColumnType::Text => "TEXT", - ColumnType::Blob => "BLOB", - } - } -} - -impl Table { - pub fn to_create_str(&self) -> String { - let mut out = String::new(); - - out.push_str(format!("CREATE TABLE {} (", self.name).as_str()); - - assert!(!self.columns.is_empty()); - for column in &self.columns { - out.push_str(format!("{} {},", column.name, column.column_type.as_str()).as_str()); - } - // remove last comma - out.pop(); - - out.push_str(");"); - out - } -} - -impl Display for Value { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Value::Null => write!(f, "NULL"), - Value::Integer(i) => write!(f, "{}", i), - Value::Float(fl) => write!(f, "{}", fl), - Value::Text(t) => write!(f, "'{}'", t), - Value::Blob(b) => write!(f, "{}", to_sqlite_blob(b)), - } - } -} - -fn to_sqlite_blob(bytes: &[u8]) -> String { - let hex: String = bytes.iter().map(|b| format!("{:02X}", b)).collect(); - format!("X'{}'", hex) -} diff --git a/simulator/model.rs b/simulator/model.rs new file mode 100644 index 000000000..5c8b5f5e0 --- /dev/null +++ b/simulator/model.rs @@ -0,0 +1,3 @@ + +pub mod table; +pub mod query; diff --git a/simulator/model/query.rs b/simulator/model/query.rs new file mode 100644 index 000000000..26dc75982 --- /dev/null +++ b/simulator/model/query.rs @@ -0,0 +1,111 @@ +use std::fmt::Display; + +use crate::model::table::{Table, Value}; + + +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum Predicate { + And(Vec), // p1 AND p2 AND p3... AND pn + Or(Vec), // p1 OR p2 OR p3... OR pn + Eq(String, Value), // column = Value + Gt(String, Value), // column > Value + Lt(String, Value), // column < Value +} + +impl Display for Predicate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Predicate::And(predicates) => { + if predicates.is_empty() { + // todo: Make this TRUE when the bug is fixed + write!(f, "TRUE") + } else { + write!(f, "(")?; + for (i, p) in predicates.iter().enumerate() { + if i != 0 { + write!(f, " AND ")?; + } + write!(f, "{}", p)?; + } + write!(f, ")") + } + } + Predicate::Or(predicates) => { + if predicates.is_empty() { + write!(f, "FALSE") + } else { + write!(f, "(")?; + for (i, p) in predicates.iter().enumerate() { + if i != 0 { + write!(f, " OR ")?; + } + write!(f, "{}", p)?; + } + write!(f, ")") + } + } + Predicate::Eq(name, value) => write!(f, "{} = {}", name, value), + Predicate::Gt(name, value) => write!(f, "{} > {}", name, value), + Predicate::Lt(name, value) => write!(f, "{} < {}", name, value), + } + } +} + +// This type represents the potential queries on the database. +#[derive(Debug)] +pub(crate) enum Query { + Create(Create), + Select(Select), + Insert(Insert), + Delete(Delete), +} + +#[derive(Debug)] +pub(crate) struct Create { + pub(crate) table: Table, +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct Select { + pub(crate) table: String, + pub(crate) predicate: Predicate, +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct Insert { + pub(crate) table: String, + pub(crate) values: Vec, +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct Delete { + pub(crate) table: String, + pub(crate) predicate: Predicate, +} + +impl Display for Query { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Query::Create(Create { table }) => write!(f, "{}", table.to_create_str()), + Query::Select(Select { + table, + predicate: guard, + }) => write!(f, "SELECT * FROM {} WHERE {}", table, guard), + Query::Insert(Insert { table, values }) => { + write!(f, "INSERT INTO {} VALUES (", table)?; + for (i, v) in values.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + write!(f, "{}", v)?; + } + write!(f, ")") + } + Query::Delete(Delete { + table, + predicate: guard, + }) => write!(f, "DELETE FROM {} WHERE {}", table, guard), + } + } +} + diff --git a/simulator/model/table.rs b/simulator/model/table.rs new file mode 100644 index 000000000..1797e54bf --- /dev/null +++ b/simulator/model/table.rs @@ -0,0 +1,91 @@ + +use std::{fmt::Display, ops::Deref}; + +pub(crate) struct Name(pub(crate) String); + +impl Deref for Name { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug)] +pub(crate) struct Table { + pub(crate) rows: Vec>, + pub(crate) name: String, + pub(crate) columns: Vec, +} + +impl Table { + pub fn to_create_str(&self) -> String { + let mut out = String::new(); + + out.push_str(format!("CREATE TABLE {} (", self.name).as_str()); + + assert!(!self.columns.is_empty()); + for column in &self.columns { + out.push_str(format!("{} {},", column.name, column.column_type.as_str()).as_str()); + } + // remove last comma + out.pop(); + + out.push_str(");"); + out + } +} + +#[derive(Debug, Clone)] +pub(crate) struct Column { + pub(crate) name: String, + pub(crate) column_type: ColumnType, + pub(crate) primary: bool, + pub(crate) unique: bool, +} + +#[derive(Debug, Clone)] +pub(crate) enum ColumnType { + Integer, + Float, + Text, + Blob, +} + +impl ColumnType { + pub fn as_str(&self) -> &str { + match self { + ColumnType::Integer => "INTEGER", + ColumnType::Float => "FLOAT", + ColumnType::Text => "TEXT", + ColumnType::Blob => "BLOB", + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum Value { + Null, + Integer(i64), + Float(f64), + Text(String), + Blob(Vec), +} + +fn to_sqlite_blob(bytes: &[u8]) -> String { + let hex: String = bytes.iter().map(|b| format!("{:02X}", b)).collect(); + format!("X'{}'", hex) +} + +impl Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Value::Null => write!(f, "NULL"), + Value::Integer(i) => write!(f, "{}", i), + Value::Float(fl) => write!(f, "{}", fl), + Value::Text(t) => write!(f, "'{}'", t), + Value::Blob(b) => write!(f, "{}", to_sqlite_blob(b)), + } + } +} + diff --git a/simulator/properties.rs b/simulator/properties.rs new file mode 100644 index 000000000..a6536d1d8 --- /dev/null +++ b/simulator/properties.rs @@ -0,0 +1,78 @@ +use std::rc::Rc; + +use limbo_core::Connection; +use rand::Rng; + +use crate::{ + compare_equal_rows, + generation::ArbitraryFrom, + get_all_rows, + model::{ + query::{Insert, Predicate, Query, Select}, + table::Value, + }, + SimulatorEnv, +}; + +pub fn property_insert_select(env: &mut SimulatorEnv, conn: &mut Rc) { + // Get a random table + let table = env.rng.gen_range(0..env.tables.len()); + + // Pick a random column + let column_index = env.rng.gen_range(0..env.tables[table].columns.len()); + let column = &env.tables[table].columns[column_index].clone(); + + let mut rng = env.rng.clone(); + + // Generate a random value of the column type + let value = Value::arbitrary_from(&mut rng, &column.column_type); + + // Create a whole new row + let mut row = Vec::new(); + for (i, column) in env.tables[table].columns.iter().enumerate() { + if i == column_index { + row.push(value.clone()); + } else { + let value = Value::arbitrary_from(&mut rng, &column.column_type); + row.push(value); + } + } + + // Insert the row + let query = Query::Insert(Insert { + table: env.tables[table].name.clone(), + values: row.clone(), + }); + let _ = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); + // Shadow operation on the table + env.tables[table].rows.push(row.clone()); + + // Create a query that selects the row + let query = Query::Select(Select { + table: env.tables[table].name.clone(), + predicate: Predicate::Eq(column.name.clone(), value), + }); + + // Get all rows + let rows = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); + + // Check that the row is there + assert!(rows.iter().any(|r| r == &row)); +} + +pub fn property_select_all(env: &mut SimulatorEnv, conn: &mut Rc) { + // Get a random table + let table = env.rng.gen_range(0..env.tables.len()); + + // Create a query that selects all rows + let query = Query::Select(Select { + table: env.tables[table].name.clone(), + predicate: Predicate::And(Vec::new()), + }); + + // Get all rows + let rows = get_all_rows(env, conn, query.to_string().as_str()).unwrap(); + + // Make sure the rows are the same + compare_equal_rows(&rows, &env.tables[table].rows); +} From 53ecedaceb492c7b7dacc105d80001ed5c66e850 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Mon, 16 Dec 2024 10:49:56 -0500 Subject: [PATCH 12/19] fix formatting --- simulator/README.md | 74 ++++++++++++++++++++++++++++++++++- simulator/generation.rs | 5 +-- simulator/generation/query.rs | 8 +--- simulator/generation/table.rs | 6 +-- simulator/main.rs | 10 +---- simulator/model.rs | 3 +- simulator/model/query.rs | 2 - simulator/model/table.rs | 2 - 8 files changed, 79 insertions(+), 31 deletions(-) diff --git a/simulator/README.md b/simulator/README.md index cdaf20c6f..ed1f688b9 100644 --- a/simulator/README.md +++ b/simulator/README.md @@ -1,2 +1,74 @@ -# Simulator +# Limbo Simulator +Limbo simulator uses randomized deterministic simulations to test the Limbo database behaviors. + +Each simulations begins with a random configurations; + +- the database workload distribution(percentages of reads, writes, deletes...), +- database parameters(page size), +- number of reader or writers, etc. + +Based on these parameters, we randomly generate **interaction plans**. Interaction plans consist of statements/queries, and assertions that will be executed in order. The building blocks of interaction plans are; + +- Randomly generated SQL queries satisfying the workload distribution, +- Properties, which contain multiple matching queries with assertions indicating the expected result. + +An example of a property is the following: + +```json +{ + "name": "Read your own writes", + "queries": [ + "INSERT INTO t1 (id) VALUES (1)", + "SELECT * FROM t1 WHERE id = 1", + ], + "assertions": [ + "result.rows.length == 1", + "result.rows[0].id == 1" + ] +} +``` + +The simulator executes the interaction plans in a loop, and checks the assertions. It can add random queries unrelated to the properties without +breaking the property invariants to reach more diverse states and respect the configured workload distribution. + +The simulator code is broken into 4 main parts: + +- **Simulator(main.rs)**: The main entry point of the simulator. It generates random configurations and interaction plans, and executes them. +- **Model(model.rs, model/table.rs, model/query.rs)**: A simpler model of the database, it contains atomic actions for insertion and selection, we use this model while deciding the next actions. +- **Generation(generation.rs, generation/table.rs, generation/query.rs, generation/plan.rs)**: Random generation functions for the database model and interaction plans. +- **Properties(properties.rs)**: Contains the properties that we want to test. + +## Running the simulator + +To run the simulator, you can use the following command: + +```bash +cargo run +``` + +This prompt (in the future) will invoke a clap command line interface to configure the simulator. For now, the simulator runs with the default configurations changing the `main.rs` file. If you want to see the logs, you can change the `RUST_LOG` environment variable. + +```bash +RUST_LOG=info cargo run --bin limbo_sim +``` + +## Adding new properties + +Todo + +## Adding new generation functions + +Todo + +## Adding new models + +Todo + +## Coverage with Limbo + +Todo + +## Automatic Compatibility Testing with SQLite + +Todo \ No newline at end of file diff --git a/simulator/generation.rs b/simulator/generation.rs index 73f84c11c..ece3a2c3b 100644 --- a/simulator/generation.rs +++ b/simulator/generation.rs @@ -1,8 +1,8 @@ use anarchist_readable_name_generator_lib::readable_name_custom; use rand::Rng; -pub mod table; pub mod query; +pub mod table; pub trait Arbitrary { fn arbitrary(rng: &mut R) -> Self; @@ -12,7 +12,6 @@ pub trait ArbitraryFrom { fn arbitrary_from(rng: &mut R, t: &T) -> Self; } - fn gen_random_text(rng: &mut T) -> String { let big_text = rng.gen_ratio(1, 1000); if big_text { @@ -28,5 +27,3 @@ fn gen_random_text(rng: &mut T) -> String { name.replace("-", "_") } } - - diff --git a/simulator/generation/query.rs b/simulator/generation/query.rs index d8fc4ae16..748409972 100644 --- a/simulator/generation/query.rs +++ b/simulator/generation/query.rs @@ -1,12 +1,10 @@ - +use crate::generation::table::{GTValue, LTValue}; use crate::generation::{Arbitrary, ArbitraryFrom}; -use crate::generation::table::{LTValue, GTValue}; use crate::model::query::{Create, Delete, Insert, Predicate, Query, Select}; use crate::model::table::{Table, Value}; use rand::Rng; - impl Arbitrary for Create { fn arbitrary(rng: &mut R) -> Self { Create { @@ -15,7 +13,6 @@ impl Arbitrary for Create { } } - impl ArbitraryFrom> for Select { fn arbitrary_from(rng: &mut R, tables: &Vec
) -> Self { let table = rng.gen_range(0..tables.len()); @@ -36,7 +33,6 @@ impl ArbitraryFrom> for Select { } } - impl ArbitraryFrom
for Insert { fn arbitrary_from(rng: &mut R, table: &Table) -> Self { let values = table @@ -51,7 +47,6 @@ impl ArbitraryFrom
for Insert { } } - impl ArbitraryFrom
for Delete { fn arbitrary_from(rng: &mut R, table: &Table) -> Self { Delete { @@ -75,7 +70,6 @@ impl ArbitraryFrom
for Query { } } - struct CompoundPredicate(Predicate); struct SimplePredicate(Predicate); diff --git a/simulator/generation/table.rs b/simulator/generation/table.rs index 8b1d6d421..8d5d70e98 100644 --- a/simulator/generation/table.rs +++ b/simulator/generation/table.rs @@ -1,7 +1,6 @@ - use rand::Rng; -use crate::generation::{Arbitrary, ArbitraryFrom, readable_name_custom, gen_random_text}; +use crate::generation::{gen_random_text, readable_name_custom, Arbitrary, ArbitraryFrom}; use crate::model::table::{Column, ColumnType, Name, Table, Value}; impl Arbitrary for Name { @@ -11,7 +10,6 @@ impl Arbitrary for Name { } } - impl Arbitrary for Table { fn arbitrary(rng: &mut R) -> Self { let name = Name::arbitrary(rng).0; @@ -39,7 +37,6 @@ impl Arbitrary for Column { } } - impl Arbitrary for ColumnType { fn arbitrary(rng: &mut R) -> Self { match rng.gen_range(0..4) { @@ -193,4 +190,3 @@ impl ArbitraryFrom for GTValue { } } } - diff --git a/simulator/main.rs b/simulator/main.rs index bfc9b658f..11c171b25 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -1,7 +1,7 @@ use generation::{Arbitrary, ArbitraryFrom}; use limbo_core::{Connection, Database, File, OpenFlags, PlatformIO, Result, RowResult, IO}; -use model::table::{Column, Name, Table, Value}; use model::query::{Insert, Predicate, Query, Select}; +use model::table::{Column, Name, Table, Value}; use properties::{property_insert_select, property_select_all}; use rand::prelude::*; use rand_chacha::ChaCha8Rng; @@ -11,8 +11,8 @@ use std::sync::Arc; use tempfile::TempDir; mod generation; -mod properties; mod model; +mod properties; struct SimulatorEnv { opts: SimulatorOpts, @@ -49,8 +49,6 @@ struct SimulatorOpts { page_size: usize, } - - #[allow(clippy::arc_with_non_send_sync)] fn main() { let _ = env_logger::try_init(); @@ -137,7 +135,6 @@ fn main() { env.io.print_stats(); } - fn process_connection(env: &mut SimulatorEnv, conn: &mut Rc) -> Result<()> { if env.tables.is_empty() { maybe_add_table(env, conn)?; @@ -250,8 +247,6 @@ fn maybe_add_table(env: &mut SimulatorEnv, conn: &mut Rc) -> Result< Ok(()) } - - fn get_all_rows( env: &mut SimulatorEnv, conn: &mut Rc, @@ -477,4 +472,3 @@ impl Drop for SimulatorFile { self.inner.unlock_file().expect("Failed to unlock file"); } } - diff --git a/simulator/model.rs b/simulator/model.rs index 5c8b5f5e0..a29f56382 100644 --- a/simulator/model.rs +++ b/simulator/model.rs @@ -1,3 +1,2 @@ - -pub mod table; pub mod query; +pub mod table; diff --git a/simulator/model/query.rs b/simulator/model/query.rs index 26dc75982..20058aead 100644 --- a/simulator/model/query.rs +++ b/simulator/model/query.rs @@ -2,7 +2,6 @@ use std::fmt::Display; use crate::model::table::{Table, Value}; - #[derive(Clone, Debug, PartialEq)] pub(crate) enum Predicate { And(Vec), // p1 AND p2 AND p3... AND pn @@ -108,4 +107,3 @@ impl Display for Query { } } } - diff --git a/simulator/model/table.rs b/simulator/model/table.rs index 1797e54bf..bc018e132 100644 --- a/simulator/model/table.rs +++ b/simulator/model/table.rs @@ -1,4 +1,3 @@ - use std::{fmt::Display, ops::Deref}; pub(crate) struct Name(pub(crate) String); @@ -88,4 +87,3 @@ impl Display for Value { } } } - From 7d4d803a13a3f473d3c1fa509c4226a11baefc3e Mon Sep 17 00:00:00 2001 From: alpaylan Date: Tue, 17 Dec 2024 18:24:39 -0500 Subject: [PATCH 13/19] implement interaction plans --- simulator/generation.rs | 32 +++- simulator/generation/plan.rs | 339 ++++++++++++++++++++++++++++++++++ simulator/generation/table.rs | 2 +- simulator/main.rs | 117 +++++------- simulator/model/table.rs | 2 +- 5 files changed, 414 insertions(+), 78 deletions(-) create mode 100644 simulator/generation/plan.rs diff --git a/simulator/generation.rs b/simulator/generation.rs index ece3a2c3b..26f42f6d6 100644 --- a/simulator/generation.rs +++ b/simulator/generation.rs @@ -2,6 +2,7 @@ use anarchist_readable_name_generator_lib::readable_name_custom; use rand::Rng; pub mod query; +pub mod plan; pub mod table; pub trait Arbitrary { @@ -12,10 +13,39 @@ pub trait ArbitraryFrom { fn arbitrary_from(rng: &mut R, t: &T) -> Self; } +pub(crate) fn frequency<'a, T, R: rand::Rng>(choices: Vec<(usize, Box T + 'a>)>, rng: &mut R) -> T { + let total = choices.iter().map(|(weight, _)| weight).sum::(); + let mut choice = rng.gen_range(0..total); + + for (weight, f) in choices { + if choice < weight { + return f(rng); + } + choice -= weight; + } + + unreachable!() +} + +pub(crate) fn one_of(choices: Vec T>>, rng: &mut R) -> T { + let index = rng.gen_range(0..choices.len()); + choices[index](rng) +} + +pub(crate) fn pick<'a, T, R: rand::Rng>(choices: &'a Vec, rng: &mut R) -> &'a T { + let index = rng.gen_range(0..choices.len()); + &choices[index] +} + +pub(crate) fn pick_index(choices: usize, rng: &mut R) -> usize { + rng.gen_range(0..choices) +} + fn gen_random_text(rng: &mut T) -> String { let big_text = rng.gen_ratio(1, 1000); if big_text { - let max_size: u64 = 2 * 1024 * 1024 * 1024; + // let max_size: u64 = 2 * 1024 * 1024 * 1024; + let max_size: u64 = 2 * 1024; // todo: change this back to 2 * 1024 * 1024 * 1024 let size = rng.gen_range(1024..max_size); let mut name = String::new(); for i in 0..size { diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs new file mode 100644 index 000000000..6e5e6d951 --- /dev/null +++ b/simulator/generation/plan.rs @@ -0,0 +1,339 @@ +use std::{f32::consts::E, fmt::Display, os::macos::raw::stat, rc::Rc}; + +use limbo_core::{Connection, Result, RowResult}; +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; + +use crate::{ + model::{ + query::{Create, Insert, Predicate, Query, Select}, + table::Value, + }, + SimulatorEnv, SimulatorOpts, +}; + +use crate::generation::{frequency, Arbitrary, ArbitraryFrom}; + +use super::{pick, pick_index}; + +pub(crate) type ResultSet = Vec>; + +pub(crate) struct InteractionPlan { + pub(crate) plan: Vec, + pub(crate) stack: Vec, +} + +impl Display for InteractionPlan { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for interaction in &self.plan { + match interaction { + Interaction::Query(query) => write!(f, "{};\n", query)?, + Interaction::Assertion(assertion) => write!(f, "-- ASSERT: {};\n", assertion.message)?, + } + } + + Ok(()) + } +} + +#[derive(Debug)] +pub(crate) struct InteractionStats { + pub(crate) read_count: usize, + pub(crate) write_count: usize, + pub(crate) delete_count: usize, +} + +impl Display for InteractionStats { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Read: {}, Write: {}, Delete: {}", + self.read_count, self.write_count, self.delete_count + ) + } +} + +pub(crate) enum Interaction { + Query(Query), + Assertion(Assertion), +} + +impl Display for Interaction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Interaction::Query(query) => write!(f, "{}", query), + Interaction::Assertion(assertion) => write!(f, "ASSERT: {}", assertion.message), + } + } +} + +pub(crate) struct Assertion { + pub(crate) func: Box) -> bool>, + pub(crate) message: String, +} + +pub(crate) struct Interactions(Vec); + +impl Interactions { + pub(crate) fn shadow(&self, env: &mut SimulatorEnv) { + for interaction in &self.0 { + match interaction { + Interaction::Query(query) => match query { + Query::Create(create) => { + env.tables.push(create.table.clone()); + } + Query::Insert(insert) => { + let table = env + .tables + .iter_mut() + .find(|t| t.name == insert.table) + .unwrap(); + table.rows.push(insert.values.clone()); + } + Query::Delete(_) => todo!(), + Query::Select(_) => {} + }, + Interaction::Assertion(_) => {} + } + } + } +} + +impl InteractionPlan { + pub(crate) fn new() -> Self { + InteractionPlan { + plan: Vec::new(), + stack: Vec::new(), + } + } + + pub(crate) fn push(&mut self, interaction: Interaction) { + self.plan.push(interaction); + } + + pub(crate) fn stats(&self) -> InteractionStats { + let mut read = 0; + let mut write = 0; + let mut delete = 0; + + for interaction in &self.plan { + match interaction { + Interaction::Query(query) => match query { + Query::Select(_) => read += 1, + Query::Insert(_) => write += 1, + Query::Delete(_) => delete += 1, + Query::Create(_) => {} + }, + Interaction::Assertion(_) => {} + } + } + + InteractionStats { + read_count: read, + write_count: write, + delete_count: delete, + } + } +} + +impl ArbitraryFrom for InteractionPlan { + fn arbitrary_from(rng: &mut R, env: &SimulatorEnv) -> Self { + let mut plan = InteractionPlan::new(); + + let mut env = SimulatorEnv { + opts: env.opts.clone(), + tables: vec![], + connections: vec![], + io: env.io.clone(), + db: env.db.clone(), + rng: ChaCha8Rng::seed_from_u64(rng.next_u64()), + }; + + let num_interactions = rng.gen_range(0..env.opts.max_interactions); + + // First create at least one table + let create_query = Create::arbitrary(rng); + env.tables.push(create_query.table.clone()); + plan.push(Interaction::Query(Query::Create(create_query))); + + while plan.plan.len() < num_interactions { + log::debug!( + "Generating interaction {}/{}", + plan.plan.len(), + num_interactions + ); + let interactions = Interactions::arbitrary_from(rng, &(&env, plan.stats())); + interactions.shadow(&mut env); + + plan.plan.extend(interactions.0.into_iter()); + } + + log::info!("Generated plan with {} interactions", plan.plan.len()); + plan + } +} + +impl Interaction { + pub(crate) fn execute_query(&self, conn: &mut Rc) -> Result { + match self { + Interaction::Query(query) => { + let query_str = query.to_string(); + let rows = conn.query(&query_str); + if rows.is_err() { + let err = rows.err(); + log::error!( + "Error running query '{}': {:?}", + &query_str[0..query_str.len().min(4096)], + err + ); + return Err(err.unwrap()); + } + let rows = rows.unwrap(); + assert!(rows.is_some()); + let mut rows = rows.unwrap(); + let mut out = Vec::new(); + while let Ok(row) = rows.next_row() { + match row { + RowResult::Row(row) => { + let mut r = Vec::new(); + for el in &row.values { + let v = match el { + limbo_core::Value::Null => Value::Null, + limbo_core::Value::Integer(i) => Value::Integer(*i), + limbo_core::Value::Float(f) => Value::Float(*f), + limbo_core::Value::Text(t) => Value::Text(t.to_string()), + limbo_core::Value::Blob(b) => Value::Blob(b.to_vec()), + }; + r.push(v); + } + + out.push(r); + } + RowResult::IO => {} + RowResult::Done => { + break; + } + } + } + + Ok(out) + } + Interaction::Assertion(_) => { + unreachable!("unexpected: this function should only be called on queries") + } + } + } + + pub(crate) fn execute_assertion(&self, stack: &Vec) -> Result<()> { + match self { + Interaction::Query(_) => { + unreachable!("unexpected: this function should only be called on assertions") + } + Interaction::Assertion(assertion) => { + if !assertion.func.as_ref()(stack) { + return Err(limbo_core::LimboError::InternalError( + assertion.message.clone(), + )); + } + Ok(()) + } + } + } +} + +fn property_insert_select(rng: &mut R, env: &SimulatorEnv) -> Interactions { + // Get a random table + let table = pick(&env.tables, rng); + // Pick a random column + let column_index = pick_index(table.columns.len(), rng); + let column = &table.columns[column_index].clone(); + // Generate a random value of the column type + let value = Value::arbitrary_from(rng, &column.column_type); + // Create a whole new row + let mut row = Vec::new(); + for (i, column) in table.columns.iter().enumerate() { + if i == column_index { + row.push(value.clone()); + } else { + let value = Value::arbitrary_from(rng, &column.column_type); + row.push(value); + } + } + // Insert the row + let insert_query = Interaction::Query(Query::Insert(Insert { + table: table.name.clone(), + values: row.clone(), + })); + + // Select the row + let select_query = Interaction::Query(Query::Select(Select { + table: table.name.clone(), + predicate: Predicate::Eq(column.name.clone(), value.clone()), + })); + + // Check that the row is there + let assertion = Interaction::Assertion(Assertion { + message: format!( + "row [{:?}] not found in table {} after inserting ({} = {})", + row.iter().map(|v| v.to_string()).collect::>(), + table.name, + column.name, + value, + ), + func: Box::new(move |stack: &Vec| { + let rows = stack.last().unwrap(); + rows.iter().any(|r| r == &row) + }), + }); + + Interactions(vec![insert_query, select_query, assertion]) +} + +fn create_table(rng: &mut R, env: &SimulatorEnv) -> Interactions { + let create_query = Interaction::Query(Query::Create(Create::arbitrary(rng))); + Interactions(vec![create_query]) +} + +fn random_read(rng: &mut R, env: &SimulatorEnv) -> Interactions { + let select_query = Interaction::Query(Query::Select(Select::arbitrary_from(rng, &env.tables))); + Interactions(vec![select_query]) +} + +fn random_write(rng: &mut R, env: &SimulatorEnv) -> Interactions { + let table = pick(&env.tables, rng); + let insert_query = Interaction::Query(Query::Insert(Insert::arbitrary_from(rng, table))); + Interactions(vec![insert_query]) +} + +impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions { + fn arbitrary_from( + rng: &mut R, + (env, stats): &(&SimulatorEnv, InteractionStats), + ) -> Self { + let remaining_read = + ((((env.opts.max_interactions * env.opts.read_percent) as f64) / 100.0) as usize) + .saturating_sub(stats.read_count); + let remaining_write = ((((env.opts.max_interactions * env.opts.write_percent) as f64) + / 100.0) as usize) + .saturating_sub(stats.write_count); + + frequency( + vec![ + ( + usize::min(remaining_read, remaining_write), + Box::new(|rng: &mut R| property_insert_select(rng, env)), + ), + ( + remaining_read, + Box::new(|rng: &mut R| random_read(rng, env)), + ), + ( + remaining_write, + Box::new(|rng: &mut R| random_write(rng, env)), + ), + (1, Box::new(|rng: &mut R| create_table(rng, env))), + ], + rng, + ) + } +} diff --git a/simulator/generation/table.rs b/simulator/generation/table.rs index 8d5d70e98..685171207 100644 --- a/simulator/generation/table.rs +++ b/simulator/generation/table.rs @@ -13,7 +13,7 @@ impl Arbitrary for Name { impl Arbitrary for Table { fn arbitrary(rng: &mut R) -> Self { let name = Name::arbitrary(rng).0; - let columns = (1..rng.gen_range(1..128)) + let columns = (1..=rng.gen_range(1..10)) .map(|_| Column::arbitrary(rng)) .collect(); Table { diff --git a/simulator/main.rs b/simulator/main.rs index 11c171b25..c7ddc9bce 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -1,4 +1,5 @@ -use generation::{Arbitrary, ArbitraryFrom}; +use generation::plan::{Interaction, ResultSet}; +use generation::{pick, pick_index, Arbitrary, ArbitraryFrom}; use limbo_core::{Connection, Database, File, OpenFlags, PlatformIO, Result, RowResult, IO}; use model::query::{Insert, Predicate, Query, Select}; use model::table::{Column, Name, Table, Value}; @@ -6,6 +7,7 @@ use properties::{property_insert_select, property_select_all}; use rand::prelude::*; use rand_chacha::ChaCha8Rng; use std::cell::RefCell; +use std::io::Write; use std::rc::Rc; use std::sync::Arc; use tempfile::TempDir; @@ -29,13 +31,7 @@ enum SimConnection { Disconnected, } -#[derive(Debug, Copy, Clone)] -enum SimulatorMode { - Random, - Workload, -} - -#[derive(Debug)] +#[derive(Debug, Clone)] struct SimulatorOpts { ticks: usize, max_connections: usize, @@ -45,7 +41,7 @@ struct SimulatorOpts { read_percent: usize, write_percent: usize, delete_percent: usize, - mode: SimulatorMode, + max_interactions: usize, page_size: usize, } @@ -77,8 +73,8 @@ fn main() { read_percent, write_percent, delete_percent, - mode: SimulatorMode::Workload, page_size: 4096, // TODO: randomize this too + max_interactions: rng.gen_range(0..10000), }; let io = Arc::new(SimulatorIO::new(seed, opts.page_size).unwrap()); @@ -104,10 +100,20 @@ fn main() { println!("Initial opts {:?}", env.opts); - for _ in 0..env.opts.ticks { - let connection_index = env.rng.gen_range(0..env.opts.max_connections); + log::info!("Generating database interaction plan..."); + let mut plan = generation::plan::InteractionPlan::arbitrary_from(&mut env.rng.clone(), &env); + + log::info!("{}", plan.stats()); + + for interaction in &plan.plan { + let connection_index = pick_index(env.connections.len(), &mut env.rng); let mut connection = env.connections[connection_index].clone(); + if matches!(connection, SimConnection::Disconnected) { + connection = SimConnection::Connected(env.db.connect()); + env.connections[connection_index] = connection.clone(); + } + match &mut connection { SimConnection::Connected(conn) => { let disconnect = env.rng.gen_ratio(1, 100); @@ -116,10 +122,20 @@ fn main() { let _ = conn.close(); env.connections[connection_index] = SimConnection::Disconnected; } else { - match process_connection(&mut env, conn) { - Ok(_) => {} + match process_connection(conn, interaction, &mut plan.stack) { + Ok(_) => { + log::info!("connection {} processed", connection_index); + } Err(err) => { log::error!("error {}", err); + log::debug!("db is at {:?}", path); + // save the interaction plan + let mut path = TempDir::new().unwrap().into_path(); + path.push("simulator.plan"); + let mut f = std::fs::File::create(path.clone()).unwrap(); + f.write(plan.to_string().as_bytes()).unwrap(); + log::debug!("plan saved at {:?}", path); + log::debug!("seed was {}", seed); break; } } @@ -130,73 +146,24 @@ fn main() { env.connections[connection_index] = SimConnection::Connected(env.db.connect()); } } + + } + env.io.print_stats(); } -fn process_connection(env: &mut SimulatorEnv, conn: &mut Rc) -> Result<()> { - if env.tables.is_empty() { - maybe_add_table(env, conn)?; - } - - match env.opts.mode { - SimulatorMode::Random => { - match env.rng.gen_range(0..2) { - // Randomly insert a value and check that the select result contains it. - 0 => property_insert_select(env, conn), - // Check that the current state of the in-memory table is the same as the one in the - // database. - 1 => property_select_all(env, conn), - // Perform a random query, update the in-memory table with the result. - 2 => { - let table_index = env.rng.gen_range(0..env.tables.len()); - let query = Query::arbitrary_from(&mut env.rng, &env.tables[table_index]); - let rows = get_all_rows(env, conn, query.to_string().as_str())?; - env.tables[table_index].rows = rows; - } - _ => unreachable!(), - } +fn process_connection(conn: &mut Rc, interaction: &Interaction, stack: &mut Vec) -> Result<()> { + match interaction { + generation::plan::Interaction::Query(_) => { + log::debug!("{}", interaction); + let results = interaction.execute_query(conn)?; + log::debug!("{:?}", results); + stack.push(results); } - SimulatorMode::Workload => { - let picked = env.rng.gen_range(0..100); - - if env.rng.gen_ratio(1, 100) { - maybe_add_table(env, conn)?; - } - - if picked < env.opts.read_percent { - let query = Select::arbitrary_from(&mut env.rng, &env.tables); - let _ = get_all_rows(env, conn, Query::Select(query).to_string().as_str())?; - } else if picked < env.opts.read_percent + env.opts.write_percent { - let table_index = env.rng.gen_range(0..env.tables.len()); - let column_index = env.rng.gen_range(0..env.tables[table_index].columns.len()); - let column = &env.tables[table_index].columns[column_index].clone(); - let mut rng = env.rng.clone(); - let value = Value::arbitrary_from(&mut rng, &column.column_type); - let mut row = Vec::new(); - for (i, column) in env.tables[table_index].columns.iter().enumerate() { - if i == column_index { - row.push(value.clone()); - } else { - let value = Value::arbitrary_from(&mut rng, &column.column_type); - row.push(value); - } - } - let query = Query::Insert(Insert { - table: env.tables[table_index].name.clone(), - values: row.clone(), - }); - let _ = get_all_rows(env, conn, query.to_string().as_str())?; - env.tables[table_index].rows.push(row.clone()); - } else { - let table_index = env.rng.gen_range(0..env.tables.len()); - let query = Query::Select(Select { - table: env.tables[table_index].name.clone(), - predicate: Predicate::And(Vec::new()), - }); - let _ = get_all_rows(env, conn, query.to_string().as_str())?; - } + generation::plan::Interaction::Assertion(_) => { + interaction.execute_assertion(stack)?; } } diff --git a/simulator/model/table.rs b/simulator/model/table.rs index bc018e132..93c3d6d74 100644 --- a/simulator/model/table.rs +++ b/simulator/model/table.rs @@ -10,7 +10,7 @@ impl Deref for Name { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct Table { pub(crate) rows: Vec>, pub(crate) name: String, From 66e7a4edecade4eb6f5970256ed5debeaf71af49 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Tue, 17 Dec 2024 18:30:55 -0500 Subject: [PATCH 14/19] fix formatting --- simulator/generation.rs | 7 +++++-- simulator/generation/plan.rs | 4 +++- simulator/main.rs | 9 +++++---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/simulator/generation.rs b/simulator/generation.rs index 26f42f6d6..15b5845ec 100644 --- a/simulator/generation.rs +++ b/simulator/generation.rs @@ -1,8 +1,8 @@ use anarchist_readable_name_generator_lib::readable_name_custom; use rand::Rng; -pub mod query; pub mod plan; +pub mod query; pub mod table; pub trait Arbitrary { @@ -13,7 +13,10 @@ pub trait ArbitraryFrom { fn arbitrary_from(rng: &mut R, t: &T) -> Self; } -pub(crate) fn frequency<'a, T, R: rand::Rng>(choices: Vec<(usize, Box T + 'a>)>, rng: &mut R) -> T { +pub(crate) fn frequency<'a, T, R: rand::Rng>( + choices: Vec<(usize, Box T + 'a>)>, + rng: &mut R, +) -> T { let total = choices.iter().map(|(weight, _)| weight).sum::(); let mut choice = rng.gen_range(0..total); diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 6e5e6d951..d7c309b93 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -28,7 +28,9 @@ impl Display for InteractionPlan { for interaction in &self.plan { match interaction { Interaction::Query(query) => write!(f, "{};\n", query)?, - Interaction::Assertion(assertion) => write!(f, "-- ASSERT: {};\n", assertion.message)?, + Interaction::Assertion(assertion) => { + write!(f, "-- ASSERT: {};\n", assertion.message)? + } } } diff --git a/simulator/main.rs b/simulator/main.rs index c7ddc9bce..0f745e7e8 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -146,15 +146,16 @@ fn main() { env.connections[connection_index] = SimConnection::Connected(env.db.connect()); } } - - } - env.io.print_stats(); } -fn process_connection(conn: &mut Rc, interaction: &Interaction, stack: &mut Vec) -> Result<()> { +fn process_connection( + conn: &mut Rc, + interaction: &Interaction, + stack: &mut Vec, +) -> Result<()> { match interaction { generation::plan::Interaction::Query(_) => { log::debug!("{}", interaction); From 39b5dbed5538be7b2c1d9e4cdee82fe04f8b0509 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Wed, 18 Dec 2024 17:09:44 -0500 Subject: [PATCH 15/19] change gen.range based queries into frequency and one_of calls --- simulator/generation.rs | 2 +- simulator/generation/query.rs | 132 ++++++++++++++++++++-------------- simulator/generation/table.rs | 30 ++++---- simulator/main.rs | 11 ++- simulator/model/query.rs | 15 +++- simulator/model/table.rs | 30 ++------ 6 files changed, 122 insertions(+), 98 deletions(-) diff --git a/simulator/generation.rs b/simulator/generation.rs index 15b5845ec..d1006a953 100644 --- a/simulator/generation.rs +++ b/simulator/generation.rs @@ -30,7 +30,7 @@ pub(crate) fn frequency<'a, T, R: rand::Rng>( unreachable!() } -pub(crate) fn one_of(choices: Vec T>>, rng: &mut R) -> T { +pub(crate) fn one_of<'a, T, R: rand::Rng>(choices: Vec T + 'a>>, rng: &mut R) -> T { let index = rng.gen_range(0..choices.len()); choices[index](rng) } diff --git a/simulator/generation/query.rs b/simulator/generation/query.rs index 748409972..9589944ab 100644 --- a/simulator/generation/query.rs +++ b/simulator/generation/query.rs @@ -1,10 +1,12 @@ use crate::generation::table::{GTValue, LTValue}; -use crate::generation::{Arbitrary, ArbitraryFrom}; +use crate::generation::{one_of, Arbitrary, ArbitraryFrom}; use crate::model::query::{Create, Delete, Insert, Predicate, Query, Select}; use crate::model::table::{Table, Value}; use rand::Rng; +use super::{frequency, pick}; + impl Arbitrary for Create { fn arbitrary(rng: &mut R) -> Self { Create { @@ -15,20 +17,20 @@ impl Arbitrary for Create { impl ArbitraryFrom> for Select { fn arbitrary_from(rng: &mut R, tables: &Vec
) -> Self { - let table = rng.gen_range(0..tables.len()); + let table = pick(tables, rng); Select { - table: tables[table].name.clone(), - predicate: Predicate::arbitrary_from(rng, &tables[table]), + table: table.name.clone(), + predicate: Predicate::arbitrary_from(rng, table), } } } impl ArbitraryFrom> for Select { fn arbitrary_from(rng: &mut R, tables: &Vec<&Table>) -> Self { - let table = rng.gen_range(0..tables.len()); + let table = pick(tables, rng); Select { - table: tables[table].name.clone(), - predicate: Predicate::arbitrary_from(rng, tables[table]), + table: table.name.clone(), + predicate: Predicate::arbitrary_from(rng, *table), } } } @@ -58,15 +60,24 @@ impl ArbitraryFrom
for Delete { impl ArbitraryFrom
for Query { fn arbitrary_from(rng: &mut R, table: &Table) -> Self { - match rng.gen_range(0..=200) { - 0 => Query::Create(Create::arbitrary(rng)), - 1..=100 => Query::Select(Select::arbitrary_from(rng, &vec![table])), - 101..=200 => Query::Insert(Insert::arbitrary_from(rng, table)), - // todo: This branch is currently never taken, as DELETE is not yet implemented. - // Change this when DELETE is implemented. - 201..=300 => Query::Delete(Delete::arbitrary_from(rng, table)), - _ => unreachable!(), - } + frequency( + vec![ + (1, Box::new(|rng| Query::Create(Create::arbitrary(rng)))), + ( + 100, + Box::new(|rng| Query::Select(Select::arbitrary_from(rng, &vec![table]))), + ), + ( + 100, + Box::new(|rng| Query::Insert(Insert::arbitrary_from(rng, table))), + ), + ( + 0, + Box::new(|rng| Query::Delete(Delete::arbitrary_from(rng, table))), + ), + ], + rng, + ) } } @@ -84,35 +95,53 @@ impl ArbitraryFrom<(&Table, bool)> for SimplePredicate { .map(|r| &r[column_index]) .collect::>(); // Pick an operator - let operator = match rng.gen_range(0..3) { - 0 => { - if *predicate_value { - Predicate::Eq( - column.name.clone(), - Value::arbitrary_from(rng, &column_values), - ) - } else { - Predicate::Eq( - column.name.clone(), - Value::arbitrary_from(rng, &column.column_type), - ) - } - } - 1 => Predicate::Gt( - column.name.clone(), - match predicate_value { - true => GTValue::arbitrary_from(rng, &column_values).0, - false => LTValue::arbitrary_from(rng, &column_values).0, - }, + let operator = match predicate_value { + true => one_of( + vec![ + Box::new(|rng| { + Predicate::Eq( + column.name.clone(), + Value::arbitrary_from(rng, &column_values), + ) + }), + Box::new(|rng| { + Predicate::Gt( + column.name.clone(), + GTValue::arbitrary_from(rng, &column_values).0, + ) + }), + Box::new(|rng| { + Predicate::Lt( + column.name.clone(), + LTValue::arbitrary_from(rng, &column_values).0, + ) + }), + ], + rng, ), - 2 => Predicate::Lt( - column.name.clone(), - match predicate_value { - true => LTValue::arbitrary_from(rng, &column_values).0, - false => GTValue::arbitrary_from(rng, &column_values).0, - }, + false => one_of( + vec![ + Box::new(|rng| { + Predicate::Neq( + column.name.clone(), + Value::arbitrary_from(rng, &column.column_type), + ) + }), + Box::new(|rng| { + Predicate::Gt( + column.name.clone(), + LTValue::arbitrary_from(rng, &column_values).0, + ) + }), + Box::new(|rng| { + Predicate::Lt( + column.name.clone(), + GTValue::arbitrary_from(rng, &column_values).0, + ) + }), + ], + rng, ), - _ => unreachable!(), }; SimplePredicate(operator) @@ -191,17 +220,10 @@ impl ArbitraryFrom
for Predicate { impl ArbitraryFrom<(&str, &Value)> for Predicate { fn arbitrary_from(rng: &mut R, (column_name, value): &(&str, &Value)) -> Self { - match rng.gen_range(0..3) { - 0 => Predicate::Eq(column_name.to_string(), (*value).clone()), - 1 => Predicate::Gt( - column_name.to_string(), - LTValue::arbitrary_from(rng, *value).0, - ), - 2 => Predicate::Lt( - column_name.to_string(), - LTValue::arbitrary_from(rng, *value).0, - ), - _ => unreachable!(), - } + one_of(vec![ + Box::new(|rng| Predicate::Eq(column_name.to_string(), (*value).clone())), + Box::new(|rng| Predicate::Gt(column_name.to_string(), GTValue::arbitrary_from(rng, *value).0)), + Box::new(|rng| Predicate::Lt(column_name.to_string(), LTValue::arbitrary_from(rng, *value).0)), + ], rng) } } diff --git a/simulator/generation/table.rs b/simulator/generation/table.rs index 685171207..46b6b0df6 100644 --- a/simulator/generation/table.rs +++ b/simulator/generation/table.rs @@ -1,6 +1,6 @@ use rand::Rng; -use crate::generation::{gen_random_text, readable_name_custom, Arbitrary, ArbitraryFrom}; +use crate::generation::{pick_index, gen_random_text, pick, readable_name_custom, Arbitrary, ArbitraryFrom}; use crate::model::table::{Column, ColumnType, Name, Table, Value}; impl Arbitrary for Name { @@ -13,7 +13,7 @@ impl Arbitrary for Name { impl Arbitrary for Table { fn arbitrary(rng: &mut R) -> Self { let name = Name::arbitrary(rng).0; - let columns = (1..=rng.gen_range(1..10)) + let columns = (1..=rng.gen_range(1..5)) .map(|_| Column::arbitrary(rng)) .collect(); Table { @@ -39,13 +39,16 @@ impl Arbitrary for Column { impl Arbitrary for ColumnType { fn arbitrary(rng: &mut R) -> Self { - match rng.gen_range(0..4) { - 0 => ColumnType::Integer, - 1 => ColumnType::Float, - 2 => ColumnType::Text, - 3 => ColumnType::Blob, - _ => unreachable!(), - } + pick( + &vec![ + ColumnType::Integer, + ColumnType::Float, + ColumnType::Text, + ColumnType::Blob, + ], + rng, + ) + .to_owned() } } @@ -55,8 +58,7 @@ impl ArbitraryFrom> for Value { return Value::Null; } - let index = rng.gen_range(0..values.len()); - values[index].clone() + pick(values, rng).to_owned().clone() } } @@ -78,8 +80,8 @@ impl ArbitraryFrom> for LTValue { if values.is_empty() { return LTValue(Value::Null); } - - let index = rng.gen_range(0..values.len()); + + let index = pick_index(values.len(), rng); LTValue::arbitrary_from(rng, values[index]) } } @@ -139,7 +141,7 @@ impl ArbitraryFrom> for GTValue { return GTValue(Value::Null); } - let index = rng.gen_range(0..values.len()); + let index = pick_index(values.len(), rng); GTValue::arbitrary_from(rng, values[index]) } } diff --git a/simulator/main.rs b/simulator/main.rs index 0f745e7e8..00b415ce9 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -1,7 +1,7 @@ use generation::plan::{Interaction, ResultSet}; use generation::{pick, pick_index, Arbitrary, ArbitraryFrom}; use limbo_core::{Connection, Database, File, OpenFlags, PlatformIO, Result, RowResult, IO}; -use model::query::{Insert, Predicate, Query, Select}; +use model::query::{Create, Insert, Predicate, Query, Select}; use model::table::{Column, Name, Table, Value}; use properties::{property_insert_select, property_select_all}; use rand::prelude::*; @@ -189,7 +189,12 @@ fn maybe_add_table(env: &mut SimulatorEnv, conn: &mut Rc) -> Result< .map(|_| Column::arbitrary(&mut env.rng)) .collect(), }; - let rows = get_all_rows(env, conn, table.to_create_str().as_str())?; + let query = Query::Create(Create { table: table.clone() }); + let rows = get_all_rows( + env, + conn, + query.to_string().as_str(), + )?; log::debug!("{:?}", rows); let rows = get_all_rows( env, @@ -207,7 +212,7 @@ fn maybe_add_table(env: &mut SimulatorEnv, conn: &mut Rc) -> Result< _ => unreachable!(), }; assert!( - *as_text != table.to_create_str(), + *as_text != query.to_string(), "table was not inserted correctly" ); env.tables.push(table); diff --git a/simulator/model/query.rs b/simulator/model/query.rs index 20058aead..ce227a252 100644 --- a/simulator/model/query.rs +++ b/simulator/model/query.rs @@ -7,6 +7,7 @@ pub(crate) enum Predicate { And(Vec), // p1 AND p2 AND p3... AND pn Or(Vec), // p1 OR p2 OR p3... OR pn Eq(String, Value), // column = Value + Neq(String, Value), // column != Value Gt(String, Value), // column > Value Lt(String, Value), // column < Value } @@ -44,6 +45,7 @@ impl Display for Predicate { } } Predicate::Eq(name, value) => write!(f, "{} = {}", name, value), + Predicate::Neq(name, value) => write!(f, "{} != {}", name, value), Predicate::Gt(name, value) => write!(f, "{} > {}", name, value), Predicate::Lt(name, value) => write!(f, "{} < {}", name, value), } @@ -85,7 +87,18 @@ pub(crate) struct Delete { impl Display for Query { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Query::Create(Create { table }) => write!(f, "{}", table.to_create_str()), + Query::Create(Create { table }) => { + write!(f, "CREATE TABLE {} (", table.name)?; + + for (i, column) in table.columns.iter().enumerate() { + if i != 0 { + write!(f, ",")?; + } + write!(f, "{} {}", column.name, column.column_type)?; + } + + write!(f, ")") + }, Query::Select(Select { table, predicate: guard, diff --git a/simulator/model/table.rs b/simulator/model/table.rs index 93c3d6d74..ccc18f738 100644 --- a/simulator/model/table.rs +++ b/simulator/model/table.rs @@ -17,24 +17,6 @@ pub(crate) struct Table { pub(crate) columns: Vec, } -impl Table { - pub fn to_create_str(&self) -> String { - let mut out = String::new(); - - out.push_str(format!("CREATE TABLE {} (", self.name).as_str()); - - assert!(!self.columns.is_empty()); - for column in &self.columns { - out.push_str(format!("{} {},", column.name, column.column_type.as_str()).as_str()); - } - // remove last comma - out.pop(); - - out.push_str(");"); - out - } -} - #[derive(Debug, Clone)] pub(crate) struct Column { pub(crate) name: String, @@ -51,13 +33,13 @@ pub(crate) enum ColumnType { Blob, } -impl ColumnType { - pub fn as_str(&self) -> &str { +impl Display for ColumnType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ColumnType::Integer => "INTEGER", - ColumnType::Float => "FLOAT", - ColumnType::Text => "TEXT", - ColumnType::Blob => "BLOB", + ColumnType::Integer => write!(f, "INTEGER"), + ColumnType::Float => write!(f, "REAL"), + ColumnType::Text => write!(f, "TEXT"), + ColumnType::Blob => write!(f, "BLOB"), } } } From cb20ca7e40be25a5be5bf01b209929af0bb3fc64 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Wed, 18 Dec 2024 17:10:18 -0500 Subject: [PATCH 16/19] fix formatting --- simulator/generation.rs | 5 ++++- simulator/generation/query.rs | 23 ++++++++++++++++++----- simulator/generation/table.rs | 6 ++++-- simulator/main.rs | 10 ++++------ simulator/model/query.rs | 2 +- 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/simulator/generation.rs b/simulator/generation.rs index d1006a953..07a93492b 100644 --- a/simulator/generation.rs +++ b/simulator/generation.rs @@ -30,7 +30,10 @@ pub(crate) fn frequency<'a, T, R: rand::Rng>( unreachable!() } -pub(crate) fn one_of<'a, T, R: rand::Rng>(choices: Vec T + 'a>>, rng: &mut R) -> T { +pub(crate) fn one_of<'a, T, R: rand::Rng>( + choices: Vec T + 'a>>, + rng: &mut R, +) -> T { let index = rng.gen_range(0..choices.len()); choices[index](rng) } diff --git a/simulator/generation/query.rs b/simulator/generation/query.rs index 9589944ab..ca6926650 100644 --- a/simulator/generation/query.rs +++ b/simulator/generation/query.rs @@ -220,10 +220,23 @@ impl ArbitraryFrom
for Predicate { impl ArbitraryFrom<(&str, &Value)> for Predicate { fn arbitrary_from(rng: &mut R, (column_name, value): &(&str, &Value)) -> Self { - one_of(vec![ - Box::new(|rng| Predicate::Eq(column_name.to_string(), (*value).clone())), - Box::new(|rng| Predicate::Gt(column_name.to_string(), GTValue::arbitrary_from(rng, *value).0)), - Box::new(|rng| Predicate::Lt(column_name.to_string(), LTValue::arbitrary_from(rng, *value).0)), - ], rng) + one_of( + vec![ + Box::new(|rng| Predicate::Eq(column_name.to_string(), (*value).clone())), + Box::new(|rng| { + Predicate::Gt( + column_name.to_string(), + GTValue::arbitrary_from(rng, *value).0, + ) + }), + Box::new(|rng| { + Predicate::Lt( + column_name.to_string(), + LTValue::arbitrary_from(rng, *value).0, + ) + }), + ], + rng, + ) } } diff --git a/simulator/generation/table.rs b/simulator/generation/table.rs index 46b6b0df6..9af2d7d8e 100644 --- a/simulator/generation/table.rs +++ b/simulator/generation/table.rs @@ -1,6 +1,8 @@ use rand::Rng; -use crate::generation::{pick_index, gen_random_text, pick, readable_name_custom, Arbitrary, ArbitraryFrom}; +use crate::generation::{ + gen_random_text, pick, pick_index, readable_name_custom, Arbitrary, ArbitraryFrom, +}; use crate::model::table::{Column, ColumnType, Name, Table, Value}; impl Arbitrary for Name { @@ -80,7 +82,7 @@ impl ArbitraryFrom> for LTValue { if values.is_empty() { return LTValue(Value::Null); } - + let index = pick_index(values.len(), rng); LTValue::arbitrary_from(rng, values[index]) } diff --git a/simulator/main.rs b/simulator/main.rs index 00b415ce9..67d9b92f9 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -189,12 +189,10 @@ fn maybe_add_table(env: &mut SimulatorEnv, conn: &mut Rc) -> Result< .map(|_| Column::arbitrary(&mut env.rng)) .collect(), }; - let query = Query::Create(Create { table: table.clone() }); - let rows = get_all_rows( - env, - conn, - query.to_string().as_str(), - )?; + let query = Query::Create(Create { + table: table.clone(), + }); + let rows = get_all_rows(env, conn, query.to_string().as_str())?; log::debug!("{:?}", rows); let rows = get_all_rows( env, diff --git a/simulator/model/query.rs b/simulator/model/query.rs index ce227a252..eeec68d08 100644 --- a/simulator/model/query.rs +++ b/simulator/model/query.rs @@ -98,7 +98,7 @@ impl Display for Query { } write!(f, ")") - }, + } Query::Select(Select { table, predicate: guard, From b3555680236489623df435200ce0f64770cda3ce Mon Sep 17 00:00:00 2001 From: alpaylan Date: Thu, 19 Dec 2024 23:40:04 -0500 Subject: [PATCH 17/19] use ticks as the main simulator driver, handle disconnects correctly, add multi-connection setup --- simulator/generation/plan.rs | 62 +++++++++++++++++- simulator/main.rs | 122 +++++++++++++++++++++-------------- 2 files changed, 136 insertions(+), 48 deletions(-) diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index d7c309b93..b1a233f9e 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -9,7 +9,7 @@ use crate::{ query::{Create, Insert, Predicate, Query, Select}, table::Value, }, - SimulatorEnv, SimulatorOpts, + SimConnection, SimulatorEnv, SimulatorOpts, }; use crate::generation::{frequency, Arbitrary, ArbitraryFrom}; @@ -21,6 +21,7 @@ pub(crate) type ResultSet = Vec>; pub(crate) struct InteractionPlan { pub(crate) plan: Vec, pub(crate) stack: Vec, + pub(crate) interaction_pointer: usize, } impl Display for InteractionPlan { @@ -31,6 +32,7 @@ impl Display for InteractionPlan { Interaction::Assertion(assertion) => { write!(f, "-- ASSERT: {};\n", assertion.message)? } + Interaction::Fault(fault) => write!(f, "-- FAULT: {};\n", fault)?, } } @@ -58,6 +60,7 @@ impl Display for InteractionStats { pub(crate) enum Interaction { Query(Query), Assertion(Assertion), + Fault(Fault), } impl Display for Interaction { @@ -65,6 +68,7 @@ impl Display for Interaction { match self { Interaction::Query(query) => write!(f, "{}", query), Interaction::Assertion(assertion) => write!(f, "ASSERT: {}", assertion.message), + Interaction::Fault(fault) => write!(f, "FAULT: {}", fault), } } } @@ -74,6 +78,18 @@ pub(crate) struct Assertion { pub(crate) message: String, } +pub(crate) enum Fault { + Disconnect, +} + +impl Display for Fault { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Fault::Disconnect => write!(f, "DISCONNECT"), + } + } +} + pub(crate) struct Interactions(Vec); impl Interactions { @@ -96,6 +112,7 @@ impl Interactions { Query::Select(_) => {} }, Interaction::Assertion(_) => {} + Interaction::Fault(_) => {} } } } @@ -106,6 +123,7 @@ impl InteractionPlan { InteractionPlan { plan: Vec::new(), stack: Vec::new(), + interaction_pointer: 0, } } @@ -127,6 +145,7 @@ impl InteractionPlan { Query::Create(_) => {} }, Interaction::Assertion(_) => {} + Interaction::Fault(_) => {} } } @@ -223,6 +242,9 @@ impl Interaction { Interaction::Assertion(_) => { unreachable!("unexpected: this function should only be called on queries") } + Interaction::Fault(fault) => { + unreachable!("unexpected: this function should only be called on queries") + } } } @@ -239,6 +261,38 @@ impl Interaction { } Ok(()) } + Interaction::Fault(_) => { + unreachable!("unexpected: this function should only be called on assertions") + } + } + } + + pub(crate) fn execute_fault(&self, env: &mut SimulatorEnv, conn_index: usize) -> Result<()> { + match self { + Interaction::Query(_) => { + unreachable!("unexpected: this function should only be called on faults") + } + Interaction::Assertion(_) => { + unreachable!("unexpected: this function should only be called on faults") + } + Interaction::Fault(fault) => { + match fault { + Fault::Disconnect => { + match env.connections[conn_index] { + SimConnection::Connected(ref mut conn) => { + conn.close()?; + } + SimConnection::Disconnected => { + return Err(limbo_core::LimboError::InternalError( + "Tried to disconnect a disconnected connection".to_string(), + )); + } + } + env.connections[conn_index] = SimConnection::Disconnected; + } + } + Ok(()) + } } } } @@ -307,6 +361,11 @@ fn random_write(rng: &mut R, env: &SimulatorEnv) -> Interactions { Interactions(vec![insert_query]) } +fn random_fault(rng: &mut R, env: &SimulatorEnv) -> Interactions { + let fault = Interaction::Fault(Fault::Disconnect); + Interactions(vec![fault]) +} + impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions { fn arbitrary_from( rng: &mut R, @@ -334,6 +393,7 @@ impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions { Box::new(|rng: &mut R| random_write(rng, env)), ), (1, Box::new(|rng: &mut R| create_table(rng, env))), + (1, Box::new(|rng: &mut R| random_fault(rng, env))), ], rng, ) diff --git a/simulator/main.rs b/simulator/main.rs index 67d9b92f9..dfdc974d3 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -1,4 +1,4 @@ -use generation::plan::{Interaction, ResultSet}; +use generation::plan::{Interaction, InteractionPlan, ResultSet}; use generation::{pick, pick_index, Arbitrary, ArbitraryFrom}; use limbo_core::{Connection, Database, File, OpenFlags, PlatformIO, Result, RowResult, IO}; use model::query::{Create, Insert, Predicate, Query, Select}; @@ -66,7 +66,7 @@ fn main() { }; let opts = SimulatorOpts { - ticks: rng.gen_range(0..4096), + ticks: rng.gen_range(0..1024), max_connections: 1, // TODO: for now let's use one connection as we didn't implement // correct transactions procesing max_tables: rng.gen_range(0..128), @@ -74,7 +74,7 @@ fn main() { write_percent, delete_percent, page_size: 4096, // TODO: randomize this too - max_interactions: rng.gen_range(0..10000), + max_interactions: rng.gen_range(0..1024), }; let io = Arc::new(SimulatorIO::new(seed, opts.page_size).unwrap()); @@ -101,63 +101,87 @@ fn main() { println!("Initial opts {:?}", env.opts); log::info!("Generating database interaction plan..."); - let mut plan = generation::plan::InteractionPlan::arbitrary_from(&mut env.rng.clone(), &env); + let mut plans = (1..=env.opts.max_connections) + .map(|_| InteractionPlan::arbitrary_from(&mut env.rng.clone(), &env)) + .collect::>(); - log::info!("{}", plan.stats()); + log::info!("{}", plans[0].stats()); - for interaction in &plan.plan { - let connection_index = pick_index(env.connections.len(), &mut env.rng); - let mut connection = env.connections[connection_index].clone(); + log::info!("Executing database interaction plan..."); + let result = execute_plans(&mut env, &mut plans); - if matches!(connection, SimConnection::Disconnected) { - connection = SimConnection::Connected(env.db.connect()); - env.connections[connection_index] = connection.clone(); - } - - match &mut connection { - SimConnection::Connected(conn) => { - let disconnect = env.rng.gen_ratio(1, 100); - if disconnect { - log::info!("disconnecting {}", connection_index); - let _ = conn.close(); - env.connections[connection_index] = SimConnection::Disconnected; - } else { - match process_connection(conn, interaction, &mut plan.stack) { - Ok(_) => { - log::info!("connection {} processed", connection_index); - } - Err(err) => { - log::error!("error {}", err); - log::debug!("db is at {:?}", path); - // save the interaction plan - let mut path = TempDir::new().unwrap().into_path(); - path.push("simulator.plan"); - let mut f = std::fs::File::create(path.clone()).unwrap(); - f.write(plan.to_string().as_bytes()).unwrap(); - log::debug!("plan saved at {:?}", path); - log::debug!("seed was {}", seed); - break; - } - } - } - } - SimConnection::Disconnected => { - log::info!("disconnecting {}", connection_index); - env.connections[connection_index] = SimConnection::Connected(env.db.connect()); - } - } + if result.is_err() { + log::error!("error executing plans: {:?}", result.err()); } + log::info!("db is at {:?}", path); + let mut path = TempDir::new().unwrap().into_path(); + path.push("simulator.plan"); + let mut f = std::fs::File::create(path.clone()).unwrap(); + f.write(plans[0].to_string().as_bytes()).unwrap(); + log::info!("plan saved at {:?}", path); + log::info!("seed was {}", seed); env.io.print_stats(); } -fn process_connection( - conn: &mut Rc, +fn execute_plans(env: &mut SimulatorEnv, plans: &mut Vec) -> Result<()> { + // todo: add history here by recording which interaction was executed at which tick + for _tick in 0..env.opts.ticks { + // Pick the connection to interact with + let connection_index = pick_index(env.connections.len(), &mut env.rng); + // Execute the interaction for the selected connection + execute_plan(env, connection_index, plans)?; + } + + Ok(()) +} + +fn execute_plan( + env: &mut SimulatorEnv, + connection_index: usize, + plans: &mut Vec, +) -> Result<()> { + let connection = &env.connections[connection_index]; + let plan = &mut plans[connection_index]; + + if plan.interaction_pointer >= plan.plan.len() { + return Ok(()); + } + + let interaction = &plan.plan[plan.interaction_pointer]; + + if let SimConnection::Disconnected = connection { + log::info!("connecting {}", connection_index); + env.connections[connection_index] = SimConnection::Connected(env.db.connect()); + } else { + match execute_interaction(env, connection_index, interaction, &mut plan.stack) { + Ok(_) => { + log::debug!("connection {} processed", connection_index); + plan.interaction_pointer += 1; + } + Err(err) => { + log::error!("error {}", err); + return Err(err); + } + } + } + + Ok(()) +} + +fn execute_interaction( + env: &mut SimulatorEnv, + connection_index: usize, interaction: &Interaction, stack: &mut Vec, ) -> Result<()> { match interaction { generation::plan::Interaction::Query(_) => { + let conn = match &mut env.connections[connection_index] { + SimConnection::Connected(conn) => conn, + SimConnection::Disconnected => unreachable!(), + }; + log::debug!("{}", interaction); let results = interaction.execute_query(conn)?; log::debug!("{:?}", results); @@ -165,6 +189,10 @@ fn process_connection( } generation::plan::Interaction::Assertion(_) => { interaction.execute_assertion(stack)?; + stack.clear(); + } + Interaction::Fault(_) => { + interaction.execute_fault(env, connection_index)?; } } From d2723b777bc01b222d5bafebf10f1ea314f7c076 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Fri, 20 Dec 2024 12:17:59 -0500 Subject: [PATCH 18/19] update table create probability, print interactions as info logs --- simulator/generation/plan.rs | 5 ++++- simulator/main.rs | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index b1a233f9e..d83911642 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -392,7 +392,10 @@ impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions { remaining_write, Box::new(|rng: &mut R| random_write(rng, env)), ), - (1, Box::new(|rng: &mut R| create_table(rng, env))), + ( + remaining_write / 10, + Box::new(|rng: &mut R| create_table(rng, env)), + ), (1, Box::new(|rng: &mut R| random_fault(rng, env))), ], rng, diff --git a/simulator/main.rs b/simulator/main.rs index 8dc477290..085711391 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -66,7 +66,7 @@ fn main() { }; let opts = SimulatorOpts { - ticks: rng.gen_range(0..1024), + ticks: rng.gen_range(0..10240), max_connections: 1, // TODO: for now let's use one connection as we didn't implement // correct transactions procesing max_tables: rng.gen_range(0..128), @@ -74,7 +74,7 @@ fn main() { write_percent, delete_percent, page_size: 4096, // TODO: randomize this too - max_interactions: rng.gen_range(0..1024), + max_interactions: rng.gen_range(0..10240), }; let io = Arc::new(SimulatorIO::new(seed, opts.page_size).unwrap()); @@ -175,6 +175,7 @@ fn execute_interaction( interaction: &Interaction, stack: &mut Vec, ) -> Result<()> { + log::info!("executing: {}", interaction); match interaction { generation::plan::Interaction::Query(_) => { let conn = match &mut env.connections[connection_index] { From 8f8b97d54b7d18a756247dcbbd5cb4701e289120 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Fri, 20 Dec 2024 12:27:54 -0500 Subject: [PATCH 19/19] add the missing rowresult variant --- simulator/generation/plan.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index d83911642..fd194de66 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -1,4 +1,4 @@ -use std::{f32::consts::E, fmt::Display, os::macos::raw::stat, rc::Rc}; +use std::{fmt::Display, rc::Rc}; use limbo_core::{Connection, Result, RowResult}; use rand::SeedableRng; @@ -231,6 +231,7 @@ impl Interaction { out.push(r); } RowResult::IO => {} + RowResult::Interrupt => {} RowResult::Done => { break; }