mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-28 20:34:24 +01:00
Merge 'Enhancement to Sim Snapshot isolation code' from Pedro Muniz
- Made the code around snapshot isolation more ergonomic with each connections having its own transaction state. Also when shadowing, we pass a ShadowTablesMut object that dynamically uses the committed tables or the connection tables depending on the transaction state. - added begin concurrent transaction before every property when mvcc is enabled (this is just so we can have some mvcc code be tested using the simulator under Begin Concurrent, I have not yet implemented the logic to have concurrent transactions in the simulator) - some small enhancements to shrinking TODOs - have proper logic to have concurrent transactions without WriteWrite conflicts. This means when generating the plans we need to make sure that we do not generate rows that will conflict with rows in other transactions. This is slightly more powerful than what we do in the fuzzer, as we just have `WriteWriteConflict` as an acceptable error there. By baking this `NoConflict` approach to the simulator, we can continuously test the what does not trigger a WriteWriteConflict and snapshot isolation. Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #3226
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2153,6 +2153,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"dirs 6.0.0",
|
||||
"either",
|
||||
"env_logger 0.11.7",
|
||||
"garde",
|
||||
"hex",
|
||||
|
||||
@@ -44,3 +44,4 @@ json5 = { version = "0.4.1" }
|
||||
strum = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
either = "1.15.0"
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use sql_generation::generation::GenerationContext;
|
||||
|
||||
use crate::runner::env::{SimulatorEnv, SimulatorTables};
|
||||
use crate::runner::env::ShadowTablesMut;
|
||||
|
||||
pub mod plan;
|
||||
pub mod property;
|
||||
@@ -17,25 +15,5 @@ pub mod query;
|
||||
/// might return a vector of rows that were inserted into the table.
|
||||
pub(crate) trait Shadow {
|
||||
type Result;
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result;
|
||||
}
|
||||
|
||||
impl GenerationContext for SimulatorEnv {
|
||||
fn tables(&self) -> &Vec<sql_generation::model::table::Table> {
|
||||
&self.tables.tables
|
||||
}
|
||||
|
||||
fn opts(&self) -> &sql_generation::generation::Opts {
|
||||
&self.profile.query.gen_opts
|
||||
}
|
||||
}
|
||||
|
||||
impl GenerationContext for &mut SimulatorEnv {
|
||||
fn tables(&self) -> &Vec<sql_generation::model::table::Table> {
|
||||
&self.tables.tables
|
||||
}
|
||||
|
||||
fn opts(&self) -> &sql_generation::generation::Opts {
|
||||
&self.profile.query.gen_opts
|
||||
}
|
||||
fn shadow(&self, tables: &mut ShadowTablesMut<'_>) -> Self::Result;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,11 @@ use serde::{Deserialize, Serialize};
|
||||
use sql_generation::{
|
||||
generation::{Arbitrary, ArbitraryFrom, GenerationContext, frequency, query::SelectFree},
|
||||
model::{
|
||||
query::{Create, CreateIndex, Delete, Drop, Insert, Select, update::Update},
|
||||
query::{
|
||||
Create, CreateIndex, Delete, Drop, Insert, Select,
|
||||
transaction::{Begin, Commit},
|
||||
update::Update,
|
||||
},
|
||||
table::SimValue,
|
||||
},
|
||||
};
|
||||
@@ -23,7 +27,7 @@ use crate::{
|
||||
SimulatorEnv,
|
||||
generation::Shadow,
|
||||
model::Query,
|
||||
runner::env::{SimConnection, SimulationType, SimulatorTables},
|
||||
runner::env::{ShadowTablesMut, SimConnection, SimulationType},
|
||||
};
|
||||
|
||||
use super::property::{Property, remaining};
|
||||
@@ -32,10 +36,46 @@ pub(crate) type ResultSet = Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct InteractionPlan {
|
||||
pub(crate) plan: Vec<Interactions>,
|
||||
pub plan: Vec<Interactions>,
|
||||
pub mvcc: bool,
|
||||
}
|
||||
|
||||
impl InteractionPlan {
|
||||
pub(crate) fn new(mvcc: bool) -> Self {
|
||||
Self {
|
||||
plan: Vec::new(),
|
||||
mvcc,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with(plan: Vec<Interactions>, mvcc: bool) -> Self {
|
||||
Self { plan, mvcc }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plan(&self) -> &[Interactions] {
|
||||
&self.plan
|
||||
}
|
||||
|
||||
// TODO: this is just simplified logic so we can get something rolling with begin concurrent
|
||||
// transactions in the simulator. Ideally when we generate the plan we will have begin and commits statements across interactions
|
||||
pub fn push(&mut self, interactions: Interactions) {
|
||||
if self.mvcc {
|
||||
let conn_index = interactions.connection_index;
|
||||
let begin = Interactions::new(
|
||||
conn_index,
|
||||
InteractionsType::Query(Query::Begin(Begin::Concurrent)),
|
||||
);
|
||||
let commit =
|
||||
Interactions::new(conn_index, InteractionsType::Query(Query::Commit(Commit)));
|
||||
self.plan.push(begin);
|
||||
self.plan.push(interactions);
|
||||
self.plan.push(commit);
|
||||
} else {
|
||||
self.plan.push(interactions);
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute via diff computes a a plan from a given `.plan` file without the need to parse
|
||||
/// sql. This is possible because there are two versions of the plan file, one that is human
|
||||
/// readable and one that is serialized as JSON. Under watch mode, the users will be able to
|
||||
@@ -121,6 +161,109 @@ impl InteractionPlan {
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn stats(&self) -> InteractionStats {
|
||||
let mut stats = InteractionStats {
|
||||
select_count: 0,
|
||||
insert_count: 0,
|
||||
delete_count: 0,
|
||||
update_count: 0,
|
||||
create_count: 0,
|
||||
create_index_count: 0,
|
||||
drop_count: 0,
|
||||
begin_count: 0,
|
||||
commit_count: 0,
|
||||
rollback_count: 0,
|
||||
};
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
for interactions in &self.plan {
|
||||
match &interactions.interactions {
|
||||
InteractionsType::Property(property) => {
|
||||
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(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
stats
|
||||
}
|
||||
|
||||
pub fn generate_plan<R: rand::Rng>(rng: &mut R, env: &mut SimulatorEnv) -> Self {
|
||||
let mut plan = InteractionPlan::new(env.profile.experimental_mvcc);
|
||||
|
||||
let num_interactions = env.opts.max_interactions as usize;
|
||||
|
||||
// First create at least one table
|
||||
let create_query = Create::arbitrary(rng, &env.connection_context(0));
|
||||
env.committed_tables.push(create_query.table.clone());
|
||||
|
||||
// initial query starts at 0th connection
|
||||
plan.plan.push(Interactions::new(
|
||||
0,
|
||||
InteractionsType::Query(Query::Create(create_query)),
|
||||
));
|
||||
|
||||
while plan.len() < num_interactions {
|
||||
tracing::debug!("Generating interaction {}/{}", plan.len(), num_interactions);
|
||||
let interactions = {
|
||||
let conn_index = env.choose_conn(rng);
|
||||
let conn_ctx = &env.connection_context(conn_index);
|
||||
Interactions::arbitrary_from(rng, conn_ctx, (env, plan.stats(), conn_index))
|
||||
};
|
||||
|
||||
interactions.shadow(&mut env.get_conn_tables_mut(interactions.connection_index));
|
||||
plan.push(interactions);
|
||||
}
|
||||
|
||||
tracing::info!("Generated plan with {} interactions", plan.plan.len());
|
||||
|
||||
plan
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for InteractionPlan {
|
||||
type Target = [Interactions];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.plan
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for InteractionPlan {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.plan
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for InteractionPlan {
|
||||
type Item = Interactions;
|
||||
|
||||
type IntoIter = <Vec<Interactions> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.plan.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -172,7 +315,7 @@ pub enum InteractionsType {
|
||||
impl Shadow for Interactions {
|
||||
type Result = ();
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) {
|
||||
fn shadow(&self, tables: &mut ShadowTablesMut) {
|
||||
match &self.interactions {
|
||||
InteractionsType::Property(property) => {
|
||||
let initial_tables = tables.clone();
|
||||
@@ -180,7 +323,7 @@ impl Shadow for Interactions {
|
||||
let res = interaction.shadow(tables);
|
||||
if res.is_err() {
|
||||
// If any interaction fails, we reset the tables to the initial state
|
||||
*tables = initial_tables.clone();
|
||||
**tables = initial_tables.clone();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -368,89 +511,6 @@ impl Display for Fault {
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractionPlan {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { plan: Vec::new() }
|
||||
}
|
||||
|
||||
pub(crate) fn stats(&self) -> InteractionStats {
|
||||
let mut stats = InteractionStats {
|
||||
select_count: 0,
|
||||
insert_count: 0,
|
||||
delete_count: 0,
|
||||
update_count: 0,
|
||||
create_count: 0,
|
||||
create_index_count: 0,
|
||||
drop_count: 0,
|
||||
begin_count: 0,
|
||||
commit_count: 0,
|
||||
rollback_count: 0,
|
||||
};
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
for interactions in &self.plan {
|
||||
match &interactions.interactions {
|
||||
InteractionsType::Property(property) => {
|
||||
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(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
stats
|
||||
}
|
||||
|
||||
pub fn generate_plan<R: rand::Rng>(rng: &mut R, env: &mut SimulatorEnv) -> Self {
|
||||
let mut plan = InteractionPlan::new();
|
||||
|
||||
let num_interactions = env.opts.max_interactions as usize;
|
||||
|
||||
// First create at least one table
|
||||
let create_query = Create::arbitrary(rng, env);
|
||||
env.tables.push(create_query.table.clone());
|
||||
|
||||
// initial query starts at 0th connection
|
||||
plan.plan.push(Interactions::new(
|
||||
0,
|
||||
InteractionsType::Query(Query::Create(create_query)),
|
||||
));
|
||||
|
||||
while plan.plan.len() < num_interactions {
|
||||
tracing::debug!(
|
||||
"Generating interaction {}/{}",
|
||||
plan.plan.len(),
|
||||
num_interactions
|
||||
);
|
||||
let interactions = Interactions::arbitrary_from(rng, env, (env, plan.stats()));
|
||||
interactions.shadow(&mut env.tables);
|
||||
plan.plan.push(interactions);
|
||||
}
|
||||
|
||||
tracing::info!("Generated plan with {} interactions", plan.plan.len());
|
||||
plan
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Interaction {
|
||||
pub connection_index: usize,
|
||||
@@ -520,7 +580,7 @@ impl Display for InteractionType {
|
||||
|
||||
impl Shadow for InteractionType {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
fn shadow(&self, env: &mut ShadowTablesMut) -> Self::Result {
|
||||
match self {
|
||||
Self::Query(query) => query.shadow(env),
|
||||
Self::FsyncQuery(query) => {
|
||||
@@ -834,66 +894,90 @@ fn reopen_database(env: &mut SimulatorEnv) {
|
||||
};
|
||||
}
|
||||
|
||||
fn random_create<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
|
||||
let mut create = Create::arbitrary(rng, env);
|
||||
while env.tables.iter().any(|t| t.name == create.table.name) {
|
||||
create = Create::arbitrary(rng, env);
|
||||
fn random_create<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions {
|
||||
let conn_ctx = env.connection_context(conn_index);
|
||||
let mut create = Create::arbitrary(rng, &conn_ctx);
|
||||
while conn_ctx
|
||||
.tables()
|
||||
.iter()
|
||||
.any(|t| t.name == create.table.name)
|
||||
{
|
||||
create = Create::arbitrary(rng, &conn_ctx);
|
||||
}
|
||||
Interactions::new(conn_index, InteractionsType::Query(Query::Create(create)))
|
||||
}
|
||||
|
||||
fn random_read<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions {
|
||||
Interactions::new(
|
||||
env.choose_conn(rng),
|
||||
InteractionsType::Query(Query::Create(create)),
|
||||
conn_index,
|
||||
InteractionsType::Query(Query::Select(Select::arbitrary(
|
||||
rng,
|
||||
&env.connection_context(conn_index),
|
||||
))),
|
||||
)
|
||||
}
|
||||
|
||||
fn random_read<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
|
||||
fn random_expr<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions {
|
||||
Interactions::new(
|
||||
env.choose_conn(rng),
|
||||
InteractionsType::Query(Query::Select(Select::arbitrary(rng, env))),
|
||||
conn_index,
|
||||
InteractionsType::Query(Query::Select(
|
||||
SelectFree::arbitrary(rng, &env.connection_context(conn_index)).0,
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
fn random_expr<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
|
||||
fn random_write<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions {
|
||||
Interactions::new(
|
||||
env.choose_conn(rng),
|
||||
InteractionsType::Query(Query::Select(SelectFree::arbitrary(rng, env).0)),
|
||||
conn_index,
|
||||
InteractionsType::Query(Query::Insert(Insert::arbitrary(
|
||||
rng,
|
||||
&env.connection_context(conn_index),
|
||||
))),
|
||||
)
|
||||
}
|
||||
|
||||
fn random_write<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
|
||||
fn random_delete<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions {
|
||||
Interactions::new(
|
||||
env.choose_conn(rng),
|
||||
InteractionsType::Query(Query::Insert(Insert::arbitrary(rng, env))),
|
||||
conn_index,
|
||||
InteractionsType::Query(Query::Delete(Delete::arbitrary(
|
||||
rng,
|
||||
&env.connection_context(conn_index),
|
||||
))),
|
||||
)
|
||||
}
|
||||
|
||||
fn random_delete<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
|
||||
fn random_update<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions {
|
||||
Interactions::new(
|
||||
env.choose_conn(rng),
|
||||
InteractionsType::Query(Query::Delete(Delete::arbitrary(rng, env))),
|
||||
conn_index,
|
||||
InteractionsType::Query(Query::Update(Update::arbitrary(
|
||||
rng,
|
||||
&env.connection_context(conn_index),
|
||||
))),
|
||||
)
|
||||
}
|
||||
|
||||
fn random_update<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
|
||||
fn random_drop<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv, conn_index: usize) -> Interactions {
|
||||
Interactions::new(
|
||||
env.choose_conn(rng),
|
||||
InteractionsType::Query(Query::Update(Update::arbitrary(rng, env))),
|
||||
conn_index,
|
||||
InteractionsType::Query(Query::Drop(Drop::arbitrary(
|
||||
rng,
|
||||
&env.connection_context(conn_index),
|
||||
))),
|
||||
)
|
||||
}
|
||||
|
||||
fn random_drop<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
|
||||
Interactions::new(
|
||||
env.choose_conn(rng),
|
||||
InteractionsType::Query(Query::Drop(Drop::arbitrary(rng, env))),
|
||||
)
|
||||
}
|
||||
|
||||
fn random_create_index<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Option<Interactions> {
|
||||
if env.tables.is_empty() {
|
||||
fn random_create_index<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
env: &SimulatorEnv,
|
||||
conn_index: usize,
|
||||
) -> Option<Interactions> {
|
||||
let conn_ctx = env.connection_context(conn_index);
|
||||
if conn_ctx.tables().is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut create_index = CreateIndex::arbitrary(rng, env);
|
||||
while env
|
||||
.tables
|
||||
let mut create_index = CreateIndex::arbitrary(rng, &conn_ctx);
|
||||
while conn_ctx
|
||||
.tables()
|
||||
.iter()
|
||||
.find(|t| t.name == create_index.table_name)
|
||||
.expect("table should exist")
|
||||
@@ -901,11 +985,11 @@ fn random_create_index<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Option<
|
||||
.iter()
|
||||
.any(|i| i == &create_index.index_name)
|
||||
{
|
||||
create_index = CreateIndex::arbitrary(rng, env);
|
||||
create_index = CreateIndex::arbitrary(rng, &conn_ctx);
|
||||
}
|
||||
|
||||
Some(Interactions::new(
|
||||
env.choose_conn(rng),
|
||||
conn_index,
|
||||
InteractionsType::Query(Query::CreateIndex(create_index)),
|
||||
))
|
||||
}
|
||||
@@ -920,23 +1004,28 @@ fn random_fault<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
|
||||
Interactions::new(env.choose_conn(rng), InteractionsType::Fault(fault))
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions {
|
||||
impl ArbitraryFrom<(&SimulatorEnv, InteractionStats, usize)> for Interactions {
|
||||
fn arbitrary_from<R: rand::Rng, C: GenerationContext>(
|
||||
rng: &mut R,
|
||||
_context: &C,
|
||||
(env, stats): (&SimulatorEnv, InteractionStats),
|
||||
conn_ctx: &C,
|
||||
(env, stats, conn_index): (&SimulatorEnv, InteractionStats, usize),
|
||||
) -> Self {
|
||||
let remaining_ = remaining(env.opts.max_interactions, &env.profile.query, &stats);
|
||||
let remaining_ = remaining(
|
||||
env.opts.max_interactions,
|
||||
&env.profile.query,
|
||||
&stats,
|
||||
env.profile.experimental_mvcc,
|
||||
);
|
||||
frequency(
|
||||
vec![
|
||||
(
|
||||
u32::min(remaining_.select, remaining_.insert) + remaining_.create,
|
||||
Box::new(|rng: &mut R| {
|
||||
Interactions::new(
|
||||
env.choose_conn(rng),
|
||||
conn_index,
|
||||
InteractionsType::Property(Property::arbitrary_from(
|
||||
rng,
|
||||
env,
|
||||
conn_ctx,
|
||||
(env, &stats),
|
||||
)),
|
||||
)
|
||||
@@ -944,43 +1033,43 @@ impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions {
|
||||
),
|
||||
(
|
||||
remaining_.select,
|
||||
Box::new(|rng: &mut R| random_read(rng, env)),
|
||||
Box::new(|rng: &mut R| random_read(rng, env, conn_index)),
|
||||
),
|
||||
(
|
||||
remaining_.select / 3,
|
||||
Box::new(|rng: &mut R| random_expr(rng, env)),
|
||||
Box::new(|rng: &mut R| random_expr(rng, env, conn_index)),
|
||||
),
|
||||
(
|
||||
remaining_.insert,
|
||||
Box::new(|rng: &mut R| random_write(rng, env)),
|
||||
Box::new(|rng: &mut R| random_write(rng, env, conn_index)),
|
||||
),
|
||||
(
|
||||
remaining_.create,
|
||||
Box::new(|rng: &mut R| random_create(rng, env)),
|
||||
Box::new(|rng: &mut R| random_create(rng, env, conn_index)),
|
||||
),
|
||||
(
|
||||
remaining_.create_index,
|
||||
Box::new(|rng: &mut R| {
|
||||
if let Some(interaction) = random_create_index(rng, env) {
|
||||
if let Some(interaction) = random_create_index(rng, env, conn_index) {
|
||||
interaction
|
||||
} else {
|
||||
// if no tables exist, we can't create an index, so fallback to creating a table
|
||||
random_create(rng, env)
|
||||
random_create(rng, env, conn_index)
|
||||
}
|
||||
}),
|
||||
),
|
||||
(
|
||||
remaining_.delete,
|
||||
Box::new(|rng: &mut R| random_delete(rng, env)),
|
||||
Box::new(|rng: &mut R| random_delete(rng, env, conn_index)),
|
||||
),
|
||||
(
|
||||
remaining_.update,
|
||||
Box::new(|rng: &mut R| random_update(rng, env)),
|
||||
Box::new(|rng: &mut R| random_update(rng, env, conn_index)),
|
||||
),
|
||||
(
|
||||
// remaining_.drop,
|
||||
0,
|
||||
Box::new(|rng: &mut R| random_drop(rng, env)),
|
||||
Box::new(|rng: &mut R| random_drop(rng, env, conn_index)),
|
||||
),
|
||||
(
|
||||
remaining_
|
||||
|
||||
@@ -226,7 +226,8 @@ impl Property {
|
||||
let assumption = InteractionType::Assumption(Assertion::new(
|
||||
format!("table {} exists", table.clone()),
|
||||
move |_: &Vec<ResultSet>, env: &mut SimulatorEnv| {
|
||||
if env.tables.iter().any(|t| t.name == table_name) {
|
||||
let conn_tables = env.get_conn_tables(connection_index);
|
||||
if conn_tables.iter().any(|t| t.name == table_name) {
|
||||
Ok(Ok(()))
|
||||
} else {
|
||||
Ok(Err(format!("table {table_name} does not exist")))
|
||||
@@ -246,8 +247,8 @@ impl Property {
|
||||
let Ok(rows) = rows else {
|
||||
return Ok(Err(format!("expected rows but got error: {rows:?}")));
|
||||
};
|
||||
let sim_table = env
|
||||
.tables
|
||||
let conn_tables = env.get_conn_tables(connection_index);
|
||||
let sim_table = conn_tables
|
||||
.iter()
|
||||
.find(|t| t.name == table)
|
||||
.expect("table should be in enviroment");
|
||||
@@ -283,7 +284,8 @@ impl Property {
|
||||
let assumption = InteractionType::Assumption(Assertion::new(
|
||||
format!("table {} exists", table.clone()),
|
||||
move |_: &Vec<ResultSet>, env: &mut SimulatorEnv| {
|
||||
if env.tables.iter().any(|t| t.name == table.clone()) {
|
||||
let conn_tables = env.get_conn_tables(connection_index);
|
||||
if conn_tables.iter().any(|t| t.name == table.clone()) {
|
||||
Ok(Ok(()))
|
||||
} else {
|
||||
Ok(Err(format!("table {} does not exist", table.clone())))
|
||||
@@ -360,7 +362,8 @@ impl Property {
|
||||
{
|
||||
let table_name = table.clone();
|
||||
move |_: &Vec<ResultSet>, env: &mut SimulatorEnv| {
|
||||
if env.tables.iter().any(|t| t.name == table_name) {
|
||||
let conn_tables = env.get_conn_tables(connection_index);
|
||||
if conn_tables.iter().any(|t| t.name == table_name) {
|
||||
Ok(Ok(()))
|
||||
} else {
|
||||
Ok(Err(format!("table {table_name} does not exist")))
|
||||
@@ -429,7 +432,8 @@ impl Property {
|
||||
let assumption = InteractionType::Assumption(Assertion::new(
|
||||
"Double-Create-Failure should not be called on an existing table".to_string(),
|
||||
move |_: &Vec<ResultSet>, env: &mut SimulatorEnv| {
|
||||
if !env.tables.iter().any(|t| t.name == table_name) {
|
||||
let conn_tables = env.get_conn_tables(connection_index);
|
||||
if !conn_tables.iter().any(|t| t.name == table_name) {
|
||||
Ok(Ok(()))
|
||||
} else {
|
||||
Ok(Err(format!("table {table_name} already exists")))
|
||||
@@ -484,15 +488,16 @@ impl Property {
|
||||
{
|
||||
let table_name = select.dependencies();
|
||||
move |_: &Vec<ResultSet>, env: &mut SimulatorEnv| {
|
||||
let conn_tables = env.get_conn_tables(connection_index);
|
||||
if table_name
|
||||
.iter()
|
||||
.all(|table| env.tables.iter().any(|t| t.name == *table))
|
||||
.all(|table| conn_tables.iter().any(|t| t.name == *table))
|
||||
{
|
||||
Ok(Ok(()))
|
||||
} else {
|
||||
let missing_tables = table_name
|
||||
.iter()
|
||||
.filter(|t| !env.tables.iter().any(|t2| t2.name == **t))
|
||||
.filter(|t| !conn_tables.iter().any(|t2| t2.name == **t))
|
||||
.collect::<Vec<&String>>();
|
||||
Ok(Err(format!("missing tables: {missing_tables:?}")))
|
||||
}
|
||||
@@ -544,12 +549,13 @@ impl Property {
|
||||
{
|
||||
let table = table.clone();
|
||||
move |_: &Vec<ResultSet>, env: &mut SimulatorEnv| {
|
||||
if env.tables.iter().any(|t| t.name == table) {
|
||||
let conn_tables = env.get_conn_tables(connection_index);
|
||||
if conn_tables.iter().any(|t| t.name == table) {
|
||||
Ok(Ok(()))
|
||||
} else {
|
||||
{
|
||||
let available_tables: Vec<String> =
|
||||
env.tables.iter().map(|t| t.name.clone()).collect();
|
||||
conn_tables.iter().map(|t| t.name.clone()).collect();
|
||||
Ok(Err(format!(
|
||||
"table \'{table}\' not found. Available tables: {available_tables:?}"
|
||||
)))
|
||||
@@ -617,12 +623,13 @@ impl Property {
|
||||
{
|
||||
let table = table.clone();
|
||||
move |_, env: &mut SimulatorEnv| {
|
||||
if env.tables.iter().any(|t| t.name == table) {
|
||||
let conn_tables = env.get_conn_tables(connection_index);
|
||||
if conn_tables.iter().any(|t| t.name == table) {
|
||||
Ok(Ok(()))
|
||||
} else {
|
||||
{
|
||||
let available_tables: Vec<String> =
|
||||
env.tables.iter().map(|t| t.name.clone()).collect();
|
||||
conn_tables.iter().map(|t| t.name.clone()).collect();
|
||||
Ok(Err(format!(
|
||||
"table \'{table}\' not found. Available tables: {available_tables:?}"
|
||||
)))
|
||||
@@ -684,12 +691,13 @@ impl Property {
|
||||
{
|
||||
let table = table.clone();
|
||||
move |_: &Vec<ResultSet>, env: &mut SimulatorEnv| {
|
||||
if env.tables.iter().any(|t| t.name == table) {
|
||||
let conn_tables = env.get_conn_tables(connection_index);
|
||||
if conn_tables.iter().any(|t| t.name == table) {
|
||||
Ok(Ok(()))
|
||||
} else {
|
||||
{
|
||||
let available_tables: Vec<String> =
|
||||
env.tables.iter().map(|t| t.name.clone()).collect();
|
||||
conn_tables.iter().map(|t| t.name.clone()).collect();
|
||||
Ok(Err(format!(
|
||||
"table \'{table}\' not found. Available tables: {available_tables:?}"
|
||||
)))
|
||||
@@ -788,7 +796,8 @@ impl Property {
|
||||
let last = stack.last().unwrap();
|
||||
match last {
|
||||
Ok(_) => {
|
||||
let _ = query_clone.shadow(&mut env.tables);
|
||||
let _ = query_clone
|
||||
.shadow(&mut env.get_conn_tables_mut(connection_index));
|
||||
Ok(Ok(()))
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -821,15 +830,16 @@ impl Property {
|
||||
{
|
||||
let tables = select.dependencies();
|
||||
move |_: &Vec<ResultSet>, env: &mut SimulatorEnv| {
|
||||
let conn_tables = env.get_conn_tables(connection_index);
|
||||
if tables
|
||||
.iter()
|
||||
.all(|table| env.tables.iter().any(|t| t.name == *table))
|
||||
.all(|table| conn_tables.iter().any(|t| t.name == *table))
|
||||
{
|
||||
Ok(Ok(()))
|
||||
} else {
|
||||
let missing_tables = tables
|
||||
.iter()
|
||||
.filter(|t| !env.tables.iter().any(|t2| t2.name == **t))
|
||||
.filter(|t| !conn_tables.iter().any(|t2| t2.name == **t))
|
||||
.collect::<Vec<&String>>();
|
||||
Ok(Err(format!("missing tables: {missing_tables:?}")))
|
||||
}
|
||||
@@ -1030,7 +1040,8 @@ fn assert_all_table_values(
|
||||
let assertion = InteractionType::Assertion(Assertion::new(format!("table {table} should contain all of its expected values"), {
|
||||
let table = table.clone();
|
||||
move |stack: &Vec<ResultSet>, env: &mut SimulatorEnv| {
|
||||
let table = env.tables.iter().find(|t| t.name == table).ok_or_else(|| {
|
||||
let conn_ctx = env.get_conn_tables(connection_index);
|
||||
let table = conn_ctx.iter().find(|t| t.name == table).ok_or_else(|| {
|
||||
LimboError::InternalError(format!(
|
||||
"table {table} should exist in simulator env"
|
||||
))
|
||||
@@ -1090,6 +1101,7 @@ pub(crate) fn remaining(
|
||||
max_interactions: u32,
|
||||
opts: &QueryProfile,
|
||||
stats: &InteractionStats,
|
||||
mvcc: bool,
|
||||
) -> Remaining {
|
||||
let total_weight = opts.select_weight
|
||||
+ opts.create_table_weight
|
||||
@@ -1116,7 +1128,7 @@ pub(crate) fn remaining(
|
||||
let remaining_create = total_create
|
||||
.checked_sub(stats.create_count)
|
||||
.unwrap_or_default();
|
||||
let remaining_create_index = total_create_index
|
||||
let mut remaining_create_index = total_create_index
|
||||
.checked_sub(stats.create_index_count)
|
||||
.unwrap_or_default();
|
||||
let remaining_delete = total_delete
|
||||
@@ -1127,6 +1139,11 @@ pub(crate) fn remaining(
|
||||
.unwrap_or_default();
|
||||
let remaining_drop = total_drop.checked_sub(stats.drop_count).unwrap_or_default();
|
||||
|
||||
if mvcc {
|
||||
// TODO: index not supported yet for mvcc
|
||||
remaining_create_index = 0;
|
||||
}
|
||||
|
||||
Remaining {
|
||||
select: remaining_select,
|
||||
insert: remaining_insert,
|
||||
@@ -1140,14 +1157,14 @@ pub(crate) fn remaining(
|
||||
|
||||
fn property_insert_values_select<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
env: &SimulatorEnv,
|
||||
remaining: &Remaining,
|
||||
ctx: &impl GenerationContext,
|
||||
) -> Property {
|
||||
// Get a random table
|
||||
let table = pick(&env.tables, rng);
|
||||
let table = pick(ctx.tables(), rng);
|
||||
// Generate rows to insert
|
||||
let rows = (0..rng.random_range(1..=5))
|
||||
.map(|_| Vec::<SimValue>::arbitrary_from(rng, env, table))
|
||||
.map(|_| Vec::<SimValue>::arbitrary_from(rng, ctx, table))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Pick a random row to select
|
||||
@@ -1176,12 +1193,14 @@ fn property_insert_values_select<R: rand::Rng>(
|
||||
// - [x] The inserted row will not be updated.
|
||||
// - [ ] The table `t` will not be renamed, dropped, or altered. (todo: add this constraint once ALTER or DROP is implemented)
|
||||
if let Some(ref interactive) = interactive {
|
||||
queries.push(Query::Begin(Begin {
|
||||
immediate: interactive.start_with_immediate,
|
||||
queries.push(Query::Begin(if interactive.start_with_immediate {
|
||||
Begin::Immediate
|
||||
} else {
|
||||
Begin::Deferred
|
||||
}));
|
||||
}
|
||||
for _ in 0..rng.random_range(0..3) {
|
||||
let query = Query::arbitrary_from(rng, env, remaining);
|
||||
let query = Query::arbitrary_from(rng, ctx, remaining);
|
||||
match &query {
|
||||
Query::Delete(Delete {
|
||||
table: t,
|
||||
@@ -1224,7 +1243,7 @@ fn property_insert_values_select<R: rand::Rng>(
|
||||
// Select the row
|
||||
let select_query = Select::simple(
|
||||
table.name.clone(),
|
||||
Predicate::arbitrary_from(rng, env, (table, &row)),
|
||||
Predicate::arbitrary_from(rng, ctx, (table, &row)),
|
||||
);
|
||||
|
||||
Property::InsertValuesSelect {
|
||||
@@ -1236,9 +1255,12 @@ fn property_insert_values_select<R: rand::Rng>(
|
||||
}
|
||||
}
|
||||
|
||||
fn property_read_your_updates_back<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Property {
|
||||
fn property_read_your_updates_back<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
ctx: &impl GenerationContext,
|
||||
) -> Property {
|
||||
// e.g. UPDATE t SET a=1, b=2 WHERE c=1;
|
||||
let update = Update::arbitrary(rng, env);
|
||||
let update = Update::arbitrary(rng, ctx);
|
||||
// e.g. SELECT a, b FROM t WHERE c=1;
|
||||
let select = Select::single(
|
||||
update.table().to_string(),
|
||||
@@ -1255,22 +1277,25 @@ fn property_read_your_updates_back<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv
|
||||
Property::ReadYourUpdatesBack { update, select }
|
||||
}
|
||||
|
||||
fn property_table_has_expected_content<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Property {
|
||||
fn property_table_has_expected_content<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
ctx: &impl GenerationContext,
|
||||
) -> Property {
|
||||
// Get a random table
|
||||
let table = pick(&env.tables, rng);
|
||||
let table = pick(ctx.tables(), rng);
|
||||
Property::TableHasExpectedContent {
|
||||
table: table.name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property_select_limit<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Property {
|
||||
fn property_select_limit<R: rand::Rng>(rng: &mut R, ctx: &impl GenerationContext) -> Property {
|
||||
// Get a random table
|
||||
let table = pick(&env.tables, rng);
|
||||
let table = pick(ctx.tables(), rng);
|
||||
// Select the table
|
||||
let select = Select::single(
|
||||
table.name.clone(),
|
||||
vec![ResultColumn::Star],
|
||||
Predicate::arbitrary_from(rng, env, table),
|
||||
Predicate::arbitrary_from(rng, ctx, table),
|
||||
Some(rng.random_range(1..=5)),
|
||||
Distinctness::All,
|
||||
);
|
||||
@@ -1279,11 +1304,11 @@ fn property_select_limit<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Prope
|
||||
|
||||
fn property_double_create_failure<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
env: &SimulatorEnv,
|
||||
remaining: &Remaining,
|
||||
ctx: &impl GenerationContext,
|
||||
) -> Property {
|
||||
// Create the table
|
||||
let create_query = Create::arbitrary(rng, env);
|
||||
let create_query = Create::arbitrary(rng, ctx);
|
||||
let table = &create_query.table;
|
||||
|
||||
// Create random queries respecting the constraints
|
||||
@@ -1292,7 +1317,7 @@ fn property_double_create_failure<R: rand::Rng>(
|
||||
// - [x] There will be no errors in the middle interactions.(best effort)
|
||||
// - [ ] Table `t` will not be renamed or dropped.(todo: add this constraint once ALTER or DROP is implemented)
|
||||
for _ in 0..rng.random_range(0..3) {
|
||||
let query = Query::arbitrary_from(rng, env, remaining);
|
||||
let query = Query::arbitrary_from(rng, ctx, remaining);
|
||||
if let Query::Create(Create { table: t }) = &query {
|
||||
// There will be no errors in the middle interactions.
|
||||
// - Creating the same table is an error
|
||||
@@ -1311,13 +1336,13 @@ fn property_double_create_failure<R: rand::Rng>(
|
||||
|
||||
fn property_delete_select<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
env: &SimulatorEnv,
|
||||
remaining: &Remaining,
|
||||
ctx: &impl GenerationContext,
|
||||
) -> Property {
|
||||
// Get a random table
|
||||
let table = pick(&env.tables, rng);
|
||||
let table = pick(ctx.tables(), rng);
|
||||
// Generate a random predicate
|
||||
let predicate = Predicate::arbitrary_from(rng, env, table);
|
||||
let predicate = Predicate::arbitrary_from(rng, ctx, table);
|
||||
|
||||
// Create random queries respecting the constraints
|
||||
let mut queries = Vec::new();
|
||||
@@ -1325,7 +1350,7 @@ fn property_delete_select<R: rand::Rng>(
|
||||
// - [x] A row that holds for the predicate will not be inserted.
|
||||
// - [ ] The table `t` will not be renamed, dropped, or altered. (todo: add this constraint once ALTER or DROP is implemented)
|
||||
for _ in 0..rng.random_range(0..3) {
|
||||
let query = Query::arbitrary_from(rng, env, remaining);
|
||||
let query = Query::arbitrary_from(rng, ctx, remaining);
|
||||
match &query {
|
||||
Query::Insert(Insert::Values { table: t, values }) => {
|
||||
// A row that holds for the predicate will not be inserted.
|
||||
@@ -1369,18 +1394,18 @@ fn property_delete_select<R: rand::Rng>(
|
||||
|
||||
fn property_drop_select<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
env: &SimulatorEnv,
|
||||
remaining: &Remaining,
|
||||
ctx: &impl GenerationContext,
|
||||
) -> Property {
|
||||
// Get a random table
|
||||
let table = pick(&env.tables, rng);
|
||||
let table = pick(ctx.tables(), rng);
|
||||
|
||||
// Create random queries respecting the constraints
|
||||
let mut queries = Vec::new();
|
||||
// - [x] There will be no errors in the middle interactions. (this constraint is impossible to check, so this is just best effort)
|
||||
// - [-] The table `t` will not be created, no table will be renamed to `t`. (todo: update this constraint once ALTER is implemented)
|
||||
for _ in 0..rng.random_range(0..3) {
|
||||
let query = Query::arbitrary_from(rng, env, remaining);
|
||||
let query = Query::arbitrary_from(rng, ctx, remaining);
|
||||
if let Query::Create(Create { table: t }) = &query {
|
||||
// - The table `t` will not be created
|
||||
if t.name == table.name {
|
||||
@@ -1392,7 +1417,7 @@ fn property_drop_select<R: rand::Rng>(
|
||||
|
||||
let select = Select::simple(
|
||||
table.name.clone(),
|
||||
Predicate::arbitrary_from(rng, env, table),
|
||||
Predicate::arbitrary_from(rng, ctx, table),
|
||||
);
|
||||
|
||||
Property::DropSelect {
|
||||
@@ -1402,11 +1427,14 @@ fn property_drop_select<R: rand::Rng>(
|
||||
}
|
||||
}
|
||||
|
||||
fn property_select_select_optimizer<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Property {
|
||||
fn property_select_select_optimizer<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
ctx: &impl GenerationContext,
|
||||
) -> Property {
|
||||
// Get a random table
|
||||
let table = pick(&env.tables, rng);
|
||||
let table = pick(ctx.tables(), rng);
|
||||
// Generate a random predicate
|
||||
let predicate = Predicate::arbitrary_from(rng, env, table);
|
||||
let predicate = Predicate::arbitrary_from(rng, ctx, table);
|
||||
// Transform into a Binary predicate to force values to be casted to a bool
|
||||
let expr = ast::Expr::Binary(
|
||||
Box::new(predicate.0),
|
||||
@@ -1420,12 +1448,15 @@ fn property_select_select_optimizer<R: rand::Rng>(rng: &mut R, env: &SimulatorEn
|
||||
}
|
||||
}
|
||||
|
||||
fn property_where_true_false_null<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Property {
|
||||
fn property_where_true_false_null<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
ctx: &impl GenerationContext,
|
||||
) -> Property {
|
||||
// Get a random table
|
||||
let table = pick(&env.tables, rng);
|
||||
let table = pick(ctx.tables(), rng);
|
||||
// Generate a random predicate
|
||||
let p1 = Predicate::arbitrary_from(rng, env, table);
|
||||
let p2 = Predicate::arbitrary_from(rng, env, table);
|
||||
let p1 = Predicate::arbitrary_from(rng, ctx, table);
|
||||
let p2 = Predicate::arbitrary_from(rng, ctx, table);
|
||||
|
||||
// Create the select query
|
||||
let select = Select::simple(table.name.clone(), p1);
|
||||
@@ -1438,13 +1469,13 @@ fn property_where_true_false_null<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv)
|
||||
|
||||
fn property_union_all_preserves_cardinality<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
env: &SimulatorEnv,
|
||||
ctx: &impl GenerationContext,
|
||||
) -> Property {
|
||||
// Get a random table
|
||||
let table = pick(&env.tables, rng);
|
||||
let table = pick(ctx.tables(), rng);
|
||||
// Generate a random predicate
|
||||
let p1 = Predicate::arbitrary_from(rng, env, table);
|
||||
let p2 = Predicate::arbitrary_from(rng, env, table);
|
||||
let p1 = Predicate::arbitrary_from(rng, ctx, table);
|
||||
let p2 = Predicate::arbitrary_from(rng, ctx, table);
|
||||
|
||||
// Create the select query
|
||||
let select = Select::single(
|
||||
@@ -1463,34 +1494,39 @@ fn property_union_all_preserves_cardinality<R: rand::Rng>(
|
||||
|
||||
fn property_fsync_no_wait<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
env: &SimulatorEnv,
|
||||
remaining: &Remaining,
|
||||
ctx: &impl GenerationContext,
|
||||
) -> Property {
|
||||
Property::FsyncNoWait {
|
||||
query: Query::arbitrary_from(rng, env, remaining),
|
||||
tables: env.tables.iter().map(|t| t.name.clone()).collect(),
|
||||
query: Query::arbitrary_from(rng, ctx, remaining),
|
||||
tables: ctx.tables().iter().map(|t| t.name.clone()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property_faulty_query<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
env: &SimulatorEnv,
|
||||
remaining: &Remaining,
|
||||
ctx: &impl GenerationContext,
|
||||
) -> Property {
|
||||
Property::FaultyQuery {
|
||||
query: Query::arbitrary_from(rng, env, remaining),
|
||||
tables: env.tables.iter().map(|t| t.name.clone()).collect(),
|
||||
query: Query::arbitrary_from(rng, ctx, remaining),
|
||||
tables: ctx.tables().iter().map(|t| t.name.clone()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
fn arbitrary_from<R: rand::Rng, C: GenerationContext>(
|
||||
rng: &mut R,
|
||||
context: &C,
|
||||
conn_ctx: &C,
|
||||
(env, stats): (&SimulatorEnv, &InteractionStats),
|
||||
) -> Self {
|
||||
let opts = context.opts();
|
||||
let remaining_ = remaining(env.opts.max_interactions, &env.profile.query, stats);
|
||||
let opts = conn_ctx.opts();
|
||||
let remaining_ = remaining(
|
||||
env.opts.max_interactions,
|
||||
&env.profile.query,
|
||||
stats,
|
||||
env.profile.experimental_mvcc,
|
||||
);
|
||||
|
||||
frequency(
|
||||
vec![
|
||||
@@ -1500,15 +1536,17 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
} else {
|
||||
0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_insert_values_select(rng, env, &remaining_)),
|
||||
Box::new(|rng: &mut R| {
|
||||
property_insert_values_select(rng, &remaining_, conn_ctx)
|
||||
}),
|
||||
),
|
||||
(
|
||||
remaining_.select,
|
||||
Box::new(|rng: &mut R| property_table_has_expected_content(rng, env)),
|
||||
Box::new(|rng: &mut R| property_table_has_expected_content(rng, conn_ctx)),
|
||||
),
|
||||
(
|
||||
u32::min(remaining_.select, remaining_.insert),
|
||||
Box::new(|rng: &mut R| property_read_your_updates_back(rng, env)),
|
||||
Box::new(|rng: &mut R| property_read_your_updates_back(rng, conn_ctx)),
|
||||
),
|
||||
(
|
||||
if !env.opts.disable_double_create_failure {
|
||||
@@ -1516,7 +1554,9 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
} else {
|
||||
0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_double_create_failure(rng, env, &remaining_)),
|
||||
Box::new(|rng: &mut R| {
|
||||
property_double_create_failure(rng, &remaining_, conn_ctx)
|
||||
}),
|
||||
),
|
||||
(
|
||||
if !env.opts.disable_select_limit {
|
||||
@@ -1524,7 +1564,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
} else {
|
||||
0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_select_limit(rng, env)),
|
||||
Box::new(|rng: &mut R| property_select_limit(rng, conn_ctx)),
|
||||
),
|
||||
(
|
||||
if !env.opts.disable_delete_select {
|
||||
@@ -1532,7 +1572,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
} else {
|
||||
0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_delete_select(rng, env, &remaining_)),
|
||||
Box::new(|rng: &mut R| property_delete_select(rng, &remaining_, conn_ctx)),
|
||||
),
|
||||
(
|
||||
if !env.opts.disable_drop_select {
|
||||
@@ -1541,7 +1581,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
} else {
|
||||
0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_drop_select(rng, env, &remaining_)),
|
||||
Box::new(|rng: &mut R| property_drop_select(rng, &remaining_, conn_ctx)),
|
||||
),
|
||||
(
|
||||
if !env.opts.disable_select_optimizer {
|
||||
@@ -1549,7 +1589,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
} else {
|
||||
0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_select_select_optimizer(rng, env)),
|
||||
Box::new(|rng: &mut R| property_select_select_optimizer(rng, conn_ctx)),
|
||||
),
|
||||
(
|
||||
if opts.indexes && !env.opts.disable_where_true_false_null {
|
||||
@@ -1557,7 +1597,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
} else {
|
||||
0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_where_true_false_null(rng, env)),
|
||||
Box::new(|rng: &mut R| property_where_true_false_null(rng, conn_ctx)),
|
||||
),
|
||||
(
|
||||
if opts.indexes && !env.opts.disable_union_all_preserves_cardinality {
|
||||
@@ -1565,7 +1605,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
} else {
|
||||
0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_union_all_preserves_cardinality(rng, env)),
|
||||
Box::new(|rng: &mut R| property_union_all_preserves_cardinality(rng, conn_ctx)),
|
||||
),
|
||||
(
|
||||
if env.profile.io.enable && !env.opts.disable_fsync_no_wait {
|
||||
@@ -1573,7 +1613,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
} else {
|
||||
0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_fsync_no_wait(rng, env, &remaining_)),
|
||||
Box::new(|rng: &mut R| property_fsync_no_wait(rng, &remaining_, conn_ctx)),
|
||||
),
|
||||
(
|
||||
if env.profile.io.enable
|
||||
@@ -1584,7 +1624,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
} else {
|
||||
0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_faulty_query(rng, env, &remaining_)),
|
||||
Box::new(|rng: &mut R| property_faulty_query(rng, &remaining_, conn_ctx)),
|
||||
),
|
||||
],
|
||||
rng,
|
||||
|
||||
@@ -330,7 +330,7 @@ fn run_simulator(
|
||||
tracing::trace!(
|
||||
"adding bug to bugbase, seed: {}, plan: {}, error: {}",
|
||||
env.opts.seed,
|
||||
plan.plan.len(),
|
||||
plan.len(),
|
||||
error
|
||||
);
|
||||
bugbase
|
||||
@@ -361,8 +361,8 @@ fn run_simulator(
|
||||
|
||||
tracing::info!(
|
||||
"shrinking succeeded, reduced the plan from {} to {}",
|
||||
plan.plan.len(),
|
||||
final_plan.plan.len()
|
||||
plan.len(),
|
||||
final_plan.len()
|
||||
);
|
||||
// Save the shrunk database
|
||||
if let Some(bugbase) = bugbase.as_deref_mut() {
|
||||
|
||||
@@ -15,7 +15,7 @@ use sql_generation::model::{
|
||||
};
|
||||
use turso_parser::ast::Distinctness;
|
||||
|
||||
use crate::{generation::Shadow, runner::env::SimulatorTables};
|
||||
use crate::{generation::Shadow, runner::env::ShadowTablesMut};
|
||||
|
||||
// This type represents the potential queries on the database.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -83,7 +83,7 @@ impl Display for Query {
|
||||
impl Shadow for Query {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
fn shadow(&self, env: &mut ShadowTablesMut) -> Self::Result {
|
||||
match self {
|
||||
Query::Create(create) => create.shadow(env),
|
||||
Query::Insert(insert) => insert.shadow(env),
|
||||
@@ -102,7 +102,7 @@ impl Shadow for Query {
|
||||
impl Shadow for Create {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result {
|
||||
if !tables.iter().any(|t| t.name == self.table.name) {
|
||||
tables.push(self.table.clone());
|
||||
Ok(vec![])
|
||||
@@ -117,9 +117,8 @@ impl Shadow for Create {
|
||||
|
||||
impl Shadow for CreateIndex {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Vec<Vec<SimValue>> {
|
||||
env.tables
|
||||
.iter_mut()
|
||||
fn shadow(&self, env: &mut ShadowTablesMut) -> Vec<Vec<SimValue>> {
|
||||
env.iter_mut()
|
||||
.find(|t| t.name == self.table_name)
|
||||
.unwrap()
|
||||
.indexes
|
||||
@@ -131,8 +130,8 @@ impl Shadow for CreateIndex {
|
||||
impl Shadow for Delete {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
let table = tables.tables.iter_mut().find(|t| t.name == self.table);
|
||||
fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result {
|
||||
let table = tables.iter_mut().find(|t| t.name == self.table);
|
||||
|
||||
if let Some(table) = table {
|
||||
// If the table exists, we can delete from it
|
||||
@@ -153,7 +152,7 @@ impl Shadow for Delete {
|
||||
impl Shadow for Drop {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result {
|
||||
if !tables.iter().any(|t| t.name == self.table) {
|
||||
// If the table does not exist, we return an error
|
||||
return Err(anyhow::anyhow!(
|
||||
@@ -162,7 +161,7 @@ impl Shadow for Drop {
|
||||
));
|
||||
}
|
||||
|
||||
tables.tables.retain(|t| t.name != self.table);
|
||||
tables.retain(|t| t.name != self.table);
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
@@ -171,10 +170,10 @@ impl Shadow for Drop {
|
||||
impl Shadow for Insert {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result {
|
||||
match self {
|
||||
Insert::Values { table, values } => {
|
||||
if let Some(t) = tables.tables.iter_mut().find(|t| &t.name == table) {
|
||||
if let Some(t) = tables.iter_mut().find(|t| &t.name == table) {
|
||||
t.rows.extend(values.clone());
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
@@ -185,7 +184,7 @@ impl Shadow for Insert {
|
||||
}
|
||||
Insert::Select { table, select } => {
|
||||
let rows = select.shadow(tables)?;
|
||||
if let Some(t) = tables.tables.iter_mut().find(|t| &t.name == table) {
|
||||
if let Some(t) = tables.iter_mut().find(|t| &t.name == table) {
|
||||
t.rows.extend(rows);
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
@@ -202,9 +201,7 @@ impl Shadow for Insert {
|
||||
|
||||
impl Shadow for FromClause {
|
||||
type Result = anyhow::Result<JoinTable>;
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
let tables = &mut env.tables;
|
||||
|
||||
fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result {
|
||||
let first_table = tables
|
||||
.iter()
|
||||
.find(|t| t.name == self.table)
|
||||
@@ -259,7 +256,7 @@ impl Shadow for FromClause {
|
||||
impl Shadow for SelectInner {
|
||||
type Result = anyhow::Result<JoinTable>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
fn shadow(&self, env: &mut ShadowTablesMut) -> Self::Result {
|
||||
if let Some(from) = &self.from {
|
||||
let mut join_table = from.shadow(env)?;
|
||||
let col_count = join_table.columns().count();
|
||||
@@ -327,7 +324,7 @@ impl Shadow for SelectInner {
|
||||
impl Shadow for Select {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
fn shadow(&self, env: &mut ShadowTablesMut) -> Self::Result {
|
||||
let first_result = self.body.select.shadow(env)?;
|
||||
|
||||
let mut rows = first_result.rows;
|
||||
@@ -357,26 +354,26 @@ impl Shadow for Select {
|
||||
|
||||
impl Shadow for Begin {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
tables.snapshot = Some(tables.tables.clone());
|
||||
fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result {
|
||||
// FIXME: currently the snapshot is taken eagerly
|
||||
// this is wrong for Deffered transactions
|
||||
tables.create_snapshot();
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Commit {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
tables.snapshot = None;
|
||||
fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result {
|
||||
tables.apply_snapshot();
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Rollback {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
if let Some(tables_) = tables.snapshot.take() {
|
||||
tables.tables = tables_;
|
||||
}
|
||||
fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result {
|
||||
tables.delete_snapshot();
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
@@ -384,8 +381,8 @@ impl Shadow for Rollback {
|
||||
impl Shadow for Update {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
let table = tables.tables.iter_mut().find(|t| t.name == self.table);
|
||||
fn shadow(&self, tables: &mut ShadowTablesMut) -> Self::Result {
|
||||
let table = tables.iter_mut().find(|t| t.name == self.table);
|
||||
|
||||
let table = if let Some(table) = table {
|
||||
table
|
||||
|
||||
@@ -59,8 +59,8 @@ pub(crate) fn execute_interactions(
|
||||
let mut env = env.lock().unwrap();
|
||||
let mut rusqlite_env = rusqlite_env.lock().unwrap();
|
||||
|
||||
env.tables.clear();
|
||||
rusqlite_env.tables.clear();
|
||||
env.clear_tables();
|
||||
rusqlite_env.clear_tables();
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
|
||||
@@ -89,8 +89,8 @@ pub(crate) fn execute_plans(
|
||||
let mut env = env.lock().unwrap();
|
||||
let mut doublecheck_env = doublecheck_env.lock().unwrap();
|
||||
|
||||
env.tables.clear();
|
||||
doublecheck_env.tables.clear();
|
||||
env.clear_tables();
|
||||
doublecheck_env.clear_tables();
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt::Display;
|
||||
use std::mem;
|
||||
use std::ops::Deref;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::panic::UnwindSafe;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
@@ -8,6 +8,7 @@ use std::sync::Arc;
|
||||
use garde::Validate;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use sql_generation::generation::GenerationContext;
|
||||
use sql_generation::model::table::Table;
|
||||
use turso_core::Database;
|
||||
|
||||
@@ -31,6 +32,79 @@ pub(crate) enum SimulationPhase {
|
||||
Shrink,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShadowTables<'a> {
|
||||
commited_tables: &'a Vec<Table>,
|
||||
transaction_tables: Option<&'a Vec<Table>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShadowTablesMut<'a> {
|
||||
commited_tables: &'a mut Vec<Table>,
|
||||
transaction_tables: &'a mut Option<Vec<Table>>,
|
||||
}
|
||||
|
||||
impl<'a> ShadowTables<'a> {
|
||||
fn tables(&self) -> &'a Vec<Table> {
|
||||
self.transaction_tables.map_or(self.commited_tables, |v| v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for ShadowTables<'a> {
|
||||
type Target = Vec<Table>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.tables()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> ShadowTablesMut<'a>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
fn tables(&'a self) -> &'a Vec<Table> {
|
||||
self.transaction_tables
|
||||
.as_ref()
|
||||
.unwrap_or(self.commited_tables)
|
||||
}
|
||||
|
||||
fn tables_mut(&'b mut self) -> &'b mut Vec<Table> {
|
||||
self.transaction_tables
|
||||
.as_mut()
|
||||
.unwrap_or(self.commited_tables)
|
||||
}
|
||||
|
||||
pub fn create_snapshot(&mut self) {
|
||||
*self.transaction_tables = Some(self.commited_tables.clone());
|
||||
}
|
||||
|
||||
pub fn apply_snapshot(&mut self) {
|
||||
// TODO: as we do not have concurrent tranasactions yet in the simulator
|
||||
// there is no conflict we are ignoring conflict problems right now
|
||||
if let Some(transation_tables) = self.transaction_tables.take() {
|
||||
*self.commited_tables = transation_tables
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_snapshot(&mut self) {
|
||||
*self.transaction_tables = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for ShadowTablesMut<'a> {
|
||||
type Target = Vec<Table>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.tables()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for ShadowTablesMut<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.tables_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct SimulatorTables {
|
||||
pub(crate) tables: Vec<Table>,
|
||||
@@ -71,8 +145,12 @@ pub(crate) struct SimulatorEnv {
|
||||
pub(crate) paths: Paths,
|
||||
pub(crate) type_: SimulationType,
|
||||
pub(crate) phase: SimulationPhase,
|
||||
pub(crate) tables: SimulatorTables,
|
||||
pub memory_io: bool,
|
||||
|
||||
/// If connection state is None, means we are not in a transaction
|
||||
pub connection_tables: Vec<Option<Vec<Table>>>,
|
||||
// Table data that is committed into the database or wal
|
||||
pub committed_tables: Vec<Table>,
|
||||
}
|
||||
|
||||
impl UnwindSafe for SimulatorEnv {}
|
||||
@@ -81,10 +159,6 @@ impl SimulatorEnv {
|
||||
pub(crate) fn clone_without_connections(&self) -> Self {
|
||||
SimulatorEnv {
|
||||
opts: self.opts.clone(),
|
||||
tables: self.tables.clone(),
|
||||
connections: (0..self.connections.len())
|
||||
.map(|_| SimConnection::Disconnected)
|
||||
.collect(),
|
||||
io: self.io.clone(),
|
||||
db: self.db.clone(),
|
||||
rng: self.rng.clone(),
|
||||
@@ -93,11 +167,17 @@ impl SimulatorEnv {
|
||||
phase: self.phase,
|
||||
memory_io: self.memory_io,
|
||||
profile: self.profile.clone(),
|
||||
connections: (0..self.connections.len())
|
||||
.map(|_| SimConnection::Disconnected)
|
||||
.collect(),
|
||||
// TODO: not sure if connection_tables should be recreated instead
|
||||
connection_tables: self.connection_tables.clone(),
|
||||
committed_tables: self.committed_tables.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.tables.clear();
|
||||
self.clear_tables();
|
||||
self.connections.iter_mut().for_each(|c| c.disconnect());
|
||||
self.rng = ChaCha8Rng::seed_from_u64(self.opts.seed);
|
||||
|
||||
@@ -284,7 +364,6 @@ impl SimulatorEnv {
|
||||
|
||||
SimulatorEnv {
|
||||
opts,
|
||||
tables: SimulatorTables::new(),
|
||||
connections,
|
||||
paths,
|
||||
rng,
|
||||
@@ -294,6 +373,8 @@ impl SimulatorEnv {
|
||||
phase: SimulationPhase::Test,
|
||||
memory_io: cli_opts.memory_io,
|
||||
profile: profile.clone(),
|
||||
committed_tables: Vec::new(),
|
||||
connection_tables: vec![None; profile.max_connections],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,6 +408,55 @@ impl SimulatorEnv {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Clears the commited tables and the connection tables
|
||||
pub fn clear_tables(&mut self) {
|
||||
self.committed_tables.clear();
|
||||
self.connection_tables.iter_mut().for_each(|t| {
|
||||
if let Some(t) = t {
|
||||
t.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: does not yet create the appropriate context to avoid WriteWriteConflitcs
|
||||
pub fn connection_context(&self, conn_index: usize) -> impl GenerationContext {
|
||||
struct ConnectionGenContext<'a> {
|
||||
tables: &'a Vec<sql_generation::model::table::Table>,
|
||||
opts: &'a sql_generation::generation::Opts,
|
||||
}
|
||||
|
||||
impl<'a> GenerationContext for ConnectionGenContext<'a> {
|
||||
fn tables(&self) -> &Vec<sql_generation::model::table::Table> {
|
||||
self.tables
|
||||
}
|
||||
|
||||
fn opts(&self) -> &sql_generation::generation::Opts {
|
||||
self.opts
|
||||
}
|
||||
}
|
||||
|
||||
let tables = self.get_conn_tables(conn_index).tables();
|
||||
|
||||
ConnectionGenContext {
|
||||
opts: &self.profile.query.gen_opts,
|
||||
tables,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_conn_tables<'a>(&'a self, conn_index: usize) -> ShadowTables<'a> {
|
||||
ShadowTables {
|
||||
transaction_tables: self.connection_tables.get(conn_index).unwrap().as_ref(),
|
||||
commited_tables: &self.committed_tables,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_conn_tables_mut<'a>(&'a mut self, conn_index: usize) -> ShadowTablesMut<'a> {
|
||||
ShadowTablesMut {
|
||||
transaction_tables: self.connection_tables.get_mut(conn_index).unwrap(),
|
||||
commited_tables: &mut self.committed_tables,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ConnectionTrait
|
||||
|
||||
@@ -65,7 +65,7 @@ pub(crate) fn execute_interactions(
|
||||
env.clear_poison();
|
||||
let mut env = env.lock().unwrap();
|
||||
|
||||
env.tables.clear();
|
||||
env.clear_tables();
|
||||
|
||||
for _tick in 0..env.opts.ticks {
|
||||
tracing::trace!("Executing tick {}", _tick);
|
||||
@@ -186,7 +186,10 @@ pub fn execute_interaction_turso(
|
||||
tracing::error!(?results);
|
||||
}
|
||||
stack.push(results);
|
||||
limbo_integrity_check(conn)?;
|
||||
// TODO: skip integrity check with mvcc
|
||||
if !env.profile.experimental_mvcc {
|
||||
limbo_integrity_check(conn)?;
|
||||
}
|
||||
}
|
||||
InteractionType::FsyncQuery(query) => {
|
||||
let results = interaction.execute_fsync_query(conn.clone(), env);
|
||||
@@ -227,10 +230,13 @@ pub fn execute_interaction_turso(
|
||||
stack.push(results);
|
||||
// Reset fault injection
|
||||
env.io.inject_fault(false);
|
||||
limbo_integrity_check(&conn)?;
|
||||
// TODO: skip integrity check with mvcc
|
||||
if !env.profile.experimental_mvcc {
|
||||
limbo_integrity_check(&conn)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = interaction.shadow(&mut env.tables);
|
||||
let _ = interaction.shadow(&mut env.get_conn_tables_mut(interaction.connection_index));
|
||||
Ok(ExecutionContinuation::NextInteraction)
|
||||
}
|
||||
|
||||
@@ -323,7 +329,7 @@ fn execute_interaction_rusqlite(
|
||||
}
|
||||
}
|
||||
|
||||
let _ = interaction.shadow(&mut env.tables);
|
||||
let _ = interaction.shadow(&mut env.get_conn_tables_mut(interaction.connection_index));
|
||||
Ok(ExecutionContinuation::NextInteraction)
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ impl InteractionPlan {
|
||||
break;
|
||||
}
|
||||
match &all_interactions[idx].1.interaction {
|
||||
InteractionType::Query(query) => {
|
||||
InteractionType::Query(query) | InteractionType::FaultyQuery(query) => {
|
||||
depending_tables = query.dependencies();
|
||||
break;
|
||||
}
|
||||
@@ -54,77 +54,126 @@ impl InteractionPlan {
|
||||
}
|
||||
}
|
||||
|
||||
let before = self.plan.len();
|
||||
let before = self.len();
|
||||
|
||||
// Remove all properties after the failing one
|
||||
plan.plan.truncate(secondary_interactions_index + 1);
|
||||
|
||||
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 {
|
||||
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 { .. } => {}
|
||||
}
|
||||
// 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));
|
||||
}
|
||||
// Check again after query clear if the interactions still uses the failing table
|
||||
has_table = interactions
|
||||
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));
|
||||
}
|
||||
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
|
||||
});
|
||||
|
||||
let after = plan.plan.len();
|
||||
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
|
||||
});
|
||||
|
||||
// Comprise of idxs of Begin interactions
|
||||
let mut begin_idx = Vec::new();
|
||||
// Comprise of idxs of the intereactions Commit and Rollback
|
||||
let mut end_tx_idx = Vec::new();
|
||||
|
||||
for (idx, interactions) in plan.plan.iter().enumerate() {
|
||||
match &interactions.interactions {
|
||||
InteractionsType::Query(Query::Begin(..)) => {
|
||||
begin_idx.push(idx);
|
||||
}
|
||||
InteractionsType::Query(Query::Commit(..))
|
||||
| InteractionsType::Query(Query::Rollback(..)) => {
|
||||
let last_begin = begin_idx.last().unwrap() + 1;
|
||||
if last_begin == idx {
|
||||
end_tx_idx.push(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().peekable();
|
||||
let mut idx = 0;
|
||||
plan.plan.retain_mut(|_| {
|
||||
let mut retain = true;
|
||||
|
||||
if let Some(txn_interaction_idx) = range_transactions.peek().copied() {
|
||||
if txn_interaction_idx == idx {
|
||||
range_transactions.next();
|
||||
}
|
||||
if txn_interaction_idx == idx || txn_interaction_idx.saturating_sub(1) == idx {
|
||||
retain = false;
|
||||
}
|
||||
}
|
||||
idx += 1;
|
||||
retain
|
||||
});
|
||||
}
|
||||
|
||||
let after = plan.len();
|
||||
|
||||
tracing::info!(
|
||||
"Shrinking interaction plan from {} to {} properties",
|
||||
@@ -184,7 +233,7 @@ impl InteractionPlan {
|
||||
}
|
||||
}
|
||||
|
||||
let before = self.plan.len();
|
||||
let before = self.len();
|
||||
|
||||
plan.plan.truncate(secondary_interactions_index + 1);
|
||||
|
||||
@@ -196,8 +245,8 @@ impl InteractionPlan {
|
||||
| Property::DoubleCreateFailure { queries, .. }
|
||||
| Property::DeleteSelect { queries, .. }
|
||||
| Property::DropSelect { queries, .. } => {
|
||||
let mut temp_plan = InteractionPlan {
|
||||
plan: queries
|
||||
let mut temp_plan = InteractionPlan::new_with(
|
||||
queries
|
||||
.iter()
|
||||
.map(|q| {
|
||||
Interactions::new(
|
||||
@@ -206,7 +255,8 @@ impl InteractionPlan {
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
self.mvcc,
|
||||
);
|
||||
|
||||
temp_plan = InteractionPlan::iterative_shrink(
|
||||
temp_plan,
|
||||
@@ -218,7 +268,6 @@ impl InteractionPlan {
|
||||
//temp_plan = Self::shrink_queries(temp_plan, failing_execution, result, env);
|
||||
|
||||
*queries = temp_plan
|
||||
.plan
|
||||
.into_iter()
|
||||
.filter_map(|i| match i.interactions {
|
||||
InteractionsType::Query(q) => Some(q),
|
||||
@@ -247,7 +296,7 @@ impl InteractionPlan {
|
||||
secondary_interactions_index,
|
||||
);
|
||||
|
||||
let after = plan.plan.len();
|
||||
let after = plan.len();
|
||||
|
||||
tracing::info!(
|
||||
"Shrinking interaction plan from {} to {} properties",
|
||||
@@ -266,7 +315,7 @@ impl InteractionPlan {
|
||||
env: Arc<Mutex<SimulatorEnv>>,
|
||||
secondary_interaction_index: usize,
|
||||
) -> InteractionPlan {
|
||||
for i in (0..plan.plan.len()).rev() {
|
||||
for i in (0..plan.len()).rev() {
|
||||
if i == secondary_interaction_index {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ use std::fmt::Display;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Begin {
|
||||
pub immediate: bool,
|
||||
pub enum Begin {
|
||||
Deferred,
|
||||
Immediate,
|
||||
Concurrent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -15,7 +17,12 @@ pub struct Rollback;
|
||||
|
||||
impl Display for Begin {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "BEGIN {}", if self.immediate { "IMMEDIATE" } else { "" })
|
||||
let keyword = match self {
|
||||
Begin::Deferred => "",
|
||||
Begin::Immediate => "IMMEDIATE",
|
||||
Begin::Concurrent => "CONCURRENT",
|
||||
};
|
||||
write!(f, "BEGIN {keyword}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user