Merge 'Simulator: Drop Index' from Pedro Muniz

Added the ability for us to generate `Drop Index` queries in the
simulator. Most of the code is just boilerplate and some checks to make
sure we do not generate `Drop Index` when we have no indexes to drop

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Reviewed-by: bit-aloo (@Shourya742)

Closes #3713
This commit is contained in:
Jussi Saurio
2025-10-14 07:25:02 +03:00
committed by GitHub
8 changed files with 116 additions and 13 deletions

View File

@@ -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<dyn Fn(&mut R) -> Interactions>)> = vec![
(
query_distr.weights().total_weight(),

View File

@@ -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,
}
}

View File

@@ -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<R: rand::Rng + ?Sized>(
Query::AlterTable(AlterTable::arbitrary(rng, conn_ctx))
}
fn random_drop_index<R: rand::Rng + ?Sized>(
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 => {

View File

@@ -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<QueryDiscriminants> 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<Vec<Vec<SimValue>>>;
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![])
}
}

View File

@@ -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,
}
}
}

View File

@@ -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<R: Rng + ?Sized, C: GenerationContext>(rng: &mut R, context: &C) -> Self {
let tables_with_indexes = context
.tables()
.iter()
.filter(|table| !table.indexes.is_empty())
.collect::<Vec<_>>();
// 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(),
}
}
}

View File

@@ -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 {

View File

@@ -68,7 +68,7 @@ struct SimulatorFiber {
struct SimulatorContext {
fibers: Vec<SimulatorFiber>,
tables: Vec<Table>,
indexes: Vec<String>,
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));