diff --git a/simulator/generation/mod.rs b/simulator/generation/mod.rs index be550aabb..8e61472ce 100644 --- a/simulator/generation/mod.rs +++ b/simulator/generation/mod.rs @@ -73,9 +73,9 @@ pub(crate) fn one_of<'a, T, R: Rng>(choices: Vec T + 'a>>, /// The function takes a list of functions that return an Option, along with number of retries /// to make before giving up. pub(crate) fn backtrack<'a, T, R: Rng>( - mut choices: Vec<(u32, Box Option + 'a>)>, + mut choices: Vec<(usize, Box Option + 'a>)>, rng: &mut R, -) -> T { +) -> Option { loop { // If there are no more choices left, we give up let choices_ = choices @@ -84,14 +84,15 @@ pub(crate) fn backtrack<'a, T, R: Rng>( .filter(|(_, (retries, _))| *retries > 0) .collect::>(); if choices_.is_empty() { - panic!("backtrack: no more choices left"); + tracing::trace!("backtrack: no more choices left"); + return None; } // Run a one_of on the remaining choices let (choice_index, choice) = pick(&choices_, rng); let choice_index = *choice_index; // If the choice returns None, we decrement the number of retries and try again let result = choice.1(rng); - if let Some(result) = result { + if result.is_some() { return result; } else { choices[choice_index].0 -= 1; diff --git a/simulator/generation/predicate/binary.rs b/simulator/generation/predicate/binary.rs index 2b590cebf..426493d76 100644 --- a/simulator/generation/predicate/binary.rs +++ b/simulator/generation/predicate/binary.rs @@ -54,24 +54,24 @@ impl Predicate { } /// Produces a true [ast::Expr::Binary] [Predicate] that is true for the provided row in the given table - pub fn true_binary(rng: &mut R, (t, row): (&Table, &Vec)) -> Predicate { + pub fn true_binary(rng: &mut R, t: &Table, row: &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]; - let predicate = backtrack( + let expr = backtrack( vec![ ( 1, Box::new(|_| { - Some(Predicate(Expr::Binary( + Some(Expr::Binary( Box::new(ast::Expr::Qualified( ast::Name(t.name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Equals, Box::new(Expr::Literal(value.into())), - ))) + )) }), ), ( @@ -81,14 +81,14 @@ impl Predicate { if &v == value { None } else { - Some(Predicate(Expr::Binary( + Some(Expr::Binary( Box::new(ast::Expr::Qualified( ast::Name(t.name.clone()), ast::Name(column.name.clone()), )), ast::Operator::NotEquals, Box::new(Expr::Literal(v.into())), - ))) + )) } }), ), @@ -96,35 +96,35 @@ impl Predicate { 1, Box::new(|rng| { let lt_value = LTValue::arbitrary_from(rng, value).0; - Some(Predicate(Expr::Binary( + Some(Expr::Binary( Box::new(ast::Expr::Qualified( ast::Name(t.name.clone()), 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( + Some(Expr::Binary( Box::new(ast::Expr::Qualified( ast::Name(t.name.clone()), 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 { + Expr::Like { lhs: Box::new(ast::Expr::Qualified( ast::Name(t.name.clone()), ast::Name(column.name.clone()), @@ -133,33 +133,34 @@ impl Predicate { op: ast::LikeOperator::Like, rhs: Box::new(Expr::Literal(like.0.into())), escape: None, // TODO: implement - }) + } }) }), ), ], rng, ); - predicate + // Backtrack will always return Some here + Predicate(expr.unwrap()) } /// Produces an [ast::Expr::Binary] [Predicate] that is false for the provided row in the given table - pub fn false_binary(rng: &mut R, (t, row): (&Table, &Vec)) -> Predicate { + pub fn false_binary(rng: &mut R, t: &Table, row: &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( + let expr = one_of( vec![ Box::new(|_| { - Predicate(Expr::Binary( + Expr::Binary( Box::new(ast::Expr::Qualified( ast::Name(t.name.clone()), ast::Name(column.name.clone()), )), ast::Operator::NotEquals, Box::new(Expr::Literal(value.into())), - )) + ) }), Box::new(|rng| { let v = loop { @@ -168,40 +169,41 @@ impl Predicate { break v; } }; - Predicate(Expr::Binary( + Expr::Binary( Box::new(ast::Expr::Qualified( ast::Name(t.name.clone()), 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( + Expr::Binary( Box::new(ast::Expr::Qualified( ast::Name(t.name.clone()), 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( + Expr::Binary( Box::new(ast::Expr::Qualified( ast::Name(t.name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Less, Box::new(Expr::Literal(lt_value.into())), - )) + ) }), ], rng, - ) + ); + Predicate(expr) } } diff --git a/simulator/generation/predicate/mod.rs b/simulator/generation/predicate/mod.rs index 8ac5d0696..fe8d24dc7 100644 --- a/simulator/generation/predicate/mod.rs +++ b/simulator/generation/predicate/mod.rs @@ -9,6 +9,7 @@ use crate::model::{ use super::{one_of, ArbitraryFrom}; mod binary; +mod unary; struct CompoundPredicate(Predicate); struct SimplePredicate(Predicate); @@ -52,11 +53,11 @@ impl ArbitraryFrom<(&Table, &Vec)> for Predicate { // Produce some true and false predicates let mut true_predicates = (1..=rng.gen_range(1..=4)) - .map(|_| Predicate::true_binary(rng, (t, row))) + .map(|_| Predicate::true_binary(rng, t, row)) .collect::>(); let false_predicates = (0..=rng.gen_range(0..=3)) - .map(|_| Predicate::false_binary(rng, (t, row))) + .map(|_| Predicate::false_binary(rng, t, row)) .collect::>(); // Start building a top level predicate from a true predicate diff --git a/simulator/generation/predicate/unary.rs b/simulator/generation/predicate/unary.rs new file mode 100644 index 000000000..7e8a72bc5 --- /dev/null +++ b/simulator/generation/predicate/unary.rs @@ -0,0 +1,101 @@ +//! Contains code for generation for [ast::Expr::Unary] Predicate + +use limbo_sqlite3_parser::ast::{self, Expr}; + +use crate::{ + generation::{ + backtrack, pick, predicate::SimplePredicate, ArbitraryFrom as _, ArbitraryFromMaybe, + }, + model::{ + query::predicate::Predicate, + table::{Table, Value}, + }, +}; + +pub struct TrueValue(pub Value); + +impl ArbitraryFromMaybe<&Value> for TrueValue { + fn arbitrary_from_maybe(_rng: &mut R, value: &Value) -> Option + where + Self: Sized, + { + // If the Value is a true value return it else you cannot return a true Value + value.into_bool().then_some(Self(value.clone())) + } +} + +impl ArbitraryFromMaybe<&Vec<&Value>> for TrueValue { + fn arbitrary_from_maybe(rng: &mut R, values: &Vec<&Value>) -> Option + where + Self: Sized, + { + if values.is_empty() { + return Some(Self(Value::TRUE)); + } + + let value = pick(values, rng); + Self::arbitrary_from_maybe(rng, *value) + } +} + +pub struct FalseValue(pub Value); + +impl ArbitraryFromMaybe<&Value> for FalseValue { + fn arbitrary_from_maybe(_rng: &mut R, value: &Value) -> Option + where + Self: Sized, + { + // If the Value is a false value return it else you cannot return a false Value + (!value.into_bool()).then_some(Self(value.clone())) + } +} + +impl ArbitraryFromMaybe<&Vec<&Value>> for FalseValue { + fn arbitrary_from_maybe(rng: &mut R, values: &Vec<&Value>) -> Option + where + Self: Sized, + { + if values.is_empty() { + return Some(Self(Value::TRUE)); + } + + let value = pick(values, rng); + Self::arbitrary_from_maybe(rng, *value) + } +} + +impl SimplePredicate { + /// Generates a true [ast::Expr::Unary] [SimplePredicate] from a [Table] + pub fn true_unary(rng: &mut R, table: &Table, column_index: usize) -> Self { + let column_values = table + .rows + .iter() + .map(|r| &r[column_index]) + .collect::>(); + let num_retries = column_values.len(); + let expr = backtrack( + vec![ + ( + num_retries, + Box::new(|rng| { + TrueValue::arbitrary_from_maybe(rng, &column_values).map(|value| { + // Positive is a no-op in Sqlite + Expr::unary(ast::UnaryOperator::Positive, Expr::Literal(value.0.into())) + }) + }), + ), + ( + num_retries, + Box::new(|rng| { + FalseValue::arbitrary_from_maybe(rng, &column_values).map(|value| { + Expr::unary(ast::UnaryOperator::Negative, Expr::Literal(value.0.into())) + }) + }), + ), + ], + rng, + ); + // If cannot generate a value + SimplePredicate(Predicate(expr.unwrap_or(Expr::Literal(Value::TRUE.into())))) + } +} diff --git a/simulator/generation/query.rs b/simulator/generation/query.rs index ca638174c..eaaeec071 100644 --- a/simulator/generation/query.rs +++ b/simulator/generation/query.rs @@ -78,6 +78,7 @@ impl ArbitraryFrom<&SimulatorEnv> for Insert { }) }; + // Backtrack here cannot return None backtrack( vec![ (1, Box::new(|rng| gen_values(rng))), @@ -86,6 +87,7 @@ impl ArbitraryFrom<&SimulatorEnv> for Insert { ], rng, ) + .unwrap() } }