fix shrinking extensional queries. Now we only keep queries and/or properties that contain a depending table

This commit is contained in:
pedrocarlo
2025-09-25 19:05:56 -03:00
parent d3c2198a75
commit 461c765b7b
3 changed files with 211 additions and 129 deletions

View File

@@ -371,6 +371,13 @@ impl Interactions {
interactions,
}
}
pub fn get_extensional_queries(&mut self) -> Option<&mut Vec<Query>> {
match &mut self.interactions {
InteractionsType::Property(property) => property.get_extensional_queries(),
InteractionsType::Query(..) | InteractionsType::Fault(..) => None,
}
}
}
impl Deref for Interactions {

View File

@@ -190,6 +190,10 @@ pub(crate) enum Property {
query: Query,
tables: Vec<String>,
},
/// Property used to subsititute a property with its queries only
Queries {
queries: Vec<Query>,
},
}
#[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<Query>> {
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<interaction> 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(),
}
}
}

View File

@@ -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<Query>,
depending_tables: &IndexSet<String>,
) {
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<usize, Vec<usize>> = HashMap::new();
// Comprises of idxs of Commit and Rollback intereactions
let mut end_tx_idx: HashMap<usize, Vec<usize>> = 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::<HashMap<_, _>>();
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<String>,
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<usize, Vec<usize>> = HashMap::new();
// Comprises of idxs of Commit and Rollback intereactions
let mut end_tx_idx: HashMap<usize, Vec<usize>> = 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::<HashMap<_, _>>();
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()