all Arbitrary traits need to pass a GenerationContext

This commit is contained in:
pedrocarlo
2025-08-27 13:03:18 -03:00
parent 1a8b78afd8
commit 9bc8bdb279
8 changed files with 392 additions and 299 deletions

View File

@@ -5,7 +5,7 @@ use turso_parser::ast::{
use crate::{
generation::{
frequency, gen_random_text, one_of, pick, pick_index, Arbitrary, ArbitraryFrom,
ArbitrarySizedFrom, GenerationContext,
ArbitrarySized, ArbitrarySizedFrom, GenerationContext,
},
model::table::SimValue,
};
@@ -14,8 +14,21 @@ impl<T> Arbitrary for Box<T>
where
T: Arbitrary,
{
fn arbitrary<R: rand::Rng>(rng: &mut R) -> Self {
Box::from(T::arbitrary(rng))
fn arbitrary<R: rand::Rng, C: GenerationContext>(rng: &mut R, context: &C) -> Self {
Box::from(T::arbitrary(rng, context))
}
}
impl<T> ArbitrarySized for Box<T>
where
T: ArbitrarySized,
{
fn arbitrary_sized<R: rand::Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
size: usize,
) -> Self {
Box::from(T::arbitrary_sized(rng, context, size))
}
}
@@ -23,8 +36,13 @@ impl<A, T> ArbitrarySizedFrom<A> for Box<T>
where
T: ArbitrarySizedFrom<A>,
{
fn arbitrary_sized_from<R: rand::Rng>(rng: &mut R, t: A, size: usize) -> Self {
Box::from(T::arbitrary_sized_from(rng, t, size))
fn arbitrary_sized_from<R: rand::Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
t: A,
size: usize,
) -> Self {
Box::from(T::arbitrary_sized_from(rng, context, t, size))
}
}
@@ -32,8 +50,8 @@ impl<T> Arbitrary for Option<T>
where
T: Arbitrary,
{
fn arbitrary<R: rand::Rng>(rng: &mut R) -> Self {
rng.random_bool(0.5).then_some(T::arbitrary(rng))
fn arbitrary<R: rand::Rng, C: GenerationContext>(rng: &mut R, context: &C) -> Self {
rng.random_bool(0.5).then_some(T::arbitrary(rng, context))
}
}
@@ -41,9 +59,14 @@ impl<A, T> ArbitrarySizedFrom<A> for Option<T>
where
T: ArbitrarySizedFrom<A>,
{
fn arbitrary_sized_from<R: rand::Rng>(rng: &mut R, t: A, size: usize) -> Self {
fn arbitrary_sized_from<R: rand::Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
t: A,
size: usize,
) -> Self {
rng.random_bool(0.5)
.then_some(T::arbitrary_sized_from(rng, t, size))
.then_some(T::arbitrary_sized_from(rng, context, t, size))
}
}
@@ -51,20 +74,26 @@ impl<A: Copy, T> ArbitraryFrom<A> for Vec<T>
where
T: ArbitraryFrom<A>,
{
fn arbitrary_from<R: rand::Rng>(rng: &mut R, t: A) -> Self {
fn arbitrary_from<R: rand::Rng, C: GenerationContext>(rng: &mut R, context: &C, t: A) -> Self {
let size = rng.random_range(0..5);
(0..size).map(|_| T::arbitrary_from(rng, t)).collect()
(0..size)
.map(|_| T::arbitrary_from(rng, context, t))
.collect()
}
}
// Freestyling generation
impl<C: GenerationContext> ArbitrarySizedFrom<&C> for Expr {
fn arbitrary_sized_from<R: rand::Rng>(rng: &mut R, t: &C, size: usize) -> Self {
impl ArbitrarySized for Expr {
fn arbitrary_sized<R: rand::Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
size: usize,
) -> Self {
frequency(
vec![
(
1,
Box::new(|rng| Expr::Literal(ast::Literal::arbitrary_from(rng, t))),
Box::new(|rng| Expr::Literal(ast::Literal::arbitrary(rng, context))),
),
(
size,
@@ -79,9 +108,9 @@ impl<C: GenerationContext> ArbitrarySizedFrom<&C> for Expr {
// }),
Box::new(|rng: &mut R| {
Expr::Binary(
Box::arbitrary_sized_from(rng, t, size - 1),
Operator::arbitrary(rng),
Box::arbitrary_sized_from(rng, t, size - 1),
Box::arbitrary_sized(rng, context, size - 1),
Operator::arbitrary(rng, context),
Box::arbitrary_sized(rng, context, size - 1),
)
}),
// Box::new(|rng| Expr::Case {
@@ -133,8 +162,8 @@ impl<C: GenerationContext> ArbitrarySizedFrom<&C> for Expr {
// })
Box::new(|rng| {
Expr::Unary(
UnaryOperator::arbitrary_from(rng, t),
Box::arbitrary_sized_from(rng, t, size - 1),
UnaryOperator::arbitrary(rng, context),
Box::arbitrary_sized(rng, context, size - 1),
)
}),
// TODO: skip Exists for now
@@ -159,7 +188,7 @@ impl<C: GenerationContext> ArbitrarySizedFrom<&C> for Expr {
}
impl Arbitrary for Operator {
fn arbitrary<R: rand::Rng>(rng: &mut R) -> Self {
fn arbitrary<R: rand::Rng, C: GenerationContext>(rng: &mut R, _context: &C) -> Self {
let choices = [
Operator::Add,
Operator::And,
@@ -190,7 +219,7 @@ impl Arbitrary for Operator {
}
impl Arbitrary for Type {
fn arbitrary<R: rand::Rng>(rng: &mut R) -> Self {
fn arbitrary<R: rand::Rng, C: GenerationContext>(rng: &mut R, _context: &C) -> Self {
let name = pick(&["INT", "INTEGER", "REAL", "TEXT", "BLOB", "ANY"], rng).to_string();
Self {
name,
@@ -199,11 +228,11 @@ impl Arbitrary for Type {
}
}
impl<C: GenerationContext> ArbitraryFrom<&C> for QualifiedName {
fn arbitrary_from<R: rand::Rng>(rng: &mut R, t: &C) -> Self {
impl Arbitrary for QualifiedName {
fn arbitrary<R: rand::Rng, C: GenerationContext>(rng: &mut R, context: &C) -> Self {
// TODO: for now just generate table name
let table_idx = pick_index(t.tables().len(), rng);
let table = &t.tables()[table_idx];
let table_idx = pick_index(context.tables().len(), rng);
let table = &context.tables()[table_idx];
// TODO: for now forego alias
Self {
db_name: None,
@@ -213,8 +242,8 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for QualifiedName {
}
}
impl<C: GenerationContext> ArbitraryFrom<&C> for LikeOperator {
fn arbitrary_from<R: rand::Rng>(rng: &mut R, _t: &C) -> Self {
impl Arbitrary for LikeOperator {
fn arbitrary<R: rand::Rng, C: GenerationContext>(rng: &mut R, _t: &C) -> Self {
let choice = rng.random_range(0..4);
match choice {
0 => LikeOperator::Glob,
@@ -227,8 +256,8 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for LikeOperator {
}
// Current implementation does not take into account the columns affinity nor if table is Strict
impl<C: GenerationContext> ArbitraryFrom<&C> for ast::Literal {
fn arbitrary_from<R: rand::Rng>(rng: &mut R, _t: &C) -> Self {
impl Arbitrary for ast::Literal {
fn arbitrary<R: rand::Rng, C: GenerationContext>(rng: &mut R, _t: &C) -> Self {
loop {
let choice = rng.random_range(0..5);
let lit = match choice {
@@ -255,7 +284,11 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for ast::Literal {
// Creates a litreal value
impl ArbitraryFrom<&Vec<&SimValue>> for ast::Expr {
fn arbitrary_from<R: rand::Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Self {
fn arbitrary_from<R: rand::Rng, C: GenerationContext>(
rng: &mut R,
_context: &C,
values: &Vec<&SimValue>,
) -> Self {
if values.is_empty() {
return Self::Literal(ast::Literal::Null);
}
@@ -265,8 +298,8 @@ impl ArbitraryFrom<&Vec<&SimValue>> for ast::Expr {
}
}
impl<C: GenerationContext> ArbitraryFrom<&C> for UnaryOperator {
fn arbitrary_from<R: rand::Rng>(rng: &mut R, _t: &C) -> Self {
impl Arbitrary for UnaryOperator {
fn arbitrary<R: rand::Rng, C: GenerationContext>(rng: &mut R, _t: &C) -> Self {
let choice = rng.random_range(0..4);
match choice {
0 => Self::BitwiseNot,

View File

@@ -19,7 +19,7 @@ type Choice<'a, R, T> = (usize, Box<dyn Fn(&mut R) -> Option<T> + 'a>);
/// the possible values of the type, with a bias towards smaller values for
/// practicality.
pub trait Arbitrary {
fn arbitrary<R: Rng>(rng: &mut R) -> Self;
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, context: &C) -> Self;
}
/// ArbitrarySized trait for generating random values of a specific size
@@ -29,7 +29,8 @@ pub trait Arbitrary {
/// must fit in the given size. This is useful for generating values that are
/// constrained by a specific size, such as integers or strings.
pub trait ArbitrarySized {
fn arbitrary_sized<R: Rng>(rng: &mut R, size: usize) -> Self;
fn arbitrary_sized<R: Rng, C: GenerationContext>(rng: &mut R, context: &C, size: usize)
-> Self;
}
/// ArbitraryFrom trait for generating random values from a given value
@@ -38,19 +39,7 @@ pub trait ArbitrarySized {
/// such as generating an integer within an interval, or a value that fits in a table,
/// or a predicate satisfying a given table row.
pub trait ArbitraryFrom<T> {
fn arbitrary_from<R: Rng>(rng: &mut R, t: T) -> Self;
}
pub trait ArbitraryContext {
fn arbitrary_with_context<R: Rng, C: GenerationContext>(rng: &mut R, context: &C) -> Self;
}
pub trait ArbitraryContextFrom<T> {
fn arbitrary_with_context_from<R: Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
t: T,
) -> Self;
fn arbitrary_from<R: Rng, C: GenerationContext>(rng: &mut R, context: &C, t: T) -> Self;
}
/// ArbitrarySizedFrom trait for generating random values from a given value
@@ -62,12 +51,21 @@ pub trait ArbitraryContextFrom<T> {
/// This is useful for generating values that are constrained by a specific size,
/// such as integers or strings, while still being dependent on the given value.
pub trait ArbitrarySizedFrom<T> {
fn arbitrary_sized_from<R: Rng>(rng: &mut R, t: T, size: usize) -> Self;
fn arbitrary_sized_from<R: Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
t: T,
size: usize,
) -> Self;
}
/// ArbitraryFromMaybe trait for fallibally generating random values from a given value
pub trait ArbitraryFromMaybe<T> {
fn arbitrary_from_maybe<R: Rng>(rng: &mut R, t: T) -> Option<Self>
fn arbitrary_from_maybe<R: Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
t: T,
) -> Option<Self>
where
Self: Sized;
}
@@ -187,3 +185,27 @@ where
}
picked
}
#[cfg(test)]
mod tests {
use crate::{
generation::{GenerationContext, Opts},
model::table::Table,
};
#[derive(Debug, Default, Clone)]
pub struct TestContext {
pub opts: Opts,
pub tables: Vec<Table>,
}
impl GenerationContext for TestContext {
fn tables(&self) -> &Vec<Table> {
&self.tables
}
fn opts(&self) -> &Opts {
&self.opts
}
}
}

View File

@@ -4,6 +4,12 @@ use rand::distr::weighted::WeightedIndex;
use crate::model::table::Table;
/// Trait used to provide context to generation functions
pub trait GenerationContext {
fn tables(&self) -> &Vec<Table>;
fn opts(&self) -> &Opts;
}
#[derive(Debug, Clone)]
pub struct Opts {
/// Indexes enabled
@@ -22,12 +28,6 @@ impl Default for Opts {
}
}
/// Trait used to provide context to generation functions
pub trait GenerationContext {
fn tables(&self) -> &Vec<Table>;
fn opts(&self) -> &Opts;
}
#[derive(Debug, Clone)]
pub struct TableOpts {
pub large_table: LargeTableOpts,

View File

@@ -7,7 +7,7 @@ use crate::{
backtrack, one_of, pick,
predicate::{CompoundPredicate, SimplePredicate},
table::{GTValue, LTValue, LikeValue},
ArbitraryFrom, ArbitraryFromMaybe as _,
ArbitraryFrom, ArbitraryFromMaybe as _, GenerationContext,
},
model::{
query::predicate::Predicate,
@@ -17,8 +17,9 @@ use crate::{
impl Predicate {
/// Generate an [ast::Expr::Binary] [Predicate] from a column and [SimValue]
pub fn from_column_binary<R: rand::Rng>(
pub fn from_column_binary<R: rand::Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
column_name: &str,
value: &SimValue,
) -> Predicate {
@@ -32,7 +33,7 @@ impl Predicate {
)
}),
Box::new(|rng| {
let gt_value = GTValue::arbitrary_from(rng, value).0;
let gt_value = GTValue::arbitrary_from(rng, context, value).0;
Expr::Binary(
Box::new(Expr::Id(ast::Name::Ident(column_name.to_string()))),
ast::Operator::Greater,
@@ -40,7 +41,7 @@ impl Predicate {
)
}),
Box::new(|rng| {
let lt_value = LTValue::arbitrary_from(rng, value).0;
let lt_value = LTValue::arbitrary_from(rng, context, value).0;
Expr::Binary(
Box::new(Expr::Id(ast::Name::Ident(column_name.to_string()))),
ast::Operator::Less,
@@ -54,7 +55,12 @@ 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: &Table, row: &[SimValue]) -> Predicate {
pub fn true_binary<R: rand::Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
t: &Table,
row: &[SimValue],
) -> Predicate {
// Pick a column
let column_index = rng.random_range(0..t.columns.len());
let mut column = t.columns[column_index].clone();
@@ -93,7 +99,7 @@ impl Predicate {
(
1,
Box::new(|rng| {
let v = SimValue::arbitrary_from(rng, &column.column_type);
let v = SimValue::arbitrary_from(rng, context, &column.column_type);
if &v == value {
None
} else {
@@ -111,7 +117,7 @@ impl Predicate {
(
1,
Box::new(|rng| {
let lt_value = LTValue::arbitrary_from(rng, value).0;
let lt_value = LTValue::arbitrary_from(rng, context, value).0;
Some(Expr::Binary(
Box::new(ast::Expr::Qualified(
ast::Name::new(&table_name),
@@ -125,7 +131,7 @@ impl Predicate {
(
1,
Box::new(|rng| {
let gt_value = GTValue::arbitrary_from(rng, value).0;
let gt_value = GTValue::arbitrary_from(rng, context, value).0;
Some(Expr::Binary(
Box::new(ast::Expr::Qualified(
ast::Name::new(&table_name),
@@ -140,7 +146,7 @@ impl Predicate {
1,
Box::new(|rng| {
// TODO: generation for Like and Glob expressions should be extracted to different module
LikeValue::arbitrary_from_maybe(rng, value).map(|like| {
LikeValue::arbitrary_from_maybe(rng, context, value).map(|like| {
Expr::Like {
lhs: Box::new(ast::Expr::Qualified(
ast::Name::new(&table_name),
@@ -162,7 +168,12 @@ impl Predicate {
}
/// 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: &Table, row: &[SimValue]) -> Predicate {
pub fn false_binary<R: rand::Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
t: &Table,
row: &[SimValue],
) -> Predicate {
// Pick a column
let column_index = rng.random_range(0..t.columns.len());
let mut column = t.columns[column_index].clone();
@@ -197,7 +208,7 @@ impl Predicate {
}),
Box::new(|rng| {
let v = loop {
let v = SimValue::arbitrary_from(rng, &column.column_type);
let v = SimValue::arbitrary_from(rng, context, &column.column_type);
if &v != value {
break v;
}
@@ -212,7 +223,7 @@ impl Predicate {
)
}),
Box::new(|rng| {
let gt_value = GTValue::arbitrary_from(rng, value).0;
let gt_value = GTValue::arbitrary_from(rng, context, value).0;
Expr::Binary(
Box::new(ast::Expr::Qualified(
ast::Name::new(&table_name),
@@ -223,7 +234,7 @@ impl Predicate {
)
}),
Box::new(|rng| {
let lt_value = LTValue::arbitrary_from(rng, value).0;
let lt_value = LTValue::arbitrary_from(rng, context, value).0;
Expr::Binary(
Box::new(ast::Expr::Qualified(
ast::Name::new(&table_name),
@@ -242,8 +253,9 @@ impl Predicate {
impl SimplePredicate {
/// Generates a true [ast::Expr::Binary] [SimplePredicate] from a [TableContext] for a row in the table
pub fn true_binary<R: rand::Rng, T: TableContext>(
pub fn true_binary<R: rand::Rng, C: GenerationContext, T: TableContext>(
rng: &mut R,
context: &C,
table: &T,
row: &[SimValue],
) -> Self {
@@ -271,7 +283,7 @@ impl SimplePredicate {
)
}),
Box::new(|rng| {
let lt_value = LTValue::arbitrary_from(rng, column_value).0;
let lt_value = LTValue::arbitrary_from(rng, context, column_value).0;
Expr::Binary(
Box::new(Expr::Qualified(
ast::Name::new(table_name),
@@ -282,7 +294,7 @@ impl SimplePredicate {
)
}),
Box::new(|rng| {
let gt_value = GTValue::arbitrary_from(rng, column_value).0;
let gt_value = GTValue::arbitrary_from(rng, context, column_value).0;
Expr::Binary(
Box::new(Expr::Qualified(
ast::Name::new(table_name),
@@ -299,8 +311,9 @@ impl SimplePredicate {
}
/// Generates a false [ast::Expr::Binary] [SimplePredicate] from a [TableContext] for a row in the table
pub fn false_binary<R: rand::Rng, T: TableContext>(
pub fn false_binary<R: rand::Rng, C: GenerationContext, T: TableContext>(
rng: &mut R,
context: &C,
table: &T,
row: &[SimValue],
) -> Self {
@@ -328,7 +341,7 @@ impl SimplePredicate {
)
}),
Box::new(|rng| {
let gt_value = GTValue::arbitrary_from(rng, column_value).0;
let gt_value = GTValue::arbitrary_from(rng, context, column_value).0;
Expr::Binary(
Box::new(ast::Expr::Qualified(
ast::Name::new(table_name),
@@ -339,7 +352,7 @@ impl SimplePredicate {
)
}),
Box::new(|rng| {
let lt_value = LTValue::arbitrary_from(rng, column_value).0;
let lt_value = LTValue::arbitrary_from(rng, context, column_value).0;
Expr::Binary(
Box::new(ast::Expr::Qualified(
ast::Name::new(table_name),
@@ -360,8 +373,9 @@ impl CompoundPredicate {
/// Decide if you want to create an AND or an OR
///
/// Creates a Compound Predicate that is TRUE or FALSE for at least a single row
pub fn from_table_binary<R: rand::Rng, T: TableContext>(
pub fn from_table_binary<R: rand::Rng, C: GenerationContext, T: TableContext>(
rng: &mut R,
context: &C,
table: &T,
predicate_value: bool,
) -> Self {
@@ -381,7 +395,7 @@ impl CompoundPredicate {
// An AND for false requires at least one of its children to be false
if predicate_value {
(0..rng.random_range(1..=3))
.map(|_| SimplePredicate::arbitrary_from(rng, (table, row, true)).0)
.map(|_| SimplePredicate::arbitrary_from(rng, context, (table, row, true)).0)
.reduce(|accum, curr| {
Predicate(Expr::Binary(
Box::new(accum.0),
@@ -405,7 +419,7 @@ impl CompoundPredicate {
booleans
.iter()
.map(|b| SimplePredicate::arbitrary_from(rng, (table, row, *b)).0)
.map(|b| SimplePredicate::arbitrary_from(rng, context, (table, row, *b)).0)
.reduce(|accum, curr| {
Predicate(Expr::Binary(
Box::new(accum.0),
@@ -431,7 +445,7 @@ impl CompoundPredicate {
booleans
.iter()
.map(|b| SimplePredicate::arbitrary_from(rng, (table, row, *b)).0)
.map(|b| SimplePredicate::arbitrary_from(rng, context, (table, row, *b)).0)
.reduce(|accum, curr| {
Predicate(Expr::Binary(
Box::new(accum.0),
@@ -442,7 +456,7 @@ impl CompoundPredicate {
.unwrap_or(Predicate::true_())
} else {
(0..rng.random_range(1..=3))
.map(|_| SimplePredicate::arbitrary_from(rng, (table, row, false)).0)
.map(|_| SimplePredicate::arbitrary_from(rng, context, (table, row, false)).0)
.reduce(|accum, curr| {
Predicate(Expr::Binary(
Box::new(accum.0),
@@ -463,7 +477,9 @@ mod tests {
use rand_chacha::ChaCha8Rng;
use crate::{
generation::{pick, predicate::SimplePredicate, Arbitrary, ArbitraryFrom as _},
generation::{
pick, predicate::SimplePredicate, tests::TestContext, Arbitrary, ArbitraryFrom as _,
},
model::{
query::predicate::{expr_to_value, Predicate},
table::{SimValue, Table},
@@ -481,20 +497,22 @@ mod tests {
fn fuzz_true_binary_predicate() {
let seed = get_seed();
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let context = &TestContext::default();
for _ in 0..10000 {
let table = Table::arbitrary(&mut rng);
let table = Table::arbitrary(&mut rng, context);
let num_rows = rng.random_range(1..10);
let values: Vec<Vec<SimValue>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(&mut rng, context, &c.column_type))
.collect()
})
.collect();
let row = pick(&values, &mut rng);
let predicate = Predicate::true_binary(&mut rng, &table, row);
let predicate = Predicate::true_binary(&mut rng, context, &table, row);
let value = expr_to_value(&predicate.0, row, &table);
assert!(
value.as_ref().is_some_and(|value| value.as_bool()),
@@ -507,20 +525,22 @@ mod tests {
fn fuzz_false_binary_predicate() {
let seed = get_seed();
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let context = &TestContext::default();
for _ in 0..10000 {
let table = Table::arbitrary(&mut rng);
let table = Table::arbitrary(&mut rng, context);
let num_rows = rng.random_range(1..10);
let values: Vec<Vec<SimValue>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(&mut rng, context, &c.column_type))
.collect()
})
.collect();
let row = pick(&values, &mut rng);
let predicate = Predicate::false_binary(&mut rng, &table, row);
let predicate = Predicate::false_binary(&mut rng, context, &table, row);
let value = expr_to_value(&predicate.0, row, &table);
assert!(
!value.as_ref().is_some_and(|value| value.as_bool()),
@@ -533,21 +553,23 @@ mod tests {
fn fuzz_true_binary_simple_predicate() {
let seed = get_seed();
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let context = &TestContext::default();
for _ in 0..10000 {
let mut table = Table::arbitrary(&mut rng);
let mut table = Table::arbitrary(&mut rng, context);
let num_rows = rng.random_range(1..10);
let values: Vec<Vec<SimValue>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(&mut rng, context, &c.column_type))
.collect()
})
.collect();
table.rows.extend(values.clone());
let row = pick(&table.rows, &mut rng);
let predicate = SimplePredicate::true_binary(&mut rng, &table, row);
let predicate = SimplePredicate::true_binary(&mut rng, context, &table, row);
let result = values
.iter()
.map(|row| predicate.0.test(row, &table))
@@ -561,21 +583,23 @@ mod tests {
fn fuzz_false_binary_simple_predicate() {
let seed = get_seed();
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let context = &TestContext::default();
for _ in 0..10000 {
let mut table = Table::arbitrary(&mut rng);
let mut table = Table::arbitrary(&mut rng, context);
let num_rows = rng.random_range(1..10);
let values: Vec<Vec<SimValue>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(&mut rng, context, &c.column_type))
.collect()
})
.collect();
table.rows.extend(values.clone());
let row = pick(&table.rows, &mut rng);
let predicate = SimplePredicate::false_binary(&mut rng, &table, row);
let predicate = SimplePredicate::false_binary(&mut rng, context, &table, row);
let result = values
.iter()
.map(|row| predicate.0.test(row, &table))

View File

@@ -1,9 +1,12 @@
use rand::{seq::SliceRandom as _, Rng};
use turso_parser::ast::{self, Expr};
use crate::model::{
query::predicate::Predicate,
table::{SimValue, Table, TableContext},
use crate::{
generation::GenerationContext,
model::{
query::predicate::Predicate,
table::{SimValue, Table, TableContext},
},
};
use super::{one_of, ArbitraryFrom};
@@ -18,20 +21,24 @@ struct CompoundPredicate(Predicate);
struct SimplePredicate(Predicate);
impl<A: AsRef<[SimValue]>, T: TableContext> ArbitraryFrom<(&T, A, bool)> for SimplePredicate {
fn arbitrary_from<R: Rng>(rng: &mut R, (table, row, predicate_value): (&T, A, bool)) -> Self {
fn arbitrary_from<R: Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
(table, row, predicate_value): (&T, A, bool),
) -> Self {
let row = row.as_ref();
// Pick an operator
let choice = rng.random_range(0..2);
// Pick an operator
match predicate_value {
true => match choice {
0 => SimplePredicate::true_binary(rng, table, row),
1 => SimplePredicate::true_unary(rng, table, row),
0 => SimplePredicate::true_binary(rng, context, table, row),
1 => SimplePredicate::true_unary(rng, context, table, row),
_ => unreachable!(),
},
false => match choice {
0 => SimplePredicate::false_binary(rng, table, row),
1 => SimplePredicate::false_unary(rng, table, row),
0 => SimplePredicate::false_binary(rng, context, table, row),
1 => SimplePredicate::false_unary(rng, context, table, row),
_ => unreachable!(),
},
}
@@ -39,43 +46,59 @@ impl<A: AsRef<[SimValue]>, T: TableContext> ArbitraryFrom<(&T, A, bool)> for Sim
}
impl<T: TableContext> ArbitraryFrom<(&T, bool)> for CompoundPredicate {
fn arbitrary_from<R: Rng>(rng: &mut R, (table, predicate_value): (&T, bool)) -> Self {
CompoundPredicate::from_table_binary(rng, table, predicate_value)
fn arbitrary_from<R: Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
(table, predicate_value): (&T, bool),
) -> Self {
CompoundPredicate::from_table_binary(rng, context, table, predicate_value)
}
}
impl<T: TableContext> ArbitraryFrom<&T> for Predicate {
fn arbitrary_from<R: Rng>(rng: &mut R, table: &T) -> Self {
fn arbitrary_from<R: Rng, C: GenerationContext>(rng: &mut R, context: &C, table: &T) -> Self {
let predicate_value = rng.random_bool(0.5);
Predicate::arbitrary_from(rng, (table, predicate_value)).parens()
Predicate::arbitrary_from(rng, context, (table, predicate_value)).parens()
}
}
impl<T: TableContext> ArbitraryFrom<(&T, bool)> for Predicate {
fn arbitrary_from<R: Rng>(rng: &mut R, (table, predicate_value): (&T, bool)) -> Self {
CompoundPredicate::arbitrary_from(rng, (table, predicate_value)).0
fn arbitrary_from<R: Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
(table, predicate_value): (&T, bool),
) -> Self {
CompoundPredicate::arbitrary_from(rng, context, (table, predicate_value)).0
}
}
impl ArbitraryFrom<(&str, &SimValue)> for Predicate {
fn arbitrary_from<R: Rng>(rng: &mut R, (column_name, value): (&str, &SimValue)) -> Self {
Predicate::from_column_binary(rng, column_name, value)
fn arbitrary_from<R: Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
(column_name, value): (&str, &SimValue),
) -> Self {
Predicate::from_column_binary(rng, context, column_name, value)
}
}
impl ArbitraryFrom<(&Table, &Vec<SimValue>)> for Predicate {
fn arbitrary_from<R: Rng>(rng: &mut R, (t, row): (&Table, &Vec<SimValue>)) -> Self {
fn arbitrary_from<R: Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
(t, row): (&Table, &Vec<SimValue>),
) -> 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.random_range(1..=4))
.map(|_| Predicate::true_binary(rng, t, row))
.map(|_| Predicate::true_binary(rng, context, t, row))
.collect::<Vec<_>>();
let false_predicates = (0..=rng.random_range(0..=3))
.map(|_| Predicate::false_binary(rng, t, row))
.map(|_| Predicate::false_binary(rng, context, t, row))
.collect::<Vec<_>>();
// Start building a top level predicate from a true predicate
@@ -231,7 +254,9 @@ mod tests {
use rand_chacha::ChaCha8Rng;
use crate::{
generation::{pick, predicate::SimplePredicate, Arbitrary, ArbitraryFrom as _},
generation::{
pick, predicate::SimplePredicate, tests::TestContext, Arbitrary, ArbitraryFrom as _,
},
model::{
query::predicate::{expr_to_value, Predicate},
table::{SimValue, Table},
@@ -249,20 +274,23 @@ mod tests {
fn fuzz_arbitrary_table_true_simple_predicate() {
let seed = get_seed();
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let context = &TestContext::default();
for _ in 0..10000 {
let table = Table::arbitrary(&mut rng);
let table = Table::arbitrary(&mut rng, context);
let num_rows = rng.random_range(1..10);
let values: Vec<Vec<SimValue>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(&mut rng, context, &c.column_type))
.collect()
})
.collect();
let row = pick(&values, &mut rng);
let predicate = SimplePredicate::arbitrary_from(&mut rng, (&table, row, true)).0;
let predicate =
SimplePredicate::arbitrary_from(&mut rng, context, (&table, row, true)).0;
let value = expr_to_value(&predicate.0, row, &table);
assert!(
value.as_ref().is_some_and(|value| value.as_bool()),
@@ -275,20 +303,23 @@ mod tests {
fn fuzz_arbitrary_table_false_simple_predicate() {
let seed = get_seed();
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let context = &TestContext::default();
for _ in 0..10000 {
let table = Table::arbitrary(&mut rng);
let table = Table::arbitrary(&mut rng, context);
let num_rows = rng.random_range(1..10);
let values: Vec<Vec<SimValue>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(&mut rng, context, &c.column_type))
.collect()
})
.collect();
let row = pick(&values, &mut rng);
let predicate = SimplePredicate::arbitrary_from(&mut rng, (&table, row, false)).0;
let predicate =
SimplePredicate::arbitrary_from(&mut rng, context, (&table, row, false)).0;
let value = expr_to_value(&predicate.0, row, &table);
assert!(
!value.as_ref().is_some_and(|value| value.as_bool()),
@@ -301,20 +332,22 @@ mod tests {
fn fuzz_arbitrary_row_table_predicate() {
let seed = get_seed();
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let context = &TestContext::default();
for _ in 0..10000 {
let table = Table::arbitrary(&mut rng);
let table = Table::arbitrary(&mut rng, context);
let num_rows = rng.random_range(1..10);
let values: Vec<Vec<SimValue>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(&mut rng, context, &c.column_type))
.collect()
})
.collect();
let row = pick(&values, &mut rng);
let predicate = Predicate::arbitrary_from(&mut rng, (&table, row));
let predicate = Predicate::arbitrary_from(&mut rng, context, (&table, row));
let value = expr_to_value(&predicate.0, row, &table);
assert!(
value.as_ref().is_some_and(|value| value.as_bool()),
@@ -327,20 +360,22 @@ mod tests {
fn fuzz_arbitrary_true_table_predicate() {
let seed = get_seed();
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let context = &TestContext::default();
for _ in 0..10000 {
let mut table = Table::arbitrary(&mut rng);
let mut table = Table::arbitrary(&mut rng, context);
let num_rows = rng.random_range(1..10);
let values: Vec<Vec<SimValue>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(&mut rng, context, &c.column_type))
.collect()
})
.collect();
table.rows.extend(values.clone());
let predicate = Predicate::arbitrary_from(&mut rng, (&table, true));
let predicate = Predicate::arbitrary_from(&mut rng, context, (&table, true));
let result = values
.iter()
.map(|row| predicate.test(row, &table))
@@ -354,20 +389,22 @@ mod tests {
fn fuzz_arbitrary_false_table_predicate() {
let seed = get_seed();
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let context = &TestContext::default();
for _ in 0..10000 {
let mut table = Table::arbitrary(&mut rng);
let mut table = Table::arbitrary(&mut rng, context);
let num_rows = rng.random_range(1..10);
let values: Vec<Vec<SimValue>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(&mut rng, context, &c.column_type))
.collect()
})
.collect();
table.rows.extend(values.clone());
let predicate = Predicate::arbitrary_from(&mut rng, (&table, false));
let predicate = Predicate::arbitrary_from(&mut rng, context, (&table, false));
let result = values
.iter()
.map(|row| predicate.test(row, &table))

View File

@@ -5,7 +5,9 @@
use turso_parser::ast::{self, Expr};
use crate::{
generation::{backtrack, pick, predicate::SimplePredicate, ArbitraryFromMaybe},
generation::{
backtrack, pick, predicate::SimplePredicate, ArbitraryFromMaybe, GenerationContext,
},
model::{
query::predicate::Predicate,
table::{SimValue, TableContext},
@@ -15,7 +17,11 @@ use crate::{
pub struct TrueValue(pub SimValue);
impl ArbitraryFromMaybe<&SimValue> for TrueValue {
fn arbitrary_from_maybe<R: rand::Rng>(_rng: &mut R, value: &SimValue) -> Option<Self>
fn arbitrary_from_maybe<R: rand::Rng, C: GenerationContext>(
_rng: &mut R,
_context: &C,
value: &SimValue,
) -> Option<Self>
where
Self: Sized,
{
@@ -25,7 +31,11 @@ impl ArbitraryFromMaybe<&SimValue> for TrueValue {
}
impl ArbitraryFromMaybe<&Vec<&SimValue>> for TrueValue {
fn arbitrary_from_maybe<R: rand::Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Option<Self>
fn arbitrary_from_maybe<R: rand::Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
values: &Vec<&SimValue>,
) -> Option<Self>
where
Self: Sized,
{
@@ -34,14 +44,18 @@ impl ArbitraryFromMaybe<&Vec<&SimValue>> for TrueValue {
}
let value = pick(values, rng);
Self::arbitrary_from_maybe(rng, *value)
Self::arbitrary_from_maybe(rng, context, *value)
}
}
pub struct FalseValue(pub SimValue);
impl ArbitraryFromMaybe<&SimValue> for FalseValue {
fn arbitrary_from_maybe<R: rand::Rng>(_rng: &mut R, value: &SimValue) -> Option<Self>
fn arbitrary_from_maybe<R: rand::Rng, C: GenerationContext>(
_rng: &mut R,
_context: &C,
value: &SimValue,
) -> Option<Self>
where
Self: Sized,
{
@@ -51,7 +65,11 @@ impl ArbitraryFromMaybe<&SimValue> for FalseValue {
}
impl ArbitraryFromMaybe<&Vec<&SimValue>> for FalseValue {
fn arbitrary_from_maybe<R: rand::Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Option<Self>
fn arbitrary_from_maybe<R: rand::Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
values: &Vec<&SimValue>,
) -> Option<Self>
where
Self: Sized,
{
@@ -60,7 +78,7 @@ impl ArbitraryFromMaybe<&Vec<&SimValue>> for FalseValue {
}
let value = pick(values, rng);
Self::arbitrary_from_maybe(rng, *value)
Self::arbitrary_from_maybe(rng, context, *value)
}
}
@@ -68,8 +86,9 @@ impl ArbitraryFromMaybe<&Vec<&SimValue>> for FalseValue {
pub struct BitNotValue(pub SimValue);
impl ArbitraryFromMaybe<(&SimValue, bool)> for BitNotValue {
fn arbitrary_from_maybe<R: rand::Rng>(
fn arbitrary_from_maybe<R: rand::Rng, C: GenerationContext>(
_rng: &mut R,
_context: &C,
(value, predicate): (&SimValue, bool),
) -> Option<Self>
where
@@ -82,8 +101,9 @@ impl ArbitraryFromMaybe<(&SimValue, bool)> for BitNotValue {
}
impl ArbitraryFromMaybe<(&Vec<&SimValue>, bool)> for BitNotValue {
fn arbitrary_from_maybe<R: rand::Rng>(
fn arbitrary_from_maybe<R: rand::Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
(values, predicate): (&Vec<&SimValue>, bool),
) -> Option<Self>
where
@@ -94,15 +114,16 @@ impl ArbitraryFromMaybe<(&Vec<&SimValue>, bool)> for BitNotValue {
}
let value = pick(values, rng);
Self::arbitrary_from_maybe(rng, (*value, predicate))
Self::arbitrary_from_maybe(rng, context, (*value, predicate))
}
}
// TODO: have some more complex generation with columns names here as well
impl SimplePredicate {
/// Generates a true [ast::Expr::Unary] [SimplePredicate] from a [TableContext] for some values in the table
pub fn true_unary<R: rand::Rng, T: TableContext>(
pub fn true_unary<R: rand::Rng, C: GenerationContext, T: TableContext>(
rng: &mut R,
context: &C,
table: &T,
row: &[SimValue],
) -> Self {
@@ -120,7 +141,7 @@ impl SimplePredicate {
(
num_retries,
Box::new(|rng| {
TrueValue::arbitrary_from_maybe(rng, column_value).map(|value| {
TrueValue::arbitrary_from_maybe(rng, context, column_value).map(|value| {
assert!(value.0.as_bool());
// Positive is a no-op in Sqlite
Expr::unary(ast::UnaryOperator::Positive, Expr::Literal(value.0.into()))
@@ -151,7 +172,7 @@ impl SimplePredicate {
(
num_retries,
Box::new(|rng| {
FalseValue::arbitrary_from_maybe(rng, column_value).map(|value| {
FalseValue::arbitrary_from_maybe(rng, context, column_value).map(|value| {
assert!(!value.0.as_bool());
Expr::unary(ast::UnaryOperator::Not, Expr::Literal(value.0.into()))
})
@@ -167,8 +188,9 @@ impl SimplePredicate {
}
/// Generates a false [ast::Expr::Unary] [SimplePredicate] from a [TableContext] for a row in the table
pub fn false_unary<R: rand::Rng, T: TableContext>(
pub fn false_unary<R: rand::Rng, C: GenerationContext, T: TableContext>(
rng: &mut R,
context: &C,
table: &T,
row: &[SimValue],
) -> Self {
@@ -217,7 +239,7 @@ impl SimplePredicate {
(
num_retries,
Box::new(|rng| {
TrueValue::arbitrary_from_maybe(rng, column_value).map(|value| {
TrueValue::arbitrary_from_maybe(rng, context, column_value).map(|value| {
assert!(value.0.as_bool());
Expr::unary(ast::UnaryOperator::Not, Expr::Literal(value.0.into()))
})
@@ -239,7 +261,9 @@ mod tests {
use rand_chacha::ChaCha8Rng;
use crate::{
generation::{pick, predicate::SimplePredicate, Arbitrary, ArbitraryFrom as _},
generation::{
pick, predicate::SimplePredicate, tests::TestContext, Arbitrary, ArbitraryFrom as _,
},
model::table::{SimValue, Table},
};
@@ -254,21 +278,23 @@ mod tests {
fn fuzz_true_unary_simple_predicate() {
let seed = get_seed();
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let context = &TestContext::default();
for _ in 0..10000 {
let mut table = Table::arbitrary(&mut rng);
let mut table = Table::arbitrary(&mut rng, context);
let num_rows = rng.random_range(1..10);
let values: Vec<Vec<SimValue>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(&mut rng, context, &c.column_type))
.collect()
})
.collect();
table.rows.extend(values.clone());
let row = pick(&table.rows, &mut rng);
let predicate = SimplePredicate::true_unary(&mut rng, &table, row);
let predicate = SimplePredicate::true_unary(&mut rng, context, &table, row);
let result = values
.iter()
.map(|row| predicate.0.test(row, &table))
@@ -282,21 +308,23 @@ mod tests {
fn fuzz_false_unary_simple_predicate() {
let seed = get_seed();
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let context = &TestContext::default();
for _ in 0..10000 {
let mut table = Table::arbitrary(&mut rng);
let mut table = Table::arbitrary(&mut rng, context);
let num_rows = rng.random_range(1..10);
let values: Vec<Vec<SimValue>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(&mut rng, context, &c.column_type))
.collect()
})
.collect();
table.rows.extend(values.clone());
let row = pick(&table.rows, &mut rng);
let predicate = SimplePredicate::false_unary(&mut rng, &table, row);
let predicate = SimplePredicate::false_unary(&mut rng, context, &table, row);
let result = values
.iter()
.map(|row| predicate.0.test(row, &table))

View File

@@ -1,6 +1,6 @@
use crate::generation::{
gen_random_text, pick_n_unique, pick_unique, Arbitrary, ArbitraryContext, ArbitraryContextFrom,
ArbitraryFrom, ArbitrarySizedFrom, GenerationContext,
gen_random_text, pick_n_unique, pick_unique, Arbitrary, ArbitraryFrom, ArbitrarySized,
GenerationContext,
};
use crate::model::query::predicate::Predicate;
use crate::model::query::select::{
@@ -17,32 +17,20 @@ use turso_parser::ast::{Expr, SortOrder};
use super::{backtrack, pick};
impl Arbitrary for Create {
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, context: &C) -> Self {
Create {
table: Table::arbitrary(rng),
table: Table::arbitrary(rng, context),
}
}
}
impl ArbitraryContext for Create {
fn arbitrary_with_context<R: Rng, C: GenerationContext>(rng: &mut R, context: &C) -> Self {
Create {
table: Table::arbitrary_with_context(rng, context),
}
}
}
impl ArbitraryContextFrom<&Vec<Table>> for FromClause {
fn arbitrary_with_context_from<R: Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
tables: &Vec<Table>,
) -> Self {
impl Arbitrary for FromClause {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, context: &C) -> Self {
let opts = &context.opts().query.from_clause;
let weights = opts.as_weighted_index();
let num_joins = opts.joins[rng.sample(weights)].num_joins;
let mut tables = tables.clone();
let mut tables = context.tables().clone();
let mut table = pick(&tables, rng).clone();
tables.retain(|t| t.name != table.name);
@@ -83,7 +71,7 @@ impl ArbitraryContextFrom<&Vec<Table>> for FromClause {
);
}
let predicate = Predicate::arbitrary_from(rng, &table);
let predicate = Predicate::arbitrary_from(rng, context, &table);
Some(JoinedTable {
table: joined_table_name,
join_type: JoinType::Inner,
@@ -95,71 +83,9 @@ impl ArbitraryContextFrom<&Vec<Table>> for FromClause {
}
}
impl ArbitraryFrom<&Vec<Table>> for FromClause {
fn arbitrary_from<R: Rng>(rng: &mut R, tables: &Vec<Table>) -> Self {
let num_joins = match rng.random_range(0..=100) {
0..=90 => 0,
91..=97 => 1,
98..=100 => 2,
_ => unreachable!(),
};
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 mut table_context = JoinTable {
tables: Vec::new(),
rows: Vec::new(),
};
let joins: Vec<_> = (0..num_joins)
.filter_map(|_| {
if tables.is_empty() {
return None;
}
let join_table = pick(&tables, rng).clone();
let joined_table_name = join_table.name.clone();
tables.retain(|t| t.name != join_table.name);
table_context.rows = table_context
.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();
// TODO: inneficient. use a Deque to push_front?
table_context.tables.insert(0, join_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);
Some(JoinedTable {
table: joined_table_name,
join_type: JoinType::Inner,
on: predicate,
})
})
.collect();
FromClause { table: name, joins }
}
}
impl<C: GenerationContext> ArbitraryFrom<&C> for SelectInner {
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
let from = FromClause::arbitrary_from(rng, env.tables());
impl Arbitrary for SelectInner {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, env: &C) -> Self {
let from = FromClause::arbitrary(rng, env);
let tables = env.tables().clone();
let join_table = from.into_join_table(&tables);
let cuml_col_count = join_table.columns().count();
@@ -205,21 +131,25 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for SelectInner {
SelectInner {
distinctness: if env.opts().indexes {
Distinctness::arbitrary(rng)
Distinctness::arbitrary(rng, env)
} else {
Distinctness::All
},
columns: vec![ResultColumn::Star],
from: Some(from),
where_clause: Predicate::arbitrary_from(rng, &join_table),
where_clause: Predicate::arbitrary_from(rng, env, &join_table),
order_by,
}
}
}
impl<C: GenerationContext> ArbitrarySizedFrom<&C> for SelectInner {
fn arbitrary_sized_from<R: Rng>(rng: &mut R, env: &C, num_result_columns: usize) -> Self {
let mut select_inner = SelectInner::arbitrary_from(rng, env);
impl ArbitrarySized for SelectInner {
fn arbitrary_sized<R: Rng, C: GenerationContext>(
rng: &mut R,
env: &C,
num_result_columns: usize,
) -> Self {
let mut select_inner = SelectInner::arbitrary(rng, env);
let select_from = &select_inner.from.as_ref().unwrap();
let table_names = select_from
.joins
@@ -251,7 +181,7 @@ impl<C: GenerationContext> ArbitrarySizedFrom<&C> for SelectInner {
}
impl Arbitrary for Distinctness {
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, _context: &C) -> Self {
match rng.random_range(0..=5) {
0..4 => Distinctness::All,
_ => Distinctness::Distinct,
@@ -259,7 +189,7 @@ impl Arbitrary for Distinctness {
}
}
impl Arbitrary for CompoundOperator {
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, _context: &C) -> Self {
match rng.random_range(0..=1) {
0 => CompoundOperator::Union,
1 => CompoundOperator::UnionAll,
@@ -273,16 +203,16 @@ impl Arbitrary for CompoundOperator {
/// arbitrary expressions without referring to the tables.
pub struct SelectFree(pub Select);
impl<C: GenerationContext> ArbitraryFrom<&C> for SelectFree {
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
let expr = Predicate(Expr::arbitrary_sized_from(rng, env, 8));
impl Arbitrary for SelectFree {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, env: &C) -> Self {
let expr = Predicate(Expr::arbitrary_sized(rng, env, 8));
let select = Select::expr(expr);
Self(select)
}
}
impl<C: GenerationContext> ArbitraryFrom<&C> for Select {
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
impl Arbitrary for Select {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, env: &C) -> Self {
// Generate a number of selects based on the query size
// If experimental indexes are enabled, we can have selects with compounds
// Otherwise, we just have a single select with no compounds
@@ -302,10 +232,10 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for Select {
let num_result_columns = rng.random_range(1..=min_column_count_across_tables);
let mut first = SelectInner::arbitrary_sized_from(rng, env, num_result_columns);
let mut first = SelectInner::arbitrary_sized(rng, env, num_result_columns);
let mut rest: Vec<SelectInner> = (0..num_compound_selects)
.map(|_| SelectInner::arbitrary_sized_from(rng, env, num_result_columns))
.map(|_| SelectInner::arbitrary_sized(rng, env, num_result_columns))
.collect();
if !rest.is_empty() {
@@ -322,7 +252,7 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for Select {
compounds: rest
.into_iter()
.map(|s| CompoundSelect {
operator: CompoundOperator::arbitrary(rng),
operator: CompoundOperator::arbitrary(rng, env),
select: Box::new(s),
})
.collect(),
@@ -332,8 +262,8 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for Select {
}
}
impl<C: GenerationContext> ArbitraryFrom<&C> for Insert {
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
impl Arbitrary for Insert {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, env: &C) -> Self {
let gen_values = |rng: &mut R| {
let table = pick(env.tables(), rng);
let num_rows = rng.random_range(1..10);
@@ -342,7 +272,7 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for Insert {
table
.columns
.iter()
.map(|c| SimValue::arbitrary_from(rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(rng, env, &c.column_type))
.collect()
})
.collect();
@@ -356,7 +286,7 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for Insert {
// Find a non-empty table
let select_table = env.tables().iter().find(|t| !t.rows.is_empty())?;
let row = pick(&select_table.rows, rng);
let predicate = Predicate::arbitrary_from(rng, (select_table, row));
let predicate = Predicate::arbitrary_from(rng, env, (select_table, row));
// Pick another table to insert into
let select = Select::simple(select_table.name.clone(), predicate);
let table = pick(env.tables(), rng);
@@ -372,18 +302,18 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for Insert {
}
}
impl<C: GenerationContext> ArbitraryFrom<&C> for Delete {
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
impl Arbitrary for Delete {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, env: &C) -> Self {
let table = pick(env.tables(), rng);
Self {
table: table.name.clone(),
predicate: Predicate::arbitrary_from(rng, table),
predicate: Predicate::arbitrary_from(rng, env, table),
}
}
}
impl<C: GenerationContext> ArbitraryFrom<&C> for Drop {
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
impl Arbitrary for Drop {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, env: &C) -> Self {
let table = pick(env.tables(), rng);
Self {
table: table.name.clone(),
@@ -391,8 +321,8 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for Drop {
}
}
impl<C: GenerationContext> ArbitraryFrom<&C> for CreateIndex {
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
impl Arbitrary for CreateIndex {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, env: &C) -> Self {
assert!(
!env.tables().is_empty(),
"Cannot create an index when no tables exist in the environment."
@@ -439,8 +369,8 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for CreateIndex {
}
}
impl<C: GenerationContext> ArbitraryFrom<&C> for Update {
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
impl Arbitrary for Update {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, env: &C) -> Self {
let table = pick(env.tables(), rng);
let num_cols = rng.random_range(1..=table.columns.len());
let columns = pick_unique(&table.columns, num_cols, rng);
@@ -449,14 +379,14 @@ impl<C: GenerationContext> ArbitraryFrom<&C> for Update {
.map(|column| {
(
column.name.clone(),
SimValue::arbitrary_from(rng, &column.column_type),
SimValue::arbitrary_from(rng, env, &column.column_type),
)
})
.collect();
Update {
table: table.name.clone(),
set_values,
predicate: Predicate::arbitrary_from(rng, table),
predicate: Predicate::arbitrary_from(rng, env, table),
}
}
}

View File

@@ -4,24 +4,23 @@ use rand::Rng;
use turso_core::Value;
use crate::generation::{
gen_random_text, pick, readable_name_custom, Arbitrary, ArbitraryContext, ArbitraryFrom,
GenerationContext, Opts,
gen_random_text, pick, readable_name_custom, Arbitrary, ArbitraryFrom, GenerationContext,
};
use crate::model::table::{Column, ColumnType, Name, SimValue, Table};
use super::ArbitraryFromMaybe;
impl Arbitrary for Name {
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, _c: &C) -> Self {
let name = readable_name_custom("_", rng);
Name(name.replace("-", "_"))
}
}
impl Table {
fn gen_table<R: Rng>(rng: &mut R, opts: &Opts) -> Self {
let opts = opts.table.clone();
let name = Name::arbitrary(rng).0;
impl Arbitrary for Table {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, context: &C) -> Self {
let opts = context.opts().table.clone();
let name = Name::arbitrary(rng, context).0;
let large_table =
opts.large_table.enable && rng.random_bool(opts.large_table.large_table_prob);
let column_size = if large_table {
@@ -30,7 +29,7 @@ impl Table {
rng.random_range(opts.column_range)
} as usize;
let mut column_set = HashSet::with_capacity(column_size);
for col in std::iter::repeat_with(|| Column::arbitrary(rng)) {
for col in std::iter::repeat_with(|| Column::arbitrary(rng, context)) {
column_set.insert(col);
if column_set.len() == column_size {
break;
@@ -46,22 +45,10 @@ impl Table {
}
}
impl Arbitrary for Table {
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
Table::gen_table(rng, &Opts::default())
}
}
impl ArbitraryContext for Table {
fn arbitrary_with_context<R: Rng, C: GenerationContext>(rng: &mut R, context: &C) -> Self {
Table::gen_table(rng, context.opts())
}
}
impl Arbitrary for Column {
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
let name = Name::arbitrary(rng).0;
let column_type = ColumnType::arbitrary(rng);
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, context: &C) -> Self {
let name = Name::arbitrary(rng, context).0;
let column_type = ColumnType::arbitrary(rng, context);
Self {
name,
column_type,
@@ -72,16 +59,20 @@ impl Arbitrary for Column {
}
impl Arbitrary for ColumnType {
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
fn arbitrary<R: Rng, C: GenerationContext>(rng: &mut R, _context: &C) -> Self {
pick(&[Self::Integer, Self::Float, Self::Text, Self::Blob], rng).to_owned()
}
}
impl ArbitraryFrom<&Table> for Vec<SimValue> {
fn arbitrary_from<R: Rng>(rng: &mut R, table: &Table) -> Self {
fn arbitrary_from<R: Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
table: &Table,
) -> Self {
let mut row = Vec::new();
for column in table.columns.iter() {
let value = SimValue::arbitrary_from(rng, &column.column_type);
let value = SimValue::arbitrary_from(rng, context, &column.column_type);
row.push(value);
}
row
@@ -89,7 +80,11 @@ impl ArbitraryFrom<&Table> for Vec<SimValue> {
}
impl ArbitraryFrom<&Vec<&SimValue>> for SimValue {
fn arbitrary_from<R: Rng>(rng: &mut R, values: &Vec<&Self>) -> Self {
fn arbitrary_from<R: Rng, C: GenerationContext>(
rng: &mut R,
_context: &C,
values: &Vec<&Self>,
) -> Self {
if values.is_empty() {
return Self(Value::Null);
}
@@ -99,7 +94,11 @@ impl ArbitraryFrom<&Vec<&SimValue>> for SimValue {
}
impl ArbitraryFrom<&ColumnType> for SimValue {
fn arbitrary_from<R: Rng>(rng: &mut R, column_type: &ColumnType) -> Self {
fn arbitrary_from<R: Rng, C: GenerationContext>(
rng: &mut R,
_context: &C,
column_type: &ColumnType,
) -> Self {
let value = match column_type {
ColumnType::Integer => Value::Integer(rng.random_range(i64::MIN..i64::MAX)),
ColumnType::Float => Value::Float(rng.random_range(-1e10..1e10)),
@@ -113,19 +112,27 @@ impl ArbitraryFrom<&ColumnType> for SimValue {
pub struct LTValue(pub SimValue);
impl ArbitraryFrom<&Vec<&SimValue>> for LTValue {
fn arbitrary_from<R: Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Self {
fn arbitrary_from<R: Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
values: &Vec<&SimValue>,
) -> Self {
if values.is_empty() {
return Self(SimValue(Value::Null));
}
// Get value less than all values
let value = Value::exec_min(values.iter().map(|value| &value.0));
Self::arbitrary_from(rng, &SimValue(value))
Self::arbitrary_from(rng, context, &SimValue(value))
}
}
impl ArbitraryFrom<&SimValue> for LTValue {
fn arbitrary_from<R: Rng>(rng: &mut R, value: &SimValue) -> Self {
fn arbitrary_from<R: Rng, C: GenerationContext>(
rng: &mut R,
_context: &C,
value: &SimValue,
) -> Self {
let new_value = match &value.0 {
Value::Integer(i) => Value::Integer(rng.random_range(i64::MIN..*i - 1)),
Value::Float(f) => Value::Float(f - rng.random_range(0.0..1e10)),
@@ -175,19 +182,27 @@ impl ArbitraryFrom<&SimValue> for LTValue {
pub struct GTValue(pub SimValue);
impl ArbitraryFrom<&Vec<&SimValue>> for GTValue {
fn arbitrary_from<R: Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Self {
fn arbitrary_from<R: Rng, C: GenerationContext>(
rng: &mut R,
context: &C,
values: &Vec<&SimValue>,
) -> Self {
if values.is_empty() {
return Self(SimValue(Value::Null));
}
// Get value greater than all values
let value = Value::exec_max(values.iter().map(|value| &value.0));
Self::arbitrary_from(rng, &SimValue(value))
Self::arbitrary_from(rng, context, &SimValue(value))
}
}
impl ArbitraryFrom<&SimValue> for GTValue {
fn arbitrary_from<R: Rng>(rng: &mut R, value: &SimValue) -> Self {
fn arbitrary_from<R: Rng, C: GenerationContext>(
rng: &mut R,
_context: &C,
value: &SimValue,
) -> Self {
let new_value = match &value.0 {
Value::Integer(i) => Value::Integer(rng.random_range(*i..i64::MAX)),
Value::Float(f) => Value::Float(rng.random_range(*f..1e10)),
@@ -237,7 +252,11 @@ impl ArbitraryFrom<&SimValue> for GTValue {
pub struct LikeValue(pub SimValue);
impl ArbitraryFromMaybe<&SimValue> for LikeValue {
fn arbitrary_from_maybe<R: Rng>(rng: &mut R, value: &SimValue) -> Option<Self> {
fn arbitrary_from_maybe<R: Rng, C: GenerationContext>(
rng: &mut R,
_context: &C,
value: &SimValue,
) -> Option<Self> {
match &value.0 {
value @ Value::Text(..) => {
let t = value.to_string();