From ea9c67a950fcce96708eab07b8d99b054f9f5f1b Mon Sep 17 00:00:00 2001 From: alpaylan Date: Mon, 7 Jul 2025 02:54:48 -0400 Subject: [PATCH] generate joins and unions --- simulator/generation/mod.rs | 4 +- simulator/generation/plan.rs | 18 +- simulator/generation/predicate/binary.rs | 81 ++++++-- simulator/generation/query.rs | 239 +++++++++++++++++++++-- simulator/main.rs | 2 +- simulator/model/query/create.rs | 6 +- simulator/model/query/create_index.rs | 12 +- simulator/model/query/delete.rs | 6 +- simulator/model/query/drop.rs | 8 +- simulator/model/query/insert.rs | 10 +- simulator/model/query/mod.rs | 6 +- simulator/model/query/predicate.rs | 6 + simulator/model/query/select.rs | 48 +++-- simulator/model/query/update.rs | 6 +- simulator/runner/execution.rs | 4 +- simulator/runner/io.rs | 4 +- 16 files changed, 367 insertions(+), 93 deletions(-) diff --git a/simulator/generation/mod.rs b/simulator/generation/mod.rs index aaa8f19a1..d17aefd79 100644 --- a/simulator/generation/mod.rs +++ b/simulator/generation/mod.rs @@ -3,7 +3,7 @@ use std::{iter::Sum, ops::SubAssign}; use anarchist_readable_name_generator_lib::readable_name_custom; use rand::{distributions::uniform::SampleUniform, Rng}; -use crate::runner::env::SimulatorEnv; +use crate::model::table::Table; mod expr; pub mod plan; @@ -50,7 +50,7 @@ pub trait ArbitraryFromMaybe { /// might return a vector of rows that were inserted into the table. pub(crate) trait Shadow { type Result; - fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result; + fn shadow(&self, tables: &mut Vec) -> Self::Result; } /// Frequency is a helper function for composing different generators with different frequency diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index b78c89deb..4dd9dbf98 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -16,7 +16,7 @@ use crate::{ generation::Shadow, model::{ query::{update::Update, Create, CreateIndex, Delete, Drop, Insert, Query, Select}, - table::SimValue, + table::{SimValue, Table}, }, runner::{env::SimConnection, io::SimulatorIO}, SimulatorEnv, @@ -113,17 +113,17 @@ pub(crate) enum Interactions { impl Shadow for Interactions { type Result = (); - fn shadow(&self, env: &mut SimulatorEnv) { + fn shadow(&self, tables: &mut Vec
) { match self { Interactions::Property(property) => { - let initial_tables = env.tables.clone(); + let initial_tables = tables.clone(); let mut is_error = false; for interaction in property.interactions() { match interaction { Interaction::Query(query) | Interaction::FsyncQuery(query) | Interaction::FaultyQuery(query) => { - is_error = is_error || query.shadow(env).is_err(); + is_error = is_error || query.shadow(tables).is_err(); } Interaction::Assertion(_) => {} Interaction::Assumption(_) => {} @@ -131,13 +131,13 @@ impl Shadow for Interactions { } if is_error { // If any interaction fails, we reset the tables to the initial state - env.tables = initial_tables.clone(); + *tables = initial_tables.clone(); break; } } } Interactions::Query(query) => { - query.shadow(env); + query.shadow(tables); } Interactions::Fault(_) => {} } @@ -400,7 +400,7 @@ impl ArbitraryFrom<&mut SimulatorEnv> for InteractionPlan { num_interactions ); let interactions = Interactions::arbitrary_from(rng, (env, plan.stats())); - interactions.shadow(env); + interactions.shadow(&mut env.tables); // println!( // "Generated interactions: {}", // interactions @@ -431,7 +431,7 @@ impl ArbitraryFrom<&mut SimulatorEnv> for InteractionPlan { impl Shadow for Interaction { type Result = anyhow::Result>>; - fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result { + fn shadow(&self, env: &mut Vec
) -> Self::Result { match self { Self::Query(query) => query.shadow(env), Self::FsyncQuery(query) => { @@ -722,7 +722,7 @@ fn random_create(rng: &mut R, _env: &SimulatorEnv) -> Interactions } fn random_read(rng: &mut R, env: &SimulatorEnv) -> Interactions { - Interactions::Query(Query::Select(Select::arbitrary_from(rng, env))) + Interactions::Query(Query::Select(Select::arbitrary_from(rng, &env.tables))) } fn random_write(rng: &mut R, env: &SimulatorEnv) -> Interactions { diff --git a/simulator/generation/predicate/binary.rs b/simulator/generation/predicate/binary.rs index 9559e8bea..14c7deb81 100644 --- a/simulator/generation/predicate/binary.rs +++ b/simulator/generation/predicate/binary.rs @@ -57,8 +57,18 @@ impl Predicate { pub fn true_binary(rng: &mut R, t: &Table, row: &[SimValue]) -> Predicate { // Pick a column let column_index = rng.gen_range(0..t.columns.len()); - let column = &t.columns[column_index]; + let mut column = t.columns[column_index].clone(); let value = &row[column_index]; + + let mut table_name = t.name.clone(); + if t.name.is_empty() { + // If the table name is empty, we cannot create a qualified expression + // so we use the column name directly + let mut splitted = column.name.split('.'); + table_name = splitted.next().expect("Column name should have a table prefix for a joined table").to_string(); + column.name = splitted.next().expect("Column name should have a column suffix for a joined table").to_string(); + } + let expr = backtrack( vec![ ( @@ -66,7 +76,7 @@ impl Predicate { Box::new(|_| { Some(Expr::Binary( Box::new(ast::Expr::Qualified( - ast::Name(t.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Equals, @@ -83,7 +93,7 @@ impl Predicate { } else { Some(Expr::Binary( Box::new(ast::Expr::Qualified( - ast::Name(t.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::NotEquals, @@ -98,7 +108,7 @@ impl Predicate { let lt_value = LTValue::arbitrary_from(rng, value).0; Some(Expr::Binary( Box::new(ast::Expr::Qualified( - ast::Name(t.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Greater, @@ -112,7 +122,7 @@ impl Predicate { let gt_value = GTValue::arbitrary_from(rng, value).0; Some(Expr::Binary( Box::new(ast::Expr::Qualified( - ast::Name(t.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Less, @@ -127,7 +137,7 @@ impl Predicate { LikeValue::arbitrary_from_maybe(rng, value).map(|like| { Expr::Like { lhs: Box::new(ast::Expr::Qualified( - ast::Name(t.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), not: false, // TODO: also generate this value eventually @@ -149,14 +159,24 @@ impl Predicate { pub fn false_binary(rng: &mut R, t: &Table, row: &[SimValue]) -> Predicate { // Pick a column let column_index = rng.gen_range(0..t.columns.len()); - let column = &t.columns[column_index]; + let mut column = t.columns[column_index].clone(); + let mut table_name = t.name.clone(); let value = &row[column_index]; + + if t.name.is_empty() { + // If the table name is empty, we cannot create a qualified expression + // so we use the column name directly + let mut splitted = column.name.split('.'); + table_name = splitted.next().expect("Column name should have a table prefix for a joined table").to_string(); + column.name = splitted.next().expect("Column name should have a column suffix for a joined table").to_string(); + } + let expr = one_of( vec![ Box::new(|_| { Expr::Binary( Box::new(ast::Expr::Qualified( - ast::Name(t.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::NotEquals, @@ -172,7 +192,7 @@ impl Predicate { }; Expr::Binary( Box::new(ast::Expr::Qualified( - ast::Name(t.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Equals, @@ -183,7 +203,7 @@ impl Predicate { let gt_value = GTValue::arbitrary_from(rng, value).0; Expr::Binary( Box::new(ast::Expr::Qualified( - ast::Name(t.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Greater, @@ -194,7 +214,7 @@ impl Predicate { let lt_value = LTValue::arbitrary_from(rng, value).0; Expr::Binary( Box::new(ast::Expr::Qualified( - ast::Name(t.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Less, @@ -213,18 +233,28 @@ impl SimplePredicate { pub fn true_binary(rng: &mut R, table: &Table, row: &[SimValue]) -> Self { // Pick a random column let column_index = rng.gen_range(0..table.columns.len()); - let column = &table.columns[column_index]; + let mut column = table.columns[column_index].clone(); let column_value = &row[column_index]; + let mut table_name = table.name.clone(); // Avoid creation of NULLs if row.is_empty() { return SimplePredicate(Predicate(Expr::Literal(SimValue::TRUE.into()))); } + + if table.name.is_empty() { + // If the table name is empty, we cannot create a qualified expression + // so we use the column name directly + let mut splitted = column.name.split('.'); + table_name = splitted.next().expect("Column name should have a table prefix for a joined table").to_string(); + column.name = splitted.next().expect("Column name should have a column suffix for a joined table").to_string(); + } + let expr = one_of( vec![ Box::new(|_rng| { Expr::Binary( Box::new(ast::Expr::Qualified( - ast::Name(table.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Equals, @@ -235,7 +265,7 @@ impl SimplePredicate { let lt_value = LTValue::arbitrary_from(rng, column_value).0; Expr::Binary( Box::new(Expr::Qualified( - ast::Name(table.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Greater, @@ -246,7 +276,7 @@ impl SimplePredicate { let gt_value = GTValue::arbitrary_from(rng, column_value).0; Expr::Binary( Box::new(Expr::Qualified( - ast::Name(table.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Less, @@ -263,18 +293,31 @@ impl SimplePredicate { pub fn false_binary(rng: &mut R, table: &Table, row: &[SimValue]) -> Self { // Pick a random column let column_index = rng.gen_range(0..table.columns.len()); - let column = &table.columns[column_index]; + // println!("column_index: {}", column_index); + // println!("table.columns: {:?}", table.columns); + // println!("row: {:?}", row); + let mut column = table.columns[column_index].clone(); let column_value = &row[column_index]; + let mut table_name = table.name.clone(); // Avoid creation of NULLs if row.is_empty() { return SimplePredicate(Predicate(Expr::Literal(SimValue::FALSE.into()))); } + + if table.name.is_empty() { + // If the table name is empty, we cannot create a qualified expression + // so we use the column name directly + let mut splitted = column.name.split('.'); + table_name = splitted.next().expect("Column name should have a table prefix for a joined table").to_string(); + column.name = splitted.next().expect("Column name should have a column suffix for a joined table").to_string(); + } + let expr = one_of( vec![ Box::new(|_rng| { Expr::Binary( Box::new(Expr::Qualified( - ast::Name(table.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::NotEquals, @@ -285,7 +328,7 @@ impl SimplePredicate { let gt_value = GTValue::arbitrary_from(rng, column_value).0; Expr::Binary( Box::new(ast::Expr::Qualified( - ast::Name(table.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Greater, @@ -296,7 +339,7 @@ impl SimplePredicate { let lt_value = LTValue::arbitrary_from(rng, column_value).0; Expr::Binary( Box::new(ast::Expr::Qualified( - ast::Name(table.name.clone()), + ast::Name(table_name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Less, diff --git a/simulator/generation/query.rs b/simulator/generation/query.rs index e875d2c99..7c683bf4a 100644 --- a/simulator/generation/query.rs +++ b/simulator/generation/query.rs @@ -1,10 +1,13 @@ -use crate::generation::{Arbitrary, ArbitraryFrom}; +use crate::generation::{Arbitrary, ArbitraryFrom, Shadow}; use crate::model::query::predicate::Predicate; -use crate::model::query::select::{Distinctness, ResultColumn}; +use crate::model::query::select::{ + CompoundOperator, CompoundSelect, Distinctness, FromClause, JoinTable, JoinType, JoinedTable, ResultColumn, SelectBody, SelectInner +}; use crate::model::query::update::Update; use crate::model::query::{Create, Delete, Drop, Insert, Query, Select}; use crate::model::table::{SimValue, Table}; use crate::SimulatorEnv; +use itertools::Itertools; use rand::Rng; use super::property::Remaining; @@ -18,16 +21,128 @@ impl Arbitrary for Create { } } -impl ArbitraryFrom<&SimulatorEnv> for Select { - fn arbitrary_from(rng: &mut R, env: &SimulatorEnv) -> Self { - let table = pick(&env.tables, rng); - Self::single( - table.name.clone(), - vec![ResultColumn::Star], - Predicate::arbitrary_from(rng, table), - Some(rng.gen_range(0..=1000)), - Distinctness::All, - ) +impl ArbitraryFrom<&Vec
> for FromClause { + fn arbitrary_from(rng: &mut R, tables: &Vec
) -> Self { + match rng.gen_range(5..=5) { + 0..=4 => FromClause { + table: pick(tables, rng).name.clone(), + joins: vec![], + }, + _ => { + let mut tables = tables.clone(); + let mut table = pick(&tables, rng).clone(); + tables.retain(|t| t.name != table.name); + let name = table.name.clone(); + let num_joins = rng.gen_range(0..=3.min(tables.len())); + + let joins: Vec<_> = (0..num_joins) + .map(|_| { + let join_table = pick(&tables, rng).clone(); + tables.retain(|t| t.name != join_table.name); + table = JoinTable { + tables: vec![table.clone(), join_table.clone()], + rows: table.rows.iter().cartesian_product(join_table.rows.iter()).map(|(t_row, j_row)| { + let mut row = t_row.clone(); + row.extend(j_row.clone()); + row + }).collect(), + }.into_table(); + for row in &mut table.rows { + assert_eq!(row.len(), table.columns.len(), "Row length does not match column length after join"); + } + + let predicate = Predicate::arbitrary_from(rng, &table); + JoinedTable { + table: join_table.name.clone(), + join_type: JoinType::Inner, + on: predicate, + } + }) + .collect(); + FromClause { + table: name, + joins, + } + } + } + } +} + +impl ArbitraryFrom<&Vec
> for SelectInner { + fn arbitrary_from(rng: &mut R, tables: &Vec
) -> Self { + let from = FromClause::arbitrary_from(rng, tables); + let mut tables = tables.clone(); + // todo: this is a temporary hack because env is not separated from the tables + let join_table = from.shadow(&mut tables).expect("Failed to shadow FromClause").into_table(); + + SelectInner { + distinctness: Distinctness::arbitrary(rng), + columns: vec![ResultColumn::Star], + from, + where_clause: Predicate::arbitrary_from(rng, &join_table), + } + } +} + +impl Arbitrary for Distinctness { + fn arbitrary(rng: &mut R) -> Self { + match rng.gen_range(0..=5) { + 0..4 => Distinctness::All, + _ => Distinctness::Distinct, + } + } +} +impl Arbitrary for CompoundOperator { + fn arbitrary(rng: &mut R) -> Self { + match rng.gen_range(0..=1) { + 0 => CompoundOperator::Union, + 1 => CompoundOperator::UnionAll, + _ => unreachable!(), + } + } +} + +impl ArbitraryFrom<&Vec
> for Select { + fn arbitrary_from(rng: &mut R, tables: &Vec
) -> Self { + let table = pick(tables, rng); + + match rng.gen_range(5..=5) { + // Generate a simple select in the form of `SELECT * FROM
` + 0..=4 => Self::single( + table.name.clone(), + vec![ResultColumn::Star], + Predicate::arbitrary_from(rng, table), + Some(rng.gen_range(0..=1000)), + Distinctness::All, + ), + // Generate a compound select with some unions + _ => { + let num_selects = rng.gen_range(1..=3); + let first = SelectInner::arbitrary_from(rng, tables); + + let rest: Vec = (0..num_selects) + .map(|_| { + let mut select = first.clone(); + select.where_clause = Predicate::arbitrary_from(rng, table); + select + }) + .collect(); + + Self { + body: SelectBody { + select: Box::new(first), + compounds: rest + .into_iter() + .map(|s| CompoundSelect { + operator: CompoundOperator::arbitrary(rng), + select: Box::new(s), + }) + .collect(), + }, + limit: None, + } + } + } } } @@ -57,10 +172,7 @@ impl ArbitraryFrom<&SimulatorEnv> for Insert { 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::simple( - select_table.name.clone(), - predicate, - ); + let select = Select::simple(select_table.name.clone(), predicate); let table = pick(&env.tables, rng); Some(Insert::Select { table: table.name.clone(), @@ -110,7 +222,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &Remaining)> for Query { ), ( remaining.read, - Box::new(|rng| Self::Select(Select::arbitrary_from(rng, env))), + Box::new(|rng| Self::Select(Select::arbitrary_from(rng, &env.tables))), ), ( remaining.write, @@ -150,3 +262,96 @@ impl ArbitraryFrom<&SimulatorEnv> for Update { } } } + +#[cfg(test)] +mod query_generation_tests { + use rand::RngCore; + use turso_core::Value; + use turso_sqlite3_parser::to_sql_string::ToSqlString; + + use super::*; + use crate::model::query::predicate::Predicate; + use crate::model::query::EmptyContext; + use crate::model::table::{Column, ColumnType}; + use crate::SimulatorEnv; + + #[test] + fn test_select_query_generation() { + let mut rng = rand::thread_rng(); + // CREATE TABLE users (id INTEGER, name TEXT); + // INSERT INTO users (id, name) VALUES (1, 'Alice'), (2, 'Bob'); + // CREATE TABLE orders (order_id INTEGER, user_id INTEGER); + // INSERT INTO orders (order_id, user_id) VALUES (1, 1), (2, 2); + + let tables = vec![ + Table { + name: "users".to_string(), + columns: vec![ + Column { + name: "id".to_string(), + column_type: ColumnType::Integer, + primary: false, + unique: false, + }, + Column { + name: "name".to_string(), + column_type: ColumnType::Text, + primary: false, + unique: false, + }, + ], + rows: vec![ + vec![SimValue(Value::Integer(1)), SimValue(Value::Text("Alice".into()))], + vec![SimValue(Value::Integer(2)), SimValue(Value::Text("Bob".into()))], + ], + }, + Table { + name: "orders".to_string(), + columns: vec![ + Column { + name: "order_id".to_string(), + column_type: ColumnType::Integer, + primary: false, + unique: false, + }, + Column { + name: "user_id".to_string(), + column_type: ColumnType::Integer, + primary: false, + unique: false, + }, + ], + rows: vec![ + vec![SimValue(Value::Integer(1)), SimValue(Value::Integer(1))], + vec![SimValue(Value::Integer(2)), SimValue(Value::Integer(2))], + ], + }, + Table { + name: "products".to_string(), + columns: vec![ + Column { + name: "product_id".to_string(), + column_type: ColumnType::Integer, + primary: true, + unique: true, + }, + Column { + name: "product_name".to_string(), + column_type: ColumnType::Text, + primary: false, + unique: false, + }, + ], + rows: vec![ + vec![SimValue(Value::Integer(1)), SimValue(Value::Text("Widget".into()))], + vec![SimValue(Value::Integer(2)), SimValue(Value::Text("Gadget".into()))], + ], + }, + ]; + for _ in 0..100 { + let query = Select::arbitrary_from(&mut rng, &tables); + println!("{}", query.to_sql_ast().to_sql_string(&EmptyContext {})); + // println!("{:?}", query.to_sql_ast()); + } + } +} diff --git a/simulator/main.rs b/simulator/main.rs index 8ce1851c7..3d7001b73 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -219,7 +219,7 @@ fn watch_mode( let mut env = SimulatorEnv::new(seed, cli_opts, &paths.db); plan.iter().for_each(|is| { is.iter().for_each(|i| { - i.shadow(&mut env); + let _ = i.shadow(&mut env.tables); }); }); let env = Arc::new(Mutex::new(env.clone_without_connections())); diff --git a/simulator/model/query/create.rs b/simulator/model/query/create.rs index 2d6c13a42..45f987c61 100644 --- a/simulator/model/query/create.rs +++ b/simulator/model/query/create.rs @@ -16,9 +16,9 @@ pub(crate) struct Create { impl Shadow for Create { type Result = anyhow::Result>>; - fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result { - if !env.tables.iter().any(|t| t.name == self.table.name) { - env.tables.push(self.table.clone()); + fn shadow(&self, tables: &mut Vec
) -> Self::Result { + if !tables.iter().any(|t| t.name == self.table.name) { + tables.push(self.table.clone()); Ok(vec![]) } else { Err(anyhow::anyhow!( diff --git a/simulator/model/query/create_index.rs b/simulator/model/query/create_index.rs index 25ea7e795..a2eeb8656 100644 --- a/simulator/model/query/create_index.rs +++ b/simulator/model/query/create_index.rs @@ -1,6 +1,5 @@ use crate::{ - generation::{gen_random_text, pick, pick_n_unique, ArbitraryFrom}, - runner::env::SimulatorEnv, + generation::{gen_random_text, pick, pick_n_unique, ArbitraryFrom, Shadow}, model::table::{SimValue, Table}, runner::env::SimulatorEnv }; use rand::Rng; use serde::{Deserialize, Serialize}; @@ -27,11 +26,12 @@ pub(crate) struct CreateIndex { pub(crate) columns: Vec<(String, SortOrder)>, } -impl CreateIndex { - pub(crate) fn shadow( +impl Shadow for CreateIndex { + type Result = Vec>; + fn shadow( &self, - _env: &mut crate::runner::env::SimulatorEnv, - ) -> Vec> { + _env: &mut Vec
, + ) -> Vec> { // CREATE INDEX doesn't require any shadowing; we don't need to keep track // in the simulator what indexes exist. vec![] diff --git a/simulator/model/query/delete.rs b/simulator/model/query/delete.rs index e044dbb0e..f49339b38 100644 --- a/simulator/model/query/delete.rs +++ b/simulator/model/query/delete.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; -use crate::{generation::Shadow, model::table::SimValue, SimulatorEnv}; +use crate::{generation::Shadow, model::table::{SimValue, Table}, SimulatorEnv}; use super::predicate::Predicate; @@ -15,8 +15,8 @@ pub(crate) struct Delete { impl Shadow for Delete { type Result = anyhow::Result>>; - fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result { - let table = env.tables.iter_mut().find(|t| t.name == self.table); + fn shadow(&self, tables: &mut Vec
) -> 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 diff --git a/simulator/model/query/drop.rs b/simulator/model/query/drop.rs index 92cb0baea..9e9439659 100644 --- a/simulator/model/query/drop.rs +++ b/simulator/model/query/drop.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; -use crate::{generation::Shadow, model::table::SimValue, SimulatorEnv}; +use crate::{generation::Shadow, model::table::{SimValue, Table}}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub(crate) struct Drop { @@ -12,8 +12,8 @@ pub(crate) struct Drop { impl Shadow for Drop { type Result = anyhow::Result>>; - fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result { - if !env.tables.iter().any(|t| t.name == self.table) { + fn shadow(&self, tables: &mut Vec
) -> 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!( "Table {} does not exist. DROP statement ignored.", @@ -21,7 +21,7 @@ impl Shadow for Drop { )); } - env.tables.retain(|t| t.name != self.table); + tables.retain(|t| t.name != self.table); Ok(vec![]) } diff --git a/simulator/model/query/insert.rs b/simulator/model/query/insert.rs index f9e119f76..b9b40d299 100644 --- a/simulator/model/query/insert.rs +++ b/simulator/model/query/insert.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; -use crate::{generation::Shadow, model::table::SimValue, SimulatorEnv}; +use crate::{generation::Shadow, model::table::{SimValue, Table}}; use super::select::Select; @@ -21,10 +21,10 @@ pub(crate) enum Insert { impl Shadow for Insert { type Result = anyhow::Result>>; - fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result { + fn shadow(&self, tables: &mut Vec
) -> Self::Result { match self { Insert::Values { table, values } => { - if let Some(t) = env.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!( @@ -34,8 +34,8 @@ impl Shadow for Insert { } } Insert::Select { table, select } => { - let rows = select.shadow(env)?; - if let Some(t) = env.tables.iter_mut().find(|t| &t.name == table) { + let rows = select.shadow(tables)?; + if let Some(t) = tables.iter_mut().find(|t| &t.name == table) { t.rows.extend(rows); } else { return Err(anyhow::anyhow!( diff --git a/simulator/model/query/mod.rs b/simulator/model/query/mod.rs index 1bf59b9c3..cb4c521e0 100644 --- a/simulator/model/query/mod.rs +++ b/simulator/model/query/mod.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use turso_sqlite3_parser::to_sql_string::ToSqlContext; use update::Update; -use crate::{generation::Shadow, model::table::SimValue, runner::env::SimulatorEnv}; +use crate::{generation::Shadow, model::table::{SimValue, Table}}; pub mod create; pub mod create_index; @@ -65,7 +65,7 @@ impl Query { impl Shadow for Query { type Result = anyhow::Result>>; - fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result { + fn shadow(&self, env: &mut Vec
) -> Self::Result { match self { Query::Create(create) => create.shadow(env), Query::Insert(insert) => insert.shadow(env), @@ -93,7 +93,7 @@ impl Display for Query { } /// Used to print sql strings that already have all the context it needs -struct EmptyContext; +pub(crate) struct EmptyContext; impl ToSqlContext for EmptyContext { fn get_column_name( diff --git a/simulator/model/query/predicate.rs b/simulator/model/query/predicate.rs index 481f0dd17..edb12cc37 100644 --- a/simulator/model/query/predicate.rs +++ b/simulator/model/query/predicate.rs @@ -1,5 +1,6 @@ use std::fmt::Display; +use regex_syntax::ast::print; use serde::{Deserialize, Serialize}; use turso_sqlite3_parser::{ast, to_sql_string::ToSqlString}; @@ -83,6 +84,11 @@ pub fn expr_to_value(expr: &ast::Expr, row: &[SimValue], table: &Table) -> Optio ast::Expr::DoublyQualified(_, _, ast::Name(col_name)) | ast::Expr::Qualified(_, ast::Name(col_name)) | ast::Expr::Id(ast::Id(col_name)) => { + // println!("Resolving column: {}", col_name); + // println!("Row: {:?}", row); + // println!("Row length: {}", row.len()); + // println!("Table: {:?}", table.columns); + // println!("Table columns length: {}", table.columns.len()); assert_eq!(row.len(), table.columns.len()); table .columns diff --git a/simulator/model/query/select.rs b/simulator/model/query/select.rs index b6580f689..83f5ab55e 100644 --- a/simulator/model/query/select.rs +++ b/simulator/model/query/select.rs @@ -12,7 +12,6 @@ use crate::{ query::EmptyContext, table::{SimValue, Table}, }, - SimulatorEnv, }; use super::predicate::Predicate; @@ -213,8 +212,8 @@ pub struct JoinTable { } impl JoinTable { - fn into_table(self) -> Table { - Table { + pub(crate) fn into_table(self) -> Table { + let t = Table { name: "".to_string(), columns: self .tables @@ -222,21 +221,28 @@ impl JoinTable { .flat_map(|t| { t.columns.iter().map(|c| { let mut c = c.clone(); - c.name = format!("{}.<{}", t.name, c.name); + c.name = format!("{}.{}", t.name, c.name); c }) }) .collect(), rows: self.rows, + }; + for row in &t.rows { + assert_eq!( + row.len(), + t.columns.len(), + "Row length does not match column length after join" + ); } + t } } impl Shadow for FromClause { type Result = anyhow::Result; - fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result { - let first_table = env - .tables + fn shadow(&self, tables: &mut Vec
) -> Self::Result { + let first_table = tables .iter() .find(|t| t.name == self.table) .context("Table not found")?; @@ -247,8 +253,7 @@ impl Shadow for FromClause { }; for join in &self.joins { - let joined_table = env - .tables + let joined_table = tables .iter() .find(|t| t.name == join.table) .context("Joined table not found")?; @@ -265,8 +270,8 @@ impl Shadow for FromClause { .cloned() .collect::>(); // take a cartesian product of the rows - let mut all_row_pairs = - first_table.rows.iter().cartesian_product(join_rows.iter()); + let all_row_pairs = + join_table.rows.clone().into_iter().cartesian_product(join_rows.iter()); for (row1, row2) in all_row_pairs { let row = row1.iter().chain(row2.iter()).cloned().collect::>(); @@ -289,9 +294,17 @@ impl Shadow for FromClause { impl Shadow for SelectInner { type Result = anyhow::Result; - fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result { + fn shadow(&self, env: &mut Vec
) -> Self::Result { let mut join_table = self.from.shadow(env)?; let as_table = join_table.clone().into_table(); + for row in &mut join_table.rows { + assert_eq!( + row.len(), + as_table.columns.len(), + "Row length does not match column length after join" + ); + } + join_table .rows .retain(|row| self.where_clause.test(row, &as_table)); @@ -308,7 +321,7 @@ impl Shadow for SelectInner { impl Shadow for Select { type Result = anyhow::Result>>; - fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result { + fn shadow(&self, env: &mut Vec
) -> Self::Result { let first_result = self.body.select.shadow(env)?; let mut rows = first_result.into_table().rows; @@ -317,7 +330,14 @@ impl Shadow for Select { let compound_results = compound.select.shadow(env)?; match compound.operator { - CompoundOperator::Union => todo!(), + CompoundOperator::Union => { + // Union means we need to combine the results, removing duplicates + let mut new_rows = compound_results.into_table().rows; + new_rows.extend(rows.clone()); + new_rows.sort_unstable(); + new_rows.dedup(); + rows = new_rows; + } CompoundOperator::UnionAll => { // Union all means we just concatenate the results rows.extend(compound_results.rows.into_iter()); diff --git a/simulator/model/query/update.rs b/simulator/model/query/update.rs index d7504005e..46b1887da 100644 --- a/simulator/model/query/update.rs +++ b/simulator/model/query/update.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; -use crate::{generation::Shadow, model::table::SimValue, SimulatorEnv}; +use crate::{generation::Shadow, model::table::{SimValue, Table}, SimulatorEnv}; use super::predicate::Predicate; @@ -16,8 +16,8 @@ pub(crate) struct Update { impl Shadow for Update { type Result = anyhow::Result>>; - fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result { - let table = env.tables.iter_mut().find(|t| t.name == self.table); + fn shadow(&self, tables: &mut Vec
) -> 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/execution.rs b/simulator/runner/execution.rs index 093d10bda..67f8c046a 100644 --- a/simulator/runner/execution.rs +++ b/simulator/runner/execution.rs @@ -129,7 +129,7 @@ fn execute_plan( tracing::debug!("connection {} already connected", connection_index); match execute_interaction(env, connection_index, interaction, &mut state.stack) { Ok(next_execution) => { - interaction.shadow(env); + interaction.shadow(&mut env.tables); tracing::debug!("connection {} processed", connection_index); // Move to the next interaction or property match next_execution { @@ -190,7 +190,7 @@ pub(crate) fn execute_interaction( SimConnection::SQLiteConnection(_) => unreachable!(), SimConnection::Disconnected => unreachable!(), }; - + tracing::debug!(?interaction); let results = interaction.execute_query(conn, &env.io); tracing::debug!(?results); stack.push(results); diff --git a/simulator/runner/io.rs b/simulator/runner/io.rs index b3c823125..027ef9933 100644 --- a/simulator/runner/io.rs +++ b/simulator/runner/io.rs @@ -5,7 +5,7 @@ use std::{ use rand::{RngCore, SeedableRng}; use rand_chacha::ChaCha8Rng; -use turso_core::{Clock, Instant, OpenFlags, PlatformIO, Result, IO}; +use turso_core::{Clock, Instant, MemoryIO, OpenFlags, PlatformIO, Result, IO}; use crate::runner::file::SimulatorFile; @@ -116,6 +116,6 @@ impl IO for SimulatorIO { } fn get_memory_io(&self) -> Arc { - todo!() + Arc::new(MemoryIO::new()) } }