diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 9d0a2c072..9d07ccb14 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, path::Path, rc::Rc, vec}; +use std::{collections::HashSet, fmt::Display, path::Path, rc::Rc, vec}; use limbo_core::{Connection, Result, StepResult, IO}; use serde::{Deserialize, Serialize}; @@ -97,7 +97,7 @@ pub(crate) struct InteractionPlanState { pub(crate) secondary_pointer: usize, } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) enum Interactions { Property(Property), Query(Query), @@ -123,13 +123,13 @@ impl Interactions { } impl Interactions { - pub(crate) fn dependencies(&self) -> Vec { + pub(crate) fn dependencies(&self) -> HashSet { match self { Interactions::Property(property) => { property .interactions() .iter() - .fold(vec![], |mut acc, i| match i { + .fold(HashSet::new(), |mut acc, i| match i { Interaction::Query(q) => { acc.extend(q.dependencies()); acc @@ -138,7 +138,7 @@ impl Interactions { }) } Interactions::Query(query) => query.dependencies(), - Interactions::Fault(_) => vec![], + Interactions::Fault(_) => HashSet::new(), } } diff --git a/simulator/generation/property.rs b/simulator/generation/property.rs index e5ab808b4..6f47610f2 100644 --- a/simulator/generation/property.rs +++ b/simulator/generation/property.rs @@ -20,7 +20,7 @@ use super::{ /// Properties are representations of executable specifications /// about the database behavior. -#[derive(Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) enum Property { /// Insert-Select is a property in which the inserted row /// must be in the resulting rows of a select query that has a diff --git a/simulator/model/query/mod.rs b/simulator/model/query/mod.rs index 59b1ee664..ed947ca9b 100644 --- a/simulator/model/query/mod.rs +++ b/simulator/model/query/mod.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::{collections::HashSet, fmt::Display}; pub(crate) use create::Create; pub(crate) use create_index::CreateIndex; @@ -32,16 +32,18 @@ pub(crate) enum Query { } impl Query { - pub(crate) fn dependencies(&self) -> Vec { + pub(crate) fn dependencies(&self) -> HashSet { match self { - Query::Create(_) => vec![], + Query::Create(_) => HashSet::new(), Query::Select(Select { table, .. }) | Query::Insert(Insert::Select { table, .. }) | Query::Insert(Insert::Values { table, .. }) | Query::Delete(Delete { table, .. }) | Query::Update(Update { table, .. }) - | Query::Drop(Drop { table, .. }) => vec![table.clone()], - Query::CreateIndex(CreateIndex { table_name, .. }) => vec![table_name.clone()], + | Query::Drop(Drop { table, .. }) => HashSet::from_iter([table.clone()]), + Query::CreateIndex(CreateIndex { table_name, .. }) => { + HashSet::from_iter([table_name.clone()]) + } } } pub(crate) fn uses(&self) -> Vec { diff --git a/simulator/shrink/plan.rs b/simulator/shrink/plan.rs index 20b553493..a42f32836 100644 --- a/simulator/shrink/plan.rs +++ b/simulator/shrink/plan.rs @@ -5,6 +5,7 @@ use crate::{ }, model::query::Query, runner::execution::Execution, + Interaction, }; impl InteractionPlan { @@ -13,36 +14,83 @@ impl InteractionPlan { // todo: this is a very naive implementation, next steps are; // - Shrink to multiple values by removing random interactions // - Shrink properties by removing their extensions, or shrinking their values - let mut plan = self.clone(); let failing_property = &self.plan[failing_execution.interaction_index]; - let depending_tables = failing_property.dependencies(); + let mut depending_tables = failing_property.dependencies(); + + let interactions = failing_property.interactions(); + + { + let mut idx = failing_execution.secondary_index; + loop { + match &interactions[idx] { + Interaction::Query(query) => { + depending_tables = query.dependencies(); + break; + } + // Fault does not depend on + Interaction::Fault(..) => break, + _ => { + // In principle we should never fail this checked_sub. + // But if there is a bug in how we count the secondary index + // we may panic if we do not use a checked_sub. + if let Some(new_idx) = idx.checked_sub(1) { + idx = new_idx; + } else { + tracing::warn!("failed to find error query"); + break; + } + } + } + } + } let before = self.plan.len(); // Remove all properties after the failing one plan.plan.truncate(failing_execution.interaction_index + 1); + + let mut idx = 0; // Remove all properties that do not use the failing tables - plan.plan - .retain(|p| p.uses().iter().any(|t| depending_tables.contains(t))); - - // Remove the extensional parts of the properties - for interaction in plan.plan.iter_mut() { - if let Interactions::Property(p) = interaction { - match p { - Property::InsertValuesSelect { queries, .. } - | Property::DoubleCreateFailure { queries, .. } - | Property::DeleteSelect { queries, .. } - | Property::DropSelect { queries, .. } => { - queries.clear(); + plan.plan.retain_mut(|interactions| { + let retain = if idx == failing_execution.interaction_index { + true + } else { + let mut has_table = interactions + .uses() + .iter() + .any(|t| depending_tables.contains(t)); + if has_table { + // Remove the extensional parts of the properties + if let Interactions::Property(p) = interactions { + match p { + Property::InsertValuesSelect { queries, .. } + | Property::DoubleCreateFailure { queries, .. } + | Property::DeleteSelect { queries, .. } + | Property::DropSelect { queries, .. } => { + queries.clear(); + } + Property::SelectLimit { .. } + | Property::SelectSelectOptimizer { .. } => {} + } } - Property::SelectLimit { .. } | Property::SelectSelectOptimizer { .. } => {} + // Check again after query clear if the interactions still uses the failing table + has_table = interactions + .uses() + .iter() + .any(|t| depending_tables.contains(t)); } - } - } - - plan.plan - .retain(|p| !matches!(p, Interactions::Query(Query::Select(_)))); + has_table + && !matches!( + interactions, + Interactions::Query(Query::Select(_)) + | Interactions::Property(Property::SelectLimit { .. }) + | Interactions::Property(Property::SelectSelectOptimizer { .. }) + ) + }; + idx += 1; + retain + }); let after = plan.plan.len();