diff --git a/simulator/generation/mod.rs b/simulator/generation/mod.rs index 04b5ee03d..71b8488be 100644 --- a/simulator/generation/mod.rs +++ b/simulator/generation/mod.rs @@ -1,4 +1,7 @@ -use std::{iter::Sum, ops::SubAssign}; +use std::{ + iter::Sum, + ops::SubAssign, +}; use anarchist_readable_name_generator_lib::readable_name_custom; use rand::{distributions::uniform::SampleUniform, Rng}; @@ -60,6 +63,36 @@ pub(crate) fn one_of<'a, T, R: Rng>(choices: Vec T + 'a>>, choices[index](rng) } +/// backtrack is a helper function for composing different "failable" generators. +/// The function takes a list of functions that return an Option, along with number of retries +/// to make before giving up. +pub(crate) fn backtrack<'a, T, R: Rng>( + mut choices: Vec<(u32, Box Option + 'a>)>, + rng: &mut R, +) -> T { + loop { + // If there are no more choices left, we give up + let choices_ = choices + .iter() + .enumerate() + .filter(|(_, (retries, _))| *retries > 0) + .collect::>(); + if choices_.is_empty() { + panic!("backtrack: no more choices left"); + } + // Run a one_of on the remaining choices + let (choice_index, choice) = pick(&choices_, rng); + let choice_index = *choice_index; + // If the choice returns None, we decrement the number of retries and try again + let result = choice.1(rng); + if let Some(result) = result { + return result; + } else { + choices[choice_index].0 -= 1; + } + } +} + /// pick is a helper function for uniformly picking a random element from a slice pub(crate) fn pick<'a, T, R: Rng>(choices: &'a [T], rng: &mut R) -> &'a T { let index = rng.gen_range(0..choices.len()); diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 2c8c940cf..a2651369d 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -14,10 +14,7 @@ use crate::{ use crate::generation::{frequency, Arbitrary, ArbitraryFrom}; -use super::{ - pick, - property::{remaining, Property}, -}; +use super::property::{remaining, Property}; pub(crate) type ResultSet = Result>>; @@ -261,7 +258,7 @@ impl Interactions { match self { Interactions::Property(property) => { match property { - Property::InsertSelect { + Property::InsertValuesSelect { insert, row_index: _, queries, @@ -284,7 +281,7 @@ impl Interactions { } Property::SelectLimit { select } => { select.shadow(env); - }, + } } for interaction in property.interactions() { match interaction { @@ -295,12 +292,16 @@ impl Interactions { } } 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) + .find(|t| t.name == insert.table()) .unwrap(); - table.rows.extend(insert.values.clone()); + table.rows.extend(values); } Query::Delete(_) => todo!(), Query::Select(_) => {} @@ -311,7 +312,9 @@ impl Interactions { } } } - Interactions::Query(query) => query.shadow(env), + Interactions::Query(query) => { + query.shadow(env); + } Interactions::Fault(_) => {} } } @@ -392,12 +395,10 @@ impl ArbitraryFrom<&mut SimulatorEnv> for InteractionPlan { } impl Interaction { - pub(crate) fn shadow(&self, env: &mut SimulatorEnv) { + pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec> { match self { Self::Query(query) => query.shadow(env), - Self::Assumption(_) => {} - Self::Assertion(_) => {} - Self::Fault(_) => {} + Self::Assumption(_) | Self::Assertion(_) | Self::Fault(_) => vec![], } } pub(crate) fn execute_query(&self, conn: &mut Rc) -> ResultSet { @@ -548,12 +549,11 @@ fn create_table(rng: &mut R, _env: &SimulatorEnv) -> Interactions } fn random_read(rng: &mut R, env: &SimulatorEnv) -> Interactions { - Interactions::Query(Query::Select(Select::arbitrary_from(rng, &env.tables))) + Interactions::Query(Query::Select(Select::arbitrary_from(rng, env))) } fn random_write(rng: &mut R, env: &SimulatorEnv) -> Interactions { - let table = pick(&env.tables, rng); - let insert_query = Query::Insert(Insert::arbitrary_from(rng, table)); + let insert_query = Query::Insert(Insert::arbitrary_from(rng, env)); Interactions::Query(insert_query) } diff --git a/simulator/generation/property.rs b/simulator/generation/property.rs index 517baf372..37c1fe744 100644 --- a/simulator/generation/property.rs +++ b/simulator/generation/property.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::{ model::{ - query::{Create, Delete, Insert, Predicate, Query, Select}, + query::{Create, Delete, Distinctness, Insert, Predicate, Query, Select}, table::Value, }, runner::env::SimulatorEnv, @@ -34,7 +34,7 @@ pub(crate) enum Property { /// - The inserted row will not be deleted. /// - The inserted row will not be updated. /// - The table `t` will not be renamed, dropped, or altered. - InsertSelect { + InsertValuesSelect { /// The insert query insert: Insert, /// Selected row index @@ -78,7 +78,7 @@ pub(crate) enum Property { impl Property { pub(crate) fn name(&self) -> String { match self { - Property::InsertSelect { .. } => "Insert-Select".to_string(), + Property::InsertValuesSelect { .. } => "Insert-Values-Select".to_string(), Property::DoubleCreateFailure { .. } => "Double-Create-Failure".to_string(), Property::SelectLimit { .. } => "Select-Limit".to_string(), } @@ -88,26 +88,33 @@ impl Property { /// and `interaction` cannot be serialized directly. pub(crate) fn interactions(&self) -> Vec { match self { - Property::InsertSelect { + Property::InsertValuesSelect { insert, row_index, queries, select, } => { + let (table, values) = if let Insert::Values { table, values } = insert { + (table, values) + } else { + unreachable!( + "insert query should be Insert::Values for Insert-Values-Select property" + ) + }; // Check that the insert query has at least 1 value assert!( - !insert.values.is_empty(), + !values.is_empty(), "insert query should have at least 1 value" ); // Pick a random row within the insert values - let row = insert.values[*row_index].clone(); + let row = values[*row_index].clone(); // Assume that the table exists let assumption = Interaction::Assumption(Assertion { - message: format!("table {} exists", insert.table), + message: format!("table {} exists", insert.table()), func: Box::new({ - let table_name = insert.table.clone(); + let table_name = table.clone(); move |_: &Vec, env: &SimulatorEnv| { Ok(env.tables.iter().any(|t| t.name == table_name)) } @@ -118,7 +125,7 @@ impl Property { message: format!( "row [{:?}] not found in table {}", row.iter().map(|v| v.to_string()).collect::>(), - insert.table, + insert.table(), ), func: Box::new(move |stack: &Vec, _: &SimulatorEnv| { let rows = stack.last().unwrap(); @@ -189,7 +196,7 @@ impl Property { }), }); - let limit = select.limit.clone().unwrap_or(0); + let limit = select.limit.unwrap_or(0); let assertion = Interaction::Assertion(Assertion { message: "select query should respect the limit clause".to_string(), @@ -212,6 +219,7 @@ impl Property { } } +#[derive(Debug)] pub(crate) struct Remaining { pub(crate) read: f64, pub(crate) write: f64, @@ -236,7 +244,7 @@ pub(crate) fn remaining(env: &SimulatorEnv, stats: &InteractionStats) -> Remaini } } -fn property_insert_select( +fn property_insert_values_select( rng: &mut R, env: &SimulatorEnv, remaining: &Remaining, @@ -253,7 +261,7 @@ fn property_insert_select( let row = rows[row_index].clone(); // Insert the rows - let insert_query = Insert { + let insert_query = Insert::Values { table: table.name.clone(), values: rows, }; @@ -265,7 +273,7 @@ fn property_insert_select( // - [ ] The inserted row will not be updated. (todo: add this constraint once UPDATE is implemented) // - [ ] The table `t` will not be renamed, dropped, or altered. (todo: add this constraint once ALTER or DROP is implemented) for _ in 0..rng.gen_range(0..3) { - let query = Query::arbitrary_from(rng, (table, remaining)); + let query = Query::arbitrary_from(rng, (env, remaining)); match &query { Query::Delete(Delete { table: t, @@ -293,9 +301,10 @@ fn property_insert_select( table: table.name.clone(), predicate: Predicate::arbitrary_from(rng, (table, &row)), limit: None, + distinct: Distinctness::All, }; - Property::InsertSelect { + Property::InsertValuesSelect { insert: insert_query, row_index, queries, @@ -303,10 +312,7 @@ fn property_insert_select( } } -fn property_select_limit( - rng: &mut R, - env: &SimulatorEnv, -) -> Property { +fn property_select_limit(rng: &mut R, env: &SimulatorEnv) -> Property { // Get a random table let table = pick(&env.tables, rng); // Select the table @@ -314,6 +320,7 @@ fn property_select_limit( table: table.name.clone(), predicate: Predicate::arbitrary_from(rng, table), limit: Some(rng.gen_range(1..=5)), + distinct: Distinctness::All, }; Property::SelectLimit { select } } @@ -336,7 +343,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.gen_range(0..3) { - let query = Query::arbitrary_from(rng, (table, remaining)); + let query = Query::arbitrary_from(rng, (env, remaining)); match &query { Query::Create(Create { table: t }) => { // There will be no errors in the middle interactions. @@ -366,7 +373,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { vec![ ( f64::min(remaining_.read, remaining_.write), - Box::new(|rng: &mut R| property_insert_select(rng, env, &remaining_)), + Box::new(|rng: &mut R| property_insert_values_select(rng, env, &remaining_)), ), ( remaining_.create / 2.0, diff --git a/simulator/generation/query.rs b/simulator/generation/query.rs index 1a0d167fc..3448bd434 100644 --- a/simulator/generation/query.rs +++ b/simulator/generation/query.rs @@ -1,13 +1,14 @@ use crate::generation::table::{GTValue, LTValue}; use crate::generation::{one_of, Arbitrary, ArbitraryFrom}; -use crate::model::query::{Create, Delete, Insert, Predicate, Query, Select}; +use crate::model::query::{Create, Delete, Distinctness, Insert, Predicate, Query, Select}; use crate::model::table::{Table, Value}; +use crate::SimulatorEnv; use rand::seq::SliceRandom as _; use rand::Rng; use super::property::Remaining; -use super::{frequency, pick}; +use super::{backtrack, frequency, pick}; impl Arbitrary for Create { fn arbitrary(rng: &mut R) -> Self { @@ -17,81 +18,85 @@ impl Arbitrary for Create { } } -impl ArbitraryFrom<&Vec> for Select { - fn arbitrary_from(rng: &mut R, tables: &Vec
) -> Self { - let table = pick(tables, rng); +impl ArbitraryFrom<&SimulatorEnv> for Select { + fn arbitrary_from(rng: &mut R, env: &SimulatorEnv) -> Self { + let table = pick(&env.tables, rng); Self { table: table.name.clone(), predicate: Predicate::arbitrary_from(rng, table), limit: Some(rng.gen_range(0..=1000)), + distinct: Distinctness::All, } } } -impl ArbitraryFrom<&Vec<&Table>> for Select { - fn arbitrary_from(rng: &mut R, tables: &Vec<&Table>) -> Self { - let table = pick(tables, rng); - Self { - table: table.name.clone(), - predicate: Predicate::arbitrary_from(rng, *table), - limit: Some(rng.gen_range(0..=1000)), - } - } -} - -impl ArbitraryFrom<&Table> for Insert { - fn arbitrary_from(rng: &mut R, table: &Table) -> Self { - let num_rows = rng.gen_range(1..10); - let values: Vec> = (0..num_rows) - .map(|_| { - table - .columns - .iter() - .map(|c| Value::arbitrary_from(rng, &c.column_type)) - .collect() +impl ArbitraryFrom<&SimulatorEnv> for Insert { + fn arbitrary_from(rng: &mut R, env: &SimulatorEnv) -> Self { + let gen_values = |rng: &mut R| { + let table = pick(&env.tables, rng); + let num_rows = rng.gen_range(1..10); + let values: Vec> = (0..num_rows) + .map(|_| { + table + .columns + .iter() + .map(|c| Value::arbitrary_from(rng, &c.column_type)) + .collect() + }) + .collect(); + Some(Insert::Values { + table: table.name.clone(), + values, }) - .collect(); - Self { - table: table.name.clone(), - values, - } - } -} + }; -impl ArbitraryFrom<&Table> for Delete { - fn arbitrary_from(rng: &mut R, table: &Table) -> Self { - Self { - table: table.name.clone(), - predicate: Predicate::arbitrary_from(rng, table), - } - } -} + let _gen_select = |rng: &mut R| { + // Find a non-empty table + let table = env.tables.iter().find(|t| !t.rows.is_empty()); + if table.is_none() { + return None; + } -impl ArbitraryFrom<&Table> for Query { - fn arbitrary_from(rng: &mut R, table: &Table) -> Self { - frequency( + let select_table = table.unwrap(); + let row = pick(&select_table.rows, rng); + let predicate = Predicate::arbitrary_from(rng, (select_table, row)); + // Pick another table to insert into + let select = Select { + table: select_table.name.clone(), + predicate, + limit: None, + distinct: Distinctness::All, + }; + let table = pick(&env.tables, rng); + Some(Insert::Select { + table: table.name.clone(), + select: Box::new(select), + }) + }; + + backtrack( vec![ - (1, Box::new(|rng| Self::Create(Create::arbitrary(rng)))), - ( - 100, - Box::new(|rng| Self::Select(Select::arbitrary_from(rng, &vec![table]))), - ), - ( - 100, - Box::new(|rng| Self::Insert(Insert::arbitrary_from(rng, table))), - ), - ( - 0, - Box::new(|rng| Self::Delete(Delete::arbitrary_from(rng, table))), - ), + (1, Box::new(|rng| gen_values(rng))), + // todo: test and enable this once `INSERT INTO
SELECT * FROM
` is supported + // (1, Box::new(|rng| gen_select(rng))), ], rng, ) } } -impl ArbitraryFrom<(&Table, &Remaining)> for Query { - fn arbitrary_from(rng: &mut R, (table, remaining): (&Table, &Remaining)) -> Self { +impl ArbitraryFrom<&SimulatorEnv> for Delete { + fn arbitrary_from(rng: &mut R, env: &SimulatorEnv) -> Self { + let table = pick(&env.tables, rng); + Self { + table: table.name.clone(), + predicate: Predicate::arbitrary_from(rng, table), + } + } +} + +impl ArbitraryFrom<(&SimulatorEnv, &Remaining)> for Query { + fn arbitrary_from(rng: &mut R, (env, remaining): (&SimulatorEnv, &Remaining)) -> Self { frequency( vec![ ( @@ -100,15 +105,15 @@ impl ArbitraryFrom<(&Table, &Remaining)> for Query { ), ( remaining.read, - Box::new(|rng| Self::Select(Select::arbitrary_from(rng, &vec![table]))), + Box::new(|rng| Self::Select(Select::arbitrary_from(rng, env))), ), ( remaining.write, - Box::new(|rng| Self::Insert(Insert::arbitrary_from(rng, table))), + Box::new(|rng| Self::Insert(Insert::arbitrary_from(rng, env))), ), ( 0.0, - Box::new(|rng| Self::Delete(Delete::arbitrary_from(rng, table))), + Box::new(|rng| Self::Delete(Delete::arbitrary_from(rng, env))), ), ], rng, diff --git a/simulator/model/query.rs b/simulator/model/query.rs index 8c861edc4..bc97be0b2 100644 --- a/simulator/model/query.rs +++ b/simulator/model/query.rs @@ -101,7 +101,8 @@ impl Query { match self { Query::Create(_) => vec![], Query::Select(Select { table, .. }) - | Query::Insert(Insert { table, .. }) + | Query::Insert(Insert::Select { table, .. }) + | Query::Insert(Insert::Values { table, .. }) | Query::Delete(Delete { table, .. }) => vec![table.clone()], } } @@ -109,12 +110,13 @@ impl Query { match self { Query::Create(Create { table }) => vec![table.name.clone()], Query::Select(Select { table, .. }) - | Query::Insert(Insert { table, .. }) + | Query::Insert(Insert::Select { table, .. }) + | Query::Insert(Insert::Values { table, .. }) | Query::Delete(Delete { table, .. }) => vec![table.clone()], } } - pub(crate) fn shadow(&self, env: &mut SimulatorEnv) { + pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec> { match self { Query::Create(create) => create.shadow(env), Query::Insert(insert) => insert.shadow(env), @@ -129,34 +131,110 @@ pub(crate) struct Create { } impl Create { - pub(crate) fn shadow(&self, env: &mut SimulatorEnv) { + 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, ")") + } +} + +/// `SELECT` distinctness +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Distinctness { + /// `DISTINCT` + Distinct, + /// `ALL` + All, +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub(crate) struct Select { pub(crate) table: String, pub(crate) predicate: Predicate, + pub(crate) distinct: Distinctness, pub(crate) limit: Option, } impl Select { - pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) {} + pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec> { + let table = env.tables.iter().find(|t| t.name == self.table.as_str()); + if let Some(table) = table { + table + .rows + .iter() + .filter(|row| self.predicate.test(row, table)) + .cloned() + .collect() + } else { + vec![] + } + } +} + +impl Display for Select { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "SELECT * FROM {} WHERE {}{}", + self.table, + self.predicate, + self.limit + .map_or("".to_string(), |l| format!(" LIMIT {}", l)) + ) + } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub(crate) struct Insert { - pub(crate) table: String, - pub(crate) values: Vec>, +pub(crate) enum Insert { + Values { + table: String, + values: Vec>, + }, + Select { + table: String, + select: Box