Merge 'simulator: add more properties and make the generated queries more complex' from Alperen Keleş

We have been working with a very small subset of SQL so far. As a rather
lightweight next phase, I propose that we make the generated queries
more realistic, slowly converging into the type definitions in
`limbo/core`. This PR will gradually implement such advances, the first
commit demonstrates how `LIMIT` is added to `SELECT`.

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #793
This commit is contained in:
Pekka Enberg
2025-02-07 13:40:23 +02:00
8 changed files with 438 additions and 146 deletions

View File

@@ -21,6 +21,8 @@ rand_chacha = "0.3.1"
log = "0.4.20"
tempfile = "3.0.7"
env_logger = "0.10.1"
regex = "1.11.1"
regex-syntax = { version = "0.8.5", default-features = false, features = ["unicode"] }
anarchist-readable-name-generator-lib = "0.1.2"
clap = { version = "4.5", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }

View File

@@ -25,6 +25,13 @@ pub trait ArbitraryFrom<T> {
fn arbitrary_from<R: Rng>(rng: &mut R, t: T) -> Self;
}
/// ArbitraryFromMaybe trait for fallibally generating random values from a given value
pub trait ArbitraryFromMaybe<T> {
fn arbitrary_from_maybe<R: Rng>(rng: &mut R, t: T) -> Option<Self>
where
Self: Sized;
}
/// Frequency is a helper function for composing different generators with different frequency
/// of occurences.
/// The type signature for the `N` parameter is a bit complex, but it
@@ -60,6 +67,36 @@ pub(crate) fn one_of<'a, T, R: Rng>(choices: Vec<Box<dyn Fn(&mut R) -> T + 'a>>,
choices[index](rng)
}
/// backtrack is a helper function for composing different "failable" generators.
/// The function takes a list of functions that return an Option<T>, along with number of retries
/// to make before giving up.
pub(crate) fn backtrack<'a, T, R: Rng>(
mut choices: Vec<(u32, Box<dyn Fn(&mut R) -> Option<T> + 'a>)>,
rng: &mut R,
) -> T {
loop {
// If there are no more choices left, we give up
let choices_ = choices
.iter()
.enumerate()
.filter(|(_, (retries, _))| *retries > 0)
.collect::<Vec<_>>();
if choices_.is_empty() {
panic!("backtrack: no more choices left");
}
// Run a one_of on the remaining choices
let (choice_index, choice) = pick(&choices_, rng);
let choice_index = *choice_index;
// If the choice returns None, we decrement the number of retries and try again
let result = choice.1(rng);
if let Some(result) = result {
return result;
} else {
choices[choice_index].0 -= 1;
}
}
}
/// pick is a helper function for uniformly picking a random element from a slice
pub(crate) fn pick<'a, T, R: Rng>(choices: &'a [T], rng: &mut R) -> &'a T {
let index = rng.gen_range(0..choices.len());

View File

@@ -14,10 +14,7 @@ use crate::{
use crate::generation::{frequency, Arbitrary, ArbitraryFrom};
use super::{
pick,
property::{remaining, Property},
};
use super::property::{remaining, Property};
pub(crate) type ResultSet = Result<Vec<Vec<Value>>>;
@@ -261,7 +258,7 @@ impl Interactions {
match self {
Interactions::Property(property) => {
match property {
Property::InsertSelect {
Property::InsertValuesSelect {
insert,
row_index: _,
queries,
@@ -282,6 +279,9 @@ impl Interactions {
query.shadow(env);
}
}
Property::SelectLimit { select } => {
select.shadow(env);
}
}
for interaction in property.interactions() {
match interaction {
@@ -292,12 +292,16 @@ impl Interactions {
}
}
Query::Insert(insert) => {
let values = match &insert {
Insert::Values { values, .. } => values.clone(),
Insert::Select { select, .. } => select.shadow(env),
};
let table = env
.tables
.iter_mut()
.find(|t| t.name == insert.table)
.find(|t| t.name == insert.table())
.unwrap();
table.rows.extend(insert.values.clone());
table.rows.extend(values);
}
Query::Delete(_) => todo!(),
Query::Select(_) => {}
@@ -308,7 +312,9 @@ impl Interactions {
}
}
}
Interactions::Query(query) => query.shadow(env),
Interactions::Query(query) => {
query.shadow(env);
}
Interactions::Fault(_) => {}
}
}
@@ -389,12 +395,10 @@ impl ArbitraryFrom<&mut SimulatorEnv> for InteractionPlan {
}
impl Interaction {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
match self {
Self::Query(query) => query.shadow(env),
Self::Assumption(_) => {}
Self::Assertion(_) => {}
Self::Fault(_) => {}
Self::Assumption(_) | Self::Assertion(_) | Self::Fault(_) => vec![],
}
}
pub(crate) fn execute_query(&self, conn: &mut Rc<Connection>) -> ResultSet {
@@ -547,12 +551,11 @@ fn create_table<R: rand::Rng>(rng: &mut R, _env: &SimulatorEnv) -> Interactions
}
fn random_read<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
Interactions::Query(Query::Select(Select::arbitrary_from(rng, &env.tables)))
Interactions::Query(Query::Select(Select::arbitrary_from(rng, env)))
}
fn random_write<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
let table = pick(&env.tables, rng);
let insert_query = Query::Insert(Insert::arbitrary_from(rng, table));
let insert_query = Query::Insert(Insert::arbitrary_from(rng, env));
Interactions::Query(insert_query)
}

View File

@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use crate::{
model::{
query::{Create, Delete, Insert, Predicate, Query, Select},
query::{Create, Delete, Distinctness, Insert, Predicate, Query, Select},
table::Value,
},
runner::env::SimulatorEnv,
@@ -34,7 +34,7 @@ pub(crate) enum Property {
/// - The inserted row will not be deleted.
/// - The inserted row will not be updated.
/// - The table `t` will not be renamed, dropped, or altered.
InsertSelect {
InsertValuesSelect {
/// The insert query
insert: Insert,
/// Selected row index
@@ -62,13 +62,25 @@ pub(crate) enum Property {
/// Additional interactions in the middle of the property
queries: Vec<Query>,
},
/// Select Limit is a property in which the select query
/// has a limit clause that is respected by the query.
/// The execution of the property is as follows
/// SELECT * FROM <t> WHERE <predicate> LIMIT <n>
/// This property is a single-interaction property.
/// The interaction has the following constraints;
/// - The select query will respect the limit clause.
SelectLimit {
/// The select query
select: Select,
},
}
impl Property {
pub(crate) fn name(&self) -> String {
match self {
Property::InsertSelect { .. } => "Insert-Select".to_string(),
Property::InsertValuesSelect { .. } => "Insert-Values-Select".to_string(),
Property::DoubleCreateFailure { .. } => "Double-Create-Failure".to_string(),
Property::SelectLimit { .. } => "Select-Limit".to_string(),
}
}
/// interactions construct a list of interactions, which is an executable representation of the property.
@@ -76,26 +88,33 @@ impl Property {
/// and `interaction` cannot be serialized directly.
pub(crate) fn interactions(&self) -> Vec<Interaction> {
match self {
Property::InsertSelect {
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!(
!insert.values.is_empty(),
!values.is_empty(),
"insert query should have at least 1 value"
);
// Pick a random row within the insert values
let row = insert.values[*row_index].clone();
let row = values[*row_index].clone();
// Assume that the table exists
let assumption = Interaction::Assumption(Assertion {
message: format!("table {} exists", insert.table),
message: format!("table {} exists", insert.table()),
func: Box::new({
let table_name = insert.table.clone();
let table_name = table.clone();
move |_: &Vec<ResultSet>, env: &SimulatorEnv| {
Ok(env.tables.iter().any(|t| t.name == table_name))
}
@@ -106,7 +125,7 @@ impl Property {
message: format!(
"row [{:?}] not found in table {}",
row.iter().map(|v| v.to_string()).collect::<Vec<String>>(),
insert.table,
insert.table(),
),
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
let rows = stack.last().unwrap();
@@ -164,10 +183,45 @@ impl Property {
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,
]
}
}
}
}
#[derive(Debug)]
pub(crate) struct Remaining {
pub(crate) read: f64,
pub(crate) write: f64,
@@ -192,7 +246,7 @@ pub(crate) fn remaining(env: &SimulatorEnv, stats: &InteractionStats) -> Remaini
}
}
fn property_insert_select<R: rand::Rng>(
fn property_insert_values_select<R: rand::Rng>(
rng: &mut R,
env: &SimulatorEnv,
remaining: &Remaining,
@@ -209,7 +263,7 @@ fn property_insert_select<R: rand::Rng>(
let row = rows[row_index].clone();
// Insert the rows
let insert_query = Insert {
let insert_query = Insert::Values {
table: table.name.clone(),
values: rows,
};
@@ -221,7 +275,7 @@ fn property_insert_select<R: rand::Rng>(
// - [ ] 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)
for _ in 0..rng.gen_range(0..3) {
let query = Query::arbitrary_from(rng, (table, remaining));
let query = Query::arbitrary_from(rng, (env, remaining));
match &query {
Query::Delete(Delete {
table: t,
@@ -248,9 +302,11 @@ fn property_insert_select<R: rand::Rng>(
let select_query = Select {
table: table.name.clone(),
predicate: Predicate::arbitrary_from(rng, (table, &row)),
limit: None,
distinct: Distinctness::All,
};
Property::InsertSelect {
Property::InsertValuesSelect {
insert: insert_query,
row_index,
queries,
@@ -258,6 +314,19 @@ fn property_insert_select<R: rand::Rng>(
}
}
fn property_select_limit<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Property {
// Get a random table
let table = pick(&env.tables, rng);
// Select the table
let select = Select {
table: table.name.clone(),
predicate: Predicate::arbitrary_from(rng, table),
limit: Some(rng.gen_range(1..=5)),
distinct: Distinctness::All,
};
Property::SelectLimit { select }
}
fn property_double_create_failure<R: rand::Rng>(
rng: &mut R,
env: &SimulatorEnv,
@@ -276,7 +345,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.gen_range(0..3) {
let query = Query::arbitrary_from(rng, (table, remaining));
let query = Query::arbitrary_from(rng, (env, remaining));
match &query {
Query::Create(Create { table: t }) => {
// There will be no errors in the middle interactions.
@@ -306,12 +375,16 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
vec![
(
f64::min(remaining_.read, remaining_.write),
Box::new(|rng: &mut R| property_insert_select(rng, env, &remaining_)),
Box::new(|rng: &mut R| property_insert_values_select(rng, env, &remaining_)),
),
(
remaining_.create / 2.0,
Box::new(|rng: &mut R| property_double_create_failure(rng, env, &remaining_)),
),
(
remaining_.read,
Box::new(|rng: &mut R| property_select_limit(rng, env)),
),
],
rng,
)

View File

@@ -1,13 +1,15 @@
use crate::generation::table::{GTValue, LTValue};
use crate::generation::{one_of, Arbitrary, ArbitraryFrom};
use crate::model::query::{Create, Delete, Insert, Predicate, Query, Select};
use crate::model::query::{Create, Delete, Distinctness, Insert, Predicate, Query, Select};
use crate::model::table::{Table, Value};
use crate::SimulatorEnv;
use rand::seq::SliceRandom as _;
use rand::Rng;
use super::property::Remaining;
use super::{frequency, pick};
use super::table::LikeValue;
use super::{backtrack, frequency, pick, ArbitraryFromMaybe};
impl Arbitrary for Create {
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
@@ -17,79 +19,85 @@ impl Arbitrary for Create {
}
}
impl ArbitraryFrom<&Vec<Table>> for Select {
fn arbitrary_from<R: Rng>(rng: &mut R, tables: &Vec<Table>) -> Self {
let table = pick(tables, rng);
impl ArbitraryFrom<&SimulatorEnv> for Select {
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
let table = pick(&env.tables, rng);
Self {
table: table.name.clone(),
predicate: Predicate::arbitrary_from(rng, table),
limit: Some(rng.gen_range(0..=1000)),
distinct: Distinctness::All,
}
}
}
impl ArbitraryFrom<&Vec<&Table>> for Select {
fn arbitrary_from<R: Rng>(rng: &mut R, tables: &Vec<&Table>) -> Self {
let table = pick(tables, rng);
Self {
table: table.name.clone(),
predicate: Predicate::arbitrary_from(rng, *table),
}
}
}
impl ArbitraryFrom<&Table> for Insert {
fn arbitrary_from<R: Rng>(rng: &mut R, table: &Table) -> Self {
let num_rows = rng.gen_range(1..10);
let values: Vec<Vec<Value>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| Value::arbitrary_from(rng, &c.column_type))
.collect()
impl ArbitraryFrom<&SimulatorEnv> for Insert {
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
let gen_values = |rng: &mut R| {
let table = pick(&env.tables, rng);
let num_rows = rng.gen_range(1..10);
let values: Vec<Vec<Value>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| Value::arbitrary_from(rng, &c.column_type))
.collect()
})
.collect();
Some(Insert::Values {
table: table.name.clone(),
values,
})
.collect();
Self {
table: table.name.clone(),
values,
}
}
}
};
impl ArbitraryFrom<&Table> for Delete {
fn arbitrary_from<R: Rng>(rng: &mut R, table: &Table) -> Self {
Self {
table: table.name.clone(),
predicate: Predicate::arbitrary_from(rng, table),
}
}
}
let _gen_select = |rng: &mut R| {
// Find a non-empty table
let table = env.tables.iter().find(|t| !t.rows.is_empty());
if table.is_none() {
return None;
}
impl ArbitraryFrom<&Table> for Query {
fn arbitrary_from<R: Rng>(rng: &mut R, table: &Table) -> Self {
frequency(
let select_table = table.unwrap();
let row = pick(&select_table.rows, rng);
let predicate = Predicate::arbitrary_from(rng, (select_table, row));
// Pick another table to insert into
let select = Select {
table: select_table.name.clone(),
predicate,
limit: None,
distinct: Distinctness::All,
};
let table = pick(&env.tables, rng);
Some(Insert::Select {
table: table.name.clone(),
select: Box::new(select),
})
};
backtrack(
vec![
(1, Box::new(|rng| Self::Create(Create::arbitrary(rng)))),
(
100,
Box::new(|rng| Self::Select(Select::arbitrary_from(rng, &vec![table]))),
),
(
100,
Box::new(|rng| Self::Insert(Insert::arbitrary_from(rng, table))),
),
(
0,
Box::new(|rng| Self::Delete(Delete::arbitrary_from(rng, table))),
),
(1, Box::new(|rng| gen_values(rng))),
// todo: test and enable this once `INSERT INTO <table> SELECT * FROM <table>` is supported
// (1, Box::new(|rng| gen_select(rng))),
],
rng,
)
}
}
impl ArbitraryFrom<(&Table, &Remaining)> for Query {
fn arbitrary_from<R: Rng>(rng: &mut R, (table, remaining): (&Table, &Remaining)) -> Self {
impl ArbitraryFrom<&SimulatorEnv> for Delete {
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
let table = pick(&env.tables, rng);
Self {
table: table.name.clone(),
predicate: Predicate::arbitrary_from(rng, table),
}
}
}
impl ArbitraryFrom<(&SimulatorEnv, &Remaining)> for Query {
fn arbitrary_from<R: Rng>(rng: &mut R, (env, remaining): (&SimulatorEnv, &Remaining)) -> Self {
frequency(
vec![
(
@@ -98,15 +106,15 @@ impl ArbitraryFrom<(&Table, &Remaining)> for Query {
),
(
remaining.read,
Box::new(|rng| Self::Select(Select::arbitrary_from(rng, &vec![table]))),
Box::new(|rng| Self::Select(Select::arbitrary_from(rng, env))),
),
(
remaining.write,
Box::new(|rng| Self::Insert(Insert::arbitrary_from(rng, table))),
Box::new(|rng| Self::Insert(Insert::arbitrary_from(rng, env))),
),
(
0.0,
Box::new(|rng| Self::Delete(Delete::arbitrary_from(rng, table))),
Box::new(|rng| Self::Delete(Delete::arbitrary_from(rng, env))),
),
],
rng,
@@ -280,24 +288,48 @@ fn produce_true_predicate<R: Rng>(rng: &mut R, (t, row): (&Table, &Vec<Value>))
let column_index = rng.gen_range(0..t.columns.len());
let column = &t.columns[column_index];
let value = &row[column_index];
one_of(
backtrack(
vec![
Box::new(|_| Predicate::Eq(column.name.clone(), value.clone())),
Box::new(|rng| {
let v = loop {
(
1,
Box::new(|_| Some(Predicate::Eq(column.name.clone(), value.clone()))),
),
(
1,
Box::new(|rng| {
let v = Value::arbitrary_from(rng, &column.column_type);
if &v != value {
break v;
if &v == value {
None
} else {
Some(Predicate::Neq(column.name.clone(), v))
}
};
Predicate::Neq(column.name.clone(), v)
}),
Box::new(|rng| {
Predicate::Gt(column.name.clone(), LTValue::arbitrary_from(rng, value).0)
}),
Box::new(|rng| {
Predicate::Lt(column.name.clone(), GTValue::arbitrary_from(rng, value).0)
}),
}),
),
(
1,
Box::new(|rng| {
Some(Predicate::Gt(
column.name.clone(),
LTValue::arbitrary_from(rng, value).0,
))
}),
),
(
1,
Box::new(|rng| {
Some(Predicate::Lt(
column.name.clone(),
GTValue::arbitrary_from(rng, value).0,
))
}),
),
(
1,
Box::new(|rng| {
LikeValue::arbitrary_from_maybe(rng, value)
.map(|like| Predicate::Like(column.name.clone(), like.0))
}),
),
],
rng,
)

View File

@@ -3,6 +3,8 @@ use rand::Rng;
use crate::generation::{gen_random_text, pick, readable_name_custom, Arbitrary, ArbitraryFrom};
use crate::model::table::{Column, ColumnType, Name, Table, Value};
use super::ArbitraryFromMaybe;
impl Arbitrary for Name {
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
let name = readable_name_custom("_", rng);
@@ -194,3 +196,34 @@ impl ArbitraryFrom<&Value> for GTValue {
}
}
}
pub(crate) struct LikeValue(pub(crate) String);
impl ArbitraryFromMaybe<&Value> for LikeValue {
fn arbitrary_from_maybe<R: Rng>(rng: &mut R, value: &Value) -> Option<Self> {
match value {
Value::Text(t) => {
let mut t = t.chars().collect::<Vec<_>>();
// Remove a number of characters, either insert `_` for each character removed, or
// insert one `%` for the whole substring
let mut i = 0;
while i < t.len() {
if rng.gen_bool(0.1) {
t[i] = '_';
} else if rng.gen_bool(0.05) {
t[i] = '%';
// skip a list of characters
for _ in 0..rng.gen_range(0..=3.min(t.len() - i - 1)) {
t.remove(i + 1);
}
}
i += 1;
}
let index = rng.gen_range(0..t.len());
t.insert(index, '%');
Some(Self(t.into_iter().collect()))
}
_ => None,
}
}
}

View File

@@ -1,5 +1,6 @@
use std::fmt::Display;
use regex::{Regex, RegexBuilder};
use serde::{Deserialize, Serialize};
use crate::{
@@ -9,12 +10,48 @@ use crate::{
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub(crate) enum Predicate {
And(Vec<Predicate>), // p1 AND p2 AND p3... AND pn
Or(Vec<Predicate>), // p1 OR p2 OR p3... OR pn
Eq(String, Value), // column = Value
Neq(String, Value), // column != Value
Gt(String, Value), // column > Value
Lt(String, Value), // column < Value
And(Vec<Predicate>), // p1 AND p2 AND p3... AND pn
Or(Vec<Predicate>), // p1 OR p2 OR p3... OR pn
Eq(String, Value), // column = Value
Neq(String, Value), // column != Value
Gt(String, Value), // column > Value
Lt(String, Value), // column < Value
Like(String, String), // column LIKE Value
}
/// This function is a duplication of the exec_like function in core/vdbe/mod.rs at commit 9b9d5f9b4c9920e066ef1237c80878f4c3968524
/// Any updates to the original function should be reflected here, otherwise the test will be incorrect.
fn construct_like_regex(pattern: &str) -> Regex {
let mut regex_pattern = String::with_capacity(pattern.len() * 2);
regex_pattern.push('^');
for c in pattern.chars() {
match c {
'\\' => regex_pattern.push_str("\\\\"),
'%' => regex_pattern.push_str(".*"),
'_' => regex_pattern.push('.'),
ch => {
if regex_syntax::is_meta_character(c) {
regex_pattern.push('\\');
}
regex_pattern.push(ch);
}
}
}
regex_pattern.push('$');
RegexBuilder::new(&regex_pattern)
.case_insensitive(true)
.dot_matches_new_line(true)
.build()
.unwrap()
}
fn exec_like(pattern: &str, text: &str) -> bool {
let re = construct_like_regex(pattern);
re.is_match(text)
}
impl Predicate {
@@ -43,6 +80,9 @@ impl Predicate {
Predicate::Neq(column, value) => get_value(column) != Some(value),
Predicate::Gt(column, value) => get_value(column).map(|v| v > value).unwrap_or(false),
Predicate::Lt(column, value) => get_value(column).map(|v| v < value).unwrap_or(false),
Predicate::Like(column, value) => get_value(column)
.map(|v| exec_like(v.to_string().as_str(), value.as_str()))
.unwrap_or(false),
}
}
}
@@ -83,6 +123,7 @@ impl Display for Predicate {
Self::Neq(name, value) => write!(f, "{} != {}", name, value),
Self::Gt(name, value) => write!(f, "{} > {}", name, value),
Self::Lt(name, value) => write!(f, "{} < {}", name, value),
Self::Like(name, value) => write!(f, "{} LIKE '{}'", name, value),
}
}
}
@@ -101,7 +142,8 @@ impl Query {
match self {
Query::Create(_) => vec![],
Query::Select(Select { table, .. })
| Query::Insert(Insert { table, .. })
| Query::Insert(Insert::Select { table, .. })
| Query::Insert(Insert::Values { table, .. })
| Query::Delete(Delete { table, .. }) => vec![table.clone()],
}
}
@@ -109,12 +151,13 @@ impl Query {
match self {
Query::Create(Create { table }) => vec![table.name.clone()],
Query::Select(Select { table, .. })
| Query::Insert(Insert { table, .. })
| Query::Insert(Insert::Select { table, .. })
| Query::Insert(Insert::Values { table, .. })
| Query::Delete(Delete { table, .. }) => vec![table.clone()],
}
}
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
match self {
Query::Create(create) => create.shadow(env),
Query::Insert(insert) => insert.shadow(env),
@@ -129,33 +172,110 @@ pub(crate) struct Create {
}
impl Create {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
if !env.tables.iter().any(|t| t.name == self.table.name) {
env.tables.push(self.table.clone());
}
vec![]
}
}
impl Display for Create {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CREATE TABLE {} (", self.table.name)?;
for (i, column) in self.table.columns.iter().enumerate() {
if i != 0 {
write!(f, ",")?;
}
write!(f, "{} {}", column.name, column.column_type)?;
}
write!(f, ")")
}
}
/// `SELECT` distinctness
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Distinctness {
/// `DISTINCT`
Distinct,
/// `ALL`
All,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub(crate) struct Select {
pub(crate) table: String,
pub(crate) predicate: Predicate,
pub(crate) distinct: Distinctness,
pub(crate) limit: Option<usize>,
}
impl Select {
pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) {}
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
let table = env.tables.iter().find(|t| t.name == self.table.as_str());
if let Some(table) = table {
table
.rows
.iter()
.filter(|row| self.predicate.test(row, table))
.cloned()
.collect()
} else {
vec![]
}
}
}
impl Display for Select {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"SELECT * FROM {} WHERE {}{}",
self.table,
self.predicate,
self.limit
.map_or("".to_string(), |l| format!(" LIMIT {}", l))
)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub(crate) struct Insert {
pub(crate) table: String,
pub(crate) values: Vec<Vec<Value>>,
pub(crate) enum Insert {
Values {
table: String,
values: Vec<Vec<Value>>,
},
Select {
table: String,
select: Box<Select>,
},
}
impl Insert {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) {
if let Some(t) = env.tables.iter_mut().find(|t| t.name == self.table) {
t.rows.extend(self.values.clone());
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
match self {
Insert::Values { table, values } => {
if let Some(t) = env.tables.iter_mut().find(|t| &t.name == table) {
t.rows.extend(values.clone());
}
}
Insert::Select { table, select } => {
let rows = select.shadow(env);
if let Some(t) = env.tables.iter_mut().find(|t| &t.name == table) {
t.rows.extend(rows);
}
}
}
vec![]
}
pub(crate) fn table(&self) -> &str {
match self {
Insert::Values { table, .. } | Insert::Select { table, .. } => table,
}
}
}
@@ -167,7 +287,7 @@ pub(crate) struct Delete {
}
impl Delete {
pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) {
pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
todo!()
}
}
@@ -175,23 +295,9 @@ impl Delete {
impl Display for Query {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Create(Create { table }) => {
write!(f, "CREATE TABLE {} (", table.name)?;
for (i, column) in table.columns.iter().enumerate() {
if i != 0 {
write!(f, ",")?;
}
write!(f, "{} {}", column.name, column.column_type)?;
}
write!(f, ")")
}
Self::Select(Select {
table,
predicate: guard,
}) => write!(f, "SELECT * FROM {} WHERE {}", table, guard),
Self::Insert(Insert { table, values }) => {
Self::Create(create) => write!(f, "{}", create),
Self::Select(select) => write!(f, "{}", select),
Self::Insert(Insert::Values { table, values }) => {
write!(f, "INSERT INTO {} VALUES ", table)?;
for (i, row) in values.iter().enumerate() {
if i != 0 {
@@ -208,6 +314,10 @@ impl Display for Query {
}
Ok(())
}
Self::Insert(Insert::Select { table, select }) => {
write!(f, "INSERT INTO {} ", table)?;
write!(f, "{}", select)
}
Self::Delete(Delete {
table,
predicate: guard,

View File

@@ -1,5 +1,8 @@
use crate::{
generation::plan::{InteractionPlan, Interactions},
generation::{
plan::{InteractionPlan, Interactions},
property::Property,
},
model::query::Query,
runner::execution::Execution,
};
@@ -27,12 +30,11 @@ impl InteractionPlan {
for interaction in plan.plan.iter_mut() {
if let Interactions::Property(p) = interaction {
match p {
crate::generation::property::Property::InsertSelect { queries, .. }
| crate::generation::property::Property::DoubleCreateFailure {
queries, ..
} => {
Property::InsertValuesSelect { queries, .. }
| Property::DoubleCreateFailure { queries, .. } => {
queries.clear();
}
Property::SelectLimit { .. } => {}
}
}
}