diff --git a/simulator/generation/predicate/unary.rs b/simulator/generation/predicate/unary.rs index 7e8a72bc5..9a712ba55 100644 --- a/simulator/generation/predicate/unary.rs +++ b/simulator/generation/predicate/unary.rs @@ -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 for Value { + fn from(value: NullableInteger) -> Self { + match value { + NullableInteger::Null => Value::Null, + NullableInteger::Integer(v) => Value::Integer(v), + } + } +} + +impl From 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( + _rng: &mut R, + (value, predicate): (&Value, bool), + ) -> Option + 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( + rng: &mut R, + (values, predicate): (&Vec<&Value>, bool), + ) -> Option + 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(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(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| { + 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, );