- add Interaction Builder to simplify code for building an interaction.

Modify `generation/property.rs` to use the Builder
- add additional metadata to `Interaction` to give more context for
  shrinking and iterating over interactions that originated from the
  same interaction.
- add Iterator like utilities for `InteractionPlan` to facilitate
  iterating over interactions that came from the same property:
This commit is contained in:
pedrocarlo
2025-10-18 12:14:25 -03:00
parent c088a653e6
commit 157a5cf10a
7 changed files with 471 additions and 399 deletions

32
Cargo.lock generated
View File

@@ -1225,6 +1225,37 @@ dependencies = [
"powerfmt",
]
[[package]]
name = "derive_builder"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "derive_builder_macro"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn 2.0.100",
]
[[package]]
name = "derive_more"
version = "2.0.1"
@@ -2617,6 +2648,7 @@ dependencies = [
"bitmaps",
"chrono",
"clap",
"derive_builder",
"dirs 6.0.0",
"either",
"garde",

View File

@@ -47,3 +47,4 @@ similar = { workspace = true }
similar-asserts = { workspace = true }
bitmaps = { workspace = true }
bitflags.workspace = true
derive_builder = "0.20.2"

View File

@@ -181,7 +181,7 @@ impl<'a, R: rand::Rng> PlanGenerator<'a, R> {
let remaining_ = Remaining::new(
env.opts.max_interactions,
&env.profile.query,
&stats,
stats,
env.profile.experimental_mvcc,
&conn_ctx,
);

View File

@@ -5,6 +5,8 @@
//! we can generate queries that reference tables that do not exist. This is not a correctness issue, but more of
//! an optimization issue that is good to point out for the future
use std::num::NonZeroUsize;
use rand::distr::{Distribution, weighted::WeightedIndex};
use sql_generation::{
generation::{Arbitrary, ArbitraryFrom, GenerationContext, pick, pick_index},
@@ -26,10 +28,12 @@ use turso_parser::ast::{self, Distinctness};
use crate::{
common::print_diff,
generation::{Shadow as _, WeightedDistribution, query::QueryDistribution},
generation::{Shadow, WeightedDistribution, query::QueryDistribution},
model::{
Query, QueryCapabilities, QueryDiscriminants, ResultSet,
interactions::{Assertion, Interaction, InteractionType},
interactions::{
Assertion, Interaction, InteractionBuilder, InteractionType, PropertyMetadata, Span,
},
metrics::Remaining,
property::{InteractiveQueryInfo, Property, PropertyDiscriminants},
},
@@ -231,7 +235,7 @@ impl Property {
Property::SelectLimit { .. }
| Property::SelectSelectOptimizer { .. }
| Property::WhereTrueFalseNull { .. }
| Property::UNIONAllPreservesCardinality { .. }
| Property::UnionAllPreservesCardinality { .. }
| Property::ReadYourUpdatesBack { .. }
| Property::TableHasExpectedContent { .. }
| Property::AllTableHaveExpectedContent { .. } => {
@@ -243,8 +247,12 @@ impl Property {
/// 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.
pub(crate) fn interactions(&self, connection_index: usize) -> Vec<Interaction> {
match self {
pub(crate) fn interactions(
&self,
connection_index: usize,
id: NonZeroUsize,
) -> Vec<Interaction> {
let mut interactions: Vec<InteractionBuilder> = match self {
Property::AllTableHaveExpectedContent { tables } => {
assert_all_table_values(tables, connection_index).collect()
}
@@ -304,9 +312,9 @@ impl Property {
));
vec![
Interaction::new(connection_index, assumption),
Interaction::new(connection_index, select_interaction),
Interaction::new(connection_index, assertion),
InteractionBuilder::with_interaction(assumption),
InteractionBuilder::with_interaction(select_interaction),
InteractionBuilder::with_interaction(assertion),
]
}
Property::ReadYourUpdatesBack { update, select } => {
@@ -368,10 +376,10 @@ impl Property {
));
vec![
Interaction::new(connection_index, assumption),
Interaction::new(connection_index, update_interaction),
Interaction::new(connection_index, select_interaction),
Interaction::new(connection_index, assertion),
InteractionBuilder::with_interaction(assumption),
InteractionBuilder::with_interaction(update_interaction),
InteractionBuilder::with_interaction(select_interaction),
InteractionBuilder::with_interaction(assertion),
]
}
Property::InsertValuesSelect {
@@ -448,22 +456,20 @@ impl Property {
));
let mut interactions = Vec::new();
interactions.push(Interaction::new(connection_index, assumption));
interactions.push(Interaction::new(
connection_index,
interactions.push(InteractionBuilder::with_interaction(assumption));
interactions.push(InteractionBuilder::with_interaction(
InteractionType::Query(Query::Insert(insert.clone())),
));
interactions.extend(
queries
.clone()
.into_iter()
.map(|q| Interaction::new(connection_index, InteractionType::Query(q))),
);
interactions.push(Interaction::new(
connection_index,
interactions.extend(queries.clone().into_iter().map(|q| {
let mut builder =
InteractionBuilder::with_interaction(InteractionType::Query(q));
builder.property_meta(PropertyMetadata::new(self, true));
builder
}));
interactions.push(InteractionBuilder::with_interaction(
InteractionType::Query(Query::Select(select.clone())),
));
interactions.push(Interaction::new(connection_index, assertion));
interactions.push(InteractionBuilder::with_interaction(assertion));
interactions
}
@@ -505,16 +511,20 @@ impl Property {
}) );
let mut interactions = Vec::new();
interactions.push(Interaction::new(connection_index, assumption));
interactions.push(Interaction::new(connection_index, cq1));
interactions.extend(
queries
.clone()
.into_iter()
.map(|q| Interaction::new(connection_index, InteractionType::Query(q))),
);
interactions.push(Interaction::new_ignore_error(connection_index, cq2));
interactions.push(Interaction::new(connection_index, assertion));
interactions.push(InteractionBuilder::with_interaction(assumption));
interactions.push(InteractionBuilder::with_interaction(cq1));
interactions.extend(queries.clone().into_iter().map(|q| {
let mut builder =
InteractionBuilder::with_interaction(InteractionType::Query(q));
builder.property_meta(PropertyMetadata::new(self, true));
builder
}));
interactions.push({
let mut builder = InteractionBuilder::with_interaction(cq2);
builder.ignore_error(true);
builder
});
interactions.push(InteractionBuilder::with_interaction(assertion));
interactions
}
@@ -574,12 +584,11 @@ impl Property {
));
vec![
Interaction::new(connection_index, assumption),
Interaction::new(
connection_index,
InteractionType::Query(Query::Select(select.clone())),
),
Interaction::new(connection_index, assertion),
InteractionBuilder::with_interaction(assumption),
InteractionBuilder::with_interaction(InteractionType::Query(Query::Select(
select.clone(),
))),
InteractionBuilder::with_interaction(assertion),
]
}
Property::DeleteSelect {
@@ -643,16 +652,16 @@ impl Property {
));
let mut interactions = Vec::new();
interactions.push(Interaction::new(connection_index, assumption));
interactions.push(Interaction::new(connection_index, delete));
interactions.extend(
queries
.clone()
.into_iter()
.map(|q| Interaction::new(connection_index, InteractionType::Query(q))),
);
interactions.push(Interaction::new(connection_index, select));
interactions.push(Interaction::new(connection_index, assertion));
interactions.push(InteractionBuilder::with_interaction(assumption));
interactions.push(InteractionBuilder::with_interaction(delete));
interactions.extend(queries.clone().into_iter().map(|q| {
let mut builder =
InteractionBuilder::with_interaction(InteractionType::Query(q));
builder.property_meta(PropertyMetadata::new(self, true));
builder
}));
interactions.push(InteractionBuilder::with_interaction(select));
interactions.push(InteractionBuilder::with_interaction(assertion));
interactions
}
@@ -715,16 +724,20 @@ impl Property {
let mut interactions = Vec::new();
interactions.push(Interaction::new(connection_index, assumption));
interactions.push(Interaction::new(connection_index, drop));
interactions.extend(
queries
.clone()
.into_iter()
.map(|q| Interaction::new(connection_index, InteractionType::Query(q))),
);
interactions.push(Interaction::new_ignore_error(connection_index, select));
interactions.push(Interaction::new(connection_index, assertion));
interactions.push(InteractionBuilder::with_interaction(assumption));
interactions.push(InteractionBuilder::with_interaction(drop));
interactions.extend(queries.clone().into_iter().map(|q| {
let mut builder =
InteractionBuilder::with_interaction(InteractionType::Query(q));
builder.property_meta(PropertyMetadata::new(self, true));
builder
}));
interactions.push({
let mut builder = InteractionBuilder::with_interaction(select);
builder.ignore_error(true);
builder
});
interactions.push(InteractionBuilder::with_interaction(assertion));
interactions
}
@@ -811,15 +824,14 @@ impl Property {
));
vec![
Interaction::new(connection_index, assumption),
Interaction::new(connection_index, select1),
Interaction::new(connection_index, select2),
Interaction::new(connection_index, assertion),
InteractionBuilder::with_interaction(assumption),
InteractionBuilder::with_interaction(select1),
InteractionBuilder::with_interaction(select2),
InteractionBuilder::with_interaction(assertion),
]
}
Property::FsyncNoWait { query } => {
vec![Interaction::new(
connection_index,
vec![InteractionBuilder::with_interaction(
InteractionType::FsyncQuery(query.clone()),
)]
}
@@ -855,7 +867,7 @@ impl Property {
InteractionType::Assertion(assert),
]
.into_iter()
.map(|i| Interaction::new(connection_index, i))
.map(InteractionBuilder::with_interaction)
.collect()
}
Property::WhereTrueFalseNull { select, predicate } => {
@@ -1010,13 +1022,13 @@ impl Property {
));
vec![
Interaction::new(connection_index, assumption),
Interaction::new(connection_index, select),
Interaction::new(connection_index, select_tlp),
Interaction::new(connection_index, assertion),
InteractionBuilder::with_interaction(assumption),
InteractionBuilder::with_interaction(select),
InteractionBuilder::with_interaction(select_tlp),
InteractionBuilder::with_interaction(assertion),
]
}
Property::UNIONAllPreservesCardinality {
Property::UnionAllPreservesCardinality {
select,
where_clause,
} => {
@@ -1062,21 +1074,42 @@ impl Property {
}
},
)),
].into_iter().map(|i| Interaction::new(connection_index, i)).collect()
].into_iter().map(InteractionBuilder::with_interaction).collect()
}
Property::Queries { queries } => queries
.clone()
.into_iter()
.map(|query| Interaction::new(connection_index, InteractionType::Query(query)))
.map(|query| InteractionBuilder::with_interaction(InteractionType::Query(query)))
.collect(),
}
};
assert!(!interactions.is_empty());
// Add a span to the interactions that matter
if interactions.len() == 1 {
interactions.first_mut().unwrap().span(Span::StartEnd);
} else {
interactions.first_mut().unwrap().span(Span::Start);
interactions.last_mut().unwrap().span(Span::End);
};
interactions
.into_iter()
.map(|mut builder| {
if !builder.has_property_meta() {
builder.property_meta(PropertyMetadata::new(self, false));
}
builder.connection_index(connection_index).id(id);
builder.build().unwrap()
})
.collect()
}
}
fn assert_all_table_values(
tables: &[String],
connection_index: usize,
) -> impl Iterator<Item = Interaction> + use<'_> {
) -> impl Iterator<Item = InteractionBuilder> + use<'_> {
tables.iter().flat_map(move |table| {
let select = InteractionType::Query(Query::Select(Select::simple(
table.clone(),
@@ -1131,7 +1164,7 @@ fn assert_all_table_values(
}
}
}));
[select, assertion].into_iter().map(move |i| Interaction::new(connection_index, i))
[select, assertion].into_iter().map(InteractionBuilder::with_interaction)
})
}
@@ -1411,7 +1444,7 @@ fn property_union_all_preserves_cardinality<R: rand::Rng + ?Sized>(
Distinctness::All,
);
Property::UNIONAllPreservesCardinality {
Property::UnionAllPreservesCardinality {
select,
where_clause: p2,
}
@@ -1460,7 +1493,7 @@ impl PropertyDiscriminants {
PropertyDiscriminants::DropSelect => property_drop_select,
PropertyDiscriminants::SelectSelectOptimizer => property_select_select_optimizer,
PropertyDiscriminants::WhereTrueFalseNull => property_where_true_false_null,
PropertyDiscriminants::UNIONAllPreservesCardinality => {
PropertyDiscriminants::UnionAllPreservesCardinality => {
property_union_all_preserves_cardinality
}
PropertyDiscriminants::FsyncNoWait => property_fsync_no_wait,
@@ -1543,7 +1576,7 @@ impl PropertyDiscriminants {
0
}
}
PropertyDiscriminants::UNIONAllPreservesCardinality => {
PropertyDiscriminants::UnionAllPreservesCardinality => {
if opts.indexes
&& !env.opts.disable_union_all_preserves_cardinality
&& !ctx.tables().is_empty()
@@ -1607,7 +1640,7 @@ impl PropertyDiscriminants {
}
PropertyDiscriminants::SelectSelectOptimizer => QueryCapabilities::SELECT,
PropertyDiscriminants::WhereTrueFalseNull => QueryCapabilities::SELECT,
PropertyDiscriminants::UNIONAllPreservesCardinality => QueryCapabilities::SELECT,
PropertyDiscriminants::UnionAllPreservesCardinality => QueryCapabilities::SELECT,
PropertyDiscriminants::FsyncNoWait => QueryCapabilities::all(),
PropertyDiscriminants::FaultyQuery => QueryCapabilities::all(),
PropertyDiscriminants::Queries => panic!("queries property should not be generated"),

View File

@@ -1,11 +1,15 @@
use std::{
fmt::{Debug, Display},
ops::{Deref, DerefMut},
marker::PhantomData,
num::NonZeroUsize,
ops::{Deref, DerefMut, Range},
panic::RefUnwindSafe,
rc::Rc,
sync::Arc,
};
use indexmap::IndexSet;
use either::Either;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use sql_generation::model::table::SimValue;
use turso_core::{Connection, Result, StepResult};
@@ -14,224 +18,262 @@ use crate::{
generation::Shadow,
model::{
Query, ResultSet,
metrics::InteractionStats,
property::{Property, PropertyDiscriminants},
},
runner::env::{ShadowTablesMut, SimConnection, SimulationType, SimulatorEnv},
};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone)]
pub(crate) struct InteractionPlan {
plan: Vec<Interactions>,
plan: Vec<Interaction>,
stats: InteractionStats,
// In the future, this should probably be a stack of interactions
// so we can have nested properties
last_interactions: Option<Interactions>,
pub mvcc: bool,
// Len should not count transactions statements, just so we can generate more meaningful interactions per run
len: usize,
/// Counts [Interactions]. Should not count transactions statements, just so we can generate more meaningful interactions per run
/// This field is only necessary and valid when generating interactions. For static iteration, we do not care about this field
len_properties: usize,
next_interaction_id: NonZeroUsize,
}
impl InteractionPlan {
pub(crate) fn new(mvcc: bool) -> Self {
Self {
plan: Vec::new(),
stats: InteractionStats::default(),
last_interactions: None,
mvcc,
len: 0,
len_properties: 0,
next_interaction_id: NonZeroUsize::new(1).unwrap(),
}
}
pub fn new_with(plan: Vec<Interactions>, mvcc: bool) -> Self {
let len = plan
.iter()
.filter(|interaction| !interaction.ignore())
.count();
Self { plan, mvcc, len }
}
#[inline]
fn new_len(&self) -> usize {
self.plan
.iter()
.filter(|interaction| !interaction.ignore())
.count()
}
/// Length of interactions that are not transaction statements
/// Count of interactions
#[inline]
pub fn len(&self) -> usize {
self.len
self.plan.len()
}
/// Count of properties
#[inline]
pub fn plan(&self) -> &[Interactions] {
&self.plan
pub fn len_properties(&self) -> usize {
self.len_properties
}
pub fn push(&mut self, interactions: Interactions) {
pub fn next_property_id(&mut self) -> NonZeroUsize {
let id = self.next_interaction_id;
self.next_interaction_id = self
.next_interaction_id
.checked_add(1)
.expect("Generated too many interactions, that overflowed ID generation");
id
}
pub fn last_interactions(&self) -> Option<&Interactions> {
self.last_interactions.as_ref()
}
pub fn push_interactions(&mut self, interactions: Interactions) {
if !interactions.ignore() {
self.len += 1;
self.len_properties += 1;
}
self.plan.push(interactions);
self.last_interactions = Some(interactions);
}
pub fn remove(&mut self, index: usize) -> Interactions {
let interactions = self.plan.remove(index);
if !interactions.ignore() {
self.len -= 1;
}
interactions
pub fn push(&mut self, interaction: Interaction) {
self.plan.push(interaction);
}
/// Finds the range of interactions that are contained between the start and end spans for a given ID.
pub fn find_interactions_range(&self, id: NonZeroUsize) -> Range<usize> {
let interactions = self.interactions_list();
let idx = interactions
.binary_search_by_key(&id, |interaction| interaction.id())
.map_err(|_| format!("Interaction containing id `{id}` should be present"))
.unwrap();
let interaction = &interactions[idx];
let backward = || -> usize {
interactions
.iter()
.rev()
.skip(interactions.len() - idx)
.position(|interaction| {
interaction.id() == id
&& interaction
.span
.is_some_and(|span| matches!(span, Span::Start))
})
.map(|idx| (interactions.len() - 1) - idx - 1)
.expect("A start span should have been emitted")
};
let forward = || -> usize {
interactions
.iter()
.skip(idx + 1)
.position(|interaction| interaction.id() != id)
.map(|idx| idx - 1)
.unwrap_or(interactions.len() - 1)
// It can happen we do not have an end Span as we can fail in the middle of a property
};
if let Some(span) = interaction.span {
match span {
Span::Start => {
// go forward and find the end span
let end_idx = forward();
idx..end_idx + 1
}
Span::End => {
// go backward and find the start span
let start_idx = backward();
start_idx..idx + 1
}
Span::StartEnd => idx..idx + 1,
}
} else {
// go backward and find the start span
let start_idx = backward();
// go forward and find the end span
let end_idx = forward();
start_idx..end_idx + 1
}
}
/// Truncates up to a particular interaction
pub fn truncate(&mut self, len: usize) {
self.plan.truncate(len);
self.len = self.new_len();
}
pub fn retain_mut<F>(&mut self, mut f: F)
/// Used to remove a particular [Interactions]
pub fn remove_property(&mut self, id: NonZeroUsize) {
let range = self.find_interactions_range(id);
// Consume the drain iterator just to be sure
for _interaction in self.plan.drain(range) {}
}
pub fn retain_mut<F>(&mut self, f: F)
where
F: FnMut(&mut Interactions) -> bool,
F: FnMut(&mut Interaction) -> bool,
{
let f = |t: &mut Interactions| {
let ignore = t.ignore();
let retain = f(t);
// removed an interaction that was not previously ignored
if !retain && !ignore {
self.len -= 1;
}
retain
};
self.plan.retain_mut(f);
}
#[expect(dead_code)]
pub fn retain<F>(&mut self, mut f: F)
where
F: FnMut(&Interactions) -> bool,
{
let f = |t: &Interactions| {
let ignore = t.ignore();
let retain = f(t);
// removed an interaction that was not previously ignored
if !retain && !ignore {
self.len -= 1;
}
retain
};
self.plan.retain(f);
self.len = self.new_len();
#[inline]
pub fn interactions_list(&self) -> &[Interaction] {
&self.plan
}
pub fn interactions_list(&self) -> Vec<Interaction> {
self.plan
.clone()
.into_iter()
.flat_map(|interactions| interactions.interactions().into_iter())
.collect()
}
pub fn interactions_list_with_secondary_index(&self) -> Vec<(usize, Interaction)> {
self.plan
.clone()
.into_iter()
.enumerate()
.flat_map(|(idx, interactions)| {
interactions
.interactions()
.into_iter()
.map(move |interaction| (idx, interaction))
})
.collect()
}
pub(crate) fn stats(&self) -> InteractionStats {
let mut stats = InteractionStats::default();
fn query_stat(q: &Query, stats: &mut InteractionStats) {
match q {
Query::Select(_) => stats.select_count += 1,
Query::Insert(_) => stats.insert_count += 1,
Query::Delete(_) => stats.delete_count += 1,
Query::Create(_) => stats.create_count += 1,
Query::Drop(_) => stats.drop_count += 1,
Query::Update(_) => stats.update_count += 1,
Query::CreateIndex(_) => stats.create_index_count += 1,
Query::Begin(_) => stats.begin_count += 1,
Query::Commit(_) => stats.commit_count += 1,
Query::Rollback(_) => stats.rollback_count += 1,
Query::AlterTable(_) => stats.alter_table_count += 1,
Query::DropIndex(_) => stats.drop_index_count += 1,
Query::Placeholder => {}
Query::Pragma(_) => stats.pragma_count += 1,
}
}
for interactions in &self.plan {
match &interactions.interactions {
InteractionsType::Property(property) => {
if matches!(property, Property::AllTableHaveExpectedContent { .. }) {
// Skip Property::AllTableHaveExpectedContent when counting stats
// this allows us to generate more relevant interactions as we count less Select's to the Stats
continue;
}
for interaction in &property.interactions(interactions.connection_index) {
if let InteractionType::Query(query) = &interaction.interaction {
query_stat(query, &mut stats);
}
}
}
InteractionsType::Query(query) => {
query_stat(query, &mut stats);
}
InteractionsType::Fault(_) => {}
}
pub fn iter_properties(
&self,
) -> IterProperty<
std::iter::Peekable<std::iter::Enumerate<std::slice::Iter<'_, Interaction>>>,
Forward,
> {
IterProperty {
iter: self.interactions_list().iter().enumerate().peekable(),
_direction: PhantomData,
}
}
stats
pub fn rev_iter_properties(
&self,
) -> IterProperty<
std::iter::Peekable<
std::iter::Enumerate<std::iter::Rev<std::slice::Iter<'_, Interaction>>>,
>,
Backward,
> {
IterProperty {
iter: self.interactions_list().iter().rev().enumerate().peekable(),
_direction: PhantomData,
}
}
pub fn stats(&self) -> &InteractionStats {
&self.stats
}
pub fn stats_mut(&mut self) -> &mut InteractionStats {
&mut self.stats
}
pub fn static_iterator(&self) -> impl InteractionPlanIterator {
PlanIterator {
iter: self.interactions_list().into_iter(),
iter: self.interactions_list().to_vec().into_iter(),
}
}
}
impl Deref for InteractionPlan {
type Target = Vec<Interactions>;
pub struct Forward;
pub struct Backward;
fn deref(&self) -> &Self::Target {
&self.plan
pub struct IterProperty<I, Dir> {
iter: I,
_direction: PhantomData<Dir>,
}
impl<'a, I> IterProperty<I, Forward>
where
I: Iterator<Item = (usize, &'a Interaction)> + itertools::PeekingNext + std::fmt::Debug,
{
pub fn next_property(&mut self) -> Option<impl Iterator<Item = (usize, &'a Interaction)>> {
let (idx, interaction) = self.iter.next()?;
let id = interaction.id();
// get interactions from a particular property
let span = interaction
.span
.expect("we should loop on interactions that have a span");
let first = std::iter::once((idx, interaction));
let property_interactions = match span {
Span::Start => Either::Left(
first.chain(
self.iter
.peeking_take_while(move |(_idx, interaction)| interaction.id() == id),
),
),
Span::End => panic!("we should always be at the start of an interaction"),
Span::StartEnd => Either::Right(first),
};
Some(property_interactions)
}
}
impl DerefMut for InteractionPlan {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.plan
}
}
impl<'a, I> IterProperty<I, Backward>
where
I: Iterator<Item = (usize, &'a Interaction)>
+ DoubleEndedIterator
+ itertools::PeekingNext
+ std::fmt::Debug,
{
pub fn next_property(&mut self) -> Option<impl Iterator<Item = (usize, &'a Interaction)>> {
let (idx, interaction) = self.iter.next()?;
let id = interaction.id();
// get interactions from a particular property
let span = interaction
.span
.expect("we should loop on interactions that have a span");
impl IntoIterator for InteractionPlan {
type Item = Interactions;
let first = std::iter::once((idx, interaction));
type IntoIter = <Vec<Interactions> as IntoIterator>::IntoIter;
let property_interactions = match span {
Span::Start => panic!("we should always be at the end of an interaction"),
Span::End => Either::Left(
self.iter
.peeking_take_while(move |(_idx, interaction)| interaction.id() == id)
.chain(first),
),
Span::StartEnd => Either::Right(first),
};
fn into_iter(self) -> Self::IntoIter {
self.plan.into_iter()
}
}
impl<'a> IntoIterator for &'a InteractionPlan {
type Item = &'a Interactions;
type IntoIter = <&'a Vec<Interactions> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.plan.iter()
}
}
impl<'a> IntoIterator for &'a mut InteractionPlan {
type Item = &'a mut Interactions;
type IntoIter = <&'a mut Vec<Interactions> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.plan.iter_mut()
Some(property_interactions.into_iter())
}
}
@@ -284,13 +326,6 @@ impl 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,
}
}
/// Whether the interaction needs to check the database tables
pub fn check_tables(&self) -> bool {
match &self.interactions {
@@ -341,75 +376,28 @@ impl InteractionsType {
}
}
impl Interactions {
pub(crate) fn interactions(&self) -> Vec<Interaction> {
match &self.interactions {
InteractionsType::Property(property) => property.interactions(self.connection_index),
InteractionsType::Query(query) => vec![Interaction::new(
self.connection_index,
InteractionType::Query(query.clone()),
)],
InteractionsType::Fault(fault) => vec![Interaction::new(
self.connection_index,
InteractionType::Fault(*fault),
)],
}
}
pub(crate) fn dependencies(&self) -> IndexSet<String> {
match &self.interactions {
InteractionsType::Property(property) => property
.interactions(self.connection_index)
.iter()
.fold(IndexSet::new(), |mut acc, i| match &i.interaction {
InteractionType::Query(q) => {
acc.extend(q.dependencies());
acc
}
_ => acc,
}),
InteractionsType::Query(query) => query.dependencies(),
InteractionsType::Fault(_) => IndexSet::new(),
}
}
pub(crate) fn uses(&self) -> Vec<String> {
match &self.interactions {
InteractionsType::Property(property) => property
.interactions(self.connection_index)
.iter()
.fold(vec![], |mut acc, i| match &i.interaction {
InteractionType::Query(q) => {
acc.extend(q.uses());
acc
}
_ => acc,
}),
InteractionsType::Query(query) => query.uses(),
InteractionsType::Fault(_) => vec![],
}
}
}
// FIXME: for the sql display come back and add connection index as a comment
impl Display for InteractionPlan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for interactions in &self.plan {
match &interactions.interactions {
InteractionsType::Property(property) => {
let name = property.name();
writeln!(f, "-- begin testing '{name}'")?;
for interaction in property.interactions(interactions.connection_index) {
writeln!(f, "\t{interaction}")?;
}
writeln!(f, "-- end testing '{name}'")?;
}
InteractionsType::Fault(fault) => {
writeln!(f, "-- FAULT '{fault}'")?;
}
InteractionsType::Query(query) => {
writeln!(f, "{query}; -- {}", interactions.connection_index)?;
}
const PAD: usize = 4;
let mut indentation_level = 0;
for interaction in &self.plan {
if let Some(name) = interaction.property_meta.map(|p| p.property.name())
&& interaction.span.is_some_and(|span| span.start())
{
indentation_level += 1;
writeln!(f, "-- begin testing '{name}'")?;
}
if indentation_level > 0 {
let padding = " ".repeat(indentation_level * PAD);
f.pad(&padding)?;
}
writeln!(f, "{interaction}")?;
if let Some(name) = interaction.property_meta.map(|p| p.property.name())
&& interaction.span.is_some_and(|span| span.end())
{
indentation_level -= 1;
writeln!(f, "-- end testing '{name}'")?;
}
}
@@ -417,45 +405,8 @@ impl Display for InteractionPlan {
}
}
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct InteractionStats {
pub select_count: u32,
pub insert_count: u32,
pub delete_count: u32,
pub update_count: u32,
pub create_count: u32,
pub create_index_count: u32,
pub drop_count: u32,
pub begin_count: u32,
pub commit_count: u32,
pub rollback_count: u32,
pub alter_table_count: u32,
pub drop_index_count: u32,
pub pragma_count: u32,
}
impl Display for InteractionStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Read: {}, Insert: {}, Delete: {}, Update: {}, Create: {}, CreateIndex: {}, Drop: {}, Begin: {}, Commit: {}, Rollback: {}, Alter Table: {}, Drop Index: {}",
self.select_count,
self.insert_count,
self.delete_count,
self.update_count,
self.create_count,
self.create_index_count,
self.drop_count,
self.begin_count,
self.commit_count,
self.rollback_count,
self.alter_table_count,
self.drop_index_count,
)
}
}
type AssertionFunc = dyn Fn(&Vec<ResultSet>, &mut SimulatorEnv) -> Result<Result<(), String>>;
type AssertionFunc =
dyn Fn(&Vec<ResultSet>, &mut SimulatorEnv) -> Result<Result<(), String>> + RefUnwindSafe;
#[derive(Clone)]
pub struct Assertion {
@@ -474,7 +425,9 @@ impl Debug for Assertion {
impl Assertion {
pub fn new<F>(name: String, func: F) -> Self
where
F: Fn(&Vec<ResultSet>, &mut SimulatorEnv) -> Result<Result<(), String>> + 'static,
F: Fn(&Vec<ResultSet>, &mut SimulatorEnv) -> Result<Result<(), String>>
+ 'static
+ RefUnwindSafe,
{
Self {
func: Rc::new(func),
@@ -532,11 +485,61 @@ impl PropertyMetadata {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, derive_builder::Builder)]
#[builder(build_fn(validate = "Self::validate"))]
pub struct Interaction {
pub connection_index: usize,
pub interaction: InteractionType,
#[builder(default)]
pub ignore_error: bool,
#[builder(setter(strip_option), default)]
pub property_meta: Option<PropertyMetadata>,
#[builder(setter(strip_option), default)]
pub span: Option<Span>,
/// 0 id means the ID was not set
id: NonZeroUsize,
}
impl InteractionBuilder {
pub fn from_interaction(interaction: &Interaction) -> Self {
let mut builder = Self::default();
builder
.connection_index(interaction.connection_index)
.id(interaction.id())
.ignore_error(interaction.ignore_error)
.interaction(interaction.interaction.clone());
if let Some(property_meta) = interaction.property_meta {
builder.property_meta(property_meta);
}
if let Some(span) = interaction.span {
builder.span(span);
}
builder
}
pub fn with_interaction(interaction: InteractionType) -> Self {
let mut builder = Self::default();
builder.interaction(interaction);
builder
}
/// Checks to see if the property metadata was already set
pub fn has_property_meta(&self) -> bool {
self.property_meta.is_some()
}
fn validate(&self) -> Result<(), InteractionBuilderError> {
// Cannot have span and property_meta.extension being true at the same time
if let Some(property_meta) = self.property_meta.flatten()
&& property_meta.extension
&& self.span.flatten().is_some()
{
return Err(InteractionBuilderError::ValidationError(
"cannot have a span set with an extension query".to_string(),
));
}
Ok(())
}
}
impl Deref for Interaction {
@@ -554,19 +557,16 @@ impl DerefMut for Interaction {
}
impl Interaction {
pub fn new(connection_index: usize, interaction: InteractionType) -> Self {
Self {
connection_index,
interaction,
ignore_error: false,
}
pub fn id(&self) -> NonZeroUsize {
self.id
}
pub fn new_ignore_error(connection_index: usize, interaction: InteractionType) -> Self {
Self {
connection_index,
interaction,
ignore_error: true,
pub fn uses(&self) -> Vec<String> {
match &self.interaction {
InteractionType::Query(query)
| InteractionType::FsyncQuery(query)
| InteractionType::FaultyQuery(query) => query.uses(),
_ => vec![],
}
}
}

View File

@@ -5,8 +5,9 @@ use crate::model::Query;
/// Properties are representations of executable specifications
/// about the database behavior.
#[derive(Debug, Clone, Serialize, Deserialize, strum::EnumDiscriminants)]
#[strum_discriminants(derive(strum::EnumIter))]
#[derive(Debug, Clone, Serialize, Deserialize, strum::EnumDiscriminants, strum::IntoStaticStr)]
#[strum_discriminants(derive(strum::EnumIter, strum::IntoStaticStr))]
#[strum(serialize_all = "Train-Case")]
pub 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
@@ -161,7 +162,7 @@ pub enum Property {
/// should return the same number of rows as `SELECT <predicate> FROM <t> WHERE <predicate>`.
/// > The property is succesfull when the UNION ALL of 2 select queries returns the same number of rows
/// > as the sum of the two select queries.
UNIONAllPreservesCardinality {
UnionAllPreservesCardinality {
select: Select,
where_clause: Predicate,
},
@@ -192,25 +193,6 @@ pub struct InteractiveQueryInfo {
}
impl Property {
pub(crate) fn name(&self) -> &str {
match self {
Property::InsertValuesSelect { .. } => "Insert-Values-Select",
Property::ReadYourUpdatesBack { .. } => "Read-Your-Updates-Back",
Property::TableHasExpectedContent { .. } => "Table-Has-Expected-Content",
Property::AllTableHaveExpectedContent { .. } => "All-Tables-Have-Expected-Content",
Property::DoubleCreateFailure { .. } => "Double-Create-Failure",
Property::SelectLimit { .. } => "Select-Limit",
Property::DeleteSelect { .. } => "Delete-Select",
Property::DropSelect { .. } => "Drop-Select",
Property::SelectSelectOptimizer { .. } => "Select-Select-Optimizer",
Property::WhereTrueFalseNull { .. } => "Where-True-False-Null",
Property::FsyncNoWait { .. } => "FsyncNoWait",
Property::FaultyQuery { .. } => "FaultyQuery",
Property::UNIONAllPreservesCardinality { .. } => "UNION-All-Preserves-Cardinality",
Property::Queries { .. } => "Queries",
}
}
/// Property Does some sort of fault injection
pub fn check_tables(&self) -> bool {
matches!(
@@ -219,6 +201,17 @@ impl Property {
)
}
pub fn has_extensional_queries(&self) -> bool {
matches!(
self,
Property::InsertValuesSelect { .. }
| Property::DoubleCreateFailure { .. }
| Property::DeleteSelect { .. }
| Property::DropSelect { .. }
| Property::Queries { .. }
)
}
pub fn get_extensional_queries(&mut self) -> Option<&mut Vec<Query>> {
match self {
Property::InsertValuesSelect { queries, .. }
@@ -230,10 +223,23 @@ impl Property {
Property::SelectLimit { .. }
| Property::SelectSelectOptimizer { .. }
| Property::WhereTrueFalseNull { .. }
| Property::UNIONAllPreservesCardinality { .. }
| Property::UnionAllPreservesCardinality { .. }
| Property::ReadYourUpdatesBack { .. }
| Property::TableHasExpectedContent { .. }
| Property::AllTableHaveExpectedContent { .. } => None,
}
}
}
impl PropertyDiscriminants {
pub fn name(&self) -> &'static str {
self.into()
}
pub fn check_tables(&self) -> bool {
matches!(
self,
Self::AllTableHaveExpectedContent | Self::TableHasExpectedContent
)
}
}

View File

@@ -9,8 +9,8 @@ use crate::{
model::{
Query, ResultSet,
interactions::{
ConnectionState, Interaction, InteractionPlanIterator, InteractionPlanState,
InteractionType,
ConnectionState, Interaction, InteractionBuilder, InteractionPlanIterator,
InteractionPlanState, InteractionType,
},
},
};
@@ -199,10 +199,10 @@ pub fn execute_interaction_turso(
stack.push(results);
let query_interaction = Interaction::new(
interaction.connection_index,
InteractionType::Query(query.clone()),
);
let query_interaction = InteractionBuilder::from_interaction(interaction)
.interaction(InteractionType::Query(query.clone()))
.build()
.unwrap();
execute_interaction(env, &query_interaction, stack)?;
}