mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-25 12:04:21 +01:00
The number of columns in the row can be less than the number of columns in the table so fix out of bounds error in indexing.
334 lines
11 KiB
Rust
334 lines
11 KiB
Rust
//! Contains code regarding generation for [ast::Expr::Unary] Predicate
|
|
//! TODO: for now just generating [ast::Literal], but want to also generate Columns and any
|
|
//! arbitrary [ast::Expr]
|
|
|
|
use turso_parser::ast::{self, Expr};
|
|
|
|
use crate::{
|
|
generation::{
|
|
backtrack, pick, predicate::SimplePredicate, ArbitraryFromMaybe, GenerationContext,
|
|
},
|
|
model::{
|
|
query::predicate::Predicate,
|
|
table::{SimValue, TableContext},
|
|
},
|
|
};
|
|
|
|
pub struct TrueValue(pub SimValue);
|
|
|
|
impl ArbitraryFromMaybe<&SimValue> for TrueValue {
|
|
fn arbitrary_from_maybe<R: rand::Rng, C: GenerationContext>(
|
|
_rng: &mut R,
|
|
_context: &C,
|
|
value: &SimValue,
|
|
) -> Option<Self>
|
|
where
|
|
Self: Sized,
|
|
{
|
|
// If the Value is a true value return it else you cannot return a true Value
|
|
value.as_bool().then_some(Self(value.clone()))
|
|
}
|
|
}
|
|
|
|
impl ArbitraryFromMaybe<&Vec<&SimValue>> for TrueValue {
|
|
fn arbitrary_from_maybe<R: rand::Rng, C: GenerationContext>(
|
|
rng: &mut R,
|
|
context: &C,
|
|
values: &Vec<&SimValue>,
|
|
) -> Option<Self>
|
|
where
|
|
Self: Sized,
|
|
{
|
|
if values.is_empty() {
|
|
return Some(Self(SimValue::TRUE));
|
|
}
|
|
|
|
let value = pick(values, rng);
|
|
Self::arbitrary_from_maybe(rng, context, *value)
|
|
}
|
|
}
|
|
|
|
pub struct FalseValue(pub SimValue);
|
|
|
|
impl ArbitraryFromMaybe<&SimValue> for FalseValue {
|
|
fn arbitrary_from_maybe<R: rand::Rng, C: GenerationContext>(
|
|
_rng: &mut R,
|
|
_context: &C,
|
|
value: &SimValue,
|
|
) -> Option<Self>
|
|
where
|
|
Self: Sized,
|
|
{
|
|
// If the Value is a false value return it else you cannot return a false Value
|
|
(!value.as_bool()).then_some(Self(value.clone()))
|
|
}
|
|
}
|
|
|
|
impl ArbitraryFromMaybe<&Vec<&SimValue>> for FalseValue {
|
|
fn arbitrary_from_maybe<R: rand::Rng, C: GenerationContext>(
|
|
rng: &mut R,
|
|
context: &C,
|
|
values: &Vec<&SimValue>,
|
|
) -> Option<Self>
|
|
where
|
|
Self: Sized,
|
|
{
|
|
if values.is_empty() {
|
|
return Some(Self(SimValue::FALSE));
|
|
}
|
|
|
|
let value = pick(values, rng);
|
|
Self::arbitrary_from_maybe(rng, context, *value)
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub struct BitNotValue(pub SimValue);
|
|
|
|
impl ArbitraryFromMaybe<(&SimValue, bool)> for BitNotValue {
|
|
fn arbitrary_from_maybe<R: rand::Rng, C: GenerationContext>(
|
|
_rng: &mut R,
|
|
_context: &C,
|
|
(value, predicate): (&SimValue, bool),
|
|
) -> Option<Self>
|
|
where
|
|
Self: Sized,
|
|
{
|
|
let bit_not_val = value.unary_exec(ast::UnaryOperator::BitwiseNot);
|
|
// If you bit not the Value and it meets the predicate return Some, else None
|
|
(bit_not_val.as_bool() == predicate).then_some(BitNotValue(value.clone()))
|
|
}
|
|
}
|
|
|
|
impl ArbitraryFromMaybe<(&Vec<&SimValue>, bool)> for BitNotValue {
|
|
fn arbitrary_from_maybe<R: rand::Rng, C: GenerationContext>(
|
|
rng: &mut R,
|
|
context: &C,
|
|
(values, predicate): (&Vec<&SimValue>, bool),
|
|
) -> Option<Self>
|
|
where
|
|
Self: Sized,
|
|
{
|
|
if values.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let value = pick(values, rng);
|
|
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, C: GenerationContext, T: TableContext>(
|
|
rng: &mut R,
|
|
context: &C,
|
|
_table: &T,
|
|
row: &[SimValue],
|
|
) -> Self {
|
|
// Pick a random column
|
|
let column_index = rng.random_range(0..row.len());
|
|
let column_value = &row[column_index];
|
|
let num_retries = row.len();
|
|
// Avoid creation of NULLs
|
|
if row.is_empty() {
|
|
return SimplePredicate(Predicate(Expr::Literal(SimValue::TRUE.into())));
|
|
}
|
|
let expr = backtrack(
|
|
vec![
|
|
(
|
|
num_retries,
|
|
Box::new(|rng| {
|
|
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()))
|
|
})
|
|
}),
|
|
),
|
|
// (
|
|
// num_retries,
|
|
// Box::new(|rng| {
|
|
// TrueValue::arbitrary_from_maybe(rng, column_value).map(|value| {
|
|
// assert!(value.0.as_bool());
|
|
// // True Value with negative is still True
|
|
// Expr::unary(ast::UnaryOperator::Negative, Expr::Literal(value.0.into()))
|
|
// })
|
|
// }),
|
|
// ),
|
|
// (
|
|
// num_retries,
|
|
// Box::new(|rng| {
|
|
// BitNotValue::arbitrary_from_maybe(rng, (column_value, true)).map(|value| {
|
|
// Expr::unary(
|
|
// ast::UnaryOperator::BitwiseNot,
|
|
// Expr::Literal(value.0.into()),
|
|
// )
|
|
// })
|
|
// }),
|
|
// ),
|
|
(
|
|
num_retries,
|
|
Box::new(|rng| {
|
|
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()))
|
|
})
|
|
}),
|
|
),
|
|
],
|
|
rng,
|
|
);
|
|
// If cannot generate a value
|
|
SimplePredicate(Predicate(
|
|
expr.unwrap_or(Expr::Literal(SimValue::TRUE.into())),
|
|
))
|
|
}
|
|
|
|
/// Generates a false [ast::Expr::Unary] [SimplePredicate] from a [TableContext] for a row in the table
|
|
pub fn false_unary<R: rand::Rng, C: GenerationContext, T: TableContext>(
|
|
rng: &mut R,
|
|
context: &C,
|
|
_table: &T,
|
|
row: &[SimValue],
|
|
) -> Self {
|
|
// Avoid creation of NULLs
|
|
if row.is_empty() {
|
|
return SimplePredicate(Predicate(Expr::Literal(SimValue::FALSE.into())));
|
|
}
|
|
// Pick a random column
|
|
let column_index = rng.random_range(0..row.len());
|
|
let column_value = &row[column_index];
|
|
let num_retries = row.len();
|
|
let expr = backtrack(
|
|
vec![
|
|
// (
|
|
// num_retries,
|
|
// Box::new(|rng| {
|
|
// FalseValue::arbitrary_from_maybe(rng, 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()))
|
|
// })
|
|
// }),
|
|
// ),
|
|
// (
|
|
// num_retries,
|
|
// Box::new(|rng| {
|
|
// FalseValue::arbitrary_from_maybe(rng, column_value).map(|value| {
|
|
// assert!(!value.0.as_bool());
|
|
// // True Value with negative is still True
|
|
// Expr::unary(ast::UnaryOperator::Negative, Expr::Literal(value.0.into()))
|
|
// })
|
|
// }),
|
|
// ),
|
|
// (
|
|
// num_retries,
|
|
// Box::new(|rng| {
|
|
// BitNotValue::arbitrary_from_maybe(rng, (column_value, false)).map(|value| {
|
|
// Expr::unary(
|
|
// ast::UnaryOperator::BitwiseNot,
|
|
// Expr::Literal(value.0.into()),
|
|
// )
|
|
// })
|
|
// }),
|
|
// ),
|
|
(
|
|
num_retries,
|
|
Box::new(|rng| {
|
|
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()))
|
|
})
|
|
}),
|
|
),
|
|
],
|
|
rng,
|
|
);
|
|
// If cannot generate a value
|
|
SimplePredicate(Predicate(
|
|
expr.unwrap_or(Expr::Literal(SimValue::FALSE.into())),
|
|
))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use rand::{Rng as _, SeedableRng as _};
|
|
use rand_chacha::ChaCha8Rng;
|
|
|
|
use crate::{
|
|
generation::{
|
|
pick, predicate::SimplePredicate, tests::TestContext, Arbitrary, ArbitraryFrom as _,
|
|
},
|
|
model::table::{SimValue, Table},
|
|
};
|
|
|
|
fn get_seed() -> u64 {
|
|
std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_secs()
|
|
}
|
|
|
|
#[test]
|
|
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, 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, 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, context, &table, row);
|
|
let result = values
|
|
.iter()
|
|
.map(|row| predicate.0.test(row, &table))
|
|
.reduce(|accum, curr| accum || curr)
|
|
.unwrap_or(false);
|
|
assert!(result, "Predicate: {predicate:#?}\nSeed: {seed}")
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
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, 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, 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, context, &table, row);
|
|
let result = values
|
|
.iter()
|
|
.map(|row| predicate.0.test(row, &table))
|
|
.any(|res| !res);
|
|
assert!(result, "Predicate: {predicate:#?}\nSeed: {seed}")
|
|
}
|
|
}
|
|
}
|