mirror of
https://github.com/tursodatabase/limbo.git
synced 2025-07-07 12:35:00 +00:00
add interactive transaction to property insert-values-select
This commit is contained in:
parent
c28c83fa2c
commit
897f13c173
5 changed files with 430 additions and 293 deletions
|
@ -217,20 +217,26 @@ pub(crate) struct InteractionStats {
|
|||
pub(crate) create_count: usize,
|
||||
pub(crate) create_index_count: usize,
|
||||
pub(crate) drop_count: usize,
|
||||
pub(crate) begin_count: usize,
|
||||
pub(crate) commit_count: usize,
|
||||
pub(crate) rollback_count: usize,
|
||||
}
|
||||
|
||||
impl Display for InteractionStats {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Read: {}, Write: {}, Delete: {}, Update: {}, Create: {}, CreateIndex: {}, Drop: {}",
|
||||
"Read: {}, Write: {}, Delete: {}, Update: {}, Create: {}, CreateIndex: {}, Drop: {}, Begin: {}, Commit: {}, Rollback: {}",
|
||||
self.read_count,
|
||||
self.write_count,
|
||||
self.delete_count,
|
||||
self.update_count,
|
||||
self.create_count,
|
||||
self.create_index_count,
|
||||
self.drop_count
|
||||
self.drop_count,
|
||||
self.begin_count,
|
||||
self.commit_count,
|
||||
self.rollback_count,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -307,6 +313,9 @@ impl InteractionPlan {
|
|||
let mut drop = 0;
|
||||
let mut update = 0;
|
||||
let mut create_index = 0;
|
||||
let mut begin = 0;
|
||||
let mut commit = 0;
|
||||
let mut rollback = 0;
|
||||
|
||||
for interactions in &self.plan {
|
||||
match interactions {
|
||||
|
@ -321,6 +330,9 @@ impl InteractionPlan {
|
|||
Query::Drop(_) => drop += 1,
|
||||
Query::Update(_) => update += 1,
|
||||
Query::CreateIndex(_) => create_index += 1,
|
||||
Query::Begin(_) => begin += 1,
|
||||
Query::Commit(_) => commit += 1,
|
||||
Query::Rollback(_) => rollback += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -333,6 +345,9 @@ impl InteractionPlan {
|
|||
Query::Drop(_) => drop += 1,
|
||||
Query::Update(_) => update += 1,
|
||||
Query::CreateIndex(_) => create_index += 1,
|
||||
Query::Begin(_) => begin += 1,
|
||||
Query::Commit(_) => commit += 1,
|
||||
Query::Rollback(_) => rollback += 1,
|
||||
},
|
||||
Interactions::Fault(_) => {}
|
||||
}
|
||||
|
@ -346,6 +361,9 @@ impl InteractionPlan {
|
|||
create_count: create,
|
||||
create_index_count: create_index,
|
||||
drop_count: drop,
|
||||
begin_count: begin,
|
||||
commit_count: commit,
|
||||
rollback_count: rollback,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,7 @@ use turso_sqlite3_parser::ast;
|
|||
use crate::{
|
||||
model::{
|
||||
query::{
|
||||
predicate::Predicate,
|
||||
select::{Distinctness, ResultColumn},
|
||||
Create, Delete, Drop, Insert, Query, Select,
|
||||
predicate::Predicate, select::{Distinctness, ResultColumn}, transaction::{Begin, Commit, Rollback}, Create, Delete, Drop, Insert, Query, Select
|
||||
},
|
||||
table::SimValue,
|
||||
},
|
||||
|
@ -20,6 +18,7 @@ use super::{
|
|||
ArbitraryFrom,
|
||||
};
|
||||
|
||||
|
||||
/// Properties are representations of executable specifications
|
||||
/// about the database behavior.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
@ -48,6 +47,8 @@ pub(crate) enum Property {
|
|||
queries: Vec<Query>,
|
||||
/// The select query
|
||||
select: Select,
|
||||
/// Interactive query information if any
|
||||
interactive: Option<InteractiveQueryInfo>
|
||||
},
|
||||
/// Double Create Failure is a property in which creating
|
||||
/// the same table twice leads to an error.
|
||||
|
@ -145,6 +146,12 @@ pub(crate) enum Property {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct InteractiveQueryInfo {
|
||||
start_with_immediate: bool,
|
||||
end_with_commit: bool,
|
||||
}
|
||||
|
||||
impl Property {
|
||||
pub(crate) fn name(&self) -> &str {
|
||||
match self {
|
||||
|
@ -164,299 +171,306 @@ impl Property {
|
|||
pub(crate) fn interactions(&self) -> Vec<Interaction> {
|
||||
match self {
|
||||
Property::InsertValuesSelect {
|
||||
insert,
|
||||
row_index,
|
||||
queries,
|
||||
select,
|
||||
} => {
|
||||
let (table, values) = if let Insert::Values { table, values } = insert {
|
||||
(table, values)
|
||||
} else {
|
||||
unreachable!(
|
||||
"insert query should be Insert::Values for Insert-Values-Select property"
|
||||
)
|
||||
};
|
||||
// Check that the insert query has at least 1 value
|
||||
assert!(
|
||||
!values.is_empty(),
|
||||
"insert query should have at least 1 value"
|
||||
);
|
||||
insert,
|
||||
row_index,
|
||||
queries,
|
||||
select,
|
||||
interactive
|
||||
} => {
|
||||
let (table, values) = if let Insert::Values { table, values } = insert {
|
||||
(table, values)
|
||||
} else {
|
||||
unreachable!(
|
||||
"insert query should be Insert::Values for Insert-Values-Select property"
|
||||
)
|
||||
};
|
||||
// Check that the insert query has at least 1 value
|
||||
assert!(
|
||||
!values.is_empty(),
|
||||
"insert query should have at least 1 value"
|
||||
);
|
||||
|
||||
// Pick a random row within the insert values
|
||||
let row = values[*row_index].clone();
|
||||
// Pick a random row within the insert values
|
||||
let row = values[*row_index].clone();
|
||||
|
||||
// Assume that the table exists
|
||||
let assumption = Interaction::Assumption(Assertion {
|
||||
message: format!("table {} exists", insert.table()),
|
||||
func: Box::new({
|
||||
let table_name = table.clone();
|
||||
move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
|
||||
Ok(env.tables.iter().any(|t| t.name == table_name))
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: format!(
|
||||
"row [{:?}] not found in table {}",
|
||||
row.iter().map(|v| v.to_string()).collect::<Vec<String>>(),
|
||||
insert.table(),
|
||||
),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let rows = stack.last().unwrap();
|
||||
match rows {
|
||||
Ok(rows) => Ok(rows.iter().any(|r| r == &row)),
|
||||
Err(err) => Err(LimboError::InternalError(err.to_string())),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let mut interactions = Vec::new();
|
||||
interactions.push(assumption);
|
||||
interactions.push(Interaction::Query(Query::Insert(insert.clone())));
|
||||
interactions.extend(queries.clone().into_iter().map(Interaction::Query));
|
||||
interactions.push(Interaction::Query(Query::Select(select.clone())));
|
||||
interactions.push(assertion);
|
||||
|
||||
interactions
|
||||
}
|
||||
Property::DoubleCreateFailure { create, queries } => {
|
||||
let table_name = create.table.name.clone();
|
||||
|
||||
let assumption = Interaction::Assumption(Assertion {
|
||||
message: "Double-Create-Failure should not be called on an existing table"
|
||||
.to_string(),
|
||||
func: Box::new(move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
|
||||
Ok(!env.tables.iter().any(|t| t.name == table_name))
|
||||
}),
|
||||
});
|
||||
|
||||
let cq1 = Interaction::Query(Query::Create(create.clone()));
|
||||
let cq2 = Interaction::Query(Query::Create(create.clone()));
|
||||
|
||||
let table_name = create.table.name.clone();
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message:
|
||||
"creating two tables with the name should result in a failure for the second query"
|
||||
.to_string(),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let last = stack.last().unwrap();
|
||||
match last {
|
||||
Ok(_) => Ok(false),
|
||||
Err(e) => Ok(e.to_string().to_lowercase().contains(&format!("table {table_name} already exists"))),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let mut interactions = Vec::new();
|
||||
interactions.push(assumption);
|
||||
interactions.push(cq1);
|
||||
interactions.extend(queries.clone().into_iter().map(Interaction::Query));
|
||||
interactions.push(cq2);
|
||||
interactions.push(assertion);
|
||||
|
||||
interactions
|
||||
}
|
||||
Property::SelectLimit { select } => {
|
||||
let table_name = select.table.clone();
|
||||
|
||||
let assumption = Interaction::Assumption(Assertion {
|
||||
message: format!("table {} exists", table_name),
|
||||
func: Box::new({
|
||||
let table_name = table_name.clone();
|
||||
move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
|
||||
Ok(env.tables.iter().any(|t| t.name == table_name))
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let limit = select
|
||||
.limit
|
||||
.expect("Property::SelectLimit without a LIMIT clause");
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: "select query should respect the limit clause".to_string(),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let last = stack.last().unwrap();
|
||||
match last {
|
||||
Ok(rows) => Ok(limit >= rows.len()),
|
||||
Err(_) => Ok(true),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
vec![
|
||||
assumption,
|
||||
Interaction::Query(Query::Select(select.clone())),
|
||||
assertion,
|
||||
]
|
||||
}
|
||||
Property::DeleteSelect {
|
||||
table,
|
||||
predicate,
|
||||
queries,
|
||||
} => {
|
||||
let assumption = Interaction::Assumption(Assertion {
|
||||
message: format!("table {} exists", table),
|
||||
func: Box::new({
|
||||
let table = table.clone();
|
||||
move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
|
||||
Ok(env.tables.iter().any(|t| t.name == table))
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let delete = Interaction::Query(Query::Delete(Delete {
|
||||
table: table.clone(),
|
||||
predicate: predicate.clone(),
|
||||
}));
|
||||
|
||||
let select = Interaction::Query(Query::Select(Select {
|
||||
table: table.clone(),
|
||||
result_columns: vec![ResultColumn::Star],
|
||||
predicate: predicate.clone(),
|
||||
limit: None,
|
||||
distinct: Distinctness::All,
|
||||
}));
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: format!("`{}` should return no values for table `{}`", select, table,),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let rows = stack.last().unwrap();
|
||||
match rows {
|
||||
Ok(rows) => Ok(rows.is_empty()),
|
||||
Err(err) => Err(LimboError::InternalError(err.to_string())),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let mut interactions = Vec::new();
|
||||
interactions.push(assumption);
|
||||
interactions.push(delete);
|
||||
interactions.extend(queries.clone().into_iter().map(Interaction::Query));
|
||||
interactions.push(select);
|
||||
interactions.push(assertion);
|
||||
|
||||
interactions
|
||||
}
|
||||
Property::DropSelect {
|
||||
table,
|
||||
queries,
|
||||
select,
|
||||
} => {
|
||||
let assumption = Interaction::Assumption(Assertion {
|
||||
message: format!("table {} exists", table),
|
||||
func: Box::new({
|
||||
let table = table.clone();
|
||||
move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
|
||||
Ok(env.tables.iter().any(|t| t.name == table))
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let table_name = table.clone();
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: format!(
|
||||
"select query should result in an error for table '{}'",
|
||||
table
|
||||
),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let last = stack.last().unwrap();
|
||||
match last {
|
||||
Ok(_) => Ok(false),
|
||||
Err(e) => Ok(e
|
||||
.to_string()
|
||||
.contains(&format!("Table {table_name} does not exist"))),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let drop = Interaction::Query(Query::Drop(Drop {
|
||||
table: table.clone(),
|
||||
}));
|
||||
|
||||
let select = Interaction::Query(Query::Select(select.clone()));
|
||||
|
||||
let mut interactions = Vec::new();
|
||||
|
||||
interactions.push(assumption);
|
||||
interactions.push(drop);
|
||||
interactions.extend(queries.clone().into_iter().map(Interaction::Query));
|
||||
interactions.push(select);
|
||||
interactions.push(assertion);
|
||||
|
||||
interactions
|
||||
}
|
||||
Property::SelectSelectOptimizer { table, predicate } => {
|
||||
let assumption = Interaction::Assumption(Assertion {
|
||||
message: format!("table {} exists", table),
|
||||
func: Box::new({
|
||||
let table = table.clone();
|
||||
move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
|
||||
Ok(env.tables.iter().any(|t| t.name == table))
|
||||
}
|
||||
}),
|
||||
});
|
||||
let select1 = Interaction::Query(Query::Select(Select {
|
||||
table: table.clone(),
|
||||
result_columns: vec![ResultColumn::Expr(predicate.clone())],
|
||||
predicate: Predicate::true_(),
|
||||
limit: None,
|
||||
distinct: Distinctness::All,
|
||||
}));
|
||||
|
||||
let select2_query = Query::Select(Select {
|
||||
table: table.clone(),
|
||||
result_columns: vec![ResultColumn::Star],
|
||||
predicate: predicate.clone(),
|
||||
limit: None,
|
||||
distinct: Distinctness::All,
|
||||
});
|
||||
let select2 = Interaction::Query(select2_query);
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: "select queries should return the same amount of results".to_string(),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let select_star = stack.last().unwrap();
|
||||
let select_predicate = stack.get(stack.len() - 2).unwrap();
|
||||
match (select_predicate, select_star) {
|
||||
(Ok(rows1), Ok(rows2)) => {
|
||||
// If rows1 results have more than 1 column, there is a problem
|
||||
if rows1.iter().any(|vs| vs.len() > 1) {
|
||||
return Err(LimboError::InternalError(
|
||||
"Select query without the star should return only one column".to_string(),
|
||||
));
|
||||
// Assume that the table exists
|
||||
let assumption = Interaction::Assumption(Assertion {
|
||||
message: format!("table {} exists", insert.table()),
|
||||
func: Box::new({
|
||||
let table_name = table.clone();
|
||||
move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
|
||||
Ok(env.tables.iter().any(|t| t.name == table_name))
|
||||
}
|
||||
// Count the 1s in the select query without the star
|
||||
let rows1_count = rows1
|
||||
.iter()
|
||||
.filter(|vs| {
|
||||
let v = vs.first().unwrap();
|
||||
v.as_bool()
|
||||
})
|
||||
.count();
|
||||
Ok(rows1_count == rows2.len())
|
||||
}
|
||||
_ => Ok(false),
|
||||
}
|
||||
}),
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
vec![assumption, select1, select2, assertion]
|
||||
}
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: format!(
|
||||
"row [{:?}] not found in table {}, interactive={} commit={}, rollback={}",
|
||||
row.iter().map(|v| v.to_string()).collect::<Vec<String>>(),
|
||||
insert.table(),
|
||||
interactive.is_some(),
|
||||
interactive.as_ref().map(|i| i.end_with_commit).unwrap_or(false),
|
||||
interactive.as_ref().map(|i| !i.end_with_commit).unwrap_or(false),
|
||||
),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let rows = stack.last().unwrap();
|
||||
match rows {
|
||||
Ok(rows) => {
|
||||
let found = rows.iter().any(|r| r == &row);
|
||||
Ok(found)
|
||||
},
|
||||
Err(err) => Err(LimboError::InternalError(err.to_string())),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let mut interactions = Vec::new();
|
||||
interactions.push(assumption);
|
||||
interactions.push(Interaction::Query(Query::Insert(insert.clone())));
|
||||
interactions.extend(queries.clone().into_iter().map(Interaction::Query));
|
||||
interactions.push(Interaction::Query(Query::Select(select.clone())));
|
||||
interactions.push(assertion);
|
||||
|
||||
interactions
|
||||
}
|
||||
Property::DoubleCreateFailure { create, queries } => {
|
||||
let table_name = create.table.name.clone();
|
||||
|
||||
let assumption = Interaction::Assumption(Assertion {
|
||||
message: "Double-Create-Failure should not be called on an existing table"
|
||||
.to_string(),
|
||||
func: Box::new(move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
|
||||
Ok(!env.tables.iter().any(|t| t.name == table_name))
|
||||
}),
|
||||
});
|
||||
|
||||
let cq1 = Interaction::Query(Query::Create(create.clone()));
|
||||
let cq2 = Interaction::Query(Query::Create(create.clone()));
|
||||
|
||||
let table_name = create.table.name.clone();
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message:
|
||||
"creating two tables with the name should result in a failure for the second query"
|
||||
.to_string(),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let last = stack.last().unwrap();
|
||||
match last {
|
||||
Ok(_) => Ok(false),
|
||||
Err(e) => Ok(e.to_string().to_lowercase().contains(&format!("table {table_name} already exists"))),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let mut interactions = Vec::new();
|
||||
interactions.push(assumption);
|
||||
interactions.push(cq1);
|
||||
interactions.extend(queries.clone().into_iter().map(Interaction::Query));
|
||||
interactions.push(cq2);
|
||||
interactions.push(assertion);
|
||||
|
||||
interactions
|
||||
}
|
||||
Property::SelectLimit { select } => {
|
||||
let table_name = select.table.clone();
|
||||
|
||||
let assumption = Interaction::Assumption(Assertion {
|
||||
message: format!("table {} exists", table_name),
|
||||
func: Box::new({
|
||||
let table_name = table_name.clone();
|
||||
move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
|
||||
Ok(env.tables.iter().any(|t| t.name == table_name))
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let limit = select
|
||||
.limit
|
||||
.expect("Property::SelectLimit without a LIMIT clause");
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: "select query should respect the limit clause".to_string(),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let last = stack.last().unwrap();
|
||||
match last {
|
||||
Ok(rows) => Ok(limit >= rows.len()),
|
||||
Err(_) => Ok(true),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
vec![
|
||||
assumption,
|
||||
Interaction::Query(Query::Select(select.clone())),
|
||||
assertion,
|
||||
]
|
||||
}
|
||||
Property::DeleteSelect {
|
||||
table,
|
||||
predicate,
|
||||
queries,
|
||||
} => {
|
||||
let assumption = Interaction::Assumption(Assertion {
|
||||
message: format!("table {} exists", table),
|
||||
func: Box::new({
|
||||
let table = table.clone();
|
||||
move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
|
||||
Ok(env.tables.iter().any(|t| t.name == table))
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let delete = Interaction::Query(Query::Delete(Delete {
|
||||
table: table.clone(),
|
||||
predicate: predicate.clone(),
|
||||
}));
|
||||
|
||||
let select = Interaction::Query(Query::Select(Select {
|
||||
table: table.clone(),
|
||||
result_columns: vec![ResultColumn::Star],
|
||||
predicate: predicate.clone(),
|
||||
limit: None,
|
||||
distinct: Distinctness::All,
|
||||
}));
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: format!("`{}` should return no values for table `{}`", select, table,),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let rows = stack.last().unwrap();
|
||||
match rows {
|
||||
Ok(rows) => Ok(rows.is_empty()),
|
||||
Err(err) => Err(LimboError::InternalError(err.to_string())),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let mut interactions = Vec::new();
|
||||
interactions.push(assumption);
|
||||
interactions.push(delete);
|
||||
interactions.extend(queries.clone().into_iter().map(Interaction::Query));
|
||||
interactions.push(select);
|
||||
interactions.push(assertion);
|
||||
|
||||
interactions
|
||||
}
|
||||
Property::DropSelect {
|
||||
table,
|
||||
queries,
|
||||
select,
|
||||
} => {
|
||||
let assumption = Interaction::Assumption(Assertion {
|
||||
message: format!("table {} exists", table),
|
||||
func: Box::new({
|
||||
let table = table.clone();
|
||||
move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
|
||||
Ok(env.tables.iter().any(|t| t.name == table))
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let table_name = table.clone();
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: format!(
|
||||
"select query should result in an error for table '{}'",
|
||||
table
|
||||
),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let last = stack.last().unwrap();
|
||||
match last {
|
||||
Ok(_) => Ok(false),
|
||||
Err(e) => Ok(e
|
||||
.to_string()
|
||||
.contains(&format!("Table {table_name} does not exist"))),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let drop = Interaction::Query(Query::Drop(Drop {
|
||||
table: table.clone(),
|
||||
}));
|
||||
|
||||
let select = Interaction::Query(Query::Select(select.clone()));
|
||||
|
||||
let mut interactions = Vec::new();
|
||||
|
||||
interactions.push(assumption);
|
||||
interactions.push(drop);
|
||||
interactions.extend(queries.clone().into_iter().map(Interaction::Query));
|
||||
interactions.push(select);
|
||||
interactions.push(assertion);
|
||||
|
||||
interactions
|
||||
}
|
||||
Property::SelectSelectOptimizer { table, predicate } => {
|
||||
let assumption = Interaction::Assumption(Assertion {
|
||||
message: format!("table {} exists", table),
|
||||
func: Box::new({
|
||||
let table = table.clone();
|
||||
move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
|
||||
Ok(env.tables.iter().any(|t| t.name == table))
|
||||
}
|
||||
}),
|
||||
});
|
||||
let select1 = Interaction::Query(Query::Select(Select {
|
||||
table: table.clone(),
|
||||
result_columns: vec![ResultColumn::Expr(predicate.clone())],
|
||||
predicate: Predicate::true_(),
|
||||
limit: None,
|
||||
distinct: Distinctness::All,
|
||||
}));
|
||||
|
||||
let select2_query = Query::Select(Select {
|
||||
table: table.clone(),
|
||||
result_columns: vec![ResultColumn::Star],
|
||||
predicate: predicate.clone(),
|
||||
limit: None,
|
||||
distinct: Distinctness::All,
|
||||
});
|
||||
let select2 = Interaction::Query(select2_query);
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: "select queries should return the same amount of results".to_string(),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let select_star = stack.last().unwrap();
|
||||
let select_predicate = stack.get(stack.len() - 2).unwrap();
|
||||
match (select_predicate, select_star) {
|
||||
(Ok(rows1), Ok(rows2)) => {
|
||||
// If rows1 results have more than 1 column, there is a problem
|
||||
if rows1.iter().any(|vs| vs.len() > 1) {
|
||||
return Err(LimboError::InternalError(
|
||||
"Select query without the star should return only one column".to_string(),
|
||||
));
|
||||
}
|
||||
// Count the 1s in the select query without the star
|
||||
let rows1_count = rows1
|
||||
.iter()
|
||||
.filter(|vs| {
|
||||
let v = vs.first().unwrap();
|
||||
v.as_bool()
|
||||
})
|
||||
.count();
|
||||
Ok(rows1_count == rows2.len())
|
||||
}
|
||||
_ => Ok(false),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
vec![assumption, select1, select2, assertion]
|
||||
}
|
||||
Property::FsyncNoWait { query, tables } => {
|
||||
let checks = assert_all_table_values(tables);
|
||||
Vec::from_iter(
|
||||
std::iter::once(Interaction::FsyncQuery(query.clone())).chain(checks),
|
||||
)
|
||||
}
|
||||
let checks = assert_all_table_values(tables);
|
||||
Vec::from_iter(
|
||||
std::iter::once(Interaction::FsyncQuery(query.clone())).chain(checks),
|
||||
)
|
||||
}
|
||||
Property::FaultyQuery { query, tables } => {
|
||||
let checks = assert_all_table_values(tables);
|
||||
let first = std::iter::once(Interaction::FaultyQuery(query.clone()));
|
||||
Vec::from_iter(first.chain(checks))
|
||||
}
|
||||
let checks = assert_all_table_values(tables);
|
||||
let first = std::iter::once(Interaction::FaultyQuery(query.clone()));
|
||||
Vec::from_iter(first.chain(checks))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -564,12 +578,26 @@ fn property_insert_values_select<R: rand::Rng>(
|
|||
values: rows,
|
||||
};
|
||||
|
||||
// Choose if we want queries to be executed in an interactive transaction
|
||||
let interactive = if rng.gen_bool(0.5) {
|
||||
Some(InteractiveQueryInfo {
|
||||
start_with_immediate: rng.gen_bool(0.5),
|
||||
end_with_commit: rng.gen_bool(0.5),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// 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)
|
||||
// - [x] The inserted row will not be deleted.
|
||||
// - [ ] The inserted row will not be updated. (todo: add this constraint once UPDATE is implemented)
|
||||
// - [ ] 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,
|
||||
}));
|
||||
}
|
||||
for _ in 0..rng.gen_range(0..3) {
|
||||
let query = Query::arbitrary_from(rng, (env, remaining));
|
||||
match &query {
|
||||
|
@ -593,6 +621,13 @@ fn property_insert_values_select<R: rand::Rng>(
|
|||
}
|
||||
queries.push(query);
|
||||
}
|
||||
if let Some(ref interactive) = interactive {
|
||||
queries.push(if interactive.end_with_commit {
|
||||
Query::Commit(Commit)
|
||||
} else {
|
||||
Query::Rollback(Rollback)
|
||||
});
|
||||
}
|
||||
|
||||
// Select the row
|
||||
let select_query = Select {
|
||||
|
@ -608,6 +643,7 @@ fn property_insert_values_select<R: rand::Rng>(
|
|||
row_index,
|
||||
queries,
|
||||
select: select_query,
|
||||
interactive
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -781,6 +817,7 @@ fn property_faulty_query<R: rand::Rng>(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
fn arbitrary_from<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
|
|
|
@ -10,7 +10,13 @@ use serde::{Deserialize, Serialize};
|
|||
use turso_sqlite3_parser::to_sql_string::ToSqlContext;
|
||||
use update::Update;
|
||||
|
||||
use crate::{model::table::SimValue, runner::env::SimulatorEnv};
|
||||
use crate::{
|
||||
model::{
|
||||
query::transaction::{Begin, Commit, Rollback},
|
||||
table::SimValue,
|
||||
},
|
||||
runner::env::SimulatorEnv,
|
||||
};
|
||||
|
||||
pub mod create;
|
||||
pub mod create_index;
|
||||
|
@ -19,6 +25,7 @@ pub mod drop;
|
|||
pub mod insert;
|
||||
pub mod predicate;
|
||||
pub mod select;
|
||||
pub mod transaction;
|
||||
pub mod update;
|
||||
|
||||
// This type represents the potential queries on the database.
|
||||
|
@ -31,6 +38,9 @@ pub(crate) enum Query {
|
|||
Update(Update),
|
||||
Drop(Drop),
|
||||
CreateIndex(CreateIndex),
|
||||
Begin(Begin),
|
||||
Commit(Commit),
|
||||
Rollback(Rollback),
|
||||
}
|
||||
|
||||
impl Query {
|
||||
|
@ -46,6 +56,7 @@ impl Query {
|
|||
Query::CreateIndex(CreateIndex { table_name, .. }) => {
|
||||
HashSet::from_iter([table_name.clone()])
|
||||
}
|
||||
Query::Begin(_) | Query::Commit(_) | Query::Rollback(_) => HashSet::new(),
|
||||
}
|
||||
}
|
||||
pub(crate) fn uses(&self) -> Vec<String> {
|
||||
|
@ -58,6 +69,7 @@ impl Query {
|
|||
| Query::Update(Update { table, .. })
|
||||
| Query::Drop(Drop { table, .. }) => vec![table.clone()],
|
||||
Query::CreateIndex(CreateIndex { table_name, .. }) => vec![table_name.clone()],
|
||||
Query::Begin(..) | Query::Commit(..) | Query::Rollback(..) => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,6 +82,9 @@ impl Query {
|
|||
Query::Update(update) => update.shadow(env),
|
||||
Query::Drop(drop) => drop.shadow(env),
|
||||
Query::CreateIndex(create_index) => create_index.shadow(env),
|
||||
Query::Begin(begin) => begin.shadow(env),
|
||||
Query::Commit(commit) => commit.shadow(env),
|
||||
Query::Rollback(rollback) => rollback.shadow(env),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +99,9 @@ impl Display for Query {
|
|||
Self::Update(update) => write!(f, "{}", update),
|
||||
Self::Drop(drop) => write!(f, "{}", drop),
|
||||
Self::CreateIndex(create_index) => write!(f, "{}", create_index),
|
||||
Self::Begin(begin) => write!(f, "{}", begin),
|
||||
Self::Commit(commit) => write!(f, "{}", commit),
|
||||
Self::Rollback(rollback) => write!(f, "{}", rollback),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
52
simulator/model/query/transaction.rs
Normal file
52
simulator/model/query/transaction.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{model::table::SimValue, runner::env::SimulatorEnv};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct Begin {
|
||||
pub(crate) immediate: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct Commit;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct Rollback;
|
||||
|
||||
impl Begin {
|
||||
pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Commit {
|
||||
pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Rollback {
|
||||
pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Begin {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "BEGIN {}", if self.immediate { "IMMEDIATE" } else { "" })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Commit {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "COMMIT")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Rollback {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "ROLLBACK")
|
||||
}
|
||||
}
|
|
@ -112,6 +112,18 @@ fn execute_query_rusqlite(
|
|||
connection.execute(create_index.to_string().as_str(), ())?;
|
||||
Ok(vec![])
|
||||
}
|
||||
Query::Begin(begin) => {
|
||||
connection.execute(begin.to_string().as_str(), ())?;
|
||||
Ok(vec![])
|
||||
}
|
||||
Query::Commit(commit) => {
|
||||
connection.execute(commit.to_string().as_str(), ())?;
|
||||
Ok(vec![])
|
||||
}
|
||||
Query::Rollback(rollback) => {
|
||||
connection.execute(rollback.to_string().as_str(), ())?;
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue