From 461c765b7b3900298e265be74798cde0e936de12 Mon Sep 17 00:00:00 2001 From: pedrocarlo Date: Thu, 25 Sep 2025 19:05:56 -0300 Subject: [PATCH] fix shrinking extensional queries. Now we only keep queries and/or properties that contain a depending table --- simulator/generation/plan.rs | 7 + simulator/generation/property.rs | 28 +++ simulator/shrink/plan.rs | 305 ++++++++++++++++++------------- 3 files changed, 211 insertions(+), 129 deletions(-) diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 4185edad7..f1b1f0c8b 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -371,6 +371,13 @@ impl Interactions { interactions, } } + + pub fn get_extensional_queries(&mut self) -> Option<&mut Vec> { + match &mut self.interactions { + InteractionsType::Property(property) => property.get_extensional_queries(), + InteractionsType::Query(..) | InteractionsType::Fault(..) => None, + } + } } impl Deref for Interactions { diff --git a/simulator/generation/property.rs b/simulator/generation/property.rs index 41e4bb217..8ea30fd7a 100644 --- a/simulator/generation/property.rs +++ b/simulator/generation/property.rs @@ -190,6 +190,10 @@ pub(crate) enum Property { query: Query, tables: Vec, }, + /// Property used to subsititute a property with its queries only + Queries { + queries: Vec, + }, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -213,8 +217,27 @@ impl Property { Property::FsyncNoWait { .. } => "FsyncNoWait", Property::FaultyQuery { .. } => "FaultyQuery", Property::UNIONAllPreservesCardinality { .. } => "UNION-All-Preserves-Cardinality", + Property::Queries { .. } => "Queries", } } + + pub fn get_extensional_queries(&mut self) -> Option<&mut Vec> { + match self { + Property::InsertValuesSelect { queries, .. } + | Property::DoubleCreateFailure { queries, .. } + | Property::DeleteSelect { queries, .. } + | Property::DropSelect { queries, .. } + | Property::Queries { queries } => Some(queries), + Property::FsyncNoWait { .. } | Property::FaultyQuery { .. } => None, + Property::SelectLimit { .. } + | Property::SelectSelectOptimizer { .. } + | Property::WhereTrueFalseNull { .. } + | Property::UNIONAllPreservesCardinality { .. } + | Property::ReadYourUpdatesBack { .. } + | Property::TableHasExpectedContent { .. } => None, + } + } + /// interactions construct a list of interactions, which is an executable representation of the property. /// the requirement of property -> vec conversion emerges from the need to serialize the property, /// and `interaction` cannot be serialized directly. @@ -1028,6 +1051,11 @@ impl Property { )), ].into_iter().map(|i| Interaction::new(connection_index, i)).collect() } + Property::Queries { queries } => queries + .clone() + .into_iter() + .map(|query| Interaction::new(connection_index, InteractionType::Query(query))) + .collect(), } } } diff --git a/simulator/shrink/plan.rs b/simulator/shrink/plan.rs index 955eaab6c..17188f9c6 100644 --- a/simulator/shrink/plan.rs +++ b/simulator/shrink/plan.rs @@ -1,3 +1,5 @@ +use indexmap::IndexSet; + use crate::{ SandboxedResult, SimulatorEnv, generation::{ @@ -13,6 +15,17 @@ use std::{ sync::{Arc, Mutex}, }; +fn retain_relevant_queries( + extensional_queries: &mut Vec, + depending_tables: &IndexSet, +) { + extensional_queries.retain(|query| { + query.is_transaction() + || (!matches!(query, Query::Select(..)) + && query.uses().iter().any(|t| depending_tables.contains(t))) + }); +} + impl InteractionPlan { /// Create a smaller interaction plan by deleting a property pub(crate) fn shrink_interaction_plan(&self, failing_execution: &Execution) -> InteractionPlan { @@ -64,134 +77,7 @@ impl InteractionPlan { // means we errored in some fault on transaction statement so just maintain the statements from before the failing one if !depending_tables.is_empty() { - let mut idx = 0; - // Remove all properties that do not use the failing tables - plan.plan.retain_mut(|interactions| { - let retain = if idx == secondary_interactions_index { - if let InteractionsType::Property( - Property::FsyncNoWait { tables, .. } | Property::FaultyQuery { tables, .. }, - ) = &mut interactions.interactions - { - tables.retain(|table| depending_tables.contains(table)); - } - true - } else if matches!( - interactions.interactions, - InteractionsType::Query(Query::Begin(..)) - | InteractionsType::Query(Query::Commit(..)) - | InteractionsType::Query(Query::Rollback(..)) - ) { - 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 InteractionsType::Property(p) = &mut interactions.interactions { - match p { - Property::InsertValuesSelect { queries, .. } - | Property::DoubleCreateFailure { queries, .. } - | Property::DeleteSelect { queries, .. } - | Property::DropSelect { queries, .. } => { - queries.clear(); - } - Property::FsyncNoWait { tables, query } - | Property::FaultyQuery { tables, query } => { - if !query.uses().iter().any(|t| depending_tables.contains(t)) { - tables.clear(); - } else { - tables.retain(|table| depending_tables.contains(table)); - } - } - Property::SelectLimit { .. } - | Property::SelectSelectOptimizer { .. } - | Property::WhereTrueFalseNull { .. } - | Property::UNIONAllPreservesCardinality { .. } - | Property::ReadYourUpdatesBack { .. } - | Property::TableHasExpectedContent { .. } => {} - } - } - // Check again after query clear if the interactions still uses the failing table - has_table = interactions - .uses() - .iter() - .any(|t| depending_tables.contains(t)); - } - let is_fault = matches!(interactions.interactions, InteractionsType::Fault(..)); - is_fault - || (has_table - && !matches!( - interactions.interactions, - InteractionsType::Query(Query::Select(_)) - | InteractionsType::Property(Property::SelectLimit { .. }) - | InteractionsType::Property( - Property::SelectSelectOptimizer { .. } - ) - )) - }; - idx += 1; - retain - }); - - // Comprises of idxs of Begin interactions - let mut begin_idx: HashMap> = HashMap::new(); - // Comprises of idxs of Commit and Rollback intereactions - let mut end_tx_idx: HashMap> = HashMap::new(); - - for (idx, interactions) in plan.plan.iter().enumerate() { - match &interactions.interactions { - InteractionsType::Query(Query::Begin(..)) => { - begin_idx - .entry(interactions.connection_index) - .or_insert_with(|| vec![idx]); - } - InteractionsType::Query(Query::Commit(..)) - | InteractionsType::Query(Query::Rollback(..)) => { - let last_begin = begin_idx - .get(&interactions.connection_index) - .and_then(|list| list.last()) - .unwrap() - + 1; - if last_begin == idx { - end_tx_idx - .entry(interactions.connection_index) - .or_insert_with(|| vec![idx]); - } - } - _ => {} - } - } - - // remove interactions if its just a Begin Commit/Rollback with no queries in the middle - let mut range_transactions = end_tx_idx - .into_iter() - .map(|(conn_index, list)| (conn_index, list.into_iter().peekable())) - .collect::>(); - let mut idx = 0; - plan.plan.retain_mut(|interactions| { - let mut retain = true; - - let iter = range_transactions.get_mut(&interactions.connection_index); - - if let Some(iter) = iter { - if let Some(txn_interaction_idx) = iter.peek().copied() { - if txn_interaction_idx == idx { - iter.next(); - } - if txn_interaction_idx == idx - || txn_interaction_idx.saturating_sub(1) == idx - { - retain = false; - } - } - } - - idx += 1; - retain - }); + plan.remove_properties(&depending_tables, secondary_interactions_index); } let after = plan.len(); @@ -204,6 +90,166 @@ impl InteractionPlan { plan } + + /// Remove all properties that do not use the failing tables + fn remove_properties( + &mut self, + depending_tables: &IndexSet, + failing_interaction_index: usize, + ) { + let mut idx = 0; + // Remove all properties that do not use the failing tables + self.plan.retain_mut(|interactions| { + let retain = if idx == failing_interaction_index { + if let InteractionsType::Property( + Property::FsyncNoWait { tables, .. } | Property::FaultyQuery { tables, .. }, + ) = &mut interactions.interactions + { + tables.retain(|table| depending_tables.contains(table)); + } + true + } else { + let mut has_table = interactions + .uses() + .iter() + .any(|t| depending_tables.contains(t)); + + if has_table { + // will contain extensional queries that reference the depending tables + let mut extensional_queries = Vec::new(); + + // Remove the extensional parts of the properties + if let InteractionsType::Property(p) = &mut interactions.interactions { + match p { + Property::InsertValuesSelect { queries, .. } + | Property::DoubleCreateFailure { queries, .. } + | Property::DeleteSelect { queries, .. } + | Property::DropSelect { queries, .. } + | Property::Queries { queries } => { + extensional_queries.append(queries); + } + Property::FsyncNoWait { tables, query } + | Property::FaultyQuery { tables, query } => { + if !query.uses().iter().any(|t| depending_tables.contains(t)) { + tables.clear(); + } else { + tables.retain(|table| depending_tables.contains(table)); + } + } + Property::SelectLimit { .. } + | Property::SelectSelectOptimizer { .. } + | Property::WhereTrueFalseNull { .. } + | Property::UNIONAllPreservesCardinality { .. } + | Property::ReadYourUpdatesBack { .. } + | Property::TableHasExpectedContent { .. } => {} + } + } + // Check again after query clear if the interactions still uses the failing table + has_table = interactions + .uses() + .iter() + .any(|t| depending_tables.contains(t)); + + // means the queries in the original property are present in the depending tables regardless of the extensional queries + if has_table { + if let Some(queries) = interactions.get_extensional_queries() { + retain_relevant_queries(&mut extensional_queries, depending_tables); + queries.append(&mut extensional_queries); + } + } else { + // original property without extensional queries does not reference the tables so convert the property to + // `Property::Queries` if `extensional_queries` is not empty + retain_relevant_queries(&mut extensional_queries, depending_tables); + if !extensional_queries.is_empty() { + has_table = true; + *interactions = Interactions::new( + interactions.connection_index, + InteractionsType::Property(Property::Queries { + queries: extensional_queries, + }), + ); + } + } + } + let is_fault = matches!(interactions.interactions, InteractionsType::Fault(..)); + let is_transaction = matches!( + interactions.interactions, + InteractionsType::Query(Query::Begin(..)) + | InteractionsType::Query(Query::Commit(..)) + | InteractionsType::Query(Query::Rollback(..)) + ); + is_fault + || is_transaction + || (has_table + && !matches!( + interactions.interactions, + InteractionsType::Query(Query::Select(_)) + | InteractionsType::Property(Property::SelectLimit { .. }) + | InteractionsType::Property( + Property::SelectSelectOptimizer { .. } + ) + )) + }; + idx += 1; + retain + }); + + // Comprises of idxs of Begin interactions + let mut begin_idx: HashMap> = HashMap::new(); + // Comprises of idxs of Commit and Rollback intereactions + let mut end_tx_idx: HashMap> = HashMap::new(); + + for (idx, interactions) in self.plan.iter().enumerate() { + match &interactions.interactions { + InteractionsType::Query(Query::Begin(..)) => { + begin_idx + .entry(interactions.connection_index) + .or_insert_with(|| vec![idx]); + } + InteractionsType::Query(Query::Commit(..)) + | InteractionsType::Query(Query::Rollback(..)) => { + let last_begin = begin_idx + .get(&interactions.connection_index) + .and_then(|list| list.last()) + .unwrap() + + 1; + if last_begin == idx { + end_tx_idx + .entry(interactions.connection_index) + .or_insert_with(|| vec![idx]); + } + } + _ => {} + } + } + + // remove interactions if its just a Begin Commit/Rollback with no queries in the middle + let mut range_transactions = end_tx_idx + .into_iter() + .map(|(conn_index, list)| (conn_index, list.into_iter().peekable())) + .collect::>(); + let mut idx = 0; + self.plan.retain_mut(|interactions| { + let mut retain = true; + + let iter = range_transactions.get_mut(&interactions.connection_index); + + if let Some(iter) = iter { + if let Some(txn_interaction_idx) = iter.peek().copied() { + if txn_interaction_idx == idx { + iter.next(); + } + if txn_interaction_idx == idx || txn_interaction_idx.saturating_sub(1) == idx { + retain = false; + } + } + } + + idx += 1; + retain + }); + } + /// Create a smaller interaction plan by deleting a property pub(crate) fn brute_shrink_interaction_plan( &self, @@ -265,7 +311,8 @@ impl InteractionPlan { Property::InsertValuesSelect { queries, .. } | Property::DoubleCreateFailure { queries, .. } | Property::DeleteSelect { queries, .. } - | Property::DropSelect { queries, .. } => { + | Property::DropSelect { queries, .. } + | Property::Queries { queries } => { let mut temp_plan = InteractionPlan::new_with( queries .iter()