From 9f2608bded8e3df782b466cfc8a2b879c87f563c Mon Sep 17 00:00:00 2001 From: pedrocarlo Date: Tue, 3 Jun 2025 02:48:27 -0300 Subject: [PATCH] remove previous predicate struct and rewrite generation with the new the struct --- simulator/generation/expr.rs | 18 + simulator/generation/mod.rs | 1 + simulator/generation/plan.rs | 3 +- simulator/generation/predicate.rs | 553 +++++++++++++++++++++++++++++ simulator/generation/property.rs | 3 +- simulator/generation/query.rs | 347 +----------------- simulator/generation/table.rs | 4 +- simulator/model/query/delete.rs | 2 +- simulator/model/query/predicate.rs | 5 +- simulator/model/query/select.rs | 128 +------ simulator/model/query/update.rs | 2 +- simulator/model/table.rs | 24 ++ 12 files changed, 614 insertions(+), 476 deletions(-) create mode 100644 simulator/generation/predicate.rs diff --git a/simulator/generation/expr.rs b/simulator/generation/expr.rs index f5aa80964..f17fe2044 100644 --- a/simulator/generation/expr.rs +++ b/simulator/generation/expr.rs @@ -4,6 +4,7 @@ use limbo_sqlite3_parser::ast::{ use crate::{ generation::{gen_random_text, pick, pick_index, Arbitrary, ArbitraryFrom}, + model::table::Value, SimulatorEnv, }; @@ -56,6 +57,7 @@ where } } +// Freestyling generation impl ArbitraryFrom<&SimulatorEnv> for Expr { fn arbitrary_from(rng: &mut R, t: &SimulatorEnv) -> Self { // Loop until we get an implmeneted expression @@ -267,6 +269,22 @@ impl ArbitraryFrom<&SimulatorEnv> for ast::Literal { } } +// Creates a litreal value +impl ArbitraryFrom<&Vec<&Value>> for ast::Expr { + fn arbitrary_from(rng: &mut R, values: &Vec<&Value>) -> Self { + if values.is_empty() { + return Self::Literal(ast::Literal::Null); + } + // TODO: for now just convert the value to an ast::Literal + let values = values + .iter() + .map(|value| ast::Expr::Literal((*value).into())) + .collect::>(); + + pick(&values, rng).to_owned().clone() + } +} + impl ArbitraryFrom<&SimulatorEnv> for UnaryOperator { fn arbitrary_from(rng: &mut R, _t: &SimulatorEnv) -> Self { let choice = rng.gen_range(0..4); diff --git a/simulator/generation/mod.rs b/simulator/generation/mod.rs index 0fea1b704..be550aabb 100644 --- a/simulator/generation/mod.rs +++ b/simulator/generation/mod.rs @@ -5,6 +5,7 @@ use rand::{distributions::uniform::SampleUniform, Rng}; mod expr; pub mod plan; +mod predicate; pub mod property; pub mod query; pub mod table; diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 9d07ccb14..49885517e 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize}; use crate::{ model::{ query::{ - select::{Distinctness, Predicate, ResultColumn}, + predicate::Predicate, + select::{Distinctness, ResultColumn}, update::Update, Create, CreateIndex, Delete, Drop, Insert, Query, Select, }, diff --git a/simulator/generation/predicate.rs b/simulator/generation/predicate.rs new file mode 100644 index 000000000..e87ee7c3e --- /dev/null +++ b/simulator/generation/predicate.rs @@ -0,0 +1,553 @@ +use limbo_sqlite3_parser::ast::{self, Expr}; +use rand::{seq::SliceRandom as _, Rng}; + +use crate::model::{ + query::predicate::Predicate, + table::{Table, Value}, +}; + +use super::{ + backtrack, one_of, + table::{GTValue, LTValue, LikeValue}, + ArbitraryFrom, ArbitraryFromMaybe as _, +}; + +struct CompoundPredicate(Predicate); +struct SimplePredicate(Predicate); + +impl ArbitraryFrom<(&Table, bool)> for SimplePredicate { + fn arbitrary_from(rng: &mut R, (table, predicate_value): (&Table, bool)) -> Self { + // Pick a random column + let column_index = rng.gen_range(0..table.columns.len()); + let column = &table.columns[column_index]; + let column_values = table + .rows + .iter() + .map(|r| &r[column_index]) + .collect::>(); + // Pick an operator + let operator = match predicate_value { + true => one_of( + vec![ + Box::new(|rng| { + Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::Equals, + Box::new(Expr::arbitrary_from(rng, &column_values)), + ) + }), + Box::new(|rng| { + let gt_value = GTValue::arbitrary_from(rng, &column_values).0; + Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::Greater, + Box::new(Expr::Literal(gt_value.into())), + ) + }), + Box::new(|rng| { + let lt_value = LTValue::arbitrary_from(rng, &column_values).0; + Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::Less, + Box::new(Expr::Literal(lt_value.into())), + ) + }), + ], + rng, + ), + false => one_of( + vec![ + Box::new(|rng| { + Expr::Binary( + Box::new(Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::NotEquals, + Box::new(Expr::arbitrary_from(rng, &column_values)), + ) + }), + Box::new(|rng| { + let lt_value = LTValue::arbitrary_from(rng, &column_values).0; + Expr::Binary( + Box::new(Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::Greater, + Box::new(Expr::Literal(lt_value.into())), + ) + }), + Box::new(|rng| { + let gt_value = GTValue::arbitrary_from(rng, &column_values).0; + Expr::Binary( + Box::new(Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::Less, + Box::new(Expr::Literal(gt_value.into())), + ) + }), + ], + rng, + ), + }; + + Self(Predicate(operator)) + } +} + +impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate { + fn arbitrary_from(rng: &mut R, (table, predicate_value): (&Table, bool)) -> Self { + // Decide if you want to create an AND or an OR + Self(if rng.gen_bool(0.7) { + // An AND for true requires each of its children to be true + // An AND for false requires at least one of its children to be false + if predicate_value { + (0..rng.gen_range(0..=3)) + .map(|_| SimplePredicate::arbitrary_from(rng, (table, true)).0) + .reduce(|accum, curr| { + Predicate(Expr::Binary( + Box::new(accum.0), + ast::Operator::And, + Box::new(curr.0), + )) + }) + .unwrap_or(Predicate::true_()) // Empty And is True + } else { + // Create a vector of random booleans + let mut booleans = (0..rng.gen_range(0..=3)) + .map(|_| rng.gen_bool(0.5)) + .collect::>(); + + let len = booleans.len(); + + // Make sure at least one of them is false + if !booleans.is_empty() && booleans.iter().all(|b| *b) { + booleans[rng.gen_range(0..len)] = false; + } + + booleans + .iter() + .map(|b| SimplePredicate::arbitrary_from(rng, (table, *b)).0) + .reduce(|accum, curr| { + Predicate(Expr::Binary( + Box::new(accum.0), + ast::Operator::And, + Box::new(curr.0), + )) + }) + .unwrap_or(Predicate::true_()) // Empty And is True + } + } else { + // An OR for true requires at least one of its children to be true + // An OR for false requires each of its children to be false + if predicate_value { + // Create a vector of random booleans + let mut booleans = (0..rng.gen_range(0..=3)) + .map(|_| rng.gen_bool(0.5)) + .collect::>(); + let len = booleans.len(); + // Make sure at least one of them is true + if !booleans.is_empty() && booleans.iter().all(|b| !*b) { + booleans[rng.gen_range(0..len)] = true; + } + + booleans + .iter() + .map(|b| SimplePredicate::arbitrary_from(rng, (table, *b)).0) + .reduce(|accum, curr| { + Predicate(Expr::Binary( + Box::new(accum.0), + ast::Operator::Or, + Box::new(curr.0), + )) + }) + .unwrap_or(Predicate::false_()) // Empty Or is False + } else { + (0..rng.gen_range(0..=3)) + .map(|_| SimplePredicate::arbitrary_from(rng, (table, false)).0) + .reduce(|accum, curr| { + Predicate(Expr::Binary( + Box::new(accum.0), + ast::Operator::Or, + Box::new(curr.0), + )) + }) + .unwrap_or(Predicate::false_()) // Empty Or is False + } + }) + } +} + +impl ArbitraryFrom<&Table> for Predicate { + fn arbitrary_from(rng: &mut R, table: &Table) -> Self { + let predicate_value = rng.gen_bool(0.5); + CompoundPredicate::arbitrary_from(rng, (table, predicate_value)).0 + } +} + +impl ArbitraryFrom<(&str, &Value)> for Predicate { + fn arbitrary_from(rng: &mut R, (column_name, value): (&str, &Value)) -> Self { + one_of( + vec![ + Box::new(|_| { + Predicate(Expr::Binary( + Box::new(Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column_name.to_string()), + )), + ast::Operator::Equals, + Box::new(Expr::Literal(value.into())), + )) + }), + Box::new(|rng| { + let gt_value = GTValue::arbitrary_from(rng, value).0; + Predicate(Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column_name.to_string()), + )), + ast::Operator::Greater, + Box::new(Expr::Literal(gt_value.into())), + )) + }), + Box::new(|rng| { + let lt_value = LTValue::arbitrary_from(rng, value).0; + Predicate(Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column_name.to_string()), + )), + ast::Operator::Less, + Box::new(Expr::Literal(lt_value.into())), + )) + }), + ], + rng, + ) + } +} + +/// Produces a predicate that is true for the provided row in the given table +fn produce_true_predicate(rng: &mut R, (t, row): (&Table, &Vec)) -> Predicate { + // Pick a column + let column_index = rng.gen_range(0..t.columns.len()); + let column = &t.columns[column_index]; + let value = &row[column_index]; + backtrack( + vec![ + ( + 1, + Box::new(|_| { + Some(Predicate(Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::Equals, + Box::new(Expr::Literal(value.into())), + ))) + }), + ), + ( + 1, + Box::new(|rng| { + let v = Value::arbitrary_from(rng, &column.column_type); + if &v == value { + None + } else { + Some(Predicate(Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::NotEquals, + Box::new(Expr::Literal(v.into())), + ))) + } + }), + ), + ( + 1, + Box::new(|rng| { + let lt_value = LTValue::arbitrary_from(rng, value).0; + Some(Predicate(Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::Greater, + Box::new(Expr::Literal(lt_value.into())), + ))) + }), + ), + ( + 1, + Box::new(|rng| { + let gt_value = GTValue::arbitrary_from(rng, value).0; + Some(Predicate(Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::Less, + Box::new(Expr::Literal(gt_value.into())), + ))) + }), + ), + ( + 1, + Box::new(|rng| { + LikeValue::arbitrary_from_maybe(rng, value).map(|like| { + Predicate(Expr::Like { + lhs: Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + not: false, // TODO: also generate this value eventually + op: ast::LikeOperator::Like, + rhs: Box::new(Expr::Literal(like.0.into())), + escape: None, // TODO: implement + }) + }) + }), + ), + ], + rng, + ) +} + +/// Produces a predicate that is false for the provided row in the given table +fn produce_false_predicate(rng: &mut R, (t, row): (&Table, &Vec)) -> Predicate { + // Pick a column + let column_index = rng.gen_range(0..t.columns.len()); + let column = &t.columns[column_index]; + let value = &row[column_index]; + one_of( + vec![ + Box::new(|_| { + Predicate(Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::NotEquals, + Box::new(Expr::Literal(value.into())), + )) + }), + Box::new(|rng| { + let v = loop { + let v = Value::arbitrary_from(rng, &column.column_type); + if &v != value { + break v; + } + }; + Predicate(Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::Equals, + Box::new(Expr::Literal(v.into())), + )) + }), + Box::new(|rng| { + let gt_value = GTValue::arbitrary_from(rng, value).0; + Predicate(Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::Greater, + Box::new(Expr::Literal(gt_value.into())), + )) + }), + Box::new(|rng| { + let lt_value = LTValue::arbitrary_from(rng, value).0; + Predicate(Expr::Binary( + Box::new(ast::Expr::Qualified( + ast::Name("".to_string()), + ast::Name(column.name.clone()), + )), + ast::Operator::Less, + Box::new(Expr::Literal(lt_value.into())), + )) + }), + ], + rng, + ) +} + +impl ArbitraryFrom<(&Table, &Vec)> for Predicate { + fn arbitrary_from(rng: &mut R, (t, row): (&Table, &Vec)) -> Self { + // We want to produce a predicate that is true for the row + // We can do this by creating several predicates that + // are true, some that are false, combiend them in ways that correspond to the creation of a true predicate + + // Produce some true and false predicates + let mut true_predicates = (1..=rng.gen_range(1..=4)) + .map(|_| produce_true_predicate(rng, (t, row))) + .collect::>(); + + let false_predicates = (0..=rng.gen_range(0..=3)) + .map(|_| produce_false_predicate(rng, (t, row))) + .collect::>(); + + // Start building a top level predicate from a true predicate + let mut result = true_predicates.pop().unwrap(); + + let mut predicates = true_predicates + .iter() + .map(|p| (true, p.clone())) + .chain(false_predicates.iter().map(|p| (false, p.clone()))) + .collect::>(); + + predicates.shuffle(rng); + + while !predicates.is_empty() { + // Create a new predicate from at least 1 and at most 3 predicates + let context = + predicates[0..rng.gen_range(0..=usize::min(3, predicates.len()))].to_vec(); + // Shift `predicates` to remove the predicates in the context + predicates = predicates[context.len()..].to_vec(); + + // `result` is true, so we have the following three options to make a true predicate: + // T or F + // T or T + // T and T + + result = one_of( + vec![ + // T or (X1 or X2 or ... or Xn) + Box::new(|_| { + Predicate(Expr::Binary( + Box::new(result.0.clone()), + ast::Operator::Or, + Box::new( + context + .iter() + .map(|(_, p)| p.clone()) + .reduce(|accum, curr| { + Predicate(Expr::Binary( + Box::new(accum.0), + ast::Operator::Or, + Box::new(curr.0), + )) + }) + .unwrap_or(Predicate::false_()) + .0, + ), + )) + }), + // T or (T1 and T2 and ... and Tn) + Box::new(|_| { + Predicate(Expr::Binary( + Box::new(result.0.clone()), + ast::Operator::Or, + Box::new( + context + .iter() + .map(|(_, p)| p.clone()) + .reduce(|accum, curr| { + Predicate(Expr::Binary( + Box::new(accum.0), + ast::Operator::And, + Box::new(curr.0), + )) + }) + .unwrap_or(Predicate::true_()) + .0, + ), + )) + }), + // T and T + Box::new(|_| { + // Check if all the predicates in the context are true + if context.iter().all(|(b, _)| *b) { + // T and (X1 or X2 or ... or Xn) + Predicate(Expr::Binary( + Box::new(result.0.clone()), + ast::Operator::And, + Box::new( + context + .iter() + .map(|(_, p)| p.clone()) + .reduce(|accum, curr| { + Predicate(Expr::Binary( + Box::new(accum.0), + ast::Operator::And, + Box::new(curr.0), + )) + }) + .unwrap_or(Predicate::true_()) + .0, + ), + )) + } + // Check if there is at least one true predicate + else if context.iter().any(|(b, _)| *b) { + // T and (X1 or X2 or ... or Xn) + Predicate(Expr::Binary( + Box::new(result.0.clone()), + ast::Operator::And, + Box::new( + context + .iter() + .map(|(_, p)| p.clone()) + .reduce(|accum, curr| { + Predicate(Expr::Binary( + Box::new(accum.0), + ast::Operator::Or, + Box::new(curr.0), + )) + }) + .unwrap_or(Predicate::false_()) + .0, + ), + )) + // Predicate::And(vec![ + // result.clone(), + // Predicate::Or(context.iter().map(|(_, p)| p.clone()).collect()), + // ]) + } else { + // T and (X1 or X2 or ... or Xn or TRUE) + Predicate(Expr::Binary( + Box::new(result.0.clone()), + ast::Operator::And, + Box::new( + context + .iter() + .map(|(_, p)| p.clone()) + .chain(std::iter::once(Predicate::true_())) + .reduce(|accum, curr| { + Predicate(Expr::Binary( + Box::new(accum.0), + ast::Operator::Or, + Box::new(curr.0), + )) + }) + .unwrap() // Chain guarantees at least one value + .0, + ), + )) + } + }), + ], + rng, + ); + } + + result + } +} diff --git a/simulator/generation/property.rs b/simulator/generation/property.rs index 6f47610f2..adc8a581d 100644 --- a/simulator/generation/property.rs +++ b/simulator/generation/property.rs @@ -4,7 +4,8 @@ use serde::{Deserialize, Serialize}; use crate::{ model::{ query::{ - select::{Distinctness, Predicate, ResultColumn}, + predicate::Predicate, + select::{Distinctness, ResultColumn}, Create, Delete, Drop, Insert, Query, Select, }, table::Value, diff --git a/simulator/generation/query.rs b/simulator/generation/query.rs index 315212f8b..ca638174c 100644 --- a/simulator/generation/query.rs +++ b/simulator/generation/query.rs @@ -1,19 +1,16 @@ use std::collections::HashSet; -use crate::generation::table::{GTValue, LTValue}; -use crate::generation::{one_of, Arbitrary, ArbitraryFrom}; - -use crate::model::query::select::{Distinctness, Predicate, ResultColumn}; +use crate::generation::{Arbitrary, ArbitraryFrom}; +use crate::model::query::predicate::Predicate; +use crate::model::query::select::{Distinctness, ResultColumn}; use crate::model::query::update::Update; use crate::model::query::{Create, Delete, Drop, Insert, 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::table::LikeValue; -use super::{backtrack, frequency, pick, ArbitraryFromMaybe}; +use super::{backtrack, frequency, pick}; impl Arbitrary for Create { fn arbitrary(rng: &mut R) -> Self { @@ -137,342 +134,6 @@ impl ArbitraryFrom<(&SimulatorEnv, &Remaining)> for Query { } } -struct CompoundPredicate(Predicate); -struct SimplePredicate(Predicate); - -impl ArbitraryFrom<(&Table, bool)> for SimplePredicate { - fn arbitrary_from(rng: &mut R, (table, predicate_value): (&Table, bool)) -> Self { - // Pick a random column - let column_index = rng.gen_range(0..table.columns.len()); - let column = &table.columns[column_index]; - let column_values = table - .rows - .iter() - .map(|r| &r[column_index]) - .collect::>(); - // Pick an operator - let operator = match predicate_value { - true => one_of( - vec![ - Box::new(|rng| { - Predicate::Eq( - column.name.clone(), - Value::arbitrary_from(rng, &column_values), - ) - }), - Box::new(|rng| { - Predicate::Gt( - column.name.clone(), - GTValue::arbitrary_from(rng, &column_values).0, - ) - }), - Box::new(|rng| { - Predicate::Lt( - column.name.clone(), - LTValue::arbitrary_from(rng, &column_values).0, - ) - }), - ], - rng, - ), - false => one_of( - vec![ - Box::new(|rng| { - Predicate::Neq( - column.name.clone(), - Value::arbitrary_from(rng, &column.column_type), - ) - }), - Box::new(|rng| { - Predicate::Gt( - column.name.clone(), - LTValue::arbitrary_from(rng, &column_values).0, - ) - }), - Box::new(|rng| { - Predicate::Lt( - column.name.clone(), - GTValue::arbitrary_from(rng, &column_values).0, - ) - }), - ], - rng, - ), - }; - - Self(operator) - } -} - -impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate { - fn arbitrary_from(rng: &mut R, (table, predicate_value): (&Table, bool)) -> Self { - // Decide if you want to create an AND or an OR - Self(if rng.gen_bool(0.7) { - // An AND for true requires each of its children to be true - // An AND for false requires at least one of its children to be false - if predicate_value { - Predicate::And( - (0..rng.gen_range(0..=3)) - .map(|_| SimplePredicate::arbitrary_from(rng, (table, true)).0) - .collect(), - ) - } else { - // Create a vector of random booleans - let mut booleans = (0..rng.gen_range(0..=3)) - .map(|_| rng.gen_bool(0.5)) - .collect::>(); - - let len = booleans.len(); - - // Make sure at least one of them is false - if !booleans.is_empty() && booleans.iter().all(|b| *b) { - booleans[rng.gen_range(0..len)] = false; - } - - Predicate::And( - booleans - .iter() - .map(|b| SimplePredicate::arbitrary_from(rng, (table, *b)).0) - .collect(), - ) - } - } else { - // An OR for true requires at least one of its children to be true - // An OR for false requires each of its children to be false - if predicate_value { - // Create a vector of random booleans - let mut booleans = (0..rng.gen_range(0..=3)) - .map(|_| rng.gen_bool(0.5)) - .collect::>(); - let len = booleans.len(); - // Make sure at least one of them is true - if !booleans.is_empty() && booleans.iter().all(|b| !*b) { - booleans[rng.gen_range(0..len)] = true; - } - - Predicate::Or( - booleans - .iter() - .map(|b| SimplePredicate::arbitrary_from(rng, (table, *b)).0) - .collect(), - ) - } else { - Predicate::Or( - (0..rng.gen_range(0..=3)) - .map(|_| SimplePredicate::arbitrary_from(rng, (table, false)).0) - .collect(), - ) - } - }) - } -} - -impl ArbitraryFrom<&Table> for Predicate { - fn arbitrary_from(rng: &mut R, table: &Table) -> Self { - let predicate_value = rng.gen_bool(0.5); - CompoundPredicate::arbitrary_from(rng, (table, predicate_value)).0 - } -} - -impl ArbitraryFrom<(&str, &Value)> for Predicate { - fn arbitrary_from(rng: &mut R, (column_name, value): (&str, &Value)) -> Self { - one_of( - vec![ - Box::new(|_| Predicate::Eq(column_name.to_string(), (*value).clone())), - Box::new(|rng| { - Self::Gt( - column_name.to_string(), - GTValue::arbitrary_from(rng, value).0, - ) - }), - Box::new(|rng| { - Self::Lt( - column_name.to_string(), - LTValue::arbitrary_from(rng, value).0, - ) - }), - ], - rng, - ) - } -} - -/// Produces a predicate that is true for the provided row in the given table -fn produce_true_predicate(rng: &mut R, (t, row): (&Table, &Vec)) -> Predicate { - // Pick a column - let column_index = rng.gen_range(0..t.columns.len()); - let column = &t.columns[column_index]; - let value = &row[column_index]; - backtrack( - vec![ - ( - 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 { - None - } else { - Some(Predicate::Neq(column.name.clone(), v)) - } - }), - ), - ( - 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, - ) -} - -/// Produces a predicate that is false for the provided row in the given table -fn produce_false_predicate(rng: &mut R, (t, row): (&Table, &Vec)) -> Predicate { - // Pick a column - let column_index = rng.gen_range(0..t.columns.len()); - let column = &t.columns[column_index]; - let value = &row[column_index]; - one_of( - vec![ - Box::new(|_| Predicate::Neq(column.name.clone(), value.clone())), - Box::new(|rng| { - let v = loop { - let v = Value::arbitrary_from(rng, &column.column_type); - if &v != value { - break v; - } - }; - Predicate::Eq(column.name.clone(), v) - }), - Box::new(|rng| { - Predicate::Gt(column.name.clone(), GTValue::arbitrary_from(rng, value).0) - }), - Box::new(|rng| { - Predicate::Lt(column.name.clone(), LTValue::arbitrary_from(rng, value).0) - }), - ], - rng, - ) -} - -impl ArbitraryFrom<(&Table, &Vec)> for Predicate { - fn arbitrary_from(rng: &mut R, (t, row): (&Table, &Vec)) -> Self { - // We want to produce a predicate that is true for the row - // We can do this by creating several predicates that - // are true, some that are false, combiend them in ways that correspond to the creation of a true predicate - - // Produce some true and false predicates - let mut true_predicates = (1..=rng.gen_range(1..=4)) - .map(|_| produce_true_predicate(rng, (t, row))) - .collect::>(); - - let false_predicates = (0..=rng.gen_range(0..=3)) - .map(|_| produce_false_predicate(rng, (t, row))) - .collect::>(); - - // Start building a top level predicate from a true predicate - let mut result = true_predicates.pop().unwrap(); - - let mut predicates = true_predicates - .iter() - .map(|p| (true, p.clone())) - .chain(false_predicates.iter().map(|p| (false, p.clone()))) - .collect::>(); - - predicates.shuffle(rng); - - while !predicates.is_empty() { - // Create a new predicate from at least 1 and at most 3 predicates - let context = - predicates[0..rng.gen_range(0..=usize::min(3, predicates.len()))].to_vec(); - // Shift `predicates` to remove the predicates in the context - predicates = predicates[context.len()..].to_vec(); - - // `result` is true, so we have the following three options to make a true predicate: - // T or F - // T or T - // T and T - - result = one_of( - vec![ - // T or (X1 or X2 or ... or Xn) - Box::new(|_| { - Predicate::Or(vec![ - result.clone(), - Predicate::Or(context.iter().map(|(_, p)| p.clone()).collect()), - ]) - }), - // T or (T1 and T2 and ... and Tn) - Box::new(|_| { - Predicate::Or(vec![ - result.clone(), - Predicate::And(context.iter().map(|(_, p)| p.clone()).collect()), - ]) - }), - // T and T - Box::new(|_| { - // Check if all the predicates in the context are true - if context.iter().all(|(b, _)| *b) { - // T and (X1 or X2 or ... or Xn) - Predicate::And(vec![ - result.clone(), - Predicate::And(context.iter().map(|(_, p)| p.clone()).collect()), - ]) - } - // Check if there is at least one true predicate - else if context.iter().any(|(b, _)| *b) { - // T and (X1 or X2 or ... or Xn) - Predicate::And(vec![ - result.clone(), - Predicate::Or(context.iter().map(|(_, p)| p.clone()).collect()), - ]) - } else { - // T and (X1 or X2 or ... or Xn or TRUE) - Predicate::And(vec![ - result.clone(), - Predicate::Or( - context - .iter() - .map(|(_, p)| p.clone()) - .chain(std::iter::once(Predicate::true_())) - .collect(), - ), - ]) - } - }), - ], - rng, - ); - } - - result - } -} - impl ArbitraryFrom<&SimulatorEnv> for Update { fn arbitrary_from(rng: &mut R, env: &SimulatorEnv) -> Self { let table = pick(&env.tables, rng); diff --git a/simulator/generation/table.rs b/simulator/generation/table.rs index 415b4b03a..2858928b7 100644 --- a/simulator/generation/table.rs +++ b/simulator/generation/table.rs @@ -197,7 +197,7 @@ impl ArbitraryFrom<&Value> for GTValue { } } -pub(crate) struct LikeValue(pub(crate) String); +pub(crate) struct LikeValue(pub(crate) Value); impl ArbitraryFromMaybe<&Value> for LikeValue { fn arbitrary_from_maybe(rng: &mut R, value: &Value) -> Option { @@ -221,7 +221,7 @@ impl ArbitraryFromMaybe<&Value> for LikeValue { } let index = rng.gen_range(0..t.len()); t.insert(index, '%'); - Some(Self(t.into_iter().collect())) + Some(Self(Value::Text(t.into_iter().collect()))) } _ => None, } diff --git a/simulator/model/query/delete.rs b/simulator/model/query/delete.rs index f6f224fb8..381de7f4d 100644 --- a/simulator/model/query/delete.rs +++ b/simulator/model/query/delete.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::{model::table::Value, SimulatorEnv}; -use super::select::Predicate; +use super::predicate::Predicate; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub(crate) struct Delete { diff --git a/simulator/model/query/predicate.rs b/simulator/model/query/predicate.rs index f87e41ca9..21f52b516 100644 --- a/simulator/model/query/predicate.rs +++ b/simulator/model/query/predicate.rs @@ -18,7 +18,7 @@ macro_rules! assert_implemented_predicate_expr { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Predicate(ast::Expr); +pub struct Predicate(pub ast::Expr); impl Predicate { pub(crate) fn true_() -> Self { @@ -59,7 +59,8 @@ impl Predicate { } } ast::Expr::Literal(literal) => Value::from(literal).into_bool(), - ast::Expr::Unary(unary_operator, expr) => todo!(), + // TODO: next implement unary operator + ast::Expr::Unary(..) => todo!(), expr => unimplemented!("{:?}", expr), } } diff --git a/simulator/model/query/select.rs b/simulator/model/query/select.rs index 8dfaf9aa8..2f1de7b5a 100644 --- a/simulator/model/query/select.rs +++ b/simulator/model/query/select.rs @@ -1,12 +1,10 @@ use std::fmt::Display; -use regex::{Regex, RegexBuilder}; use serde::{Deserialize, Serialize}; -use crate::{ - model::table::{Table, Value}, - SimulatorEnv, -}; +use crate::{model::table::Value, SimulatorEnv}; + +use super::predicate::Predicate; /// `SELECT` distinctness #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -80,123 +78,3 @@ impl Display for Select { ) } } - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub(crate) enum Predicate { - And(Vec), // p1 AND p2 AND p3... AND pn - Or(Vec), // 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(®ex_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 { - pub(crate) fn true_() -> Self { - Self::And(vec![]) - } - - pub(crate) fn false_() -> Self { - Self::Or(vec![]) - } - - pub(crate) fn test(&self, row: &[Value], table: &Table) -> bool { - let get_value = |name: &str| { - table - .columns - .iter() - .zip(row.iter()) - .find(|(column, _)| column.name == name) - .map(|(_, value)| value) - }; - - match self { - Predicate::And(vec) => vec.iter().all(|p| p.test(row, table)), - Predicate::Or(vec) => vec.iter().any(|p| p.test(row, table)), - Predicate::Eq(column, value) => get_value(column) == Some(value), - 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), - } - } -} - -impl Display for Predicate { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::And(predicates) => { - if predicates.is_empty() { - // todo: Make this TRUE when the bug is fixed - write!(f, "TRUE") - } else { - write!(f, "(")?; - for (i, p) in predicates.iter().enumerate() { - if i != 0 { - write!(f, " AND ")?; - } - write!(f, "{}", p)?; - } - write!(f, ")") - } - } - Self::Or(predicates) => { - if predicates.is_empty() { - write!(f, "FALSE") - } else { - write!(f, "(")?; - for (i, p) in predicates.iter().enumerate() { - if i != 0 { - write!(f, " OR ")?; - } - write!(f, "{}", p)?; - } - write!(f, ")") - } - } - Self::Eq(name, value) => write!(f, "{} = {}", name, value), - 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), - } - } -} diff --git a/simulator/model/query/update.rs b/simulator/model/query/update.rs index acfc1e8e4..c88e7de5d 100644 --- a/simulator/model/query/update.rs +++ b/simulator/model/query/update.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::{model::table::Value, SimulatorEnv}; -use super::select::Predicate; +use super::predicate::Predicate; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub(crate) struct Update { diff --git a/simulator/model/table.rs b/simulator/model/table.rs index 0884843f6..a1e5f6948 100644 --- a/simulator/model/table.rs +++ b/simulator/model/table.rs @@ -248,6 +248,30 @@ impl From for Value { } } +impl From for ast::Literal { + fn from(value: Value) -> Self { + match value { + Value::Null => Self::Null, + Value::Integer(i) => Self::Numeric(i.to_string()), + Value::Float(f) => Self::Numeric(f.to_string()), + Value::Text(string) => Self::String(string), + Value::Blob(blob) => Self::Blob(hex::encode(blob)), + } + } +} + +impl From<&Value> for ast::Literal { + fn from(value: &Value) -> Self { + match value { + Value::Null => Self::Null, + Value::Integer(i) => Self::Numeric(i.to_string()), + Value::Float(f) => Self::Numeric(f.to_string()), + Value::Text(string) => Self::String(string.clone()), + Value::Blob(blob) => Self::Blob(hex::encode(blob)), + } + } +} + impl From for Value { fn from(value: Numeric) -> Self { match value {