diff --git a/Cargo.lock b/Cargo.lock index 0a0345ac8..dfdb48cf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2153,6 +2153,7 @@ dependencies = [ "chrono", "clap", "dirs 6.0.0", + "either", "env_logger 0.11.7", "garde", "hex", diff --git a/simulator/Cargo.toml b/simulator/Cargo.toml index 9ea6d093e..8c37dd8f0 100644 --- a/simulator/Cargo.toml +++ b/simulator/Cargo.toml @@ -44,3 +44,4 @@ json5 = { version = "0.4.1" } strum = { workspace = true } parking_lot = { workspace = true } indexmap = { workspace = true } +either = "1.15.0" diff --git a/simulator/generation/mod.rs b/simulator/generation/mod.rs index 88a40d708..80a2d0cff 100644 --- a/simulator/generation/mod.rs +++ b/simulator/generation/mod.rs @@ -1,6 +1,4 @@ -use sql_generation::generation::GenerationContext; - -use crate::runner::env::{SimulatorEnv, SimulatorTables}; +use crate::runner::env::ShadowTablesMut; pub mod plan; pub mod property; @@ -17,25 +15,5 @@ pub mod query; /// might return a vector of rows that were inserted into the table. pub(crate) trait Shadow { type Result; - fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result; -} - -impl GenerationContext for SimulatorEnv { - fn tables(&self) -> &Vec { - &self.tables.tables - } - - fn opts(&self) -> &sql_generation::generation::Opts { - &self.profile.query.gen_opts - } -} - -impl GenerationContext for &mut SimulatorEnv { - fn tables(&self) -> &Vec { - &self.tables.tables - } - - fn opts(&self) -> &sql_generation::generation::Opts { - &self.profile.query.gen_opts - } + fn shadow(&self, tables: &mut ShadowTablesMut<'_>) -> Self::Result; } diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 5dde1d11e..64a6574b8 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -13,7 +13,11 @@ use serde::{Deserialize, Serialize}; use sql_generation::{ generation::{Arbitrary, ArbitraryFrom, GenerationContext, frequency, query::SelectFree}, model::{ - query::{Create, CreateIndex, Delete, Drop, Insert, Select, update::Update}, + query::{ + Create, CreateIndex, Delete, Drop, Insert, Select, + transaction::{Begin, Commit}, + update::Update, + }, table::SimValue, }, }; @@ -23,7 +27,7 @@ use crate::{ SimulatorEnv, generation::Shadow, model::Query, - runner::env::{SimConnection, SimulationType, SimulatorTables}, + runner::env::{ShadowTablesMut, SimConnection, SimulationType}, }; use super::property::{Property, remaining}; @@ -32,10 +36,46 @@ pub(crate) type ResultSet = Result>>; #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct InteractionPlan { - pub(crate) plan: Vec, + pub plan: Vec, + pub mvcc: bool, } impl InteractionPlan { + pub(crate) fn new(mvcc: bool) -> Self { + Self { + plan: Vec::new(), + mvcc, + } + } + + pub fn new_with(plan: Vec, mvcc: bool) -> Self { + Self { plan, mvcc } + } + + #[inline] + pub fn plan(&self) -> &[Interactions] { + &self.plan + } + + // TODO: this is just simplified logic so we can get something rolling with begin concurrent + // transactions in the simulator. Ideally when we generate the plan we will have begin and commits statements across interactions + pub fn push(&mut self, interactions: Interactions) { + if self.mvcc { + let conn_index = interactions.connection_index; + let begin = Interactions::new( + conn_index, + InteractionsType::Query(Query::Begin(Begin::Concurrent)), + ); + let commit = + Interactions::new(conn_index, InteractionsType::Query(Query::Commit(Commit))); + self.plan.push(begin); + self.plan.push(interactions); + self.plan.push(commit); + } else { + self.plan.push(interactions); + } + } + /// Compute via diff computes a a plan from a given `.plan` file without the need to parse /// sql. This is possible because there are two versions of the plan file, one that is human /// readable and one that is serialized as JSON. Under watch mode, the users will be able to @@ -121,6 +161,109 @@ impl InteractionPlan { }) .collect() } + + pub(crate) fn stats(&self) -> InteractionStats { + let mut stats = InteractionStats { + select_count: 0, + insert_count: 0, + delete_count: 0, + update_count: 0, + create_count: 0, + create_index_count: 0, + drop_count: 0, + begin_count: 0, + commit_count: 0, + rollback_count: 0, + }; + + fn query_stat(q: &Query, stats: &mut InteractionStats) { + match q { + Query::Select(_) => stats.select_count += 1, + Query::Insert(_) => stats.insert_count += 1, + Query::Delete(_) => stats.delete_count += 1, + Query::Create(_) => stats.create_count += 1, + Query::Drop(_) => stats.drop_count += 1, + Query::Update(_) => stats.update_count += 1, + Query::CreateIndex(_) => stats.create_index_count += 1, + Query::Begin(_) => stats.begin_count += 1, + Query::Commit(_) => stats.commit_count += 1, + Query::Rollback(_) => stats.rollback_count += 1, + } + } + for interactions in &self.plan { + match &interactions.interactions { + InteractionsType::Property(property) => { + for interaction in &property.interactions(interactions.connection_index) { + if let InteractionType::Query(query) = &interaction.interaction { + query_stat(query, &mut stats); + } + } + } + InteractionsType::Query(query) => { + query_stat(query, &mut stats); + } + InteractionsType::Fault(_) => {} + } + } + + stats + } + + pub fn generate_plan(rng: &mut R, env: &mut SimulatorEnv) -> Self { + let mut plan = InteractionPlan::new(env.profile.experimental_mvcc); + + let num_interactions = env.opts.max_interactions as usize; + + // First create at least one table + let create_query = Create::arbitrary(rng, &env.connection_context(0)); + env.committed_tables.push(create_query.table.clone()); + + // initial query starts at 0th connection + plan.plan.push(Interactions::new( + 0, + InteractionsType::Query(Query::Create(create_query)), + )); + + while plan.len() < num_interactions { + tracing::debug!("Generating interaction {}/{}", plan.len(), num_interactions); + let interactions = { + let conn_index = env.choose_conn(rng); + let conn_ctx = &env.connection_context(conn_index); + Interactions::arbitrary_from(rng, conn_ctx, (env, plan.stats(), conn_index)) + }; + + interactions.shadow(&mut env.get_conn_tables_mut(interactions.connection_index)); + plan.push(interactions); + } + + tracing::info!("Generated plan with {} interactions", plan.plan.len()); + + plan + } +} + +impl Deref for InteractionPlan { + type Target = [Interactions]; + + fn deref(&self) -> &Self::Target { + &self.plan + } +} + +impl DerefMut for InteractionPlan { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.plan + } +} + +impl IntoIterator for InteractionPlan { + type Item = Interactions; + + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.plan.into_iter() + } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -172,7 +315,7 @@ pub enum InteractionsType { impl Shadow for Interactions { type Result = (); - fn shadow(&self, tables: &mut SimulatorTables) { + fn shadow(&self, tables: &mut ShadowTablesMut) { match &self.interactions { InteractionsType::Property(property) => { let initial_tables = tables.clone(); @@ -180,7 +323,7 @@ impl Shadow for Interactions { let res = interaction.shadow(tables); if res.is_err() { // If any interaction fails, we reset the tables to the initial state - *tables = initial_tables.clone(); + **tables = initial_tables.clone(); break; } } @@ -368,89 +511,6 @@ impl Display for Fault { } } -impl InteractionPlan { - pub(crate) fn new() -> Self { - Self { plan: Vec::new() } - } - - pub(crate) fn stats(&self) -> InteractionStats { - let mut stats = InteractionStats { - select_count: 0, - insert_count: 0, - delete_count: 0, - update_count: 0, - create_count: 0, - create_index_count: 0, - drop_count: 0, - begin_count: 0, - commit_count: 0, - rollback_count: 0, - }; - - fn query_stat(q: &Query, stats: &mut InteractionStats) { - match q { - Query::Select(_) => stats.select_count += 1, - Query::Insert(_) => stats.insert_count += 1, - Query::Delete(_) => stats.delete_count += 1, - Query::Create(_) => stats.create_count += 1, - Query::Drop(_) => stats.drop_count += 1, - Query::Update(_) => stats.update_count += 1, - Query::CreateIndex(_) => stats.create_index_count += 1, - Query::Begin(_) => stats.begin_count += 1, - Query::Commit(_) => stats.commit_count += 1, - Query::Rollback(_) => stats.rollback_count += 1, - } - } - for interactions in &self.plan { - match &interactions.interactions { - InteractionsType::Property(property) => { - for interaction in &property.interactions(interactions.connection_index) { - if let InteractionType::Query(query) = &interaction.interaction { - query_stat(query, &mut stats); - } - } - } - InteractionsType::Query(query) => { - query_stat(query, &mut stats); - } - InteractionsType::Fault(_) => {} - } - } - - stats - } - - pub fn generate_plan(rng: &mut R, env: &mut SimulatorEnv) -> Self { - let mut plan = InteractionPlan::new(); - - let num_interactions = env.opts.max_interactions as usize; - - // First create at least one table - let create_query = Create::arbitrary(rng, env); - env.tables.push(create_query.table.clone()); - - // initial query starts at 0th connection - plan.plan.push(Interactions::new( - 0, - InteractionsType::Query(Query::Create(create_query)), - )); - - while plan.plan.len() < num_interactions { - tracing::debug!( - "Generating interaction {}/{}", - plan.plan.len(), - num_interactions - ); - let interactions = Interactions::arbitrary_from(rng, env, (env, plan.stats())); - interactions.shadow(&mut env.tables); - plan.plan.push(interactions); - } - - tracing::info!("Generated plan with {} interactions", plan.plan.len()); - plan - } -} - #[derive(Debug, Clone)] pub struct Interaction { pub connection_index: usize, @@ -520,7 +580,7 @@ impl Display for InteractionType { impl Shadow for InteractionType { type Result = anyhow::Result>>; - fn shadow(&self, env: &mut SimulatorTables) -> Self::Result { + fn shadow(&self, env: &mut ShadowTablesMut) -> Self::Result { match self { Self::Query(query) => query.shadow(env), Self::FsyncQuery(query) => { @@ -834,66 +894,90 @@ fn reopen_database(env: &mut SimulatorEnv) { }; } -fn random_create(rng: &mut R, env: &SimulatorEnv) -> Interactions { - let mut create = Create::arbitrary(rng, env); - while env.tables.iter().any(|t| t.name == create.table.name) { - create = Create::arbitrary(rng, env); +fn random_create(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions { + let conn_ctx = env.connection_context(conn_index); + let mut create = Create::arbitrary(rng, &conn_ctx); + while conn_ctx + .tables() + .iter() + .any(|t| t.name == create.table.name) + { + create = Create::arbitrary(rng, &conn_ctx); } + Interactions::new(conn_index, InteractionsType::Query(Query::Create(create))) +} + +fn random_read(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions { Interactions::new( - env.choose_conn(rng), - InteractionsType::Query(Query::Create(create)), + conn_index, + InteractionsType::Query(Query::Select(Select::arbitrary( + rng, + &env.connection_context(conn_index), + ))), ) } -fn random_read(rng: &mut R, env: &SimulatorEnv) -> Interactions { +fn random_expr(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions { Interactions::new( - env.choose_conn(rng), - InteractionsType::Query(Query::Select(Select::arbitrary(rng, env))), + conn_index, + InteractionsType::Query(Query::Select( + SelectFree::arbitrary(rng, &env.connection_context(conn_index)).0, + )), ) } -fn random_expr(rng: &mut R, env: &SimulatorEnv) -> Interactions { +fn random_write(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions { Interactions::new( - env.choose_conn(rng), - InteractionsType::Query(Query::Select(SelectFree::arbitrary(rng, env).0)), + conn_index, + InteractionsType::Query(Query::Insert(Insert::arbitrary( + rng, + &env.connection_context(conn_index), + ))), ) } -fn random_write(rng: &mut R, env: &SimulatorEnv) -> Interactions { +fn random_delete(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions { Interactions::new( - env.choose_conn(rng), - InteractionsType::Query(Query::Insert(Insert::arbitrary(rng, env))), + conn_index, + InteractionsType::Query(Query::Delete(Delete::arbitrary( + rng, + &env.connection_context(conn_index), + ))), ) } -fn random_delete(rng: &mut R, env: &SimulatorEnv) -> Interactions { +fn random_update(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions { Interactions::new( - env.choose_conn(rng), - InteractionsType::Query(Query::Delete(Delete::arbitrary(rng, env))), + conn_index, + InteractionsType::Query(Query::Update(Update::arbitrary( + rng, + &env.connection_context(conn_index), + ))), ) } -fn random_update(rng: &mut R, env: &SimulatorEnv) -> Interactions { +fn random_drop(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions { Interactions::new( - env.choose_conn(rng), - InteractionsType::Query(Query::Update(Update::arbitrary(rng, env))), + conn_index, + InteractionsType::Query(Query::Drop(Drop::arbitrary( + rng, + &env.connection_context(conn_index), + ))), ) } -fn random_drop(rng: &mut R, env: &SimulatorEnv) -> Interactions { - Interactions::new( - env.choose_conn(rng), - InteractionsType::Query(Query::Drop(Drop::arbitrary(rng, env))), - ) -} - -fn random_create_index(rng: &mut R, env: &SimulatorEnv) -> Option { - if env.tables.is_empty() { +fn random_create_index( + rng: &mut R, + env: &SimulatorEnv, + conn_index: usize, +) -> Option { + let conn_ctx = env.connection_context(conn_index); + if conn_ctx.tables().is_empty() { return None; } - let mut create_index = CreateIndex::arbitrary(rng, env); - while env - .tables + let mut create_index = CreateIndex::arbitrary(rng, &conn_ctx); + while conn_ctx + .tables() .iter() .find(|t| t.name == create_index.table_name) .expect("table should exist") @@ -901,11 +985,11 @@ fn random_create_index(rng: &mut R, env: &SimulatorEnv) -> Option< .iter() .any(|i| i == &create_index.index_name) { - create_index = CreateIndex::arbitrary(rng, env); + create_index = CreateIndex::arbitrary(rng, &conn_ctx); } Some(Interactions::new( - env.choose_conn(rng), + conn_index, InteractionsType::Query(Query::CreateIndex(create_index)), )) } @@ -920,23 +1004,28 @@ fn random_fault(rng: &mut R, env: &SimulatorEnv) -> Interactions { Interactions::new(env.choose_conn(rng), InteractionsType::Fault(fault)) } -impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions { +impl ArbitraryFrom<(&SimulatorEnv, InteractionStats, usize)> for Interactions { fn arbitrary_from( rng: &mut R, - _context: &C, - (env, stats): (&SimulatorEnv, InteractionStats), + conn_ctx: &C, + (env, stats, conn_index): (&SimulatorEnv, InteractionStats, usize), ) -> Self { - let remaining_ = remaining(env.opts.max_interactions, &env.profile.query, &stats); + let remaining_ = remaining( + env.opts.max_interactions, + &env.profile.query, + &stats, + env.profile.experimental_mvcc, + ); frequency( vec![ ( u32::min(remaining_.select, remaining_.insert) + remaining_.create, Box::new(|rng: &mut R| { Interactions::new( - env.choose_conn(rng), + conn_index, InteractionsType::Property(Property::arbitrary_from( rng, - env, + conn_ctx, (env, &stats), )), ) @@ -944,43 +1033,43 @@ impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions { ), ( remaining_.select, - Box::new(|rng: &mut R| random_read(rng, env)), + Box::new(|rng: &mut R| random_read(rng, env, conn_index)), ), ( remaining_.select / 3, - Box::new(|rng: &mut R| random_expr(rng, env)), + Box::new(|rng: &mut R| random_expr(rng, env, conn_index)), ), ( remaining_.insert, - Box::new(|rng: &mut R| random_write(rng, env)), + Box::new(|rng: &mut R| random_write(rng, env, conn_index)), ), ( remaining_.create, - Box::new(|rng: &mut R| random_create(rng, env)), + Box::new(|rng: &mut R| random_create(rng, env, conn_index)), ), ( remaining_.create_index, Box::new(|rng: &mut R| { - if let Some(interaction) = random_create_index(rng, env) { + if let Some(interaction) = random_create_index(rng, env, conn_index) { interaction } else { // if no tables exist, we can't create an index, so fallback to creating a table - random_create(rng, env) + random_create(rng, env, conn_index) } }), ), ( remaining_.delete, - Box::new(|rng: &mut R| random_delete(rng, env)), + Box::new(|rng: &mut R| random_delete(rng, env, conn_index)), ), ( remaining_.update, - Box::new(|rng: &mut R| random_update(rng, env)), + Box::new(|rng: &mut R| random_update(rng, env, conn_index)), ), ( // remaining_.drop, 0, - Box::new(|rng: &mut R| random_drop(rng, env)), + Box::new(|rng: &mut R| random_drop(rng, env, conn_index)), ), ( remaining_ diff --git a/simulator/generation/property.rs b/simulator/generation/property.rs index 4ab9e9ff1..847c30593 100644 --- a/simulator/generation/property.rs +++ b/simulator/generation/property.rs @@ -226,7 +226,8 @@ impl Property { let assumption = InteractionType::Assumption(Assertion::new( format!("table {} exists", table.clone()), move |_: &Vec, env: &mut SimulatorEnv| { - if env.tables.iter().any(|t| t.name == table_name) { + let conn_tables = env.get_conn_tables(connection_index); + if conn_tables.iter().any(|t| t.name == table_name) { Ok(Ok(())) } else { Ok(Err(format!("table {table_name} does not exist"))) @@ -246,8 +247,8 @@ impl Property { let Ok(rows) = rows else { return Ok(Err(format!("expected rows but got error: {rows:?}"))); }; - let sim_table = env - .tables + let conn_tables = env.get_conn_tables(connection_index); + let sim_table = conn_tables .iter() .find(|t| t.name == table) .expect("table should be in enviroment"); @@ -283,7 +284,8 @@ impl Property { let assumption = InteractionType::Assumption(Assertion::new( format!("table {} exists", table.clone()), move |_: &Vec, env: &mut SimulatorEnv| { - if env.tables.iter().any(|t| t.name == table.clone()) { + let conn_tables = env.get_conn_tables(connection_index); + if conn_tables.iter().any(|t| t.name == table.clone()) { Ok(Ok(())) } else { Ok(Err(format!("table {} does not exist", table.clone()))) @@ -360,7 +362,8 @@ impl Property { { let table_name = table.clone(); move |_: &Vec, env: &mut SimulatorEnv| { - if env.tables.iter().any(|t| t.name == table_name) { + let conn_tables = env.get_conn_tables(connection_index); + if conn_tables.iter().any(|t| t.name == table_name) { Ok(Ok(())) } else { Ok(Err(format!("table {table_name} does not exist"))) @@ -429,7 +432,8 @@ impl Property { let assumption = InteractionType::Assumption(Assertion::new( "Double-Create-Failure should not be called on an existing table".to_string(), move |_: &Vec, env: &mut SimulatorEnv| { - if !env.tables.iter().any(|t| t.name == table_name) { + let conn_tables = env.get_conn_tables(connection_index); + if !conn_tables.iter().any(|t| t.name == table_name) { Ok(Ok(())) } else { Ok(Err(format!("table {table_name} already exists"))) @@ -484,15 +488,16 @@ impl Property { { let table_name = select.dependencies(); move |_: &Vec, env: &mut SimulatorEnv| { + let conn_tables = env.get_conn_tables(connection_index); if table_name .iter() - .all(|table| env.tables.iter().any(|t| t.name == *table)) + .all(|table| conn_tables.iter().any(|t| t.name == *table)) { Ok(Ok(())) } else { let missing_tables = table_name .iter() - .filter(|t| !env.tables.iter().any(|t2| t2.name == **t)) + .filter(|t| !conn_tables.iter().any(|t2| t2.name == **t)) .collect::>(); Ok(Err(format!("missing tables: {missing_tables:?}"))) } @@ -544,12 +549,13 @@ impl Property { { let table = table.clone(); move |_: &Vec, env: &mut SimulatorEnv| { - if env.tables.iter().any(|t| t.name == table) { + let conn_tables = env.get_conn_tables(connection_index); + if conn_tables.iter().any(|t| t.name == table) { Ok(Ok(())) } else { { let available_tables: Vec = - env.tables.iter().map(|t| t.name.clone()).collect(); + conn_tables.iter().map(|t| t.name.clone()).collect(); Ok(Err(format!( "table \'{table}\' not found. Available tables: {available_tables:?}" ))) @@ -617,12 +623,13 @@ impl Property { { let table = table.clone(); move |_, env: &mut SimulatorEnv| { - if env.tables.iter().any(|t| t.name == table) { + let conn_tables = env.get_conn_tables(connection_index); + if conn_tables.iter().any(|t| t.name == table) { Ok(Ok(())) } else { { let available_tables: Vec = - env.tables.iter().map(|t| t.name.clone()).collect(); + conn_tables.iter().map(|t| t.name.clone()).collect(); Ok(Err(format!( "table \'{table}\' not found. Available tables: {available_tables:?}" ))) @@ -684,12 +691,13 @@ impl Property { { let table = table.clone(); move |_: &Vec, env: &mut SimulatorEnv| { - if env.tables.iter().any(|t| t.name == table) { + let conn_tables = env.get_conn_tables(connection_index); + if conn_tables.iter().any(|t| t.name == table) { Ok(Ok(())) } else { { let available_tables: Vec = - env.tables.iter().map(|t| t.name.clone()).collect(); + conn_tables.iter().map(|t| t.name.clone()).collect(); Ok(Err(format!( "table \'{table}\' not found. Available tables: {available_tables:?}" ))) @@ -788,7 +796,8 @@ impl Property { let last = stack.last().unwrap(); match last { Ok(_) => { - let _ = query_clone.shadow(&mut env.tables); + let _ = query_clone + .shadow(&mut env.get_conn_tables_mut(connection_index)); Ok(Ok(())) } Err(err) => { @@ -821,15 +830,16 @@ impl Property { { let tables = select.dependencies(); move |_: &Vec, env: &mut SimulatorEnv| { + let conn_tables = env.get_conn_tables(connection_index); if tables .iter() - .all(|table| env.tables.iter().any(|t| t.name == *table)) + .all(|table| conn_tables.iter().any(|t| t.name == *table)) { Ok(Ok(())) } else { let missing_tables = tables .iter() - .filter(|t| !env.tables.iter().any(|t2| t2.name == **t)) + .filter(|t| !conn_tables.iter().any(|t2| t2.name == **t)) .collect::>(); Ok(Err(format!("missing tables: {missing_tables:?}"))) } @@ -1030,7 +1040,8 @@ fn assert_all_table_values( let assertion = InteractionType::Assertion(Assertion::new(format!("table {table} should contain all of its expected values"), { let table = table.clone(); move |stack: &Vec, env: &mut SimulatorEnv| { - let table = env.tables.iter().find(|t| t.name == table).ok_or_else(|| { + let conn_ctx = env.get_conn_tables(connection_index); + let table = conn_ctx.iter().find(|t| t.name == table).ok_or_else(|| { LimboError::InternalError(format!( "table {table} should exist in simulator env" )) @@ -1090,6 +1101,7 @@ pub(crate) fn remaining( max_interactions: u32, opts: &QueryProfile, stats: &InteractionStats, + mvcc: bool, ) -> Remaining { let total_weight = opts.select_weight + opts.create_table_weight @@ -1116,7 +1128,7 @@ pub(crate) fn remaining( let remaining_create = total_create .checked_sub(stats.create_count) .unwrap_or_default(); - let remaining_create_index = total_create_index + let mut remaining_create_index = total_create_index .checked_sub(stats.create_index_count) .unwrap_or_default(); let remaining_delete = total_delete @@ -1127,6 +1139,11 @@ pub(crate) fn remaining( .unwrap_or_default(); let remaining_drop = total_drop.checked_sub(stats.drop_count).unwrap_or_default(); + if mvcc { + // TODO: index not supported yet for mvcc + remaining_create_index = 0; + } + Remaining { select: remaining_select, insert: remaining_insert, @@ -1140,14 +1157,14 @@ pub(crate) fn remaining( fn property_insert_values_select( rng: &mut R, - env: &SimulatorEnv, remaining: &Remaining, + ctx: &impl GenerationContext, ) -> Property { // Get a random table - let table = pick(&env.tables, rng); + let table = pick(ctx.tables(), rng); // Generate rows to insert let rows = (0..rng.random_range(1..=5)) - .map(|_| Vec::::arbitrary_from(rng, env, table)) + .map(|_| Vec::::arbitrary_from(rng, ctx, table)) .collect::>(); // Pick a random row to select @@ -1176,12 +1193,14 @@ fn property_insert_values_select( // - [x] The inserted row will not be updated. // - [ ] The table `t` will not be renamed, dropped, or altered. (todo: add this constraint once ALTER or DROP is implemented) if let Some(ref interactive) = interactive { - queries.push(Query::Begin(Begin { - immediate: interactive.start_with_immediate, + queries.push(Query::Begin(if interactive.start_with_immediate { + Begin::Immediate + } else { + Begin::Deferred })); } for _ in 0..rng.random_range(0..3) { - let query = Query::arbitrary_from(rng, env, remaining); + let query = Query::arbitrary_from(rng, ctx, remaining); match &query { Query::Delete(Delete { table: t, @@ -1224,7 +1243,7 @@ fn property_insert_values_select( // Select the row let select_query = Select::simple( table.name.clone(), - Predicate::arbitrary_from(rng, env, (table, &row)), + Predicate::arbitrary_from(rng, ctx, (table, &row)), ); Property::InsertValuesSelect { @@ -1236,9 +1255,12 @@ fn property_insert_values_select( } } -fn property_read_your_updates_back(rng: &mut R, env: &SimulatorEnv) -> Property { +fn property_read_your_updates_back( + rng: &mut R, + ctx: &impl GenerationContext, +) -> Property { // e.g. UPDATE t SET a=1, b=2 WHERE c=1; - let update = Update::arbitrary(rng, env); + let update = Update::arbitrary(rng, ctx); // e.g. SELECT a, b FROM t WHERE c=1; let select = Select::single( update.table().to_string(), @@ -1255,22 +1277,25 @@ fn property_read_your_updates_back(rng: &mut R, env: &SimulatorEnv Property::ReadYourUpdatesBack { update, select } } -fn property_table_has_expected_content(rng: &mut R, env: &SimulatorEnv) -> Property { +fn property_table_has_expected_content( + rng: &mut R, + ctx: &impl GenerationContext, +) -> Property { // Get a random table - let table = pick(&env.tables, rng); + let table = pick(ctx.tables(), rng); Property::TableHasExpectedContent { table: table.name.clone(), } } -fn property_select_limit(rng: &mut R, env: &SimulatorEnv) -> Property { +fn property_select_limit(rng: &mut R, ctx: &impl GenerationContext) -> Property { // Get a random table - let table = pick(&env.tables, rng); + let table = pick(ctx.tables(), rng); // Select the table let select = Select::single( table.name.clone(), vec![ResultColumn::Star], - Predicate::arbitrary_from(rng, env, table), + Predicate::arbitrary_from(rng, ctx, table), Some(rng.random_range(1..=5)), Distinctness::All, ); @@ -1279,11 +1304,11 @@ fn property_select_limit(rng: &mut R, env: &SimulatorEnv) -> Prope fn property_double_create_failure( rng: &mut R, - env: &SimulatorEnv, remaining: &Remaining, + ctx: &impl GenerationContext, ) -> Property { // Create the table - let create_query = Create::arbitrary(rng, env); + let create_query = Create::arbitrary(rng, ctx); let table = &create_query.table; // Create random queries respecting the constraints @@ -1292,7 +1317,7 @@ fn property_double_create_failure( // - [x] There will be no errors in the middle interactions.(best effort) // - [ ] Table `t` will not be renamed or dropped.(todo: add this constraint once ALTER or DROP is implemented) for _ in 0..rng.random_range(0..3) { - let query = Query::arbitrary_from(rng, env, remaining); + let query = Query::arbitrary_from(rng, ctx, remaining); if let Query::Create(Create { table: t }) = &query { // There will be no errors in the middle interactions. // - Creating the same table is an error @@ -1311,13 +1336,13 @@ fn property_double_create_failure( fn property_delete_select( rng: &mut R, - env: &SimulatorEnv, remaining: &Remaining, + ctx: &impl GenerationContext, ) -> Property { // Get a random table - let table = pick(&env.tables, rng); + let table = pick(ctx.tables(), rng); // Generate a random predicate - let predicate = Predicate::arbitrary_from(rng, env, table); + let predicate = Predicate::arbitrary_from(rng, ctx, table); // Create random queries respecting the constraints let mut queries = Vec::new(); @@ -1325,7 +1350,7 @@ fn property_delete_select( // - [x] A row that holds for the predicate will not be inserted. // - [ ] The table `t` will not be renamed, dropped, or altered. (todo: add this constraint once ALTER or DROP is implemented) for _ in 0..rng.random_range(0..3) { - let query = Query::arbitrary_from(rng, env, remaining); + let query = Query::arbitrary_from(rng, ctx, remaining); match &query { Query::Insert(Insert::Values { table: t, values }) => { // A row that holds for the predicate will not be inserted. @@ -1369,18 +1394,18 @@ fn property_delete_select( fn property_drop_select( rng: &mut R, - env: &SimulatorEnv, remaining: &Remaining, + ctx: &impl GenerationContext, ) -> Property { // Get a random table - let table = pick(&env.tables, rng); + let table = pick(ctx.tables(), rng); // Create random queries respecting the constraints let mut queries = Vec::new(); // - [x] There will be no errors in the middle interactions. (this constraint is impossible to check, so this is just best effort) // - [-] The table `t` will not be created, no table will be renamed to `t`. (todo: update this constraint once ALTER is implemented) for _ in 0..rng.random_range(0..3) { - let query = Query::arbitrary_from(rng, env, remaining); + let query = Query::arbitrary_from(rng, ctx, remaining); if let Query::Create(Create { table: t }) = &query { // - The table `t` will not be created if t.name == table.name { @@ -1392,7 +1417,7 @@ fn property_drop_select( let select = Select::simple( table.name.clone(), - Predicate::arbitrary_from(rng, env, table), + Predicate::arbitrary_from(rng, ctx, table), ); Property::DropSelect { @@ -1402,11 +1427,14 @@ fn property_drop_select( } } -fn property_select_select_optimizer(rng: &mut R, env: &SimulatorEnv) -> Property { +fn property_select_select_optimizer( + rng: &mut R, + ctx: &impl GenerationContext, +) -> Property { // Get a random table - let table = pick(&env.tables, rng); + let table = pick(ctx.tables(), rng); // Generate a random predicate - let predicate = Predicate::arbitrary_from(rng, env, table); + let predicate = Predicate::arbitrary_from(rng, ctx, table); // Transform into a Binary predicate to force values to be casted to a bool let expr = ast::Expr::Binary( Box::new(predicate.0), @@ -1420,12 +1448,15 @@ fn property_select_select_optimizer(rng: &mut R, env: &SimulatorEn } } -fn property_where_true_false_null(rng: &mut R, env: &SimulatorEnv) -> Property { +fn property_where_true_false_null( + rng: &mut R, + ctx: &impl GenerationContext, +) -> Property { // Get a random table - let table = pick(&env.tables, rng); + let table = pick(ctx.tables(), rng); // Generate a random predicate - let p1 = Predicate::arbitrary_from(rng, env, table); - let p2 = Predicate::arbitrary_from(rng, env, table); + let p1 = Predicate::arbitrary_from(rng, ctx, table); + let p2 = Predicate::arbitrary_from(rng, ctx, table); // Create the select query let select = Select::simple(table.name.clone(), p1); @@ -1438,13 +1469,13 @@ fn property_where_true_false_null(rng: &mut R, env: &SimulatorEnv) fn property_union_all_preserves_cardinality( rng: &mut R, - env: &SimulatorEnv, + ctx: &impl GenerationContext, ) -> Property { // Get a random table - let table = pick(&env.tables, rng); + let table = pick(ctx.tables(), rng); // Generate a random predicate - let p1 = Predicate::arbitrary_from(rng, env, table); - let p2 = Predicate::arbitrary_from(rng, env, table); + let p1 = Predicate::arbitrary_from(rng, ctx, table); + let p2 = Predicate::arbitrary_from(rng, ctx, table); // Create the select query let select = Select::single( @@ -1463,34 +1494,39 @@ fn property_union_all_preserves_cardinality( fn property_fsync_no_wait( rng: &mut R, - env: &SimulatorEnv, remaining: &Remaining, + ctx: &impl GenerationContext, ) -> Property { Property::FsyncNoWait { - query: Query::arbitrary_from(rng, env, remaining), - tables: env.tables.iter().map(|t| t.name.clone()).collect(), + query: Query::arbitrary_from(rng, ctx, remaining), + tables: ctx.tables().iter().map(|t| t.name.clone()).collect(), } } fn property_faulty_query( rng: &mut R, - env: &SimulatorEnv, remaining: &Remaining, + ctx: &impl GenerationContext, ) -> Property { Property::FaultyQuery { - query: Query::arbitrary_from(rng, env, remaining), - tables: env.tables.iter().map(|t| t.name.clone()).collect(), + query: Query::arbitrary_from(rng, ctx, remaining), + tables: ctx.tables().iter().map(|t| t.name.clone()).collect(), } } impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { fn arbitrary_from( rng: &mut R, - context: &C, + conn_ctx: &C, (env, stats): (&SimulatorEnv, &InteractionStats), ) -> Self { - let opts = context.opts(); - let remaining_ = remaining(env.opts.max_interactions, &env.profile.query, stats); + let opts = conn_ctx.opts(); + let remaining_ = remaining( + env.opts.max_interactions, + &env.profile.query, + stats, + env.profile.experimental_mvcc, + ); frequency( vec![ @@ -1500,15 +1536,17 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { } else { 0 }, - Box::new(|rng: &mut R| property_insert_values_select(rng, env, &remaining_)), + Box::new(|rng: &mut R| { + property_insert_values_select(rng, &remaining_, conn_ctx) + }), ), ( remaining_.select, - Box::new(|rng: &mut R| property_table_has_expected_content(rng, env)), + Box::new(|rng: &mut R| property_table_has_expected_content(rng, conn_ctx)), ), ( u32::min(remaining_.select, remaining_.insert), - Box::new(|rng: &mut R| property_read_your_updates_back(rng, env)), + Box::new(|rng: &mut R| property_read_your_updates_back(rng, conn_ctx)), ), ( if !env.opts.disable_double_create_failure { @@ -1516,7 +1554,9 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { } else { 0 }, - Box::new(|rng: &mut R| property_double_create_failure(rng, env, &remaining_)), + Box::new(|rng: &mut R| { + property_double_create_failure(rng, &remaining_, conn_ctx) + }), ), ( if !env.opts.disable_select_limit { @@ -1524,7 +1564,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { } else { 0 }, - Box::new(|rng: &mut R| property_select_limit(rng, env)), + Box::new(|rng: &mut R| property_select_limit(rng, conn_ctx)), ), ( if !env.opts.disable_delete_select { @@ -1532,7 +1572,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { } else { 0 }, - Box::new(|rng: &mut R| property_delete_select(rng, env, &remaining_)), + Box::new(|rng: &mut R| property_delete_select(rng, &remaining_, conn_ctx)), ), ( if !env.opts.disable_drop_select { @@ -1541,7 +1581,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { } else { 0 }, - Box::new(|rng: &mut R| property_drop_select(rng, env, &remaining_)), + Box::new(|rng: &mut R| property_drop_select(rng, &remaining_, conn_ctx)), ), ( if !env.opts.disable_select_optimizer { @@ -1549,7 +1589,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { } else { 0 }, - Box::new(|rng: &mut R| property_select_select_optimizer(rng, env)), + Box::new(|rng: &mut R| property_select_select_optimizer(rng, conn_ctx)), ), ( if opts.indexes && !env.opts.disable_where_true_false_null { @@ -1557,7 +1597,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { } else { 0 }, - Box::new(|rng: &mut R| property_where_true_false_null(rng, env)), + Box::new(|rng: &mut R| property_where_true_false_null(rng, conn_ctx)), ), ( if opts.indexes && !env.opts.disable_union_all_preserves_cardinality { @@ -1565,7 +1605,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { } else { 0 }, - Box::new(|rng: &mut R| property_union_all_preserves_cardinality(rng, env)), + Box::new(|rng: &mut R| property_union_all_preserves_cardinality(rng, conn_ctx)), ), ( if env.profile.io.enable && !env.opts.disable_fsync_no_wait { @@ -1573,7 +1613,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { } else { 0 }, - Box::new(|rng: &mut R| property_fsync_no_wait(rng, env, &remaining_)), + Box::new(|rng: &mut R| property_fsync_no_wait(rng, &remaining_, conn_ctx)), ), ( if env.profile.io.enable @@ -1584,7 +1624,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { } else { 0 }, - Box::new(|rng: &mut R| property_faulty_query(rng, env, &remaining_)), + Box::new(|rng: &mut R| property_faulty_query(rng, &remaining_, conn_ctx)), ), ], rng, diff --git a/simulator/main.rs b/simulator/main.rs index 00f64b5fb..5fdba980e 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -330,7 +330,7 @@ fn run_simulator( tracing::trace!( "adding bug to bugbase, seed: {}, plan: {}, error: {}", env.opts.seed, - plan.plan.len(), + plan.len(), error ); bugbase @@ -361,8 +361,8 @@ fn run_simulator( tracing::info!( "shrinking succeeded, reduced the plan from {} to {}", - plan.plan.len(), - final_plan.plan.len() + plan.len(), + final_plan.len() ); // Save the shrunk database if let Some(bugbase) = bugbase.as_deref_mut() { diff --git a/simulator/model/mod.rs b/simulator/model/mod.rs index 20adbbe9d..9a3c81e9f 100644 --- a/simulator/model/mod.rs +++ b/simulator/model/mod.rs @@ -15,7 +15,7 @@ use sql_generation::model::{ }; use turso_parser::ast::Distinctness; -use crate::{generation::Shadow, runner::env::SimulatorTables}; +use crate::{generation::Shadow, runner::env::ShadowTablesMut}; // This type represents the potential queries on the database. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -83,7 +83,7 @@ impl Display for Query { impl Shadow for Query { type Result = anyhow::Result>>; - fn shadow(&self, env: &mut SimulatorTables) -> Self::Result { + fn shadow(&self, env: &mut ShadowTablesMut) -> Self::Result { match self { Query::Create(create) => create.shadow(env), Query::Insert(insert) => insert.shadow(env), @@ -102,7 +102,7 @@ impl Shadow for Query { impl Shadow for Create { type Result = anyhow::Result>>; - fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result { + fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result { if !tables.iter().any(|t| t.name == self.table.name) { tables.push(self.table.clone()); Ok(vec![]) @@ -117,9 +117,8 @@ impl Shadow for Create { impl Shadow for CreateIndex { type Result = Vec>; - fn shadow(&self, env: &mut SimulatorTables) -> Vec> { - env.tables - .iter_mut() + fn shadow(&self, env: &mut ShadowTablesMut) -> Vec> { + env.iter_mut() .find(|t| t.name == self.table_name) .unwrap() .indexes @@ -131,8 +130,8 @@ impl Shadow for CreateIndex { impl Shadow for Delete { type Result = anyhow::Result>>; - fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result { - let table = tables.tables.iter_mut().find(|t| t.name == self.table); + fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result { + let table = tables.iter_mut().find(|t| t.name == self.table); if let Some(table) = table { // If the table exists, we can delete from it @@ -153,7 +152,7 @@ impl Shadow for Delete { impl Shadow for Drop { type Result = anyhow::Result>>; - fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result { + fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result { if !tables.iter().any(|t| t.name == self.table) { // If the table does not exist, we return an error return Err(anyhow::anyhow!( @@ -162,7 +161,7 @@ impl Shadow for Drop { )); } - tables.tables.retain(|t| t.name != self.table); + tables.retain(|t| t.name != self.table); Ok(vec![]) } @@ -171,10 +170,10 @@ impl Shadow for Drop { impl Shadow for Insert { type Result = anyhow::Result>>; - fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result { + fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result { match self { Insert::Values { table, values } => { - if let Some(t) = tables.tables.iter_mut().find(|t| &t.name == table) { + if let Some(t) = tables.iter_mut().find(|t| &t.name == table) { t.rows.extend(values.clone()); } else { return Err(anyhow::anyhow!( @@ -185,7 +184,7 @@ impl Shadow for Insert { } Insert::Select { table, select } => { let rows = select.shadow(tables)?; - if let Some(t) = tables.tables.iter_mut().find(|t| &t.name == table) { + if let Some(t) = tables.iter_mut().find(|t| &t.name == table) { t.rows.extend(rows); } else { return Err(anyhow::anyhow!( @@ -202,9 +201,7 @@ impl Shadow for Insert { impl Shadow for FromClause { type Result = anyhow::Result; - fn shadow(&self, env: &mut SimulatorTables) -> Self::Result { - let tables = &mut env.tables; - + fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result { let first_table = tables .iter() .find(|t| t.name == self.table) @@ -259,7 +256,7 @@ impl Shadow for FromClause { impl Shadow for SelectInner { type Result = anyhow::Result; - fn shadow(&self, env: &mut SimulatorTables) -> Self::Result { + fn shadow(&self, env: &mut ShadowTablesMut) -> Self::Result { if let Some(from) = &self.from { let mut join_table = from.shadow(env)?; let col_count = join_table.columns().count(); @@ -327,7 +324,7 @@ impl Shadow for SelectInner { impl Shadow for Select { type Result = anyhow::Result>>; - fn shadow(&self, env: &mut SimulatorTables) -> Self::Result { + fn shadow(&self, env: &mut ShadowTablesMut) -> Self::Result { let first_result = self.body.select.shadow(env)?; let mut rows = first_result.rows; @@ -357,26 +354,26 @@ impl Shadow for Select { impl Shadow for Begin { type Result = Vec>; - fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result { - tables.snapshot = Some(tables.tables.clone()); + fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result { + // FIXME: currently the snapshot is taken eagerly + // this is wrong for Deffered transactions + tables.create_snapshot(); vec![] } } impl Shadow for Commit { type Result = Vec>; - fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result { - tables.snapshot = None; + fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result { + tables.apply_snapshot(); vec![] } } impl Shadow for Rollback { type Result = Vec>; - fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result { - if let Some(tables_) = tables.snapshot.take() { - tables.tables = tables_; - } + fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result { + tables.delete_snapshot(); vec![] } } @@ -384,8 +381,8 @@ impl Shadow for Rollback { impl Shadow for Update { type Result = anyhow::Result>>; - fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result { - let table = tables.tables.iter_mut().find(|t| t.name == self.table); + fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result { + let table = tables.iter_mut().find(|t| t.name == self.table); let table = if let Some(table) = table { table diff --git a/simulator/runner/differential.rs b/simulator/runner/differential.rs index 6dd5803ee..c2be34b38 100644 --- a/simulator/runner/differential.rs +++ b/simulator/runner/differential.rs @@ -59,8 +59,8 @@ pub(crate) fn execute_interactions( let mut env = env.lock().unwrap(); let mut rusqlite_env = rusqlite_env.lock().unwrap(); - env.tables.clear(); - rusqlite_env.tables.clear(); + env.clear_tables(); + rusqlite_env.clear_tables(); let now = std::time::Instant::now(); diff --git a/simulator/runner/doublecheck.rs b/simulator/runner/doublecheck.rs index a2c98b424..d90408686 100644 --- a/simulator/runner/doublecheck.rs +++ b/simulator/runner/doublecheck.rs @@ -89,8 +89,8 @@ pub(crate) fn execute_plans( let mut env = env.lock().unwrap(); let mut doublecheck_env = doublecheck_env.lock().unwrap(); - env.tables.clear(); - doublecheck_env.tables.clear(); + env.clear_tables(); + doublecheck_env.clear_tables(); let now = std::time::Instant::now(); diff --git a/simulator/runner/env.rs b/simulator/runner/env.rs index c5423f97c..9ef9b612a 100644 --- a/simulator/runner/env.rs +++ b/simulator/runner/env.rs @@ -1,6 +1,6 @@ use std::fmt::Display; use std::mem; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use std::panic::UnwindSafe; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -8,6 +8,7 @@ use std::sync::Arc; use garde::Validate; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; +use sql_generation::generation::GenerationContext; use sql_generation::model::table::Table; use turso_core::Database; @@ -31,6 +32,79 @@ pub(crate) enum SimulationPhase { Shrink, } +#[derive(Debug)] +pub struct ShadowTables<'a> { + commited_tables: &'a Vec, + transaction_tables: Option<&'a Vec
>, +} + +#[derive(Debug)] +pub struct ShadowTablesMut<'a> { + commited_tables: &'a mut Vec
, + transaction_tables: &'a mut Option>, +} + +impl<'a> ShadowTables<'a> { + fn tables(&self) -> &'a Vec
{ + self.transaction_tables.map_or(self.commited_tables, |v| v) + } +} + +impl<'a> Deref for ShadowTables<'a> { + type Target = Vec
; + + fn deref(&self) -> &Self::Target { + self.tables() + } +} + +impl<'a, 'b> ShadowTablesMut<'a> +where + 'a: 'b, +{ + fn tables(&'a self) -> &'a Vec
{ + self.transaction_tables + .as_ref() + .unwrap_or(self.commited_tables) + } + + fn tables_mut(&'b mut self) -> &'b mut Vec
{ + self.transaction_tables + .as_mut() + .unwrap_or(self.commited_tables) + } + + pub fn create_snapshot(&mut self) { + *self.transaction_tables = Some(self.commited_tables.clone()); + } + + pub fn apply_snapshot(&mut self) { + // TODO: as we do not have concurrent tranasactions yet in the simulator + // there is no conflict we are ignoring conflict problems right now + if let Some(transation_tables) = self.transaction_tables.take() { + *self.commited_tables = transation_tables + } + } + + pub fn delete_snapshot(&mut self) { + *self.transaction_tables = None; + } +} + +impl<'a> Deref for ShadowTablesMut<'a> { + type Target = Vec
; + + fn deref(&self) -> &Self::Target { + self.tables() + } +} + +impl<'a> DerefMut for ShadowTablesMut<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.tables_mut() + } +} + #[derive(Debug, Clone)] pub(crate) struct SimulatorTables { pub(crate) tables: Vec
, @@ -71,8 +145,12 @@ pub(crate) struct SimulatorEnv { pub(crate) paths: Paths, pub(crate) type_: SimulationType, pub(crate) phase: SimulationPhase, - pub(crate) tables: SimulatorTables, pub memory_io: bool, + + /// If connection state is None, means we are not in a transaction + pub connection_tables: Vec>>, + // Table data that is committed into the database or wal + pub committed_tables: Vec
, } impl UnwindSafe for SimulatorEnv {} @@ -81,10 +159,6 @@ impl SimulatorEnv { pub(crate) fn clone_without_connections(&self) -> Self { SimulatorEnv { opts: self.opts.clone(), - tables: self.tables.clone(), - connections: (0..self.connections.len()) - .map(|_| SimConnection::Disconnected) - .collect(), io: self.io.clone(), db: self.db.clone(), rng: self.rng.clone(), @@ -93,11 +167,17 @@ impl SimulatorEnv { phase: self.phase, memory_io: self.memory_io, profile: self.profile.clone(), + connections: (0..self.connections.len()) + .map(|_| SimConnection::Disconnected) + .collect(), + // TODO: not sure if connection_tables should be recreated instead + connection_tables: self.connection_tables.clone(), + committed_tables: self.committed_tables.clone(), } } pub(crate) fn clear(&mut self) { - self.tables.clear(); + self.clear_tables(); self.connections.iter_mut().for_each(|c| c.disconnect()); self.rng = ChaCha8Rng::seed_from_u64(self.opts.seed); @@ -284,7 +364,6 @@ impl SimulatorEnv { SimulatorEnv { opts, - tables: SimulatorTables::new(), connections, paths, rng, @@ -294,6 +373,8 @@ impl SimulatorEnv { phase: SimulationPhase::Test, memory_io: cli_opts.memory_io, profile: profile.clone(), + committed_tables: Vec::new(), + connection_tables: vec![None; profile.max_connections], } } @@ -327,6 +408,55 @@ impl SimulatorEnv { } }; } + + /// Clears the commited tables and the connection tables + pub fn clear_tables(&mut self) { + self.committed_tables.clear(); + self.connection_tables.iter_mut().for_each(|t| { + if let Some(t) = t { + t.clear(); + } + }); + } + + // TODO: does not yet create the appropriate context to avoid WriteWriteConflitcs + pub fn connection_context(&self, conn_index: usize) -> impl GenerationContext { + struct ConnectionGenContext<'a> { + tables: &'a Vec, + opts: &'a sql_generation::generation::Opts, + } + + impl<'a> GenerationContext for ConnectionGenContext<'a> { + fn tables(&self) -> &Vec { + self.tables + } + + fn opts(&self) -> &sql_generation::generation::Opts { + self.opts + } + } + + let tables = self.get_conn_tables(conn_index).tables(); + + ConnectionGenContext { + opts: &self.profile.query.gen_opts, + tables, + } + } + + pub fn get_conn_tables<'a>(&'a self, conn_index: usize) -> ShadowTables<'a> { + ShadowTables { + transaction_tables: self.connection_tables.get(conn_index).unwrap().as_ref(), + commited_tables: &self.committed_tables, + } + } + + pub fn get_conn_tables_mut<'a>(&'a mut self, conn_index: usize) -> ShadowTablesMut<'a> { + ShadowTablesMut { + transaction_tables: self.connection_tables.get_mut(conn_index).unwrap(), + commited_tables: &mut self.committed_tables, + } + } } pub trait ConnectionTrait diff --git a/simulator/runner/execution.rs b/simulator/runner/execution.rs index 5c3f81a6b..3657b995b 100644 --- a/simulator/runner/execution.rs +++ b/simulator/runner/execution.rs @@ -65,7 +65,7 @@ pub(crate) fn execute_interactions( env.clear_poison(); let mut env = env.lock().unwrap(); - env.tables.clear(); + env.clear_tables(); for _tick in 0..env.opts.ticks { tracing::trace!("Executing tick {}", _tick); @@ -186,7 +186,10 @@ pub fn execute_interaction_turso( tracing::error!(?results); } stack.push(results); - limbo_integrity_check(conn)?; + // TODO: skip integrity check with mvcc + if !env.profile.experimental_mvcc { + limbo_integrity_check(conn)?; + } } InteractionType::FsyncQuery(query) => { let results = interaction.execute_fsync_query(conn.clone(), env); @@ -227,10 +230,13 @@ pub fn execute_interaction_turso( stack.push(results); // Reset fault injection env.io.inject_fault(false); - limbo_integrity_check(&conn)?; + // TODO: skip integrity check with mvcc + if !env.profile.experimental_mvcc { + limbo_integrity_check(&conn)?; + } } } - let _ = interaction.shadow(&mut env.tables); + let _ = interaction.shadow(&mut env.get_conn_tables_mut(interaction.connection_index)); Ok(ExecutionContinuation::NextInteraction) } @@ -323,7 +329,7 @@ fn execute_interaction_rusqlite( } } - let _ = interaction.shadow(&mut env.tables); + let _ = interaction.shadow(&mut env.get_conn_tables_mut(interaction.connection_index)); Ok(ExecutionContinuation::NextInteraction) } diff --git a/simulator/shrink/plan.rs b/simulator/shrink/plan.rs index 143c1d0d6..58f5eb156 100644 --- a/simulator/shrink/plan.rs +++ b/simulator/shrink/plan.rs @@ -33,7 +33,7 @@ impl InteractionPlan { break; } match &all_interactions[idx].1.interaction { - InteractionType::Query(query) => { + InteractionType::Query(query) | InteractionType::FaultyQuery(query) => { depending_tables = query.dependencies(); break; } @@ -54,77 +54,126 @@ impl InteractionPlan { } } - let before = self.plan.len(); + let before = self.len(); // Remove all properties after the failing one plan.plan.truncate(secondary_interactions_index + 1); - let mut idx = 0; - // Remove all properties that do not use the failing tables - plan.plan.retain_mut(|interactions| { - let retain = if idx == secondary_interactions_index { - if let InteractionsType::Property( - Property::FsyncNoWait { tables, .. } | Property::FaultyQuery { tables, .. }, - ) = &mut interactions.interactions - { - tables.retain(|table| depending_tables.contains(table)); - } - true - } else { - let mut has_table = interactions - .uses() - .iter() - .any(|t| depending_tables.contains(t)); - - if has_table { - // Remove the extensional parts of the properties - if let InteractionsType::Property(p) = &mut interactions.interactions { - match p { - Property::InsertValuesSelect { queries, .. } - | Property::DoubleCreateFailure { queries, .. } - | Property::DeleteSelect { queries, .. } - | Property::DropSelect { queries, .. } => { - queries.clear(); - } - Property::FsyncNoWait { tables, query } - | Property::FaultyQuery { tables, query } => { - if !query.uses().iter().any(|t| depending_tables.contains(t)) { - tables.clear(); - } else { - tables.retain(|table| depending_tables.contains(table)); - } - } - Property::SelectLimit { .. } - | Property::SelectSelectOptimizer { .. } - | Property::WhereTrueFalseNull { .. } - | Property::UNIONAllPreservesCardinality { .. } - | Property::ReadYourUpdatesBack { .. } - | Property::TableHasExpectedContent { .. } => {} - } + // means we errored in some fault on transaction statement so just maintain the statements from before the failing one + if !depending_tables.is_empty() { + let mut idx = 0; + // Remove all properties that do not use the failing tables + plan.plan.retain_mut(|interactions| { + let retain = if idx == secondary_interactions_index { + if let InteractionsType::Property( + Property::FsyncNoWait { tables, .. } | Property::FaultyQuery { tables, .. }, + ) = &mut interactions.interactions + { + tables.retain(|table| depending_tables.contains(table)); } - // Check again after query clear if the interactions still uses the failing table - has_table = interactions + true + } else if matches!( + interactions.interactions, + InteractionsType::Query(Query::Begin(..)) + | InteractionsType::Query(Query::Commit(..)) + | InteractionsType::Query(Query::Rollback(..)) + ) { + true + } else { + let mut has_table = interactions .uses() .iter() .any(|t| depending_tables.contains(t)); - } - let is_fault = matches!(interactions.interactions, InteractionsType::Fault(..)); - is_fault - || (has_table - && !matches!( - interactions.interactions, - InteractionsType::Query(Query::Select(_)) - | InteractionsType::Property(Property::SelectLimit { .. }) - | InteractionsType::Property( - Property::SelectSelectOptimizer { .. } - ) - )) - }; - idx += 1; - retain - }); - let after = plan.plan.len(); + if has_table { + // Remove the extensional parts of the properties + if let InteractionsType::Property(p) = &mut interactions.interactions { + match p { + Property::InsertValuesSelect { queries, .. } + | Property::DoubleCreateFailure { queries, .. } + | Property::DeleteSelect { queries, .. } + | Property::DropSelect { queries, .. } => { + queries.clear(); + } + Property::FsyncNoWait { tables, query } + | Property::FaultyQuery { tables, query } => { + if !query.uses().iter().any(|t| depending_tables.contains(t)) { + tables.clear(); + } else { + tables.retain(|table| depending_tables.contains(table)); + } + } + Property::SelectLimit { .. } + | Property::SelectSelectOptimizer { .. } + | Property::WhereTrueFalseNull { .. } + | Property::UNIONAllPreservesCardinality { .. } + | Property::ReadYourUpdatesBack { .. } + | Property::TableHasExpectedContent { .. } => {} + } + } + // Check again after query clear if the interactions still uses the failing table + has_table = interactions + .uses() + .iter() + .any(|t| depending_tables.contains(t)); + } + let is_fault = matches!(interactions.interactions, InteractionsType::Fault(..)); + is_fault + || (has_table + && !matches!( + interactions.interactions, + InteractionsType::Query(Query::Select(_)) + | InteractionsType::Property(Property::SelectLimit { .. }) + | InteractionsType::Property( + Property::SelectSelectOptimizer { .. } + ) + )) + }; + idx += 1; + retain + }); + + // Comprise of idxs of Begin interactions + let mut begin_idx = Vec::new(); + // Comprise of idxs of the intereactions Commit and Rollback + let mut end_tx_idx = Vec::new(); + + for (idx, interactions) in plan.plan.iter().enumerate() { + match &interactions.interactions { + InteractionsType::Query(Query::Begin(..)) => { + begin_idx.push(idx); + } + InteractionsType::Query(Query::Commit(..)) + | InteractionsType::Query(Query::Rollback(..)) => { + let last_begin = begin_idx.last().unwrap() + 1; + if last_begin == idx { + end_tx_idx.push(idx); + } + } + _ => {} + } + } + + // remove interactions if its just a Begin Commit/Rollback with no queries in the middle + let mut range_transactions = end_tx_idx.into_iter().peekable(); + let mut idx = 0; + plan.plan.retain_mut(|_| { + let mut retain = true; + + if let Some(txn_interaction_idx) = range_transactions.peek().copied() { + if txn_interaction_idx == idx { + range_transactions.next(); + } + if txn_interaction_idx == idx || txn_interaction_idx.saturating_sub(1) == idx { + retain = false; + } + } + idx += 1; + retain + }); + } + + let after = plan.len(); tracing::info!( "Shrinking interaction plan from {} to {} properties", @@ -184,7 +233,7 @@ impl InteractionPlan { } } - let before = self.plan.len(); + let before = self.len(); plan.plan.truncate(secondary_interactions_index + 1); @@ -196,8 +245,8 @@ impl InteractionPlan { | Property::DoubleCreateFailure { queries, .. } | Property::DeleteSelect { queries, .. } | Property::DropSelect { queries, .. } => { - let mut temp_plan = InteractionPlan { - plan: queries + let mut temp_plan = InteractionPlan::new_with( + queries .iter() .map(|q| { Interactions::new( @@ -206,7 +255,8 @@ impl InteractionPlan { ) }) .collect(), - }; + self.mvcc, + ); temp_plan = InteractionPlan::iterative_shrink( temp_plan, @@ -218,7 +268,6 @@ impl InteractionPlan { //temp_plan = Self::shrink_queries(temp_plan, failing_execution, result, env); *queries = temp_plan - .plan .into_iter() .filter_map(|i| match i.interactions { InteractionsType::Query(q) => Some(q), @@ -247,7 +296,7 @@ impl InteractionPlan { secondary_interactions_index, ); - let after = plan.plan.len(); + let after = plan.len(); tracing::info!( "Shrinking interaction plan from {} to {} properties", @@ -266,7 +315,7 @@ impl InteractionPlan { env: Arc>, secondary_interaction_index: usize, ) -> InteractionPlan { - for i in (0..plan.plan.len()).rev() { + for i in (0..plan.len()).rev() { if i == secondary_interaction_index { continue; } diff --git a/sql_generation/model/query/transaction.rs b/sql_generation/model/query/transaction.rs index 1114200a0..40ced59cd 100644 --- a/sql_generation/model/query/transaction.rs +++ b/sql_generation/model/query/transaction.rs @@ -3,8 +3,10 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Begin { - pub immediate: bool, +pub enum Begin { + Deferred, + Immediate, + Concurrent, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -15,7 +17,12 @@ pub struct Rollback; impl Display for Begin { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "BEGIN {}", if self.immediate { "IMMEDIATE" } else { "" }) + let keyword = match self { + Begin::Deferred => "", + Begin::Immediate => "IMMEDIATE", + Begin::Concurrent => "CONCURRENT", + }; + write!(f, "BEGIN {keyword}") } }