diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index cf41c1060..f79b6fa5d 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use crate::{ model::{ - query::{Create, Delete, Distinctness, Insert, Query, Select}, + query::{select::Distinctness, Create, Delete, Drop, Insert, Query, Select}, table::Value, }, runner::env::SimConnection, @@ -201,14 +201,19 @@ pub(crate) struct InteractionStats { pub(crate) write_count: usize, pub(crate) delete_count: usize, pub(crate) create_count: usize, + pub(crate) drop_count: usize, } impl Display for InteractionStats { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "Read: {}, Write: {}, Delete: {}, Create: {}", - self.read_count, self.write_count, self.delete_count, self.create_count + "Read: {}, Write: {}, Delete: {}, Create: {}, Drop: {}", + self.read_count, + self.write_count, + self.delete_count, + self.create_count, + self.drop_count ) } } @@ -307,37 +312,40 @@ impl Interactions { } select.shadow(env); } + Property::DropSelect { + table, + queries, + select, + } => { + let drop = Query::Drop(Drop { + table: table.clone(), + }); + + drop.shadow(env); + for query in queries { + query.shadow(env); + } + select.shadow(env); + } } for interaction in property.interactions() { match interaction { Interaction::Query(query) => match query { Query::Create(create) => { - if !env.tables.iter().any(|t| t.name == create.table.name) { - env.tables.push(create.table.clone()); - } + create.shadow(env); } Query::Insert(insert) => { - let values = match &insert { - Insert::Values { values, .. } => values.clone(), - Insert::Select { select, .. } => select.shadow(env), - }; - let table = env - .tables - .iter_mut() - .find(|t| t.name == insert.table()) - .unwrap(); - table.rows.extend(values); + insert.shadow(env); } Query::Delete(delete) => { - let table = env - .tables - .iter_mut() - .find(|t| t.name == delete.table) - .unwrap(); - let t2 = &table.clone(); - table.rows.retain_mut(|r| delete.predicate.test(r, t2)); + delete.shadow(env); + } + Query::Drop(drop) => { + drop.shadow(env); + } + Query::Select(select) => { + select.shadow(env); } - Query::Select(_) => {} }, Interaction::Assertion(_) => {} Interaction::Assumption(_) => {} @@ -363,6 +371,7 @@ impl InteractionPlan { let mut write = 0; let mut delete = 0; let mut create = 0; + let mut drop = 0; for interactions in &self.plan { match interactions { @@ -374,6 +383,7 @@ impl InteractionPlan { Query::Insert(_) => write += 1, Query::Delete(_) => delete += 1, Query::Create(_) => create += 1, + Query::Drop(_) => drop += 1, } } } @@ -383,6 +393,7 @@ impl InteractionPlan { Query::Insert(_) => write += 1, Query::Delete(_) => delete += 1, Query::Create(_) => create += 1, + Query::Drop(_) => drop += 1, }, Interactions::Fault(_) => {} } @@ -393,6 +404,7 @@ impl InteractionPlan { write_count: write, delete_count: delete, create_count: create, + drop_count: drop, } } } @@ -579,7 +591,7 @@ impl Interaction { } } -fn create_table(rng: &mut R, _env: &SimulatorEnv) -> Interactions { +fn random_create(rng: &mut R, _env: &SimulatorEnv) -> Interactions { Interactions::Query(Query::Create(Create::arbitrary(rng))) } @@ -588,8 +600,15 @@ fn random_read(rng: &mut R, env: &SimulatorEnv) -> Interactions { } fn random_write(rng: &mut R, env: &SimulatorEnv) -> Interactions { - let insert_query = Query::Insert(Insert::arbitrary_from(rng, env)); - Interactions::Query(insert_query) + Interactions::Query(Query::Insert(Insert::arbitrary_from(rng, env))) +} + +fn random_delete(rng: &mut R, env: &SimulatorEnv) -> Interactions { + Interactions::Query(Query::Delete(Delete::arbitrary_from(rng, env))) +} + +fn random_drop(rng: &mut R, env: &SimulatorEnv) -> Interactions { + Interactions::Query(Query::Drop(Drop::arbitrary_from(rng, env))) } fn random_fault(_rng: &mut R, _env: &SimulatorEnv) -> Interactions { @@ -620,7 +639,16 @@ impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions { ), ( remaining_.create, - Box::new(|rng: &mut R| create_table(rng, env)), + Box::new(|rng: &mut R| random_create(rng, env)), + ), + ( + remaining_.delete, + Box::new(|rng: &mut R| random_delete(rng, env)), + ), + ( + // remaining_.drop, + 0.0, + Box::new(|rng: &mut R| random_drop(rng, env)), ), ( remaining_ diff --git a/simulator/generation/property.rs b/simulator/generation/property.rs index fe454e9fd..a16adf416 100644 --- a/simulator/generation/property.rs +++ b/simulator/generation/property.rs @@ -3,7 +3,10 @@ use serde::{Deserialize, Serialize}; use crate::{ model::{ - query::{Create, Delete, Distinctness, Insert, Predicate, Query, Select}, + query::{ + select::{Distinctness, Predicate}, + Create, Delete, Drop, Insert, Query, Select, + }, table::Value, }, runner::env::SimulatorEnv, @@ -94,6 +97,23 @@ pub(crate) enum Property { predicate: Predicate, queries: Vec, }, + // Drop-Select is a property in which selecting from a dropped table + // should result in an error. + // The execution of the property is as follows + // DROP TABLE + // I_0 + // I_1 + // ... + // I_n + // SELECT * FROM WHERE -> Error + // The interactions in the middle has the following constraints; + // - There will be no errors in the middle interactions. + // - The table `t` will not be created, no table will be renamed to `t`. + DropSelect { + table: String, + queries: Vec, + select: Select, + }, } impl Property { @@ -103,6 +123,7 @@ impl Property { Property::DoubleCreateFailure { .. } => "Double-Create-Failure".to_string(), Property::SelectLimit { .. } => "Select-Limit".to_string(), Property::DeleteSelect { .. } => "Delete-Select".to_string(), + Property::DropSelect { .. } => "Drop-Select".to_string(), } } /// interactions construct a list of interactions, which is an executable representation of the property. @@ -287,6 +308,55 @@ impl Property { interactions.push(select); interactions.push(assertion); + interactions + } + Property::DropSelect { + table, + queries, + select, + } => { + let assumption = Interaction::Assumption(Assertion { + message: format!("table {} exists", table), + func: Box::new({ + let table = table.clone(); + move |_: &Vec, env: &SimulatorEnv| { + Ok(env.tables.iter().any(|t| t.name == table)) + } + }), + }); + + let table_name = table.clone(); + + let assertion = Interaction::Assertion(Assertion { + message: format!( + "select query should result in an error for table '{}'", + table + ), + func: Box::new(move |stack: &Vec, _: &SimulatorEnv| { + let last = stack.last().unwrap(); + match last { + Ok(_) => Ok(false), + Err(e) => Ok(e + .to_string() + .contains(&format!("Table {table_name} does not exist"))), + } + }), + }); + + let drop = Interaction::Query(Query::Drop(Drop { + table: table.clone(), + })); + + let select = Interaction::Query(Query::Select(select.clone())); + + let mut interactions = Vec::new(); + + interactions.push(assumption); + interactions.push(drop); + interactions.extend(queries.clone().into_iter().map(Interaction::Query)); + interactions.push(select); + interactions.push(assertion); + interactions } } @@ -298,6 +368,8 @@ pub(crate) struct Remaining { pub(crate) read: f64, pub(crate) write: f64, pub(crate) create: f64, + pub(crate) delete: f64, + pub(crate) drop: f64, } pub(crate) fn remaining(env: &SimulatorEnv, stats: &InteractionStats) -> Remaining { @@ -310,11 +382,19 @@ pub(crate) fn remaining(env: &SimulatorEnv, stats: &InteractionStats) -> Remaini let remaining_create = ((env.opts.max_interactions as f64 * env.opts.create_percent / 100.0) - (stats.create_count as f64)) .max(0.0); + let remaining_delete = ((env.opts.max_interactions as f64 * env.opts.delete_percent / 100.0) + - (stats.delete_count as f64)) + .max(0.0); + let remaining_drop = ((env.opts.max_interactions as f64 * env.opts.drop_percent / 100.0) + - (stats.drop_count as f64)) + .max(0.0); Remaining { read: remaining_read, write: remaining_write, create: remaining_create, + delete: remaining_delete, + drop: remaining_drop, } } @@ -479,6 +559,47 @@ fn property_delete_select( queries, } } + +fn property_drop_select( + rng: &mut R, + env: &SimulatorEnv, + remaining: &Remaining, +) -> Property { + // Get a random table + let table = pick(&env.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.gen_range(0..3) { + let query = Query::arbitrary_from(rng, (env, remaining)); + match &query { + Query::Create(Create { table: t }) => { + // - The table `t` will not be created + if t.name == table.name { + continue; + } + } + _ => (), + } + queries.push(query); + } + + let select = Select { + table: table.name.clone(), + predicate: Predicate::arbitrary_from(rng, table), + limit: None, + distinct: Distinctness::All, + }; + + Property::DropSelect { + table: table.name.clone(), + queries, + select, + } +} + impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { fn arbitrary_from( rng: &mut R, @@ -503,6 +624,11 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { f64::min(remaining_.read, remaining_.write), Box::new(|rng: &mut R| property_delete_select(rng, env, &remaining_)), ), + ( + // remaining_.drop, + 0.0, + Box::new(|rng: &mut R| property_drop_select(rng, env, &remaining_)), + ), ], rng, ) diff --git a/simulator/generation/query.rs b/simulator/generation/query.rs index 7624367a5..4fa53844e 100644 --- a/simulator/generation/query.rs +++ b/simulator/generation/query.rs @@ -1,7 +1,8 @@ use crate::generation::table::{GTValue, LTValue}; use crate::generation::{one_of, Arbitrary, ArbitraryFrom}; -use crate::model::query::{Create, Delete, Distinctness, Insert, Predicate, Query, Select}; +use crate::model::query::select::{Distinctness, Predicate}; +use crate::model::query::{Create, Delete, Drop, Insert, Query, Select}; use crate::model::table::{Table, Value}; use crate::SimulatorEnv; use rand::seq::SliceRandom as _; @@ -96,6 +97,15 @@ impl ArbitraryFrom<&SimulatorEnv> for Delete { } } +impl ArbitraryFrom<&SimulatorEnv> for Drop { + fn arbitrary_from(rng: &mut R, env: &SimulatorEnv) -> Self { + let table = pick(&env.tables, rng); + Self { + table: table.name.clone(), + } + } +} + impl ArbitraryFrom<(&SimulatorEnv, &Remaining)> for Query { fn arbitrary_from(rng: &mut R, (env, remaining): (&SimulatorEnv, &Remaining)) -> Self { frequency( diff --git a/simulator/model/query/create.rs b/simulator/model/query/create.rs new file mode 100644 index 000000000..172fd9c9d --- /dev/null +++ b/simulator/model/query/create.rs @@ -0,0 +1,38 @@ +use std::fmt::Display; + +use serde::{Deserialize, Serialize}; + +use crate::{ + model::table::{Table, Value}, + SimulatorEnv, +}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct Create { + pub(crate) table: Table, +} + +impl Create { + pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec> { + if !env.tables.iter().any(|t| t.name == self.table.name) { + env.tables.push(self.table.clone()); + } + + vec![] + } +} + +impl Display for Create { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "CREATE TABLE {} (", self.table.name)?; + + for (i, column) in self.table.columns.iter().enumerate() { + if i != 0 { + write!(f, ",")?; + } + write!(f, "{} {}", column.name, column.column_type)?; + } + + write!(f, ")") + } +} diff --git a/simulator/model/query/delete.rs b/simulator/model/query/delete.rs new file mode 100644 index 000000000..f6f224fb8 --- /dev/null +++ b/simulator/model/query/delete.rs @@ -0,0 +1,35 @@ +use std::fmt::Display; + +use serde::{Deserialize, Serialize}; + +use crate::{model::table::Value, SimulatorEnv}; + +use super::select::Predicate; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub(crate) struct Delete { + pub(crate) table: String, + pub(crate) predicate: Predicate, +} + +impl Delete { + pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec> { + let table = env + .tables + .iter_mut() + .find(|t| t.name == self.table) + .unwrap(); + + let t2 = table.clone(); + + table.rows.retain_mut(|r| self.predicate.test(r, &t2)); + + vec![] + } +} + +impl Display for Delete { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "DELETE FROM {} WHERE {}", self.table, self.predicate) + } +} diff --git a/simulator/model/query/drop.rs b/simulator/model/query/drop.rs new file mode 100644 index 000000000..b38c9dc71 --- /dev/null +++ b/simulator/model/query/drop.rs @@ -0,0 +1,23 @@ +use std::fmt::Display; + +use serde::{Deserialize, Serialize}; + +use crate::{model::table::Value, SimulatorEnv}; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub(crate) struct Drop { + pub(crate) table: String, +} + +impl Drop { + pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec> { + env.tables.retain(|t| t.name != self.table); + vec![] + } +} + +impl Display for Drop { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "DROP TABLE {}", self.table) + } +} diff --git a/simulator/model/query/insert.rs b/simulator/model/query/insert.rs new file mode 100644 index 000000000..109c9df3a --- /dev/null +++ b/simulator/model/query/insert.rs @@ -0,0 +1,73 @@ +use std::fmt::Display; + +use serde::{Deserialize, Serialize}; + +use crate::{model::table::Value, SimulatorEnv}; + +use super::select::Select; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub(crate) enum Insert { + Values { + table: String, + values: Vec>, + }, + Select { + table: String, + select: Box, - }, -} - -impl Insert { - pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec> { - match self { - Insert::Values { table, values } => { - if let Some(t) = env.tables.iter_mut().find(|t| &t.name == table) { - t.rows.extend(values.clone()); - } - } - Insert::Select { table, select } => { - let rows = select.shadow(env); - if let Some(t) = env.tables.iter_mut().find(|t| &t.name == table) { - t.rows.extend(rows); - } - } - } - - vec![] - } - - pub(crate) fn table(&self) -> &str { - match self { - Insert::Values { table, .. } | Insert::Select { table, .. } => table, - } - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub(crate) struct Delete { - pub(crate) table: String, - pub(crate) predicate: Predicate, -} - -impl Delete { - pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) -> Vec> { - vec![] - } -} - -impl Display for Query { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Create(create) => write!(f, "{}", create), - Self::Select(select) => write!(f, "{}", select), - Self::Insert(Insert::Values { table, values }) => { - write!(f, "INSERT INTO {} VALUES ", table)?; - for (i, row) in values.iter().enumerate() { - if i != 0 { - write!(f, ", ")?; - } - write!(f, "(")?; - for (j, value) in row.iter().enumerate() { - if j != 0 { - write!(f, ", ")?; - } - write!(f, "{}", value)?; - } - write!(f, ")")?; - } - Ok(()) - } - Self::Insert(Insert::Select { table, select }) => { - write!(f, "INSERT INTO {} ", table)?; - write!(f, "{}", select) - } - Self::Delete(Delete { - table, - predicate: guard, - }) => write!(f, "DELETE FROM {} WHERE {}", table, guard), - } - } -} diff --git a/simulator/runner/env.rs b/simulator/runner/env.rs index 2813b80e8..f6cea1a96 100644 --- a/simulator/runner/env.rs +++ b/simulator/runner/env.rs @@ -26,18 +26,28 @@ impl SimulatorEnv { pub(crate) fn new(seed: u64, cli_opts: &SimulatorCLI, db_path: &Path) -> Self { let mut rng = ChaCha8Rng::seed_from_u64(seed); - let (create_percent, read_percent, write_percent, delete_percent) = { - let mut remaining = 100.0; - let read_percent = rng.gen_range(0.0..=remaining); - remaining -= read_percent; - let write_percent = rng.gen_range(0.0..=remaining); - remaining -= write_percent; - let delete_percent = remaining; - - let create_percent = write_percent / 10.0; - let write_percent = write_percent - create_percent; - - (create_percent, read_percent, write_percent, delete_percent) + let (create_percent, read_percent, write_percent, delete_percent, drop_percent) = { + let total = 100.0; + + let read_percent = rng.gen_range(0.0..=total); + let write_percent = total - read_percent; + + // Create percent should be 5-15% of the write percent + let create_percent = rng.gen_range(0.05..=0.15) * write_percent; + // Drop percent should be 2-5% of the write percent + let drop_percent = rng.gen_range(0.02..=0.05) * write_percent; + // Delete percent should be 10-20% of the write percent + let delete_percent = rng.gen_range(0.1..=0.2) * write_percent; + + let write_percent = write_percent - create_percent - delete_percent - drop_percent; + + ( + create_percent, + read_percent, + write_percent, + delete_percent, + drop_percent, + ) }; let opts = SimulatorOpts { @@ -49,6 +59,7 @@ impl SimulatorEnv { read_percent, write_percent, delete_percent, + drop_percent, page_size: 4096, // TODO: randomize this too max_interactions: rng.gen_range(cli_opts.minimum_size..=cli_opts.maximum_size), max_time_simulation: cli_opts.maximum_time, @@ -98,6 +109,7 @@ pub(crate) struct SimulatorOpts { pub(crate) read_percent: f64, pub(crate) write_percent: f64, pub(crate) delete_percent: f64, + pub(crate) drop_percent: f64, pub(crate) max_interactions: usize, pub(crate) page_size: usize, pub(crate) max_time_simulation: usize, diff --git a/simulator/shrink/plan.rs b/simulator/shrink/plan.rs index 0ce978ab4..2365dc2ff 100644 --- a/simulator/shrink/plan.rs +++ b/simulator/shrink/plan.rs @@ -32,7 +32,8 @@ impl InteractionPlan { match p { Property::InsertValuesSelect { queries, .. } | Property::DoubleCreateFailure { queries, .. } - | Property::DeleteSelect { queries, .. } => { + | Property::DeleteSelect { queries, .. } + | Property::DropSelect { queries, .. } => { queries.clear(); } Property::SelectLimit { .. } => {}