adjustments to binary functions + backtrack return Option<T> + start of unary Predicate

This commit is contained in:
pedrocarlo
2025-06-05 01:21:13 -03:00
parent c12fc23516
commit 176ec3b0ea
5 changed files with 137 additions and 30 deletions

View File

@@ -73,9 +73,9 @@ pub(crate) fn one_of<'a, T, R: Rng>(choices: Vec<Box<dyn Fn(&mut R) -> T + 'a>>,
/// The function takes a list of functions that return an Option<T>, along with number of retries
/// to make before giving up.
pub(crate) fn backtrack<'a, T, R: Rng>(
mut choices: Vec<(u32, Box<dyn Fn(&mut R) -> Option<T> + 'a>)>,
mut choices: Vec<(usize, Box<dyn Fn(&mut R) -> Option<T> + 'a>)>,
rng: &mut R,
) -> T {
) -> Option<T> {
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::<Vec<_>>();
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;

View File

@@ -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<R: rand::Rng>(rng: &mut R, (t, row): (&Table, &Vec<Value>)) -> Predicate {
pub fn true_binary<R: rand::Rng>(rng: &mut R, t: &Table, row: &Vec<Value>) -> 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<R: rand::Rng>(rng: &mut R, (t, row): (&Table, &Vec<Value>)) -> Predicate {
pub fn false_binary<R: rand::Rng>(rng: &mut R, t: &Table, row: &Vec<Value>) -> 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)
}
}

View File

@@ -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<Value>)> 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::<Vec<_>>();
let false_predicates = (0..=rng.gen_range(0..=3))
.map(|_| Predicate::false_binary(rng, (t, row)))
.map(|_| Predicate::false_binary(rng, t, row))
.collect::<Vec<_>>();
// Start building a top level predicate from a true predicate

View File

@@ -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<R: rand::Rng>(_rng: &mut R, value: &Value) -> Option<Self>
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<R: rand::Rng>(rng: &mut R, values: &Vec<&Value>) -> Option<Self>
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<R: rand::Rng>(_rng: &mut R, value: &Value) -> Option<Self>
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<R: rand::Rng>(rng: &mut R, values: &Vec<&Value>) -> Option<Self>
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<R: rand::Rng>(rng: &mut R, table: &Table, column_index: usize) -> Self {
let column_values = table
.rows
.iter()
.map(|r| &r[column_index])
.collect::<Vec<_>>();
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()))))
}
}

View File

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