generate joins and unions

This commit is contained in:
alpaylan
2025-07-07 02:54:48 -04:00
parent b0cf2ba92c
commit ea9c67a950
16 changed files with 367 additions and 93 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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());
}
}
}

View File

@@ -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()));

View File

@@ -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!(

View File

@@ -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![]

View File

@@ -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

View File

@@ -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![])
}

View File

@@ -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!(

View File

@@ -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(

View File

@@ -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

View File

@@ -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());

View File

@@ -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

View File

@@ -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);

View File

@@ -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())
}
}