From f5139f086ed2facf1fc6fc9f1b5a2410d19bc6e5 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Mon, 27 Jan 2025 01:24:20 +0300 Subject: [PATCH 1/5] add select-limit property --- simulator/generation/plan.rs | 3 ++ simulator/generation/property.rs | 64 ++++++++++++++++++++++++++++++++ simulator/generation/query.rs | 2 + simulator/model/query.rs | 10 ++++- simulator/shrink/plan.rs | 12 +++--- 5 files changed, 85 insertions(+), 6 deletions(-) diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 2a794fbbc..2c8c940cf 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -282,6 +282,9 @@ impl Interactions { query.shadow(env); } } + Property::SelectLimit { select } => { + select.shadow(env); + }, } for interaction in property.interactions() { match interaction { diff --git a/simulator/generation/property.rs b/simulator/generation/property.rs index bfa1e1ed5..517baf372 100644 --- a/simulator/generation/property.rs +++ b/simulator/generation/property.rs @@ -62,6 +62,17 @@ pub(crate) enum Property { /// Additional interactions in the middle of the property queries: Vec, }, + /// Select Limit is a property in which the select query + /// has a limit clause that is respected by the query. + /// The execution of the property is as follows + /// SELECT * FROM WHERE LIMIT + /// This property is a single-interaction property. + /// The interaction has the following constraints; + /// - The select query will respect the limit clause. + SelectLimit { + /// The select query + select: Select, + }, } impl Property { @@ -69,6 +80,7 @@ impl Property { match self { Property::InsertSelect { .. } => "Insert-Select".to_string(), Property::DoubleCreateFailure { .. } => "Double-Create-Failure".to_string(), + Property::SelectLimit { .. } => "Select-Limit".to_string(), } } /// interactions construct a list of interactions, which is an executable representation of the property. @@ -164,6 +176,38 @@ impl Property { interactions } + Property::SelectLimit { select } => { + let table_name = select.table.clone(); + + let assumption = Interaction::Assumption(Assertion { + message: format!("table {} exists", table_name), + func: Box::new({ + let table_name = table_name.clone(); + move |_: &Vec, env: &SimulatorEnv| { + Ok(env.tables.iter().any(|t| t.name == table_name)) + } + }), + }); + + let limit = select.limit.clone().unwrap_or(0); + + let assertion = Interaction::Assertion(Assertion { + message: "select query should respect the limit clause".to_string(), + func: Box::new(move |stack: &Vec, _: &SimulatorEnv| { + let last = stack.last().unwrap(); + match last { + Ok(rows) => Ok(limit >= rows.len()), + Err(_) => Ok(true), + } + }), + }); + + vec![ + assumption, + Interaction::Query(Query::Select(select.clone())), + assertion, + ] + } } } } @@ -248,6 +292,7 @@ fn property_insert_select( let select_query = Select { table: table.name.clone(), predicate: Predicate::arbitrary_from(rng, (table, &row)), + limit: None, }; Property::InsertSelect { @@ -258,6 +303,21 @@ fn property_insert_select( } } +fn property_select_limit( + rng: &mut R, + env: &SimulatorEnv, +) -> Property { + // Get a random table + let table = pick(&env.tables, rng); + // Select the table + let select = Select { + table: table.name.clone(), + predicate: Predicate::arbitrary_from(rng, table), + limit: Some(rng.gen_range(1..=5)), + }; + Property::SelectLimit { select } +} + fn property_double_create_failure( rng: &mut R, env: &SimulatorEnv, @@ -312,6 +372,10 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property { remaining_.create / 2.0, Box::new(|rng: &mut R| property_double_create_failure(rng, env, &remaining_)), ), + ( + remaining_.read, + Box::new(|rng: &mut R| property_select_limit(rng, env)), + ), ], rng, ) diff --git a/simulator/generation/query.rs b/simulator/generation/query.rs index 8b93fa993..1a0d167fc 100644 --- a/simulator/generation/query.rs +++ b/simulator/generation/query.rs @@ -23,6 +23,7 @@ impl ArbitraryFrom<&Vec> for Select { Self { table: table.name.clone(), predicate: Predicate::arbitrary_from(rng, table), + limit: Some(rng.gen_range(0..=1000)), } } } @@ -33,6 +34,7 @@ impl ArbitraryFrom<&Vec<&Table>> for Select { Self { table: table.name.clone(), predicate: Predicate::arbitrary_from(rng, *table), + limit: Some(rng.gen_range(0..=1000)), } } } diff --git a/simulator/model/query.rs b/simulator/model/query.rs index f03bbde6f..8c861edc4 100644 --- a/simulator/model/query.rs +++ b/simulator/model/query.rs @@ -140,6 +140,7 @@ impl Create { pub(crate) struct Select { pub(crate) table: String, pub(crate) predicate: Predicate, + pub(crate) limit: Option, } impl Select { @@ -190,7 +191,14 @@ impl Display for Query { Self::Select(Select { table, predicate: guard, - }) => write!(f, "SELECT * FROM {} WHERE {}", table, guard), + limit, + }) => write!( + f, + "SELECT * FROM {} WHERE {}{}", + table, + guard, + limit.map_or("".to_string(), |l| format!(" LIMIT {}", l)) + ), Self::Insert(Insert { table, values }) => { write!(f, "INSERT INTO {} VALUES ", table)?; for (i, row) in values.iter().enumerate() { diff --git a/simulator/shrink/plan.rs b/simulator/shrink/plan.rs index 92867d82e..bc5eaa4d0 100644 --- a/simulator/shrink/plan.rs +++ b/simulator/shrink/plan.rs @@ -1,5 +1,8 @@ use crate::{ - generation::plan::{InteractionPlan, Interactions}, + generation::{ + plan::{InteractionPlan, Interactions}, + property::Property, + }, model::query::Query, runner::execution::Execution, }; @@ -27,12 +30,11 @@ impl InteractionPlan { for interaction in plan.plan.iter_mut() { if let Interactions::Property(p) = interaction { match p { - crate::generation::property::Property::InsertSelect { queries, .. } - | crate::generation::property::Property::DoubleCreateFailure { - queries, .. - } => { + Property::InsertSelect { queries, .. } + | Property::DoubleCreateFailure { queries, .. } => { queries.clear(); } + Property::SelectLimit { .. } => {} } } } From 48d091e112757abaf091376fd84f06aca7a2dc37 Mon Sep 17 00:00:00 2001 From: alpaylan Date: Sat, 1 Feb 2025 10:40:07 -0500 Subject: [PATCH 2/5] add insert into
> 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