diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index b5ff4e475..6c7e66384 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -235,6 +235,7 @@ impl InteractionPlan { Query::Commit(_) => stats.commit_count += 1, Query::Rollback(_) => stats.rollback_count += 1, Query::AlterTable(_) => stats.alter_table_count += 1, + Query::DropIndex(_) => stats.drop_index_count += 1, Query::Placeholder => {} } } @@ -472,11 +473,14 @@ impl<'a, R: rand::Rng> PlanGenerator<'a, R> { if let InteractionType::Query(Query::Placeholder) = &interaction.interaction { let stats = self.plan.stats(); + let conn_ctx = env.connection_context(interaction.connection_index); + let remaining_ = remaining( env.opts.max_interactions, &env.profile.query, &stats, env.profile.experimental_mvcc, + &conn_ctx, ); let InteractionsType::Property(property) = @@ -485,8 +489,6 @@ impl<'a, R: rand::Rng> PlanGenerator<'a, R> { unreachable!("only properties have extensional queries"); }; - let conn_ctx = env.connection_context(interaction.connection_index); - let queries = possible_queries(conn_ctx.tables()); let query_distr = QueryDistribution::new(queries, &remaining_); @@ -768,13 +770,14 @@ pub(crate) struct InteractionStats { pub commit_count: u32, pub rollback_count: u32, pub alter_table_count: u32, + pub drop_index_count: u32, } impl Display for InteractionStats { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "Read: {}, Insert: {}, Delete: {}, Update: {}, Create: {}, CreateIndex: {}, Drop: {}, Begin: {}, Commit: {}, Rollback: {}, Alter Table: {}", + "Read: {}, Insert: {}, Delete: {}, Update: {}, Create: {}, CreateIndex: {}, Drop: {}, Begin: {}, Commit: {}, Rollback: {}, Alter Table: {}, Drop Index: {}", self.select_count, self.insert_count, self.delete_count, @@ -786,6 +789,7 @@ impl Display for InteractionStats { self.commit_count, self.rollback_count, self.alter_table_count, + self.drop_index_count, ) } } @@ -1272,12 +1276,13 @@ impl ArbitraryFrom<(&SimulatorEnv, InteractionStats, usize)> for Interactions { &env.profile.query, &stats, env.profile.experimental_mvcc, + conn_ctx, ); let queries = possible_queries(conn_ctx.tables()); let query_distr = QueryDistribution::new(queries, &remaining_); - #[allow(clippy::type_complexity)] + #[expect(clippy::type_complexity)] let mut choices: Vec<(u32, Box Interactions>)> = vec![ ( query_distr.weights().total_weight(), diff --git a/simulator/generation/property.rs b/simulator/generation/property.rs index cadd42155..f2530dfeb 100644 --- a/simulator/generation/property.rs +++ b/simulator/generation/property.rs @@ -1380,6 +1380,7 @@ pub(super) struct Remaining { pub update: u32, pub drop: u32, pub alter_table: u32, + pub drop_index: u32, } pub(super) fn remaining( @@ -1387,6 +1388,7 @@ pub(super) fn remaining( opts: &QueryProfile, stats: &InteractionStats, mvcc: bool, + context: &impl GenerationContext, ) -> Remaining { let total_weight = opts.total_weight(); @@ -1398,6 +1400,7 @@ pub(super) fn remaining( let total_update = (max_interactions * opts.update_weight) / total_weight; let total_drop = (max_interactions * opts.drop_table_weight) / total_weight; let total_alter_table = (max_interactions * opts.alter_table_weight) / total_weight; + let total_drop_index = (max_interactions * opts.drop_index) / total_weight; let remaining_select = total_select .checked_sub(stats.select_count) @@ -1423,9 +1426,23 @@ pub(super) fn remaining( .checked_sub(stats.alter_table_count) .unwrap_or_default(); + let mut remaining_drop_index = total_drop_index + .checked_sub(stats.alter_table_count) + .unwrap_or_default(); + if mvcc { // TODO: index not supported yet for mvcc remaining_create_index = 0; + remaining_drop_index = 0; + } + + // if there are no indexes do not allow creation of drop_index + if !context + .tables() + .iter() + .any(|table| !table.indexes.is_empty()) + { + remaining_drop_index = 0; } Remaining { @@ -1437,6 +1454,7 @@ pub(super) fn remaining( drop: remaining_drop, update: remaining_update, alter_table: remaining_alter_table, + drop_index: remaining_drop_index, } } diff --git a/simulator/generation/query.rs b/simulator/generation/query.rs index 45140dfe6..7445bd744 100644 --- a/simulator/generation/query.rs +++ b/simulator/generation/query.rs @@ -10,7 +10,8 @@ use sql_generation::{ generation::{Arbitrary, ArbitraryFrom, GenerationContext, query::SelectFree}, model::{ query::{ - Create, CreateIndex, Delete, Insert, Select, alter_table::AlterTable, update::Update, + Create, CreateIndex, Delete, DropIndex, Insert, Select, alter_table::AlterTable, + update::Update, }, table::Table, }, @@ -89,6 +90,19 @@ fn random_alter_table( Query::AlterTable(AlterTable::arbitrary(rng, conn_ctx)) } +fn random_drop_index( + rng: &mut R, + conn_ctx: &impl GenerationContext, +) -> Query { + assert!( + conn_ctx + .tables() + .iter() + .any(|table| !table.indexes.is_empty()) + ); + Query::DropIndex(DropIndex::arbitrary(rng, conn_ctx)) +} + /// Possible queries that can be generated given the table state /// /// Does not take into account transactional statements @@ -117,6 +131,7 @@ impl QueryDiscriminants { QueryDiscriminants::Drop => random_drop, QueryDiscriminants::CreateIndex => random_create_index, QueryDiscriminants::AlterTable => random_alter_table, + QueryDiscriminants::DropIndex => random_drop_index, QueryDiscriminants::Begin | QueryDiscriminants::Commit | QueryDiscriminants::Rollback => { @@ -140,6 +155,7 @@ impl QueryDiscriminants { QueryDiscriminants::Drop => remaining.drop, QueryDiscriminants::CreateIndex => remaining.create_index, QueryDiscriminants::AlterTable => remaining.alter_table, + QueryDiscriminants::DropIndex => remaining.drop_index, QueryDiscriminants::Begin | QueryDiscriminants::Commit | QueryDiscriminants::Rollback => { diff --git a/simulator/model/mod.rs b/simulator/model/mod.rs index 20726cbbb..dac1ec5cb 100644 --- a/simulator/model/mod.rs +++ b/simulator/model/mod.rs @@ -7,7 +7,7 @@ use itertools::Itertools; use serde::{Deserialize, Serialize}; use sql_generation::model::{ query::{ - Create, CreateIndex, Delete, Drop, Insert, Select, + Create, CreateIndex, Delete, Drop, DropIndex, Insert, Select, alter_table::{AlterTable, AlterTableType}, select::{CompoundOperator, FromClause, ResultColumn, SelectInner}, transaction::{Begin, Commit, Rollback}, @@ -30,6 +30,7 @@ pub enum Query { Drop(Drop), CreateIndex(CreateIndex), AlterTable(AlterTable), + DropIndex(DropIndex), Begin(Begin), Commit(Commit), Rollback(Rollback), @@ -76,6 +77,9 @@ impl Query { }) | Query::AlterTable(AlterTable { table_name: table, .. + }) + | Query::DropIndex(DropIndex { + table_name: table, .. }) => IndexSet::from_iter([table.clone()]), Query::Begin(_) | Query::Commit(_) | Query::Rollback(_) => IndexSet::new(), Query::Placeholder => IndexSet::new(), @@ -97,6 +101,9 @@ impl Query { }) | Query::AlterTable(AlterTable { table_name: table, .. + }) + | Query::DropIndex(DropIndex { + table_name: table, .. }) => vec![table.clone()], Query::Begin(..) | Query::Commit(..) | Query::Rollback(..) => vec![], Query::Placeholder => vec![], @@ -115,7 +122,11 @@ impl Query { pub fn is_ddl(&self) -> bool { matches!( self, - Self::Create(..) | Self::CreateIndex(..) | Self::Drop(..) | Self::AlterTable(..) + Self::Create(..) + | Self::CreateIndex(..) + | Self::Drop(..) + | Self::AlterTable(..) + | Self::DropIndex(..) ) } } @@ -131,6 +142,7 @@ impl Display for Query { Self::Drop(drop) => write!(f, "{drop}"), Self::CreateIndex(create_index) => write!(f, "{create_index}"), Self::AlterTable(alter_table) => write!(f, "{alter_table}"), + Self::DropIndex(drop_index) => write!(f, "{drop_index}"), Self::Begin(begin) => write!(f, "{begin}"), Self::Commit(commit) => write!(f, "{commit}"), Self::Rollback(rollback) => write!(f, "{rollback}"), @@ -152,6 +164,7 @@ impl Shadow for Query { Query::Drop(drop) => drop.shadow(env), Query::CreateIndex(create_index) => Ok(create_index.shadow(env)), Query::AlterTable(alter_table) => alter_table.shadow(env), + Query::DropIndex(drop_index) => drop_index.shadow(env), Query::Begin(begin) => Ok(begin.shadow(env)), Query::Commit(commit) => Ok(commit.shadow(env)), Query::Rollback(rollback) => Ok(rollback.shadow(env)), @@ -170,6 +183,7 @@ bitflags! { const DROP = 1 << 5; const CREATE_INDEX = 1 << 6; const ALTER_TABLE = 1 << 7; + const DROP_INDEX = 1 << 8; } } @@ -199,6 +213,7 @@ impl From for QueryCapabilities { QueryDiscriminants::Drop => Self::DROP, QueryDiscriminants::CreateIndex => Self::CREATE_INDEX, QueryDiscriminants::AlterTable => Self::ALTER_TABLE, + QueryDiscriminants::DropIndex => Self::DROP_INDEX, QueryDiscriminants::Begin | QueryDiscriminants::Commit | QueryDiscriminants::Rollback => { @@ -221,6 +236,7 @@ impl QueryDiscriminants { QueryDiscriminants::Drop, QueryDiscriminants::CreateIndex, QueryDiscriminants::AlterTable, + QueryDiscriminants::DropIndex, ]; } @@ -583,3 +599,19 @@ impl Shadow for AlterTable { Ok(vec![]) } } + +impl Shadow for DropIndex { + type Result = anyhow::Result>>; + + fn shadow(&self, tables: &mut ShadowTablesMut<'_>) -> Self::Result { + let table = tables + .iter_mut() + .find(|t| t.name == self.table_name) + .ok_or_else(|| anyhow::anyhow!("Table {} does not exist", self.table_name))?; + + table + .indexes + .retain(|index| index.index_name != self.index_name); + Ok(vec![]) + } +} diff --git a/simulator/profiles/query.rs b/simulator/profiles/query.rs index ee9583596..95bcf146a 100644 --- a/simulator/profiles/query.rs +++ b/simulator/profiles/query.rs @@ -24,6 +24,8 @@ pub struct QueryProfile { pub drop_table_weight: u32, #[garde(skip)] pub alter_table_weight: u32, + #[garde(skip)] + pub drop_index: u32, } impl Default for QueryProfile { @@ -38,6 +40,7 @@ impl Default for QueryProfile { delete_weight: 20, drop_table_weight: 2, alter_table_weight: 2, + drop_index: 2, } } } diff --git a/sql_generation/generation/query.rs b/sql_generation/generation/query.rs index 17fe0f843..c4be7f2d8 100644 --- a/sql_generation/generation/query.rs +++ b/sql_generation/generation/query.rs @@ -9,7 +9,7 @@ use crate::model::query::select::{ SelectInner, }; use crate::model::query::update::Update; -use crate::model::query::{Create, CreateIndex, Delete, Drop, Insert, Select}; +use crate::model::query::{Create, CreateIndex, Delete, Drop, DropIndex, Insert, Select}; use crate::model::table::{ Column, Index, JoinTable, JoinType, JoinedTable, Name, SimValue, Table, TableContext, }; @@ -535,3 +535,22 @@ impl Arbitrary for AlterTable { } } } + +impl Arbitrary for DropIndex { + fn arbitrary(rng: &mut R, context: &C) -> Self { + let tables_with_indexes = context + .tables() + .iter() + .filter(|table| !table.indexes.is_empty()) + .collect::>(); + + // Cannot DROP INDEX if there is no index to drop + assert!(!tables_with_indexes.is_empty()); + let table = tables_with_indexes.choose(rng).unwrap(); + let index = table.indexes.choose(rng).unwrap(); + Self { + index_name: index.index_name.clone(), + table_name: table.name.clone(), + } + } +} diff --git a/sql_generation/model/query/drop_index.rs b/sql_generation/model/query/drop_index.rs index 18cadb12d..670636efb 100644 --- a/sql_generation/model/query/drop_index.rs +++ b/sql_generation/model/query/drop_index.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct DropIndex { pub index_name: String, + pub table_name: String, } impl std::fmt::Display for DropIndex { diff --git a/whopper/main.rs b/whopper/main.rs index affcdb807..0a18edf2c 100644 --- a/whopper/main.rs +++ b/whopper/main.rs @@ -68,7 +68,7 @@ struct SimulatorFiber { struct SimulatorContext { fibers: Vec, tables: Vec, - indexes: Vec, + indexes: Vec<(String, String)>, opts: Opts, stats: Stats, disable_indexes: bool, @@ -210,7 +210,10 @@ fn main() -> anyhow::Result<()> { let mut context = SimulatorContext { fibers, tables, - indexes: indexes.iter().map(|idx| idx.index_name.clone()).collect(), + indexes: indexes + .iter() + .map(|idx| (idx.table_name.clone(), idx.index_name.clone())) + .collect(), opts: Opts::default(), stats: Stats::default(), disable_indexes: args.disable_indexes, @@ -567,7 +570,10 @@ fn perform_work( let sql = create_index.to_string(); if let Ok(stmt) = context.fibers[fiber_idx].connection.prepare(&sql) { context.fibers[fiber_idx].statement.replace(Some(stmt)); - context.indexes.push(create_index.index_name.clone()); + context.indexes.push(( + create_index.index.table_name.clone(), + create_index.index_name.clone(), + )); } trace!("{} CREATE INDEX: {}", fiber_idx, sql); } @@ -576,8 +582,11 @@ fn perform_work( // DROP INDEX (2%) if !context.disable_indexes && !context.indexes.is_empty() { let index_idx = rng.random_range(0..context.indexes.len()); - let index_name = context.indexes.remove(index_idx); - let drop_index = DropIndex { index_name }; + let (table_name, index_name) = context.indexes.remove(index_idx); + let drop_index = DropIndex { + table_name, + index_name, + }; let sql = drop_index.to_string(); if let Ok(stmt) = context.fibers[fiber_idx].connection.prepare(&sql) { context.fibers[fiber_idx].statement.replace(Some(stmt));