mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-09 10:14:21 +01:00
generate joins and unions
This commit is contained in:
@@ -3,7 +3,7 @@ use std::{iter::Sum, ops::SubAssign};
|
||||
use anarchist_readable_name_generator_lib::readable_name_custom;
|
||||
use rand::{distributions::uniform::SampleUniform, Rng};
|
||||
|
||||
use crate::runner::env::SimulatorEnv;
|
||||
use crate::model::table::Table;
|
||||
|
||||
mod expr;
|
||||
pub mod plan;
|
||||
@@ -50,7 +50,7 @@ pub trait ArbitraryFromMaybe<T> {
|
||||
/// might return a vector of rows that were inserted into the table.
|
||||
pub(crate) trait Shadow {
|
||||
type Result;
|
||||
fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result;
|
||||
fn shadow(&self, tables: &mut Vec<Table>) -> Self::Result;
|
||||
}
|
||||
|
||||
/// Frequency is a helper function for composing different generators with different frequency
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::{
|
||||
generation::Shadow,
|
||||
model::{
|
||||
query::{update::Update, Create, CreateIndex, Delete, Drop, Insert, Query, Select},
|
||||
table::SimValue,
|
||||
table::{SimValue, Table},
|
||||
},
|
||||
runner::{env::SimConnection, io::SimulatorIO},
|
||||
SimulatorEnv,
|
||||
@@ -113,17 +113,17 @@ pub(crate) enum Interactions {
|
||||
impl Shadow for Interactions {
|
||||
type Result = ();
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorEnv) {
|
||||
fn shadow(&self, tables: &mut Vec<Table>) {
|
||||
match self {
|
||||
Interactions::Property(property) => {
|
||||
let initial_tables = env.tables.clone();
|
||||
let initial_tables = tables.clone();
|
||||
let mut is_error = false;
|
||||
for interaction in property.interactions() {
|
||||
match interaction {
|
||||
Interaction::Query(query)
|
||||
| Interaction::FsyncQuery(query)
|
||||
| Interaction::FaultyQuery(query) => {
|
||||
is_error = is_error || query.shadow(env).is_err();
|
||||
is_error = is_error || query.shadow(tables).is_err();
|
||||
}
|
||||
Interaction::Assertion(_) => {}
|
||||
Interaction::Assumption(_) => {}
|
||||
@@ -131,13 +131,13 @@ impl Shadow for Interactions {
|
||||
}
|
||||
if is_error {
|
||||
// If any interaction fails, we reset the tables to the initial state
|
||||
env.tables = initial_tables.clone();
|
||||
*tables = initial_tables.clone();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Interactions::Query(query) => {
|
||||
query.shadow(env);
|
||||
query.shadow(tables);
|
||||
}
|
||||
Interactions::Fault(_) => {}
|
||||
}
|
||||
@@ -400,7 +400,7 @@ impl ArbitraryFrom<&mut SimulatorEnv> for InteractionPlan {
|
||||
num_interactions
|
||||
);
|
||||
let interactions = Interactions::arbitrary_from(rng, (env, plan.stats()));
|
||||
interactions.shadow(env);
|
||||
interactions.shadow(&mut env.tables);
|
||||
// println!(
|
||||
// "Generated interactions: {}",
|
||||
// interactions
|
||||
@@ -431,7 +431,7 @@ impl ArbitraryFrom<&mut SimulatorEnv> for InteractionPlan {
|
||||
|
||||
impl Shadow for Interaction {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result {
|
||||
fn shadow(&self, env: &mut Vec<Table>) -> Self::Result {
|
||||
match self {
|
||||
Self::Query(query) => query.shadow(env),
|
||||
Self::FsyncQuery(query) => {
|
||||
@@ -722,7 +722,7 @@ fn random_create<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)))
|
||||
Interactions::Query(Query::Select(Select::arbitrary_from(rng, &env.tables)))
|
||||
}
|
||||
|
||||
fn random_write<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
|
||||
|
||||
@@ -57,8 +57,18 @@ impl Predicate {
|
||||
pub fn true_binary<R: rand::Rng>(rng: &mut R, t: &Table, row: &[SimValue]) -> Predicate {
|
||||
// Pick a column
|
||||
let column_index = rng.gen_range(0..t.columns.len());
|
||||
let column = &t.columns[column_index];
|
||||
let mut column = t.columns[column_index].clone();
|
||||
let value = &row[column_index];
|
||||
|
||||
let mut table_name = t.name.clone();
|
||||
if t.name.is_empty() {
|
||||
// If the table name is empty, we cannot create a qualified expression
|
||||
// so we use the column name directly
|
||||
let mut splitted = column.name.split('.');
|
||||
table_name = splitted.next().expect("Column name should have a table prefix for a joined table").to_string();
|
||||
column.name = splitted.next().expect("Column name should have a column suffix for a joined table").to_string();
|
||||
}
|
||||
|
||||
let expr = backtrack(
|
||||
vec![
|
||||
(
|
||||
@@ -66,7 +76,7 @@ impl Predicate {
|
||||
Box::new(|_| {
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
@@ -83,7 +93,7 @@ impl Predicate {
|
||||
} else {
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
@@ -98,7 +108,7 @@ impl Predicate {
|
||||
let lt_value = LTValue::arbitrary_from(rng, value).0;
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
@@ -112,7 +122,7 @@ impl Predicate {
|
||||
let gt_value = GTValue::arbitrary_from(rng, value).0;
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
@@ -127,7 +137,7 @@ impl Predicate {
|
||||
LikeValue::arbitrary_from_maybe(rng, value).map(|like| {
|
||||
Expr::Like {
|
||||
lhs: Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
not: false, // TODO: also generate this value eventually
|
||||
@@ -149,14 +159,24 @@ impl Predicate {
|
||||
pub fn false_binary<R: rand::Rng>(rng: &mut R, t: &Table, row: &[SimValue]) -> Predicate {
|
||||
// Pick a column
|
||||
let column_index = rng.gen_range(0..t.columns.len());
|
||||
let column = &t.columns[column_index];
|
||||
let mut column = t.columns[column_index].clone();
|
||||
let mut table_name = t.name.clone();
|
||||
let value = &row[column_index];
|
||||
|
||||
if t.name.is_empty() {
|
||||
// If the table name is empty, we cannot create a qualified expression
|
||||
// so we use the column name directly
|
||||
let mut splitted = column.name.split('.');
|
||||
table_name = splitted.next().expect("Column name should have a table prefix for a joined table").to_string();
|
||||
column.name = splitted.next().expect("Column name should have a column suffix for a joined table").to_string();
|
||||
}
|
||||
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_| {
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
@@ -172,7 +192,7 @@ impl Predicate {
|
||||
};
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
@@ -183,7 +203,7 @@ impl Predicate {
|
||||
let gt_value = GTValue::arbitrary_from(rng, value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
@@ -194,7 +214,7 @@ impl Predicate {
|
||||
let lt_value = LTValue::arbitrary_from(rng, value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
@@ -213,18 +233,28 @@ impl SimplePredicate {
|
||||
pub fn true_binary<R: rand::Rng>(rng: &mut R, table: &Table, row: &[SimValue]) -> Self {
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..table.columns.len());
|
||||
let column = &table.columns[column_index];
|
||||
let mut column = table.columns[column_index].clone();
|
||||
let column_value = &row[column_index];
|
||||
let mut table_name = table.name.clone();
|
||||
// Avoid creation of NULLs
|
||||
if row.is_empty() {
|
||||
return SimplePredicate(Predicate(Expr::Literal(SimValue::TRUE.into())));
|
||||
}
|
||||
|
||||
if table.name.is_empty() {
|
||||
// If the table name is empty, we cannot create a qualified expression
|
||||
// so we use the column name directly
|
||||
let mut splitted = column.name.split('.');
|
||||
table_name = splitted.next().expect("Column name should have a table prefix for a joined table").to_string();
|
||||
column.name = splitted.next().expect("Column name should have a column suffix for a joined table").to_string();
|
||||
}
|
||||
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_rng| {
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(table.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
@@ -235,7 +265,7 @@ impl SimplePredicate {
|
||||
let lt_value = LTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name(table.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
@@ -246,7 +276,7 @@ impl SimplePredicate {
|
||||
let gt_value = GTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name(table.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
@@ -263,18 +293,31 @@ impl SimplePredicate {
|
||||
pub fn false_binary<R: rand::Rng>(rng: &mut R, table: &Table, row: &[SimValue]) -> Self {
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..table.columns.len());
|
||||
let column = &table.columns[column_index];
|
||||
// println!("column_index: {}", column_index);
|
||||
// println!("table.columns: {:?}", table.columns);
|
||||
// println!("row: {:?}", row);
|
||||
let mut column = table.columns[column_index].clone();
|
||||
let column_value = &row[column_index];
|
||||
let mut table_name = table.name.clone();
|
||||
// Avoid creation of NULLs
|
||||
if row.is_empty() {
|
||||
return SimplePredicate(Predicate(Expr::Literal(SimValue::FALSE.into())));
|
||||
}
|
||||
|
||||
if table.name.is_empty() {
|
||||
// If the table name is empty, we cannot create a qualified expression
|
||||
// so we use the column name directly
|
||||
let mut splitted = column.name.split('.');
|
||||
table_name = splitted.next().expect("Column name should have a table prefix for a joined table").to_string();
|
||||
column.name = splitted.next().expect("Column name should have a column suffix for a joined table").to_string();
|
||||
}
|
||||
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_rng| {
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name(table.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
@@ -285,7 +328,7 @@ impl SimplePredicate {
|
||||
let gt_value = GTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(table.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
@@ -296,7 +339,7 @@ impl SimplePredicate {
|
||||
let lt_value = LTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(table.name.clone()),
|
||||
ast::Name(table_name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use crate::generation::{Arbitrary, ArbitraryFrom};
|
||||
use crate::generation::{Arbitrary, ArbitraryFrom, Shadow};
|
||||
use crate::model::query::predicate::Predicate;
|
||||
use crate::model::query::select::{Distinctness, ResultColumn};
|
||||
use crate::model::query::select::{
|
||||
CompoundOperator, CompoundSelect, Distinctness, FromClause, JoinTable, JoinType, JoinedTable, ResultColumn, SelectBody, SelectInner
|
||||
};
|
||||
use crate::model::query::update::Update;
|
||||
use crate::model::query::{Create, Delete, Drop, Insert, Query, Select};
|
||||
use crate::model::table::{SimValue, Table};
|
||||
use crate::SimulatorEnv;
|
||||
use itertools::Itertools;
|
||||
use rand::Rng;
|
||||
|
||||
use super::property::Remaining;
|
||||
@@ -18,16 +21,128 @@ impl Arbitrary for Create {
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for Select {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
|
||||
let table = pick(&env.tables, rng);
|
||||
Self::single(
|
||||
table.name.clone(),
|
||||
vec![ResultColumn::Star],
|
||||
Predicate::arbitrary_from(rng, table),
|
||||
Some(rng.gen_range(0..=1000)),
|
||||
Distinctness::All,
|
||||
)
|
||||
impl ArbitraryFrom<&Vec<Table>> for FromClause {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, tables: &Vec<Table>) -> Self {
|
||||
match rng.gen_range(5..=5) {
|
||||
0..=4 => FromClause {
|
||||
table: pick(tables, rng).name.clone(),
|
||||
joins: vec![],
|
||||
},
|
||||
_ => {
|
||||
let mut tables = tables.clone();
|
||||
let mut table = pick(&tables, rng).clone();
|
||||
tables.retain(|t| t.name != table.name);
|
||||
let name = table.name.clone();
|
||||
let num_joins = rng.gen_range(0..=3.min(tables.len()));
|
||||
|
||||
let joins: Vec<_> = (0..num_joins)
|
||||
.map(|_| {
|
||||
let join_table = pick(&tables, rng).clone();
|
||||
tables.retain(|t| t.name != join_table.name);
|
||||
table = JoinTable {
|
||||
tables: vec![table.clone(), join_table.clone()],
|
||||
rows: table.rows.iter().cartesian_product(join_table.rows.iter()).map(|(t_row, j_row)| {
|
||||
let mut row = t_row.clone();
|
||||
row.extend(j_row.clone());
|
||||
row
|
||||
}).collect(),
|
||||
}.into_table();
|
||||
for row in &mut table.rows {
|
||||
assert_eq!(row.len(), table.columns.len(), "Row length does not match column length after join");
|
||||
}
|
||||
|
||||
let predicate = Predicate::arbitrary_from(rng, &table);
|
||||
JoinedTable {
|
||||
table: join_table.name.clone(),
|
||||
join_type: JoinType::Inner,
|
||||
on: predicate,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
FromClause {
|
||||
table: name,
|
||||
joins,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&Vec<Table>> for SelectInner {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, tables: &Vec<Table>) -> Self {
|
||||
let from = FromClause::arbitrary_from(rng, tables);
|
||||
let mut tables = tables.clone();
|
||||
// todo: this is a temporary hack because env is not separated from the tables
|
||||
let join_table = from.shadow(&mut tables).expect("Failed to shadow FromClause").into_table();
|
||||
|
||||
SelectInner {
|
||||
distinctness: Distinctness::arbitrary(rng),
|
||||
columns: vec![ResultColumn::Star],
|
||||
from,
|
||||
where_clause: Predicate::arbitrary_from(rng, &join_table),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for Distinctness {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
|
||||
match rng.gen_range(0..=5) {
|
||||
0..4 => Distinctness::All,
|
||||
_ => Distinctness::Distinct,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Arbitrary for CompoundOperator {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
|
||||
match rng.gen_range(0..=1) {
|
||||
0 => CompoundOperator::Union,
|
||||
1 => CompoundOperator::UnionAll,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&Vec<Table>> for Select {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, tables: &Vec<Table>) -> Self {
|
||||
let table = pick(tables, rng);
|
||||
|
||||
match rng.gen_range(5..=5) {
|
||||
// Generate a simple select in the form of `SELECT * FROM <table>`
|
||||
0..=4 => Self::single(
|
||||
table.name.clone(),
|
||||
vec![ResultColumn::Star],
|
||||
Predicate::arbitrary_from(rng, table),
|
||||
Some(rng.gen_range(0..=1000)),
|
||||
Distinctness::All,
|
||||
),
|
||||
// Generate a compound select with some unions
|
||||
_ => {
|
||||
let num_selects = rng.gen_range(1..=3);
|
||||
let first = SelectInner::arbitrary_from(rng, tables);
|
||||
|
||||
let rest: Vec<SelectInner> = (0..num_selects)
|
||||
.map(|_| {
|
||||
let mut select = first.clone();
|
||||
select.where_clause = Predicate::arbitrary_from(rng, table);
|
||||
select
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
body: SelectBody {
|
||||
select: Box::new(first),
|
||||
compounds: rest
|
||||
.into_iter()
|
||||
.map(|s| CompoundSelect {
|
||||
operator: CompoundOperator::arbitrary(rng),
|
||||
select: Box::new(s),
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
limit: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +172,7 @@ impl ArbitraryFrom<&SimulatorEnv> for Insert {
|
||||
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::simple(
|
||||
select_table.name.clone(),
|
||||
predicate,
|
||||
);
|
||||
let select = Select::simple(select_table.name.clone(), predicate);
|
||||
let table = pick(&env.tables, rng);
|
||||
Some(Insert::Select {
|
||||
table: table.name.clone(),
|
||||
@@ -110,7 +222,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &Remaining)> for Query {
|
||||
),
|
||||
(
|
||||
remaining.read,
|
||||
Box::new(|rng| Self::Select(Select::arbitrary_from(rng, env))),
|
||||
Box::new(|rng| Self::Select(Select::arbitrary_from(rng, &env.tables))),
|
||||
),
|
||||
(
|
||||
remaining.write,
|
||||
@@ -150,3 +262,96 @@ impl ArbitraryFrom<&SimulatorEnv> for Update {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod query_generation_tests {
|
||||
use rand::RngCore;
|
||||
use turso_core::Value;
|
||||
use turso_sqlite3_parser::to_sql_string::ToSqlString;
|
||||
|
||||
use super::*;
|
||||
use crate::model::query::predicate::Predicate;
|
||||
use crate::model::query::EmptyContext;
|
||||
use crate::model::table::{Column, ColumnType};
|
||||
use crate::SimulatorEnv;
|
||||
|
||||
#[test]
|
||||
fn test_select_query_generation() {
|
||||
let mut rng = rand::thread_rng();
|
||||
// CREATE TABLE users (id INTEGER, name TEXT);
|
||||
// INSERT INTO users (id, name) VALUES (1, 'Alice'), (2, 'Bob');
|
||||
// CREATE TABLE orders (order_id INTEGER, user_id INTEGER);
|
||||
// INSERT INTO orders (order_id, user_id) VALUES (1, 1), (2, 2);
|
||||
|
||||
let tables = vec![
|
||||
Table {
|
||||
name: "users".to_string(),
|
||||
columns: vec![
|
||||
Column {
|
||||
name: "id".to_string(),
|
||||
column_type: ColumnType::Integer,
|
||||
primary: false,
|
||||
unique: false,
|
||||
},
|
||||
Column {
|
||||
name: "name".to_string(),
|
||||
column_type: ColumnType::Text,
|
||||
primary: false,
|
||||
unique: false,
|
||||
},
|
||||
],
|
||||
rows: vec![
|
||||
vec![SimValue(Value::Integer(1)), SimValue(Value::Text("Alice".into()))],
|
||||
vec![SimValue(Value::Integer(2)), SimValue(Value::Text("Bob".into()))],
|
||||
],
|
||||
},
|
||||
Table {
|
||||
name: "orders".to_string(),
|
||||
columns: vec![
|
||||
Column {
|
||||
name: "order_id".to_string(),
|
||||
column_type: ColumnType::Integer,
|
||||
primary: false,
|
||||
unique: false,
|
||||
},
|
||||
Column {
|
||||
name: "user_id".to_string(),
|
||||
column_type: ColumnType::Integer,
|
||||
primary: false,
|
||||
unique: false,
|
||||
},
|
||||
],
|
||||
rows: vec![
|
||||
vec![SimValue(Value::Integer(1)), SimValue(Value::Integer(1))],
|
||||
vec![SimValue(Value::Integer(2)), SimValue(Value::Integer(2))],
|
||||
],
|
||||
},
|
||||
Table {
|
||||
name: "products".to_string(),
|
||||
columns: vec![
|
||||
Column {
|
||||
name: "product_id".to_string(),
|
||||
column_type: ColumnType::Integer,
|
||||
primary: true,
|
||||
unique: true,
|
||||
},
|
||||
Column {
|
||||
name: "product_name".to_string(),
|
||||
column_type: ColumnType::Text,
|
||||
primary: false,
|
||||
unique: false,
|
||||
},
|
||||
],
|
||||
rows: vec![
|
||||
vec![SimValue(Value::Integer(1)), SimValue(Value::Text("Widget".into()))],
|
||||
vec![SimValue(Value::Integer(2)), SimValue(Value::Text("Gadget".into()))],
|
||||
],
|
||||
},
|
||||
];
|
||||
for _ in 0..100 {
|
||||
let query = Select::arbitrary_from(&mut rng, &tables);
|
||||
println!("{}", query.to_sql_ast().to_sql_string(&EmptyContext {}));
|
||||
// println!("{:?}", query.to_sql_ast());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ fn watch_mode(
|
||||
let mut env = SimulatorEnv::new(seed, cli_opts, &paths.db);
|
||||
plan.iter().for_each(|is| {
|
||||
is.iter().for_each(|i| {
|
||||
i.shadow(&mut env);
|
||||
let _ = i.shadow(&mut env.tables);
|
||||
});
|
||||
});
|
||||
let env = Arc::new(Mutex::new(env.clone_without_connections()));
|
||||
|
||||
@@ -16,9 +16,9 @@ pub(crate) struct Create {
|
||||
impl Shadow for Create {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result {
|
||||
if !env.tables.iter().any(|t| t.name == self.table.name) {
|
||||
env.tables.push(self.table.clone());
|
||||
fn shadow(&self, tables: &mut Vec<Table>) -> Self::Result {
|
||||
if !tables.iter().any(|t| t.name == self.table.name) {
|
||||
tables.push(self.table.clone());
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::{
|
||||
generation::{gen_random_text, pick, pick_n_unique, ArbitraryFrom},
|
||||
runner::env::SimulatorEnv,
|
||||
generation::{gen_random_text, pick, pick_n_unique, ArbitraryFrom, Shadow}, model::table::{SimValue, Table}, runner::env::SimulatorEnv
|
||||
};
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -27,11 +26,12 @@ pub(crate) struct CreateIndex {
|
||||
pub(crate) columns: Vec<(String, SortOrder)>,
|
||||
}
|
||||
|
||||
impl CreateIndex {
|
||||
pub(crate) fn shadow(
|
||||
impl Shadow for CreateIndex {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(
|
||||
&self,
|
||||
_env: &mut crate::runner::env::SimulatorEnv,
|
||||
) -> Vec<Vec<crate::model::table::SimValue>> {
|
||||
_env: &mut Vec<Table>,
|
||||
) -> Vec<Vec<SimValue>> {
|
||||
// CREATE INDEX doesn't require any shadowing; we don't need to keep track
|
||||
// in the simulator what indexes exist.
|
||||
vec![]
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{generation::Shadow, model::table::SimValue, SimulatorEnv};
|
||||
use crate::{generation::Shadow, model::table::{SimValue, Table}, SimulatorEnv};
|
||||
|
||||
use super::predicate::Predicate;
|
||||
|
||||
@@ -15,8 +15,8 @@ pub(crate) struct Delete {
|
||||
impl Shadow for Delete {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result {
|
||||
let table = env.tables.iter_mut().find(|t| t.name == self.table);
|
||||
fn shadow(&self, tables: &mut Vec<Table>) -> 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
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{generation::Shadow, model::table::SimValue, SimulatorEnv};
|
||||
use crate::{generation::Shadow, model::table::{SimValue, Table}};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub(crate) struct Drop {
|
||||
@@ -12,8 +12,8 @@ pub(crate) struct Drop {
|
||||
impl Shadow for Drop {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result {
|
||||
if !env.tables.iter().any(|t| t.name == self.table) {
|
||||
fn shadow(&self, tables: &mut Vec<Table>) -> 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!(
|
||||
"Table {} does not exist. DROP statement ignored.",
|
||||
@@ -21,7 +21,7 @@ impl Shadow for Drop {
|
||||
));
|
||||
}
|
||||
|
||||
env.tables.retain(|t| t.name != self.table);
|
||||
tables.retain(|t| t.name != self.table);
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{generation::Shadow, model::table::SimValue, SimulatorEnv};
|
||||
use crate::{generation::Shadow, model::table::{SimValue, Table}};
|
||||
|
||||
use super::select::Select;
|
||||
|
||||
@@ -21,10 +21,10 @@ pub(crate) enum Insert {
|
||||
impl Shadow for Insert {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result {
|
||||
fn shadow(&self, tables: &mut Vec<Table>) -> Self::Result {
|
||||
match self {
|
||||
Insert::Values { table, values } => {
|
||||
if let Some(t) = env.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!(
|
||||
@@ -34,8 +34,8 @@ impl Shadow for Insert {
|
||||
}
|
||||
}
|
||||
Insert::Select { table, select } => {
|
||||
let rows = select.shadow(env)?;
|
||||
if let Some(t) = env.tables.iter_mut().find(|t| &t.name == table) {
|
||||
let rows = select.shadow(tables)?;
|
||||
if let Some(t) = tables.iter_mut().find(|t| &t.name == table) {
|
||||
t.rows.extend(rows);
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
|
||||
@@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
|
||||
use turso_sqlite3_parser::to_sql_string::ToSqlContext;
|
||||
use update::Update;
|
||||
|
||||
use crate::{generation::Shadow, model::table::SimValue, runner::env::SimulatorEnv};
|
||||
use crate::{generation::Shadow, model::table::{SimValue, Table}};
|
||||
|
||||
pub mod create;
|
||||
pub mod create_index;
|
||||
@@ -65,7 +65,7 @@ impl Query {
|
||||
impl Shadow for Query {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result {
|
||||
fn shadow(&self, env: &mut Vec<Table>) -> Self::Result {
|
||||
match self {
|
||||
Query::Create(create) => create.shadow(env),
|
||||
Query::Insert(insert) => insert.shadow(env),
|
||||
@@ -93,7 +93,7 @@ impl Display for Query {
|
||||
}
|
||||
|
||||
/// Used to print sql strings that already have all the context it needs
|
||||
struct EmptyContext;
|
||||
pub(crate) struct EmptyContext;
|
||||
|
||||
impl ToSqlContext for EmptyContext {
|
||||
fn get_column_name(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use regex_syntax::ast::print;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use turso_sqlite3_parser::{ast, to_sql_string::ToSqlString};
|
||||
|
||||
@@ -83,6 +84,11 @@ pub fn expr_to_value(expr: &ast::Expr, row: &[SimValue], table: &Table) -> Optio
|
||||
ast::Expr::DoublyQualified(_, _, ast::Name(col_name))
|
||||
| ast::Expr::Qualified(_, ast::Name(col_name))
|
||||
| ast::Expr::Id(ast::Id(col_name)) => {
|
||||
// println!("Resolving column: {}", col_name);
|
||||
// println!("Row: {:?}", row);
|
||||
// println!("Row length: {}", row.len());
|
||||
// println!("Table: {:?}", table.columns);
|
||||
// println!("Table columns length: {}", table.columns.len());
|
||||
assert_eq!(row.len(), table.columns.len());
|
||||
table
|
||||
.columns
|
||||
|
||||
@@ -12,7 +12,6 @@ use crate::{
|
||||
query::EmptyContext,
|
||||
table::{SimValue, Table},
|
||||
},
|
||||
SimulatorEnv,
|
||||
};
|
||||
|
||||
use super::predicate::Predicate;
|
||||
@@ -213,8 +212,8 @@ pub struct JoinTable {
|
||||
}
|
||||
|
||||
impl JoinTable {
|
||||
fn into_table(self) -> Table {
|
||||
Table {
|
||||
pub(crate) fn into_table(self) -> Table {
|
||||
let t = Table {
|
||||
name: "".to_string(),
|
||||
columns: self
|
||||
.tables
|
||||
@@ -222,21 +221,28 @@ impl JoinTable {
|
||||
.flat_map(|t| {
|
||||
t.columns.iter().map(|c| {
|
||||
let mut c = c.clone();
|
||||
c.name = format!("{}.<{}", t.name, c.name);
|
||||
c.name = format!("{}.{}", t.name, c.name);
|
||||
c
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
rows: self.rows,
|
||||
};
|
||||
for row in &t.rows {
|
||||
assert_eq!(
|
||||
row.len(),
|
||||
t.columns.len(),
|
||||
"Row length does not match column length after join"
|
||||
);
|
||||
}
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for FromClause {
|
||||
type Result = anyhow::Result<JoinTable>;
|
||||
fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result {
|
||||
let first_table = env
|
||||
.tables
|
||||
fn shadow(&self, tables: &mut Vec<Table>) -> Self::Result {
|
||||
let first_table = tables
|
||||
.iter()
|
||||
.find(|t| t.name == self.table)
|
||||
.context("Table not found")?;
|
||||
@@ -247,8 +253,7 @@ impl Shadow for FromClause {
|
||||
};
|
||||
|
||||
for join in &self.joins {
|
||||
let joined_table = env
|
||||
.tables
|
||||
let joined_table = tables
|
||||
.iter()
|
||||
.find(|t| t.name == join.table)
|
||||
.context("Joined table not found")?;
|
||||
@@ -265,8 +270,8 @@ impl Shadow for FromClause {
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
// take a cartesian product of the rows
|
||||
let mut all_row_pairs =
|
||||
first_table.rows.iter().cartesian_product(join_rows.iter());
|
||||
let all_row_pairs =
|
||||
join_table.rows.clone().into_iter().cartesian_product(join_rows.iter());
|
||||
|
||||
for (row1, row2) in all_row_pairs {
|
||||
let row = row1.iter().chain(row2.iter()).cloned().collect::<Vec<_>>();
|
||||
@@ -289,9 +294,17 @@ impl Shadow for FromClause {
|
||||
impl Shadow for SelectInner {
|
||||
type Result = anyhow::Result<JoinTable>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result {
|
||||
fn shadow(&self, env: &mut Vec<Table>) -> Self::Result {
|
||||
let mut join_table = self.from.shadow(env)?;
|
||||
let as_table = join_table.clone().into_table();
|
||||
for row in &mut join_table.rows {
|
||||
assert_eq!(
|
||||
row.len(),
|
||||
as_table.columns.len(),
|
||||
"Row length does not match column length after join"
|
||||
);
|
||||
}
|
||||
|
||||
join_table
|
||||
.rows
|
||||
.retain(|row| self.where_clause.test(row, &as_table));
|
||||
@@ -308,7 +321,7 @@ impl Shadow for SelectInner {
|
||||
impl Shadow for Select {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result {
|
||||
fn shadow(&self, env: &mut Vec<Table>) -> Self::Result {
|
||||
let first_result = self.body.select.shadow(env)?;
|
||||
|
||||
let mut rows = first_result.into_table().rows;
|
||||
@@ -317,7 +330,14 @@ impl Shadow for Select {
|
||||
let compound_results = compound.select.shadow(env)?;
|
||||
|
||||
match compound.operator {
|
||||
CompoundOperator::Union => todo!(),
|
||||
CompoundOperator::Union => {
|
||||
// Union means we need to combine the results, removing duplicates
|
||||
let mut new_rows = compound_results.into_table().rows;
|
||||
new_rows.extend(rows.clone());
|
||||
new_rows.sort_unstable();
|
||||
new_rows.dedup();
|
||||
rows = new_rows;
|
||||
}
|
||||
CompoundOperator::UnionAll => {
|
||||
// Union all means we just concatenate the results
|
||||
rows.extend(compound_results.rows.into_iter());
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{generation::Shadow, model::table::SimValue, SimulatorEnv};
|
||||
use crate::{generation::Shadow, model::table::{SimValue, Table}, SimulatorEnv};
|
||||
|
||||
use super::predicate::Predicate;
|
||||
|
||||
@@ -16,8 +16,8 @@ pub(crate) struct Update {
|
||||
impl Shadow for Update {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorEnv) -> Self::Result {
|
||||
let table = env.tables.iter_mut().find(|t| t.name == self.table);
|
||||
fn shadow(&self, tables: &mut Vec<Table>) -> Self::Result {
|
||||
let table = tables.iter_mut().find(|t| t.name == self.table);
|
||||
|
||||
let table = if let Some(table) = table {
|
||||
table
|
||||
|
||||
@@ -129,7 +129,7 @@ fn execute_plan(
|
||||
tracing::debug!("connection {} already connected", connection_index);
|
||||
match execute_interaction(env, connection_index, interaction, &mut state.stack) {
|
||||
Ok(next_execution) => {
|
||||
interaction.shadow(env);
|
||||
interaction.shadow(&mut env.tables);
|
||||
tracing::debug!("connection {} processed", connection_index);
|
||||
// Move to the next interaction or property
|
||||
match next_execution {
|
||||
@@ -190,7 +190,7 @@ pub(crate) fn execute_interaction(
|
||||
SimConnection::SQLiteConnection(_) => unreachable!(),
|
||||
SimConnection::Disconnected => unreachable!(),
|
||||
};
|
||||
|
||||
tracing::debug!(?interaction);
|
||||
let results = interaction.execute_query(conn, &env.io);
|
||||
tracing::debug!(?results);
|
||||
stack.push(results);
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
|
||||
use rand::{RngCore, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use turso_core::{Clock, Instant, OpenFlags, PlatformIO, Result, IO};
|
||||
use turso_core::{Clock, Instant, MemoryIO, OpenFlags, PlatformIO, Result, IO};
|
||||
|
||||
use crate::runner::file::SimulatorFile;
|
||||
|
||||
@@ -116,6 +116,6 @@ impl IO for SimulatorIO {
|
||||
}
|
||||
|
||||
fn get_memory_io(&self) -> Arc<turso_core::MemoryIO> {
|
||||
todo!()
|
||||
Arc::new(MemoryIO::new())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user