filter for the tables that the failing query depended on + second pass after query clear

This commit is contained in:
pedrocarlo
2025-06-11 02:48:39 -03:00
parent 4794b022a5
commit 63cf648e2e
4 changed files with 65 additions and 30 deletions

View File

@@ -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<String> {
pub(crate) fn dependencies(&self) -> HashSet<String> {
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(),
}
}

View File

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

View File

@@ -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<String> {
pub(crate) fn dependencies(&self) -> HashSet<String> {
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<String> {

View File

@@ -5,6 +5,7 @@ use crate::{
},
model::query::Query,
runner::execution::Execution,
Interaction,
};
impl InteractionPlan {
@@ -13,36 +14,68 @@ 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);
// 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 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(_)))
});
let after = plan.plan.len();