mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 17:05:36 +01:00
fix shrinking extensional queries. Now we only keep queries and/or properties that contain a depending table
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user