add insert into <table> <select> generation

This commit is contained in:
alpaylan 2025-02-01 10:40:07 -05:00
parent f5139f086e
commit 48d091e112
6 changed files with 242 additions and 136 deletions

View file

@ -1,4 +1,7 @@
use std::{iter::Sum, ops::SubAssign};
use std::{
iter::Sum,
ops::SubAssign,
};
use anarchist_readable_name_generator_lib::readable_name_custom;
use rand::{distributions::uniform::SampleUniform, Rng};
@ -60,6 +63,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,
@ -284,7 +281,7 @@ impl Interactions {
}
Property::SelectLimit { select } => {
select.shadow(env);
},
}
}
for interaction in property.interactions() {
match interaction {
@ -295,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(_) => {}
@ -311,7 +312,9 @@ impl Interactions {
}
}
}
Interactions::Query(query) => query.shadow(env),
Interactions::Query(query) => {
query.shadow(env);
}
Interactions::Fault(_) => {}
}
}
@ -392,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 {
@ -548,12 +549,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
@ -78,7 +78,7 @@ pub(crate) enum Property {
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(),
}
@ -88,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))
}
@ -118,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();
@ -189,7 +196,7 @@ impl Property {
}),
});
let limit = select.limit.clone().unwrap_or(0);
let limit = select.limit.unwrap_or(0);
let assertion = Interaction::Assertion(Assertion {
message: "select query should respect the limit clause".to_string(),
@ -212,6 +219,7 @@ impl Property {
}
}
#[derive(Debug)]
pub(crate) struct Remaining {
pub(crate) read: f64,
pub(crate) write: f64,
@ -236,7 +244,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,
@ -253,7 +261,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,
};
@ -265,7 +273,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,
@ -293,9 +301,10 @@ fn property_insert_select<R: rand::Rng>(
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,
@ -303,10 +312,7 @@ fn property_insert_select<R: rand::Rng>(
}
}
fn property_select_limit<R: rand::Rng>(
rng: &mut R,
env: &SimulatorEnv,
) -> Property {
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
@ -314,6 +320,7 @@ fn property_select_limit<R: rand::Rng>(
table: table.name.clone(),
predicate: Predicate::arbitrary_from(rng, table),
limit: Some(rng.gen_range(1..=5)),
distinct: Distinctness::All,
};
Property::SelectLimit { select }
}
@ -336,7 +343,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.
@ -366,7 +373,7 @@ 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,

View file

@ -1,13 +1,14 @@
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::{backtrack, frequency, pick};
impl Arbitrary for Create {
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
@ -17,81 +18,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),
limit: Some(rng.gen_range(0..=1000)),
}
}
}
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![
(
@ -100,15 +105,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,

View file

@ -101,7 +101,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 +110,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,34 +131,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,
}
}
}
@ -168,7 +246,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!()
}
}
@ -176,30 +254,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,
limit,
}) => write!(
f,
"SELECT * FROM {} WHERE {}{}",
table,
guard,
limit.map_or("".to_string(), |l| format!(" LIMIT {}", l))
),
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 {
@ -216,6 +273,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

@ -30,7 +30,7 @@ impl InteractionPlan {
for interaction in plan.plan.iter_mut() {
if let Interactions::Property(p) = interaction {
match p {
Property::InsertSelect { queries, .. }
Property::InsertValuesSelect { queries, .. }
| Property::DoubleCreateFailure { queries, .. } => {
queries.clear();
}