migrate to using limbo_core::Value inside Simulator

This commit is contained in:
pedrocarlo 2025-06-05 15:10:40 -03:00
parent 2424b1b1c9
commit 6b58c4a33f
20 changed files with 201 additions and 286 deletions

View file

@ -15,7 +15,7 @@ name = "limbo_sim"
path = "main.rs"
[dependencies]
limbo_core = { path = "../core", features = ["fuzz"]}
limbo_core = { path = "../core", features = ["simulator"]}
rand = "0.8.5"
rand_chacha = "0.3.1"
log = "0.4.20"

View file

@ -4,7 +4,7 @@ use limbo_sqlite3_parser::ast::{
use crate::{
generation::{gen_random_text, pick, pick_index, Arbitrary, ArbitraryFrom},
model::table::Value,
model::table::SimValue,
SimulatorEnv,
};
@ -251,8 +251,8 @@ impl ArbitraryFrom<&SimulatorEnv> for ast::Literal {
}
// Creates a litreal value
impl ArbitraryFrom<&Vec<&Value>> for ast::Expr {
fn arbitrary_from<R: rand::Rng>(rng: &mut R, values: &Vec<&Value>) -> Self {
impl ArbitraryFrom<&Vec<&SimValue>> for ast::Expr {
fn arbitrary_from<R: rand::Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Self {
if values.is_empty() {
return Self::Literal(ast::Literal::Null);
}

View file

@ -11,7 +11,7 @@ use crate::{
update::Update,
Create, CreateIndex, Delete, Drop, Insert, Query, Select,
},
table::Value,
table::SimValue,
},
runner::{env::SimConnection, io::SimulatorIO},
SimulatorEnv,
@ -21,7 +21,7 @@ use crate::generation::{frequency, Arbitrary, ArbitraryFrom};
use super::property::{remaining, Property};
pub(crate) type ResultSet = Result<Vec<Vec<Value>>>;
pub(crate) type ResultSet = Result<Vec<Vec<SimValue>>>;
#[derive(Clone, Serialize, Deserialize)]
pub(crate) struct InteractionPlan {
@ -483,7 +483,7 @@ impl ArbitraryFrom<&mut SimulatorEnv> for InteractionPlan {
}
impl Interaction {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
match self {
Self::Query(query) => query.shadow(env),
Self::Assumption(_) | Self::Assertion(_) | Self::Fault(_) => vec![],
@ -512,13 +512,7 @@ impl Interaction {
let row = rows.row().unwrap();
let mut r = Vec::new();
for v in row.get_values() {
let v = match v {
limbo_core::Value::Null => Value::Null,
limbo_core::Value::Integer(i) => Value::Integer(*i),
limbo_core::Value::Float(f) => Value::Float(*f),
limbo_core::Value::Text(t) => Value::Text(t.as_str().to_string()),
limbo_core::Value::Blob(b) => Value::Blob(b.to_vec()),
};
let v = v.into();
r.push(v);
}
out.push(r);

View file

@ -11,7 +11,7 @@ use crate::{
},
model::{
query::predicate::Predicate,
table::{Table, Value},
table::{SimValue, Table},
},
};
@ -20,7 +20,7 @@ impl Predicate {
pub fn from_column_binary<R: rand::Rng>(
rng: &mut R,
column_name: &str,
value: &Value,
value: &SimValue,
) -> Predicate {
let expr = one_of(
vec![
@ -54,7 +54,7 @@ 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: &Vec<Value>) -> Predicate {
pub fn true_binary<R: rand::Rng>(rng: &mut R, t: &Table, row: &Vec<SimValue>) -> Predicate {
// Pick a column
let column_index = rng.gen_range(0..t.columns.len());
let column = &t.columns[column_index];
@ -77,7 +77,7 @@ impl Predicate {
(
1,
Box::new(|rng| {
let v = Value::arbitrary_from(rng, &column.column_type);
let v = SimValue::arbitrary_from(rng, &column.column_type);
if &v == value {
None
} else {
@ -145,7 +145,7 @@ 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: &Vec<Value>) -> Predicate {
pub fn false_binary<R: rand::Rng>(rng: &mut R, t: &Table, row: &Vec<SimValue>) -> Predicate {
// Pick a column
let column_index = rng.gen_range(0..t.columns.len());
let column = &t.columns[column_index];
@ -164,7 +164,7 @@ impl Predicate {
}),
Box::new(|rng| {
let v = loop {
let v = Value::arbitrary_from(rng, &column.column_type);
let v = SimValue::arbitrary_from(rng, &column.column_type);
if &v != value {
break v;
}

View file

@ -3,7 +3,7 @@ use rand::{seq::SliceRandom as _, Rng};
use crate::model::{
query::predicate::Predicate,
table::{Table, Value},
table::{SimValue, Table},
};
use super::{one_of, ArbitraryFrom};
@ -48,14 +48,14 @@ impl ArbitraryFrom<&Table> for Predicate {
}
}
impl ArbitraryFrom<(&str, &Value)> for Predicate {
fn arbitrary_from<R: Rng>(rng: &mut R, (column_name, value): (&str, &Value)) -> Self {
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)
}
}
impl ArbitraryFrom<(&Table, &Vec<Value>)> for Predicate {
fn arbitrary_from<R: Rng>(rng: &mut R, (t, row): (&Table, &Vec<Value>)) -> Self {
impl ArbitraryFrom<(&Table, &Vec<SimValue>)> for Predicate {
fn arbitrary_from<R: Rng>(rng: &mut R, (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

View file

@ -8,14 +8,14 @@ use crate::{
generation::{backtrack, pick, predicate::SimplePredicate, ArbitraryFromMaybe},
model::{
query::predicate::Predicate,
table::{Table, Value},
table::{SimValue, Table},
},
};
pub struct TrueValue(pub Value);
pub struct TrueValue(pub SimValue);
impl ArbitraryFromMaybe<&Value> for TrueValue {
fn arbitrary_from_maybe<R: rand::Rng>(_rng: &mut R, value: &Value) -> Option<Self>
impl ArbitraryFromMaybe<&SimValue> for TrueValue {
fn arbitrary_from_maybe<R: rand::Rng>(_rng: &mut R, value: &SimValue) -> Option<Self>
where
Self: Sized,
{
@ -24,13 +24,13 @@ impl ArbitraryFromMaybe<&Value> for TrueValue {
}
}
impl ArbitraryFromMaybe<&Vec<&Value>> for TrueValue {
fn arbitrary_from_maybe<R: rand::Rng>(rng: &mut R, values: &Vec<&Value>) -> Option<Self>
impl ArbitraryFromMaybe<&Vec<&SimValue>> for TrueValue {
fn arbitrary_from_maybe<R: rand::Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Option<Self>
where
Self: Sized,
{
if values.is_empty() {
return Some(Self(Value::TRUE));
return Some(Self(SimValue::TRUE));
}
let value = pick(values, rng);
@ -38,10 +38,10 @@ impl ArbitraryFromMaybe<&Vec<&Value>> for TrueValue {
}
}
pub struct FalseValue(pub Value);
pub struct FalseValue(pub SimValue);
impl ArbitraryFromMaybe<&Value> for FalseValue {
fn arbitrary_from_maybe<R: rand::Rng>(_rng: &mut R, value: &Value) -> Option<Self>
impl ArbitraryFromMaybe<&SimValue> for FalseValue {
fn arbitrary_from_maybe<R: rand::Rng>(_rng: &mut R, value: &SimValue) -> Option<Self>
where
Self: Sized,
{
@ -50,13 +50,13 @@ impl ArbitraryFromMaybe<&Value> for FalseValue {
}
}
impl ArbitraryFromMaybe<&Vec<&Value>> for FalseValue {
fn arbitrary_from_maybe<R: rand::Rng>(rng: &mut R, values: &Vec<&Value>) -> Option<Self>
impl ArbitraryFromMaybe<&Vec<&SimValue>> for FalseValue {
fn arbitrary_from_maybe<R: rand::Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Option<Self>
where
Self: Sized,
{
if values.is_empty() {
return Some(Self(Value::FALSE));
return Some(Self(SimValue::FALSE));
}
let value = pick(values, rng);
@ -64,12 +64,12 @@ impl ArbitraryFromMaybe<&Vec<&Value>> for FalseValue {
}
}
pub struct BitNotValue(pub Value);
pub struct BitNotValue(pub SimValue);
impl ArbitraryFromMaybe<(&Value, bool)> for BitNotValue {
impl ArbitraryFromMaybe<(&SimValue, bool)> for BitNotValue {
fn arbitrary_from_maybe<R: rand::Rng>(
_rng: &mut R,
(value, predicate): (&Value, bool),
(value, predicate): (&SimValue, bool),
) -> Option<Self>
where
Self: Sized,
@ -80,10 +80,10 @@ impl ArbitraryFromMaybe<(&Value, bool)> for BitNotValue {
}
}
impl ArbitraryFromMaybe<(&Vec<&Value>, bool)> for BitNotValue {
impl ArbitraryFromMaybe<(&Vec<&SimValue>, bool)> for BitNotValue {
fn arbitrary_from_maybe<R: rand::Rng>(
rng: &mut R,
(values, predicate): (&Vec<&Value>, bool),
(values, predicate): (&Vec<&SimValue>, bool),
) -> Option<Self>
where
Self: Sized,
@ -151,7 +151,9 @@ impl SimplePredicate {
rng,
);
// If cannot generate a value
SimplePredicate(Predicate(expr.unwrap_or(Expr::Literal(Value::TRUE.into()))))
SimplePredicate(Predicate(
expr.unwrap_or(Expr::Literal(SimValue::TRUE.into())),
))
}
/// Generates a false [ast::Expr::Unary] [SimplePredicate] from a [Table]
@ -207,6 +209,8 @@ impl SimplePredicate {
rng,
);
// If cannot generate a value
SimplePredicate(Predicate(expr.unwrap_or(Expr::Literal(Value::TRUE.into()))))
SimplePredicate(Predicate(
expr.unwrap_or(Expr::Literal(SimValue::TRUE.into())),
))
}
}

View file

@ -1,4 +1,4 @@
use limbo_core::LimboError;
use limbo_core::{LimboError, Value};
use serde::{Deserialize, Serialize};
use crate::{
@ -8,7 +8,7 @@ use crate::{
select::{Distinctness, ResultColumn},
Create, Delete, Drop, Insert, Query, Select,
},
table::Value,
table::SimValue,
},
runner::env::SimulatorEnv,
};
@ -418,7 +418,7 @@ impl Property {
.iter()
.filter(|vs| {
let v = vs.first().unwrap();
if let Value::Integer(i) = v {
if let Value::Integer(i) = &v.0 {
*i == 1
} else {
false
@ -496,7 +496,7 @@ fn property_insert_values_select<R: rand::Rng>(
let table = pick(&env.tables, rng);
// Generate rows to insert
let rows = (0..rng.gen_range(1..=5))
.map(|_| Vec::<Value>::arbitrary_from(rng, table))
.map(|_| Vec::<SimValue>::arbitrary_from(rng, table))
.collect::<Vec<_>>();
// Pick a random row to select

View file

@ -5,7 +5,7 @@ use crate::model::query::predicate::Predicate;
use crate::model::query::select::{Distinctness, ResultColumn};
use crate::model::query::update::Update;
use crate::model::query::{Create, Delete, Drop, Insert, Query, Select};
use crate::model::table::{Table, Value};
use crate::model::table::{SimValue, Table};
use crate::SimulatorEnv;
use rand::Rng;
@ -38,12 +38,12 @@ impl ArbitraryFrom<&SimulatorEnv> for Insert {
let gen_values = |rng: &mut R| {
let table = pick(&env.tables, rng);
let num_rows = rng.gen_range(1..10);
let values: Vec<Vec<Value>> = (0..num_rows)
let values: Vec<Vec<SimValue>> = (0..num_rows)
.map(|_| {
table
.columns
.iter()
.map(|c| Value::arbitrary_from(rng, &c.column_type))
.map(|c| SimValue::arbitrary_from(rng, &c.column_type))
.collect()
})
.collect();
@ -141,7 +141,7 @@ impl ArbitraryFrom<&SimulatorEnv> for Update {
let table = pick(&env.tables, rng);
let mut seen = HashSet::new();
let num_cols = rng.gen_range(1..=table.columns.len());
let set_values: Vec<(String, Value)> = (0..num_cols)
let set_values: Vec<(String, SimValue)> = (0..num_cols)
.map(|_| {
let column = loop {
let column = pick(&table.columns, rng);
@ -153,7 +153,7 @@ impl ArbitraryFrom<&SimulatorEnv> for Update {
seen.insert(column.name.clone());
(
column.name.clone(),
Value::arbitrary_from(rng, &column.column_type),
SimValue::arbitrary_from(rng, &column.column_type),
)
})
.collect();

View file

@ -1,7 +1,8 @@
use limbo_core::Value;
use rand::Rng;
use crate::generation::{gen_random_text, pick, readable_name_custom, Arbitrary, ArbitraryFrom};
use crate::model::table::{Column, ColumnType, Name, Table, Value};
use crate::model::table::{Column, ColumnType, Name, SimValue, Table};
use super::ArbitraryFromMaybe;
@ -45,44 +46,45 @@ impl Arbitrary for ColumnType {
}
}
impl ArbitraryFrom<&Table> for Vec<Value> {
impl ArbitraryFrom<&Table> for Vec<SimValue> {
fn arbitrary_from<R: Rng>(rng: &mut R, table: &Table) -> Self {
let mut row = Vec::new();
for column in table.columns.iter() {
let value = Value::arbitrary_from(rng, &column.column_type);
let value = SimValue::arbitrary_from(rng, &column.column_type);
row.push(value);
}
row
}
}
impl ArbitraryFrom<&Vec<&Value>> for Value {
impl ArbitraryFrom<&Vec<&SimValue>> for SimValue {
fn arbitrary_from<R: Rng>(rng: &mut R, values: &Vec<&Self>) -> Self {
if values.is_empty() {
return Self::Null;
return Self(Value::Null);
}
pick(values, rng).to_owned().clone()
}
}
impl ArbitraryFrom<&ColumnType> for Value {
impl ArbitraryFrom<&ColumnType> for SimValue {
fn arbitrary_from<R: Rng>(rng: &mut R, column_type: &ColumnType) -> Self {
match column_type {
ColumnType::Integer => Self::Integer(rng.gen_range(i64::MIN..i64::MAX)),
ColumnType::Float => Self::Float(rng.gen_range(-1e10..1e10)),
ColumnType::Text => Self::Text(gen_random_text(rng)),
ColumnType::Blob => Self::Blob(gen_random_text(rng).as_bytes().to_vec()),
}
let value = match column_type {
ColumnType::Integer => Value::Integer(rng.gen_range(i64::MIN..i64::MAX)),
ColumnType::Float => Value::Float(rng.gen_range(-1e10..1e10)),
ColumnType::Text => Value::build_text(gen_random_text(rng)),
ColumnType::Blob => Value::Blob(gen_random_text(rng).as_bytes().to_vec()),
};
SimValue(value)
}
}
pub(crate) struct LTValue(pub(crate) Value);
pub(crate) struct LTValue(pub(crate) SimValue);
impl ArbitraryFrom<&Vec<&Value>> for LTValue {
fn arbitrary_from<R: Rng>(rng: &mut R, values: &Vec<&Value>) -> Self {
impl ArbitraryFrom<&Vec<&SimValue>> for LTValue {
fn arbitrary_from<R: Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Self {
if values.is_empty() {
return Self(Value::Null);
return Self(SimValue(Value::Null));
}
let value = pick(values, rng);
@ -90,17 +92,17 @@ impl ArbitraryFrom<&Vec<&Value>> for LTValue {
}
}
impl ArbitraryFrom<&Value> for LTValue {
fn arbitrary_from<R: Rng>(rng: &mut R, value: &Value) -> Self {
match value {
Value::Integer(i) => Self(Value::Integer(rng.gen_range(i64::MIN..*i - 1))),
Value::Float(f) => Self(Value::Float(f - rng.gen_range(0.0..1e10))),
Value::Text(t) => {
impl ArbitraryFrom<&SimValue> for LTValue {
fn arbitrary_from<R: Rng>(rng: &mut R, value: &SimValue) -> Self {
let new_value = match &value.0 {
Value::Integer(i) => Value::Integer(rng.gen_range(i64::MIN..*i - 1)),
Value::Float(f) => Value::Float(f - rng.gen_range(0.0..1e10)),
value @ Value::Text(..) => {
// Either shorten the string, or make at least one character smaller and mutate the rest
let mut t = t.clone();
let mut t = value.to_string();
if rng.gen_bool(0.01) {
t.pop();
Self(Value::Text(t))
Value::build_text(t)
} else {
let mut t = t.chars().map(|c| c as u32).collect::<Vec<_>>();
let index = rng.gen_range(0..t.len());
@ -113,7 +115,7 @@ impl ArbitraryFrom<&Value> for LTValue {
.into_iter()
.map(|c| char::from_u32(c).unwrap_or('z'))
.collect::<String>();
Self(Value::Text(t))
Value::build_text(t)
}
}
Value::Blob(b) => {
@ -121,7 +123,7 @@ impl ArbitraryFrom<&Value> for LTValue {
let mut b = b.clone();
if rng.gen_bool(0.01) {
b.pop();
Self(Value::Blob(b))
Value::Blob(b)
} else {
let index = rng.gen_range(0..b.len());
b[index] -= 1;
@ -129,20 +131,21 @@ impl ArbitraryFrom<&Value> for LTValue {
for i in (index + 1)..b.len() {
b[i] = rng.gen_range(0..=255);
}
Self(Value::Blob(b))
Value::Blob(b)
}
}
_ => unreachable!(),
}
};
Self(SimValue(new_value))
}
}
pub(crate) struct GTValue(pub(crate) Value);
pub(crate) struct GTValue(pub(crate) SimValue);
impl ArbitraryFrom<&Vec<&Value>> for GTValue {
fn arbitrary_from<R: Rng>(rng: &mut R, values: &Vec<&Value>) -> Self {
impl ArbitraryFrom<&Vec<&SimValue>> for GTValue {
fn arbitrary_from<R: Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Self {
if values.is_empty() {
return Self(Value::Null);
return Self(SimValue(Value::Null));
}
let value = pick(values, rng);
@ -150,17 +153,17 @@ impl ArbitraryFrom<&Vec<&Value>> for GTValue {
}
}
impl ArbitraryFrom<&Value> for GTValue {
fn arbitrary_from<R: Rng>(rng: &mut R, value: &Value) -> Self {
match value {
Value::Integer(i) => Self(Value::Integer(rng.gen_range(*i..i64::MAX))),
Value::Float(f) => Self(Value::Float(rng.gen_range(*f..1e10))),
Value::Text(t) => {
impl ArbitraryFrom<&SimValue> for GTValue {
fn arbitrary_from<R: Rng>(rng: &mut R, value: &SimValue) -> Self {
let new_value = match &value.0 {
Value::Integer(i) => Value::Integer(rng.gen_range(*i..i64::MAX)),
Value::Float(f) => Value::Float(rng.gen_range(*f..1e10)),
value @ Value::Text(..) => {
// Either lengthen the string, or make at least one character smaller and mutate the rest
let mut t = t.clone();
let mut t = value.to_string();
if rng.gen_bool(0.01) {
t.push(rng.gen_range(0..=255) as u8 as char);
Self(Value::Text(t))
Value::build_text(t)
} else {
let mut t = t.chars().map(|c| c as u32).collect::<Vec<_>>();
let index = rng.gen_range(0..t.len());
@ -173,7 +176,7 @@ impl ArbitraryFrom<&Value> for GTValue {
.into_iter()
.map(|c| char::from_u32(c).unwrap_or('a'))
.collect::<String>();
Self(Value::Text(t))
Value::build_text(t)
}
}
Value::Blob(b) => {
@ -181,7 +184,7 @@ impl ArbitraryFrom<&Value> for GTValue {
let mut b = b.clone();
if rng.gen_bool(0.01) {
b.push(rng.gen_range(0..=255));
Self(Value::Blob(b))
Value::Blob(b)
} else {
let index = rng.gen_range(0..b.len());
b[index] += 1;
@ -189,20 +192,22 @@ impl ArbitraryFrom<&Value> for GTValue {
for i in (index + 1)..b.len() {
b[i] = rng.gen_range(0..=255);
}
Self(Value::Blob(b))
Value::Blob(b)
}
}
_ => unreachable!(),
}
};
Self(SimValue(new_value))
}
}
pub(crate) struct LikeValue(pub(crate) Value);
pub(crate) struct LikeValue(pub(crate) SimValue);
impl ArbitraryFromMaybe<&Value> for LikeValue {
fn arbitrary_from_maybe<R: Rng>(rng: &mut R, value: &Value) -> Option<Self> {
match value {
Value::Text(t) => {
impl ArbitraryFromMaybe<&SimValue> for LikeValue {
fn arbitrary_from_maybe<R: Rng>(rng: &mut R, value: &SimValue) -> Option<Self> {
match &value.0 {
value @ Value::Text(..) => {
let t = value.to_string();
let mut t = t.chars().collect::<Vec<_>>();
// Remove a number of characters, either insert `_` for each character removed, or
// insert one `%` for the whole substring
@ -221,7 +226,9 @@ impl ArbitraryFromMaybe<&Value> for LikeValue {
}
let index = rng.gen_range(0..t.len());
t.insert(index, '%');
Some(Self(Value::Text(t.into_iter().collect())))
Some(Self(SimValue(Value::build_text(
t.into_iter().collect::<String>(),
))))
}
_ => None,
}

View file

@ -3,7 +3,7 @@ use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{
model::table::{Table, Value},
model::table::{SimValue, Table},
SimulatorEnv,
};
@ -13,7 +13,7 @@ pub(crate) struct Create {
}
impl Create {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
if !env.tables.iter().any(|t| t.name == self.table.name) {
env.tables.push(self.table.clone());
}

View file

@ -31,7 +31,7 @@ impl CreateIndex {
pub(crate) fn shadow(
&self,
_env: &mut crate::runner::env::SimulatorEnv,
) -> Vec<Vec<crate::model::table::Value>> {
) -> Vec<Vec<crate::model::table::SimValue>> {
// CREATE INDEX doesn't require any shadowing; we don't need to keep track
// in the simulator what indexes exist.
vec![]

View file

@ -2,7 +2,7 @@ use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{model::table::Value, SimulatorEnv};
use crate::{model::table::SimValue, SimulatorEnv};
use super::predicate::Predicate;
@ -13,7 +13,7 @@ pub(crate) struct Delete {
}
impl Delete {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
let table = env
.tables
.iter_mut()

View file

@ -2,7 +2,7 @@ use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{model::table::Value, SimulatorEnv};
use crate::{model::table::SimValue, SimulatorEnv};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub(crate) struct Drop {
@ -10,7 +10,7 @@ pub(crate) struct Drop {
}
impl Drop {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
env.tables.retain(|t| t.name != self.table);
vec![]
}

View file

@ -2,7 +2,7 @@ use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{model::table::Value, SimulatorEnv};
use crate::{model::table::SimValue, SimulatorEnv};
use super::select::Select;
@ -10,7 +10,7 @@ use super::select::Select;
pub(crate) enum Insert {
Values {
table: String,
values: Vec<Vec<Value>>,
values: Vec<Vec<SimValue>>,
},
Select {
table: String,
@ -19,7 +19,7 @@ pub(crate) enum Insert {
}
impl Insert {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
match self {
Insert::Values { table, values } => {
if let Some(t) = env.tables.iter_mut().find(|t| &t.name == table) {

View file

@ -10,7 +10,7 @@ pub(crate) use select::Select;
use serde::{Deserialize, Serialize};
use update::Update;
use crate::{model::table::Value, runner::env::SimulatorEnv};
use crate::{model::table::SimValue, runner::env::SimulatorEnv};
pub mod create;
pub mod create_index;
@ -61,7 +61,7 @@ impl Query {
}
}
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
match self {
Query::Create(create) => create.shadow(env),
Query::Insert(insert) => insert.shadow(env),

View file

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use crate::model::{
query::EmptyContext,
table::{Table, Value},
table::{SimValue, Table},
};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -20,7 +20,7 @@ impl Predicate {
Self(ast::Expr::Literal(ast::Literal::Numeric("0".to_string())))
}
pub(crate) fn test(&self, row: &[Value], table: &Table) -> bool {
pub(crate) fn test(&self, row: &[SimValue], table: &Table) -> bool {
let value = expr_to_value(&self.0, row, table);
value.map_or(false, |value| value.into_bool())
}
@ -30,7 +30,7 @@ impl Predicate {
// This function attempts to convert an simpler easily computable expression into values
// TODO: In the future, we can try to expand this computation if we want to support harder properties that require us
// to already know more values before hand
fn expr_to_value(expr: &ast::Expr, row: &[Value], table: &Table) -> Option<Value> {
fn expr_to_value(expr: &ast::Expr, row: &[SimValue], table: &Table) -> Option<SimValue> {
match expr {
ast::Expr::DoublyQualified(_, _, ast::Name(col_name))
| ast::Expr::Qualified(_, ast::Name(col_name))
@ -57,7 +57,7 @@ fn expr_to_value(expr: &ast::Expr, row: &[Value], table: &Table) -> Option<Value
let lhs = expr_to_value(lhs, row, table)?;
let rhs = expr_to_value(rhs, row, table)?;
let res = lhs.like_compare(&rhs, *op);
let value: Value = if *not { !res } else { res }.into();
let value: SimValue = if *not { !res } else { res }.into();
Some(value)
}
ast::Expr::Unary(op, expr) => {

View file

@ -2,7 +2,7 @@ use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{model::table::Value, SimulatorEnv};
use crate::{model::table::SimValue, SimulatorEnv};
use super::predicate::Predicate;
@ -46,7 +46,7 @@ pub(crate) struct Select {
}
impl Select {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
let table = env.tables.iter().find(|t| t.name == self.table.as_str());
if let Some(table) = table {
table

View file

@ -2,19 +2,19 @@ use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{model::table::Value, SimulatorEnv};
use crate::{model::table::SimValue, SimulatorEnv};
use super::predicate::Predicate;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub(crate) struct Update {
pub(crate) table: String,
pub(crate) set_values: Vec<(String, Value)>, // Pair of value for set expressions => SET name=value
pub(crate) set_values: Vec<(String, SimValue)>, // Pair of value for set expressions => SET name=value
pub(crate) predicate: Predicate,
}
impl Update {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<Value>> {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) -> Vec<Vec<SimValue>> {
let table = env
.tables
.iter_mut()

View file

@ -1,6 +1,6 @@
use std::{fmt::Display, ops::Deref};
use limbo_core::numeric::{nonnan::NonNan, NullableInteger, Numeric};
use limbo_core::{numeric::Numeric, types};
use limbo_sqlite3_parser::ast;
use regex::{Regex, RegexBuilder};
use serde::{Deserialize, Serialize};
@ -17,7 +17,7 @@ impl Deref for Name {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Table {
pub(crate) rows: Vec<Vec<Value>>,
pub(crate) rows: Vec<Vec<SimValue>>,
pub(crate) name: String,
pub(crate) columns: Vec<Column>,
}
@ -64,35 +64,8 @@ where
s.parse().map_err(serde::de::Error::custom)
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub(crate) enum Value {
Null,
Integer(i64),
// we use custom serialization to preserve float precision
#[serde(
serialize_with = "float_to_string",
deserialize_with = "string_to_float"
)]
Float(f64),
Text(String),
Blob(Vec<u8>),
}
impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(Self::Null, Self::Null) => Some(std::cmp::Ordering::Equal),
(Self::Null, _) => Some(std::cmp::Ordering::Less),
(_, Self::Null) => Some(std::cmp::Ordering::Greater),
(Self::Integer(i1), Self::Integer(i2)) => i1.partial_cmp(i2),
(Self::Float(f1), Self::Float(f2)) => f1.partial_cmp(f2),
(Self::Text(t1), Self::Text(t2)) => t1.partial_cmp(t2),
(Self::Blob(b1), Self::Blob(b2)) => b1.partial_cmp(b2),
// todo: add type coercions here
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub(crate) struct SimValue(pub limbo_core::Value);
fn to_sqlite_blob(bytes: &[u8]) -> String {
format!(
@ -103,29 +76,29 @@ fn to_sqlite_blob(bytes: &[u8]) -> String {
)
}
impl Display for Value {
impl Display for SimValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Null => write!(f, "NULL"),
Self::Integer(i) => write!(f, "{}", i),
Self::Float(fl) => write!(f, "{}", fl),
Self::Text(t) => write!(f, "'{}'", t),
Self::Blob(b) => write!(f, "{}", to_sqlite_blob(b)),
match &self.0 {
types::Value::Null => write!(f, "NULL"),
types::Value::Integer(i) => write!(f, "{}", i),
types::Value::Float(fl) => write!(f, "{}", fl),
value @ types::Value::Text(..) => write!(f, "'{}'", value.to_string()),
types::Value::Blob(b) => write!(f, "{}", to_sqlite_blob(b)),
}
}
}
impl Value {
pub const FALSE: Self = Value::Integer(0);
pub const TRUE: Self = Value::Integer(1);
impl SimValue {
pub const FALSE: Self = SimValue(types::Value::Integer(0));
pub const TRUE: Self = SimValue(types::Value::Integer(1));
pub fn into_bool(&self) -> bool {
Numeric::from(self).try_into_bool().unwrap_or_default()
Numeric::from(&self.0).try_into_bool().unwrap_or_default()
}
// TODO: support more predicates
/// Returns a Value::TRUE or VALUE::FALSE
pub fn binary_compare(&self, other: &Self, operator: ast::Operator) -> Value {
pub fn binary_compare(&self, other: &Self, operator: ast::Operator) -> SimValue {
match operator {
ast::Operator::Add => todo!(),
ast::Operator::And => self.into_bool() && other.into_bool(),
@ -167,13 +140,14 @@ impl Value {
}
}
pub fn unary_exec(&self, operator: ast::UnaryOperator) -> Value {
match operator {
ast::UnaryOperator::BitwiseNot => exec_bit_not(self),
pub fn unary_exec(&self, operator: ast::UnaryOperator) -> SimValue {
let new_value = match operator {
ast::UnaryOperator::BitwiseNot => self.0.exec_bit_not(),
ast::UnaryOperator::Negative => todo!(),
ast::UnaryOperator::Not => todo!(),
ast::UnaryOperator::Positive => todo!(),
}
};
Self(new_value)
}
}
@ -212,35 +186,19 @@ fn exec_like(pattern: &str, text: &str) -> bool {
re.is_match(text)
}
impl From<&ast::Literal> for Value {
fn from(value: &ast::Literal) -> Self {
match value {
ast::Literal::Null => Self::Null,
ast::Literal::Numeric(number) => Numeric::from(number).into(),
ast::Literal::String(string) => Self::Text(string.clone()),
ast::Literal::Blob(blob) => Self::Blob(
blob.as_bytes()
.chunks_exact(2)
.map(|pair| {
// We assume that sqlite3-parser has already validated that
// the input is valid hex string, thus unwrap is safe.
let hex_byte = std::str::from_utf8(pair).unwrap();
u8::from_str_radix(hex_byte, 16).unwrap()
})
.collect(),
),
lit => unimplemented!("{:?}", lit),
}
}
}
impl From<ast::Literal> for Value {
impl From<ast::Literal> for SimValue {
fn from(value: ast::Literal) -> Self {
match value {
ast::Literal::Null => Self::Null,
Self::from(&value)
}
}
impl From<&ast::Literal> for SimValue {
fn from(value: &ast::Literal) -> Self {
let new_value = match value {
ast::Literal::Null => types::Value::Null,
ast::Literal::Numeric(number) => Numeric::from(number).into(),
ast::Literal::String(string) => Self::Text(string),
ast::Literal::Blob(blob) => Self::Blob(
ast::Literal::String(string) => types::Value::build_text(string),
ast::Literal::Blob(blob) => types::Value::Blob(
blob.as_bytes()
.chunks_exact(2)
.map(|pair| {
@ -252,106 +210,55 @@ impl From<ast::Literal> for Value {
.collect(),
),
lit => unimplemented!("{:?}", lit),
}
};
Self(new_value)
}
}
impl From<Value> for ast::Literal {
fn from(value: Value) -> Self {
match value {
Value::Null => Self::Null,
Value::Integer(i) => Self::Numeric(i.to_string()),
Value::Float(f) => Self::Numeric(f.to_string()),
Value::Text(string) => Self::String(format!("'{}'", string)),
Value::Blob(blob) => Self::Blob(hex::encode(blob)),
}
}
}
impl From<&Value> for ast::Literal {
fn from(value: &Value) -> Self {
match value {
Value::Null => Self::Null,
Value::Integer(i) => Self::Numeric(i.to_string()),
Value::Float(f) => Self::Numeric(f.to_string()),
Value::Text(string) => Self::String(format!("'{}'", string)),
Value::Blob(blob) => Self::Blob(hex::encode(blob)),
}
}
}
impl From<Numeric> for Value {
fn from(value: Numeric) -> Self {
match value {
Numeric::Null => Value::Null,
Numeric::Integer(i) => Value::Integer(i),
Numeric::Float(f) => Value::Float(f.into()),
}
}
}
// Copied from numeric in Core
impl From<Value> for Numeric {
fn from(value: Value) -> Self {
impl From<SimValue> for ast::Literal {
fn from(value: SimValue) -> Self {
Self::from(&value)
}
}
impl From<&Value> for Numeric {
fn from(value: &Value) -> Self {
match value {
Value::Null => Self::Null,
Value::Integer(v) => Self::Integer(*v),
Value::Float(v) => match NonNan::new(*v) {
Some(v) => Self::Float(v),
None => Self::Null,
},
Value::Text(text) => Numeric::from(text.as_str()),
Value::Blob(blob) => {
let text = String::from_utf8_lossy(blob.as_slice());
Numeric::from(&text)
}
impl From<&SimValue> for ast::Literal {
fn from(value: &SimValue) -> Self {
match &value.0 {
types::Value::Null => Self::Null,
types::Value::Integer(i) => Self::Numeric(i.to_string()),
types::Value::Float(f) => Self::Numeric(f.to_string()),
text @ types::Value::Text(..) => Self::String(format!("'{}'", text)),
types::Value::Blob(blob) => Self::Blob(hex::encode(blob)),
}
}
}
impl From<bool> for Value {
impl From<bool> for SimValue {
fn from(value: bool) -> Self {
value.then_some(Value::TRUE).unwrap_or(Value::FALSE)
value.then_some(SimValue::TRUE).unwrap_or(SimValue::FALSE)
}
}
// 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<SimValue> for limbo_core::types::Value {
fn from(value: SimValue) -> Self {
value.0
}
}
impl From<Value> for NullableInteger {
fn from(value: Value) -> Self {
Self::from(&value)
impl From<&SimValue> for limbo_core::types::Value {
fn from(value: &SimValue) -> Self {
value.0.clone()
}
}
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)
}
}
impl From<limbo_core::types::Value> for SimValue {
fn from(value: limbo_core::types::Value) -> Self {
Self(value)
}
}
fn exec_bit_not(reg: &Value) -> Value {
(!NullableInteger::from(reg)).into()
impl From<&limbo_core::types::Value> for SimValue {
fn from(value: &limbo_core::types::Value) -> Self {
Self(value.clone())
}
}

View file

@ -1,11 +1,13 @@
use std::sync::{Arc, Mutex};
use limbo_core::Value;
use crate::{
generation::{
pick_index,
plan::{Interaction, InteractionPlanState, ResultSet},
},
model::{query::Query, table::Value},
model::{query::Query, table::SimValue},
runner::execution::ExecutionContinuation,
InteractionPlan,
};
@ -60,7 +62,7 @@ pub(crate) fn run_simulation(
fn execute_query_rusqlite(
connection: &rusqlite::Connection,
query: &Query,
) -> rusqlite::Result<Vec<Vec<Value>>> {
) -> rusqlite::Result<Vec<Vec<SimValue>>> {
match query {
Query::Create(create) => {
connection.execute(create.to_string().as_str(), ())?;
@ -73,13 +75,14 @@ fn execute_query_rusqlite(
let mut values = vec![];
for i in 0..columns {
let value = row.get_unwrap(i);
match value {
rusqlite::types::Value::Null => values.push(Value::Null),
rusqlite::types::Value::Integer(i) => values.push(Value::Integer(i)),
rusqlite::types::Value::Real(f) => values.push(Value::Float(f)),
rusqlite::types::Value::Text(s) => values.push(Value::Text(s)),
rusqlite::types::Value::Blob(b) => values.push(Value::Blob(b)),
}
let value = match value {
rusqlite::types::Value::Null => Value::Null,
rusqlite::types::Value::Integer(i) => Value::Integer(i),
rusqlite::types::Value::Real(f) => Value::Float(f),
rusqlite::types::Value::Text(s) => Value::build_text(s),
rusqlite::types::Value::Blob(b) => Value::Blob(b),
};
values.push(SimValue(value));
}
Ok(values)
})?;