implement true_unary + false_unary

This commit is contained in:
pedrocarlo
2025-06-05 12:25:23 -03:00
parent 176ec3b0ea
commit dc901a019c

View File

@@ -1,17 +1,53 @@
//! Contains code for 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 limbo_core::numeric::NullableInteger;
use limbo_sqlite3_parser::ast::{self, Expr};
use crate::{
generation::{
backtrack, pick, predicate::SimplePredicate, ArbitraryFrom as _, ArbitraryFromMaybe,
},
generation::{backtrack, pick, predicate::SimplePredicate, ArbitraryFromMaybe},
model::{
query::predicate::Predicate,
table::{Table, Value},
},
};
// NullableInteger Code copied from Core. Ideally we should use Limbo's Core Value for everything to avoid code repetition
impl From<NullableInteger> for Value {
fn from(value: NullableInteger) -> Self {
match value {
NullableInteger::Null => Value::Null,
NullableInteger::Integer(v) => Value::Integer(v),
}
}
}
impl From<Value> for NullableInteger {
fn from(value: Value) -> Self {
Self::from(&value)
}
}
impl From<&Value> for NullableInteger {
fn from(value: &Value) -> Self {
match value {
Value::Null => Self::Null,
Value::Integer(v) => Self::Integer(*v),
Value::Float(v) => Self::Integer(*v as i64),
Value::Text(text) => Self::from(text.as_str()),
Value::Blob(blob) => {
let text = String::from_utf8_lossy(blob.as_slice());
Self::from(text)
}
}
}
}
fn exec_bit_not(reg: &Value) -> Value {
(!NullableInteger::from(reg)).into()
}
pub struct TrueValue(pub Value);
impl ArbitraryFromMaybe<&Value> for TrueValue {
@@ -56,7 +92,7 @@ impl ArbitraryFromMaybe<&Vec<&Value>> for FalseValue {
Self: Sized,
{
if values.is_empty() {
return Some(Self(Value::TRUE));
return Some(Self(Value::FALSE));
}
let value = pick(values, rng);
@@ -64,6 +100,39 @@ impl ArbitraryFromMaybe<&Vec<&Value>> for FalseValue {
}
}
pub struct BitNotValue(pub Value);
impl ArbitraryFromMaybe<(&Value, bool)> for BitNotValue {
fn arbitrary_from_maybe<R: rand::Rng>(
_rng: &mut R,
(value, predicate): (&Value, bool),
) -> Option<Self>
where
Self: Sized,
{
let bit_not_val = exec_bit_not(value);
// If you bit not the Value and it meets the predicate return Some, else None
(bit_not_val.into_bool() == predicate).then_some(BitNotValue(value.clone()))
}
}
impl ArbitraryFromMaybe<(&Vec<&Value>, bool)> for BitNotValue {
fn arbitrary_from_maybe<R: rand::Rng>(
rng: &mut R,
(values, predicate): (&Vec<&Value>, bool),
) -> Option<Self>
where
Self: Sized,
{
if values.is_empty() {
return None;
}
let value = pick(values, rng);
Self::arbitrary_from_maybe(rng, (*value, predicate))
}
}
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 {
@@ -87,11 +156,89 @@ impl SimplePredicate {
(
num_retries,
Box::new(|rng| {
FalseValue::arbitrary_from_maybe(rng, &column_values).map(|value| {
TrueValue::arbitrary_from_maybe(rng, &column_values).map(|value| {
// 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_values, true)).map(
|value| {
Expr::unary(
ast::UnaryOperator::BitwiseNot,
Expr::Literal(value.0.into()),
)
},
)
}),
),
(
num_retries,
Box::new(|rng| {
FalseValue::arbitrary_from_maybe(rng, &column_values).map(|value| {
Expr::unary(ast::UnaryOperator::Not, Expr::Literal(value.0.into()))
})
}),
),
],
rng,
);
// If cannot generate a value
SimplePredicate(Predicate(expr.unwrap_or(Expr::Literal(Value::TRUE.into()))))
}
/// Generates a false [ast::Expr::Unary] [SimplePredicate] from a [Table]
pub fn false_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| {
FalseValue::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| {
// 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_values, false)).map(
|value| {
Expr::unary(
ast::UnaryOperator::BitwiseNot,
Expr::Literal(value.0.into()),
)
},
)
}),
),
(
num_retries,
Box::new(|rng| {
TrueValue::arbitrary_from_maybe(rng, &column_values).map(|value| {
Expr::unary(ast::UnaryOperator::Not, Expr::Literal(value.0.into()))
})
}),
),
],
rng,
);