diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 910794faf..8fa2dcf02 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -217,20 +217,26 @@ pub(crate) struct InteractionStats { pub(crate) create_count: usize, pub(crate) create_index_count: usize, pub(crate) drop_count: usize, + pub(crate) begin_count: usize, + pub(crate) commit_count: usize, + pub(crate) rollback_count: usize, } impl Display for InteractionStats { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "Read: {}, Write: {}, Delete: {}, Update: {}, Create: {}, CreateIndex: {}, Drop: {}", + "Read: {}, Write: {}, Delete: {}, Update: {}, Create: {}, CreateIndex: {}, Drop: {}, Begin: {}, Commit: {}, Rollback: {}", self.read_count, self.write_count, self.delete_count, self.update_count, self.create_count, self.create_index_count, - self.drop_count + self.drop_count, + self.begin_count, + self.commit_count, + self.rollback_count, ) } } @@ -307,6 +313,9 @@ impl InteractionPlan { let mut drop = 0; let mut update = 0; let mut create_index = 0; + let mut begin = 0; + let mut commit = 0; + let mut rollback = 0; for interactions in &self.plan { match interactions { @@ -321,6 +330,9 @@ impl InteractionPlan { Query::Drop(_) => drop += 1, Query::Update(_) => update += 1, Query::CreateIndex(_) => create_index += 1, + Query::Begin(_) => begin += 1, + Query::Commit(_) => commit += 1, + Query::Rollback(_) => rollback += 1, } } } @@ -333,6 +345,9 @@ impl InteractionPlan { Query::Drop(_) => drop += 1, Query::Update(_) => update += 1, Query::CreateIndex(_) => create_index += 1, + Query::Begin(_) => begin += 1, + Query::Commit(_) => commit += 1, + Query::Rollback(_) => rollback += 1, }, Interactions::Fault(_) => {} } @@ -346,6 +361,9 @@ impl InteractionPlan { create_count: create, create_index_count: create_index, drop_count: drop, + begin_count: begin, + commit_count: commit, + rollback_count: rollback, } } } diff --git a/simulator/generation/property.rs b/simulator/generation/property.rs index 9266e41a7..f851b9a78 100644 --- a/simulator/generation/property.rs +++ b/simulator/generation/property.rs @@ -7,6 +7,7 @@ use crate::{ query::{ predicate::Predicate, select::{Distinctness, ResultColumn}, + transaction::{Begin, Commit, Rollback}, Create, Delete, Drop, Insert, Query, Select, }, table::SimValue, @@ -48,6 +49,8 @@ pub(crate) enum Property { queries: Vec, /// The select query select: Select, + /// Interactive query information if any + interactive: Option, }, /// Double Create Failure is a property in which creating /// the same table twice leads to an error. @@ -145,6 +148,12 @@ pub(crate) enum Property { }, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InteractiveQueryInfo { + start_with_immediate: bool, + end_with_commit: bool, +} + impl Property { pub(crate) fn name(&self) -> &str { match self { @@ -168,6 +177,7 @@ impl Property { row_index, queries, select, + interactive, } => { let (table, values) = if let Insert::Values { table, values } = insert { (table, values) @@ -198,14 +208,26 @@ impl Property { let assertion = Interaction::Assertion(Assertion { message: format!( - "row [{:?}] not found in table {}", + "row [{:?}] not found in table {}, interactive={} commit={}, rollback={}", row.iter().map(|v| v.to_string()).collect::>(), insert.table(), + interactive.is_some(), + interactive + .as_ref() + .map(|i| i.end_with_commit) + .unwrap_or(false), + interactive + .as_ref() + .map(|i| !i.end_with_commit) + .unwrap_or(false), ), func: Box::new(move |stack: &Vec, _: &SimulatorEnv| { let rows = stack.last().unwrap(); match rows { - Ok(rows) => Ok(rows.iter().any(|r| r == &row)), + Ok(rows) => { + let found = rows.iter().any(|r| r == &row); + Ok(found) + } Err(err) => Err(LimboError::InternalError(err.to_string())), } }), @@ -237,17 +259,17 @@ impl Property { let table_name = create.table.name.clone(); let assertion = Interaction::Assertion(Assertion { - message: - "creating two tables with the name should result in a failure for the second query" - .to_string(), - func: Box::new(move |stack: &Vec, _: &SimulatorEnv| { - let last = stack.last().unwrap(); - match last { - Ok(_) => Ok(false), - Err(e) => Ok(e.to_string().to_lowercase().contains(&format!("table {table_name} already exists"))), - } - }), - }); + message: + "creating two tables with the name should result in a failure for the second query" + .to_string(), + func: Box::new(move |stack: &Vec, _: &SimulatorEnv| { + let last = stack.last().unwrap(); + match last { + Ok(_) => Ok(false), + Err(e) => Ok(e.to_string().to_lowercase().contains(&format!("table {table_name} already exists"))), + } + }), + }); let mut interactions = Vec::new(); interactions.push(assumption); @@ -426,8 +448,8 @@ impl Property { // If rows1 results have more than 1 column, there is a problem if rows1.iter().any(|vs| vs.len() > 1) { return Err(LimboError::InternalError( - "Select query without the star should return only one column".to_string(), - )); + "Select query without the star should return only one column".to_string(), + )); } // Count the 1s in the select query without the star let rows1_count = rows1 @@ -564,12 +586,26 @@ fn property_insert_values_select( values: rows, }; + // Choose if we want queries to be executed in an interactive transaction + let interactive = if rng.gen_bool(0.5) { + Some(InteractiveQueryInfo { + start_with_immediate: rng.gen_bool(0.5), + end_with_commit: rng.gen_bool(0.5), + }) + } else { + None + }; // 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) // - [x] The inserted row will not be deleted. // - [ ] 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) + if let Some(ref interactive) = interactive { + queries.push(Query::Begin(Begin { + immediate: interactive.start_with_immediate, + })); + } for _ in 0..rng.gen_range(0..3) { let query = Query::arbitrary_from(rng, (env, remaining)); match &query { @@ -593,6 +629,13 @@ fn property_insert_values_select( } queries.push(query); } + if let Some(ref interactive) = interactive { + queries.push(if interactive.end_with_commit { + Query::Commit(Commit) + } else { + Query::Rollback(Rollback) + }); + } // Select the row let select_query = Select { @@ -608,6 +651,7 @@ fn property_insert_values_select( row_index, queries, select: select_query, + interactive, } } diff --git a/simulator/model/query/mod.rs b/simulator/model/query/mod.rs index 8a52514f3..22c4bfb24 100644 --- a/simulator/model/query/mod.rs +++ b/simulator/model/query/mod.rs @@ -10,7 +10,13 @@ use serde::{Deserialize, Serialize}; use turso_sqlite3_parser::to_sql_string::ToSqlContext; use update::Update; -use crate::{model::table::SimValue, runner::env::SimulatorEnv}; +use crate::{ + model::{ + query::transaction::{Begin, Commit, Rollback}, + table::SimValue, + }, + runner::env::SimulatorEnv, +}; pub mod create; pub mod create_index; @@ -19,6 +25,7 @@ pub mod drop; pub mod insert; pub mod predicate; pub mod select; +pub mod transaction; pub mod update; // This type represents the potential queries on the database. @@ -31,6 +38,9 @@ pub(crate) enum Query { Update(Update), Drop(Drop), CreateIndex(CreateIndex), + Begin(Begin), + Commit(Commit), + Rollback(Rollback), } impl Query { @@ -46,6 +56,7 @@ impl Query { Query::CreateIndex(CreateIndex { table_name, .. }) => { HashSet::from_iter([table_name.clone()]) } + Query::Begin(_) | Query::Commit(_) | Query::Rollback(_) => HashSet::new(), } } pub(crate) fn uses(&self) -> Vec { @@ -58,6 +69,7 @@ impl Query { | Query::Update(Update { table, .. }) | Query::Drop(Drop { table, .. }) => vec![table.clone()], Query::CreateIndex(CreateIndex { table_name, .. }) => vec![table_name.clone()], + Query::Begin(..) | Query::Commit(..) | Query::Rollback(..) => vec![], } } @@ -70,6 +82,9 @@ impl Query { Query::Update(update) => update.shadow(env), Query::Drop(drop) => drop.shadow(env), Query::CreateIndex(create_index) => create_index.shadow(env), + Query::Begin(begin) => begin.shadow(env), + Query::Commit(commit) => commit.shadow(env), + Query::Rollback(rollback) => rollback.shadow(env), } } } @@ -84,6 +99,9 @@ impl Display for Query { Self::Update(update) => write!(f, "{}", update), Self::Drop(drop) => write!(f, "{}", drop), Self::CreateIndex(create_index) => write!(f, "{}", create_index), + Self::Begin(begin) => write!(f, "{}", begin), + Self::Commit(commit) => write!(f, "{}", commit), + Self::Rollback(rollback) => write!(f, "{}", rollback), } } } diff --git a/simulator/model/query/transaction.rs b/simulator/model/query/transaction.rs new file mode 100644 index 000000000..b51510efe --- /dev/null +++ b/simulator/model/query/transaction.rs @@ -0,0 +1,52 @@ +use std::fmt::Display; + +use serde::{Deserialize, Serialize}; + +use crate::{model::table::SimValue, runner::env::SimulatorEnv}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct Begin { + pub(crate) immediate: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct Commit; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct Rollback; + +impl Begin { + pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) -> Vec> { + vec![] + } +} + +impl Commit { + pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) -> Vec> { + vec![] + } +} + +impl Rollback { + pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) -> Vec> { + vec![] + } +} + +impl Display for Begin { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "BEGIN {}", if self.immediate { "IMMEDIATE" } else { "" }) + } +} + +impl Display for Commit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "COMMIT") + } +} + +impl Display for Rollback { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ROLLBACK") + } +} diff --git a/simulator/runner/differential.rs b/simulator/runner/differential.rs index 440b9cdd9..1cd623bb1 100644 --- a/simulator/runner/differential.rs +++ b/simulator/runner/differential.rs @@ -112,6 +112,18 @@ fn execute_query_rusqlite( connection.execute(create_index.to_string().as_str(), ())?; Ok(vec![]) } + Query::Begin(begin) => { + connection.execute(begin.to_string().as_str(), ())?; + Ok(vec![]) + } + Query::Commit(commit) => { + connection.execute(commit.to_string().as_str(), ())?; + Ok(vec![]) + } + Query::Rollback(rollback) => { + connection.execute(rollback.to_string().as_str(), ())?; + Ok(vec![]) + } } }