mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 08:55:40 +01:00
Merge 'Simulator Ast Generation + Simulator Unary Operator + Refactor to use limbo_core::Value in Simulator for massive code reuse' from Pedro Muniz
This PR is a Drop-In replacement to the Predicate defined in the Simulator. Predicate is basically the same as our ast::Expr, but it supports a small number of the SQL expression syntax. By creating a NewType that wraps ast::Expr we can tap into our already mostly correctly defined parser structs. This change will enable us to easily add generation for more types of sql queries. I also added an ArbitraryFrom impl for ast::Expr that can be used in a freestyle way (for now) for differential testing. This PR also aims to implement Unary Operator logic similar to the Binary Operator logic we have for predicate. After this change we may need to adjust the Logic for how some assertions are triggered. <s>Sometimes the `Select-Select-Optimizer` property thinks that these two queries should return the same thing: ```sql SELECT (twinkling_winstanley.sensible_federations > x'66616e7461737469625e0f37879823db' AND twinkling_winstanley.sincere_niemeyer < -7428368947470022783) FROM twinkling_winstanley WHERE 1; SELECT * FROM twinkling_winstanley WHERE twinkling_winstanley.sensible_federations > x'66616e7461737469625e0f37879823db' AND twinkling_winstanley.sincere_niemeyer < -7428368947470022783; ``` However after running the shrunk plan manually, the simulator was incorrect in asserting that. Maybe this a bug a in the generation of such a query? Not sure yet. </s> <b>EDIT: The simulator was correctly catching a bug and I thought I was the problem. The bug was in `exec_if` and I fixed it in this PR.</b> I still need to expand the Unary Operator generation to other types of predicates. For now, I just implemented it for `SimplePredicate` as I'm trying to avoid to bloat even more this PR. <b>EDIT: I decided to just have one PR open for all the changes I'm making to make my life a bit easier and to avoid merge conflicts with my own branches that I keep spawning for new code.</b> PS: This should only be considered for merging after https://github.com/tursodatabase/limbo/pull/1619 is merged. Then, I will remove the draft status from this PR. Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #1674
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,7 +19,6 @@ env
|
||||
dist/
|
||||
.tmp/
|
||||
|
||||
|
||||
*.db
|
||||
**/*.db-wal
|
||||
**/*.db-shm
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -240,6 +240,9 @@ name = "bitflags"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
@@ -1428,6 +1431,7 @@ checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1960,7 +1964,9 @@ dependencies = [
|
||||
"clap",
|
||||
"dirs 6.0.0",
|
||||
"env_logger 0.10.2",
|
||||
"hex",
|
||||
"limbo_core",
|
||||
"limbo_sqlite3_parser",
|
||||
"log",
|
||||
"notify",
|
||||
"rand 0.8.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::Value;
|
||||
|
||||
mod nonnan;
|
||||
pub mod nonnan;
|
||||
|
||||
use nonnan::NonNan;
|
||||
|
||||
@@ -5818,12 +5818,10 @@ impl Value {
|
||||
|
||||
// exec_if returns whether you should jump
|
||||
pub fn exec_if(&self, jump_if_null: bool, not: bool) -> bool {
|
||||
match self {
|
||||
Value::Integer(0) | Value::Float(0.0) => not,
|
||||
Value::Integer(_) | Value::Float(_) => !not,
|
||||
Value::Null => jump_if_null,
|
||||
_ => false,
|
||||
}
|
||||
Numeric::from(self)
|
||||
.try_into_bool()
|
||||
.map(|jump| if not { !jump } else { jump })
|
||||
.unwrap_or(jump_if_null)
|
||||
}
|
||||
|
||||
pub fn exec_cast(&self, datatype: &str) -> Value {
|
||||
|
||||
@@ -15,7 +15,7 @@ name = "limbo_sim"
|
||||
path = "main.rs"
|
||||
|
||||
[dependencies]
|
||||
limbo_core = { path = "../core" }
|
||||
limbo_core = { path = "../core", features = ["simulator"]}
|
||||
rand = "0.8.5"
|
||||
rand_chacha = "0.3.1"
|
||||
log = "0.4.20"
|
||||
@@ -35,4 +35,5 @@ chrono = { version = "0.4.40", features = ["serde"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
anyhow.workspace = true
|
||||
|
||||
limbo_sqlite3_parser = { workspace = true, features = ["serde"]}
|
||||
hex = "0.4.3"
|
||||
|
||||
276
simulator/generation/expr.rs
Normal file
276
simulator/generation/expr.rs
Normal file
@@ -0,0 +1,276 @@
|
||||
use limbo_sqlite3_parser::ast::{
|
||||
self, Expr, LikeOperator, Name, Operator, QualifiedName, Type, UnaryOperator,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
generation::{gen_random_text, pick, pick_index, Arbitrary, ArbitraryFrom},
|
||||
model::table::SimValue,
|
||||
SimulatorEnv,
|
||||
};
|
||||
|
||||
impl<T> Arbitrary for Box<T>
|
||||
where
|
||||
T: Arbitrary,
|
||||
{
|
||||
fn arbitrary<R: rand::Rng>(rng: &mut R) -> Self {
|
||||
Box::from(T::arbitrary(rng))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, T> ArbitraryFrom<A> for Box<T>
|
||||
where
|
||||
T: ArbitraryFrom<A>,
|
||||
{
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, t: A) -> Self {
|
||||
Box::from(T::arbitrary_from(rng, t))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Arbitrary for Option<T>
|
||||
where
|
||||
T: Arbitrary,
|
||||
{
|
||||
fn arbitrary<R: rand::Rng>(rng: &mut R) -> Self {
|
||||
rng.gen_bool(0.5).then_some(T::arbitrary(rng))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, T> ArbitraryFrom<A> for Option<T>
|
||||
where
|
||||
T: ArbitraryFrom<A>,
|
||||
{
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, t: A) -> Self {
|
||||
rng.gen_bool(0.5).then_some(T::arbitrary_from(rng, t))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
let size = rng.gen_range(0..5);
|
||||
(0..size)
|
||||
.into_iter()
|
||||
.map(|_| T::arbitrary_from(rng, t))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
// Freestyling generation
|
||||
impl ArbitraryFrom<&SimulatorEnv> for Expr {
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, t: &SimulatorEnv) -> Self {
|
||||
let choice = rng.gen_range(0..13);
|
||||
let expr = match choice {
|
||||
0 => Expr::Between {
|
||||
lhs: Box::arbitrary_from(rng, t),
|
||||
not: rng.gen_bool(0.5),
|
||||
start: Box::arbitrary_from(rng, t),
|
||||
end: Box::arbitrary_from(rng, t),
|
||||
},
|
||||
1 => Expr::Binary(
|
||||
Box::arbitrary_from(rng, t),
|
||||
Operator::arbitrary(rng),
|
||||
Box::arbitrary_from(rng, t),
|
||||
),
|
||||
2 => Expr::Case {
|
||||
base: Option::arbitrary_from(rng, t),
|
||||
when_then_pairs: {
|
||||
let size = rng.gen_range(0..5);
|
||||
(0..size)
|
||||
.into_iter()
|
||||
.map(|_| (Self::arbitrary_from(rng, t), Self::arbitrary_from(rng, t)))
|
||||
.collect()
|
||||
},
|
||||
else_expr: Option::arbitrary_from(rng, t),
|
||||
},
|
||||
3 => Expr::Cast {
|
||||
expr: Box::arbitrary_from(rng, t),
|
||||
type_name: Option::arbitrary(rng),
|
||||
},
|
||||
4 => Expr::Collate(Box::arbitrary_from(rng, t), CollateName::arbitrary(rng).0),
|
||||
5 => Expr::InList {
|
||||
lhs: Box::arbitrary_from(rng, t),
|
||||
not: rng.gen_bool(0.5),
|
||||
rhs: Option::arbitrary_from(rng, t),
|
||||
},
|
||||
6 => Expr::IsNull(Box::arbitrary_from(rng, t)),
|
||||
7 => {
|
||||
let op = LikeOperator::arbitrary_from(rng, t);
|
||||
let escape = if matches!(op, LikeOperator::Like) {
|
||||
Option::arbitrary_from(rng, t)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Expr::Like {
|
||||
lhs: Box::arbitrary_from(rng, t),
|
||||
not: rng.gen_bool(0.5),
|
||||
op,
|
||||
rhs: Box::arbitrary_from(rng, t),
|
||||
escape,
|
||||
}
|
||||
}
|
||||
8 => Expr::Literal(ast::Literal::arbitrary_from(rng, t)),
|
||||
9 => Expr::NotNull(Box::arbitrary_from(rng, t)),
|
||||
// TODO: only supports one paranthesized expression
|
||||
10 => Expr::Parenthesized(vec![Expr::arbitrary_from(rng, t)]),
|
||||
11 => {
|
||||
let table_idx = pick_index(t.tables.len(), rng);
|
||||
let table = &t.tables[table_idx];
|
||||
let col_idx = pick_index(table.columns.len(), rng);
|
||||
let col = &table.columns[col_idx];
|
||||
Expr::Qualified(Name(table.name.clone()), Name(col.name.clone()))
|
||||
}
|
||||
12 => Expr::Unary(
|
||||
UnaryOperator::arbitrary_from(rng, t),
|
||||
Box::arbitrary_from(rng, t),
|
||||
),
|
||||
// TODO: skip Exists for now
|
||||
// TODO: skip Function Call for now
|
||||
// TODO: skip Function Call Star for now
|
||||
// TODO: skip ID for now
|
||||
// TODO: skip InSelect as still need to implement ArbitratyFrom for Select
|
||||
// TODO: skip InTable
|
||||
// TODO: skip Name
|
||||
// TODO: Skip DoublyQualified for now
|
||||
// TODO: skip Raise
|
||||
// TODO: skip subquery
|
||||
_ => unreachable!(),
|
||||
};
|
||||
expr
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for Operator {
|
||||
fn arbitrary<R: rand::Rng>(rng: &mut R) -> Self {
|
||||
let choice = rng.gen_range(0..23);
|
||||
match choice {
|
||||
0 => Operator::Add,
|
||||
1 => Operator::And,
|
||||
2 => Operator::ArrowRight,
|
||||
3 => Operator::ArrowRightShift,
|
||||
4 => Operator::BitwiseAnd,
|
||||
5 => Operator::BitwiseNot,
|
||||
6 => Operator::BitwiseOr,
|
||||
7 => Operator::Concat,
|
||||
8 => Operator::Divide,
|
||||
9 => Operator::Equals,
|
||||
10 => Operator::Greater,
|
||||
11 => Operator::GreaterEquals,
|
||||
12 => Operator::Is,
|
||||
13 => Operator::IsNot,
|
||||
14 => Operator::LeftShift,
|
||||
15 => Operator::Less,
|
||||
16 => Operator::LessEquals,
|
||||
17 => Operator::Modulus,
|
||||
18 => Operator::Multiply,
|
||||
19 => Operator::NotEquals,
|
||||
20 => Operator::Or,
|
||||
21 => Operator::RightShift,
|
||||
22 => Operator::Subtract,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for Type {
|
||||
fn arbitrary<R: rand::Rng>(rng: &mut R) -> Self {
|
||||
let name = pick(&["INT", "INTEGER", "REAL", "TEXT", "BLOB", "ANY"], rng).to_string();
|
||||
Self {
|
||||
name,
|
||||
size: None, // TODO: come back later here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CollateName(String);
|
||||
|
||||
impl Arbitrary for CollateName {
|
||||
fn arbitrary<R: rand::Rng>(rng: &mut R) -> Self {
|
||||
let choice = rng.gen_range(0..3);
|
||||
CollateName(
|
||||
match choice {
|
||||
0 => "BINARY",
|
||||
1 => "RTRIM",
|
||||
2 => "NOCASE",
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for QualifiedName {
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, t: &SimulatorEnv) -> Self {
|
||||
// TODO: for now just generate table name
|
||||
let table_idx = pick_index(t.tables.len(), rng);
|
||||
let table = &t.tables[table_idx];
|
||||
// TODO: for now forego alias
|
||||
Self::single(Name(table.name.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for LikeOperator {
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, _t: &SimulatorEnv) -> Self {
|
||||
let choice = rng.gen_range(0..4);
|
||||
match choice {
|
||||
0 => LikeOperator::Glob,
|
||||
1 => LikeOperator::Like,
|
||||
2 => LikeOperator::Match,
|
||||
3 => LikeOperator::Regexp,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Current implementation does not take into account the columns affinity nor if table is Strict
|
||||
impl ArbitraryFrom<&SimulatorEnv> for ast::Literal {
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, _t: &SimulatorEnv) -> Self {
|
||||
loop {
|
||||
let choice = rng.gen_range(0..5);
|
||||
let lit = match choice {
|
||||
0 => ast::Literal::Numeric({
|
||||
let integer = rng.gen_bool(0.5);
|
||||
if integer {
|
||||
rng.gen_range(i64::MIN..i64::MAX).to_string()
|
||||
} else {
|
||||
rng.gen_range(-1e10..1e10).to_string()
|
||||
}
|
||||
}),
|
||||
1 => ast::Literal::String(format!("'{}'", gen_random_text(rng))),
|
||||
2 => ast::Literal::Blob(hex::encode(gen_random_text(rng).as_bytes())),
|
||||
// TODO: skip Keyword
|
||||
3 => continue,
|
||||
4 => ast::Literal::Null,
|
||||
// TODO: Ignore Date stuff for now
|
||||
_ => continue,
|
||||
};
|
||||
break lit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a litreal value
|
||||
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);
|
||||
}
|
||||
// TODO: for now just convert the value to an ast::Literal
|
||||
let value = pick(&values, rng);
|
||||
Expr::Literal((*value).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for UnaryOperator {
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, _t: &SimulatorEnv) -> Self {
|
||||
let choice = rng.gen_range(0..4);
|
||||
match choice {
|
||||
0 => Self::BitwiseNot,
|
||||
1 => Self::Negative,
|
||||
2 => Self::Not,
|
||||
3 => Self::Positive,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@ use std::{iter::Sum, ops::SubAssign};
|
||||
use anarchist_readable_name_generator_lib::readable_name_custom;
|
||||
use rand::{distributions::uniform::SampleUniform, Rng};
|
||||
|
||||
mod expr;
|
||||
pub mod plan;
|
||||
mod predicate;
|
||||
pub mod property;
|
||||
pub mod query;
|
||||
pub mod table;
|
||||
@@ -71,9 +73,9 @@ pub(crate) fn one_of<'a, T, R: Rng>(choices: Vec<Box<dyn Fn(&mut R) -> T + 'a>>,
|
||||
/// The function takes a list of functions that return an Option<T>, along with number of retries
|
||||
/// to make before giving up.
|
||||
pub(crate) fn backtrack<'a, T, R: Rng>(
|
||||
mut choices: Vec<(u32, Box<dyn Fn(&mut R) -> Option<T> + 'a>)>,
|
||||
mut choices: Vec<(usize, Box<dyn Fn(&mut R) -> Option<T> + 'a>)>,
|
||||
rng: &mut R,
|
||||
) -> T {
|
||||
) -> Option<T> {
|
||||
loop {
|
||||
// If there are no more choices left, we give up
|
||||
let choices_ = choices
|
||||
@@ -82,14 +84,15 @@ pub(crate) fn backtrack<'a, T, R: Rng>(
|
||||
.filter(|(_, (retries, _))| *retries > 0)
|
||||
.collect::<Vec<_>>();
|
||||
if choices_.is_empty() {
|
||||
panic!("backtrack: no more choices left");
|
||||
tracing::trace!("backtrack: no more choices left");
|
||||
return None;
|
||||
}
|
||||
// Run a one_of on the remaining choices
|
||||
let (choice_index, choice) = pick(&choices_, rng);
|
||||
let choice_index = *choice_index;
|
||||
// If the choice returns None, we decrement the number of retries and try again
|
||||
let result = choice.1(rng);
|
||||
if let Some(result) = result {
|
||||
if result.is_some() {
|
||||
return result;
|
||||
} else {
|
||||
choices[choice_index].0 -= 1;
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use std::{collections::HashSet, fmt::Display, path::Path, rc::Rc, vec};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fmt::{Debug, Display},
|
||||
path::Path,
|
||||
rc::Rc,
|
||||
vec,
|
||||
};
|
||||
|
||||
use limbo_core::{Connection, Result, StepResult, IO};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -6,11 +12,12 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
model::{
|
||||
query::{
|
||||
select::{Distinctness, Predicate, ResultColumn},
|
||||
predicate::Predicate,
|
||||
select::{Distinctness, ResultColumn},
|
||||
update::Update,
|
||||
Create, CreateIndex, Delete, Drop, Insert, Query, Select,
|
||||
},
|
||||
table::Value,
|
||||
table::SimValue,
|
||||
},
|
||||
runner::{env::SimConnection, io::SimulatorIO},
|
||||
SimulatorEnv,
|
||||
@@ -20,7 +27,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 {
|
||||
@@ -105,7 +112,7 @@ pub(crate) enum Interactions {
|
||||
}
|
||||
|
||||
impl Interactions {
|
||||
pub(crate) fn name(&self) -> Option<String> {
|
||||
pub(crate) fn name(&self) -> Option<&str> {
|
||||
match self {
|
||||
Interactions::Property(property) => Some(property.name()),
|
||||
Interactions::Query(_) => None,
|
||||
@@ -225,6 +232,7 @@ impl Display for InteractionStats {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Interaction {
|
||||
Query(Query),
|
||||
Assumption(Assertion),
|
||||
@@ -254,6 +262,14 @@ pub(crate) struct Assertion {
|
||||
pub(crate) message: String,
|
||||
}
|
||||
|
||||
impl Debug for Assertion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Assertion")
|
||||
.field("message", &self.message)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) enum Fault {
|
||||
Disconnect,
|
||||
@@ -482,7 +498,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![],
|
||||
@@ -511,13 +527,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);
|
||||
|
||||
544
simulator/generation/predicate/binary.rs
Normal file
544
simulator/generation/predicate/binary.rs
Normal file
@@ -0,0 +1,544 @@
|
||||
//! Contains code for generation for [ast::Expr::Binary] Predicate
|
||||
|
||||
use limbo_sqlite3_parser::ast::{self, Expr};
|
||||
|
||||
use crate::{
|
||||
generation::{
|
||||
backtrack, one_of, pick,
|
||||
predicate::{CompoundPredicate, SimplePredicate},
|
||||
table::{GTValue, LTValue, LikeValue},
|
||||
ArbitraryFrom, ArbitraryFromMaybe as _,
|
||||
},
|
||||
model::{
|
||||
query::predicate::Predicate,
|
||||
table::{SimValue, Table},
|
||||
},
|
||||
};
|
||||
|
||||
impl Predicate {
|
||||
/// Generate an [ast::Expr::Binary] [Predicate] from a column and [SimValue]
|
||||
pub fn from_column_binary<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
column_name: &str,
|
||||
value: &SimValue,
|
||||
) -> Predicate {
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_| {
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Id(ast::Id(column_name.to_string()))),
|
||||
ast::Operator::Equals,
|
||||
Box::new(Expr::Literal(value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let gt_value = GTValue::arbitrary_from(rng, value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Id(ast::Id(column_name.to_string()))),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let lt_value = LTValue::arbitrary_from(rng, value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Id(ast::Id(column_name.to_string()))),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
)
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
Predicate(expr)
|
||||
}
|
||||
|
||||
/// 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<SimValue>) -> Predicate {
|
||||
// Pick a column
|
||||
let column_index = rng.gen_range(0..t.columns.len());
|
||||
let column = &t.columns[column_index];
|
||||
let value = &row[column_index];
|
||||
let expr = backtrack(
|
||||
vec![
|
||||
(
|
||||
1,
|
||||
Box::new(|_| {
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
Box::new(Expr::Literal(value.into())),
|
||||
))
|
||||
}),
|
||||
),
|
||||
(
|
||||
1,
|
||||
Box::new(|rng| {
|
||||
let v = SimValue::arbitrary_from(rng, &column.column_type);
|
||||
if &v == value {
|
||||
None
|
||||
} else {
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
Box::new(Expr::Literal(v.into())),
|
||||
))
|
||||
}
|
||||
}),
|
||||
),
|
||||
(
|
||||
1,
|
||||
Box::new(|rng| {
|
||||
let lt_value = LTValue::arbitrary_from(rng, value).0;
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
))
|
||||
}),
|
||||
),
|
||||
(
|
||||
1,
|
||||
Box::new(|rng| {
|
||||
let gt_value = GTValue::arbitrary_from(rng, value).0;
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
))
|
||||
}),
|
||||
),
|
||||
(
|
||||
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| {
|
||||
Expr::Like {
|
||||
lhs: Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
not: false, // TODO: also generate this value eventually
|
||||
op: ast::LikeOperator::Like,
|
||||
rhs: Box::new(Expr::Literal(like.0.into())),
|
||||
escape: None, // TODO: implement
|
||||
}
|
||||
})
|
||||
}),
|
||||
),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
// Backtrack will always return Some here
|
||||
Predicate(expr.unwrap())
|
||||
}
|
||||
|
||||
/// 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<SimValue>) -> Predicate {
|
||||
// Pick a column
|
||||
let column_index = rng.gen_range(0..t.columns.len());
|
||||
let column = &t.columns[column_index];
|
||||
let value = &row[column_index];
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_| {
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
Box::new(Expr::Literal(value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let v = loop {
|
||||
let v = SimValue::arbitrary_from(rng, &column.column_type);
|
||||
if &v != value {
|
||||
break v;
|
||||
}
|
||||
};
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
Box::new(Expr::Literal(v.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let gt_value = GTValue::arbitrary_from(rng, value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let lt_value = LTValue::arbitrary_from(rng, value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(t.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
)
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
Predicate(expr)
|
||||
}
|
||||
}
|
||||
|
||||
impl SimplePredicate {
|
||||
/// Generates a true [ast::Expr::Binary] [SimplePredicate] from a [Table] for a row in the table
|
||||
pub fn true_binary<R: rand::Rng>(rng: &mut R, table: &Table, row: &[SimValue]) -> Self {
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..table.columns.len());
|
||||
let column = &table.columns[column_index];
|
||||
let column_value = &row[column_index];
|
||||
// Avoid creation of NULLs
|
||||
if row.is_empty() {
|
||||
return SimplePredicate(Predicate(Expr::Literal(SimValue::TRUE.into())));
|
||||
}
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_rng| {
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(table.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
Box::new(Expr::Literal(column_value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let lt_value = LTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name(table.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let gt_value = GTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name(table.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
)
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
SimplePredicate(Predicate(expr))
|
||||
}
|
||||
|
||||
/// Generates a false [ast::Expr::Binary] [SimplePredicate] from a [Table] for a row in the table
|
||||
pub fn false_binary<R: rand::Rng>(rng: &mut R, table: &Table, row: &[SimValue]) -> Self {
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..table.columns.len());
|
||||
let column = &table.columns[column_index];
|
||||
let column_value = &row[column_index];
|
||||
// Avoid creation of NULLs
|
||||
if row.is_empty() {
|
||||
return SimplePredicate(Predicate(Expr::Literal(SimValue::FALSE.into())));
|
||||
}
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_rng| {
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name(table.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
Box::new(Expr::Literal(column_value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let gt_value = GTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(table.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let lt_value = LTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name(table.name.clone()),
|
||||
ast::Name(column.name.clone()),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
)
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
SimplePredicate(Predicate(expr))
|
||||
}
|
||||
}
|
||||
|
||||
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>(
|
||||
rng: &mut R,
|
||||
table: &Table,
|
||||
predicate_value: bool,
|
||||
) -> Self {
|
||||
// Cannot pick a row if the table is empty
|
||||
if table.rows.is_empty() {
|
||||
return Self(
|
||||
predicate_value
|
||||
.then_some(Predicate::true_())
|
||||
.unwrap_or(Predicate::false_()),
|
||||
);
|
||||
}
|
||||
let row = pick(&table.rows, rng);
|
||||
let predicate = if rng.gen_bool(0.7) {
|
||||
// An AND for true requires each of its children to be true
|
||||
// An AND for false requires at least one of its children to be false
|
||||
if predicate_value {
|
||||
(0..rng.gen_range(0..=3))
|
||||
.map(|_| SimplePredicate::arbitrary_from(rng, (table, row, true)).0)
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::And,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::true_())
|
||||
} else {
|
||||
// Create a vector of random booleans
|
||||
let mut booleans = (0..rng.gen_range(0..=3))
|
||||
.map(|_| rng.gen_bool(0.5))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let len = booleans.len();
|
||||
|
||||
// Make sure at least one of them is false
|
||||
if !booleans.is_empty() && booleans.iter().all(|b| *b) {
|
||||
booleans[rng.gen_range(0..len)] = false;
|
||||
}
|
||||
|
||||
booleans
|
||||
.iter()
|
||||
.map(|b| SimplePredicate::arbitrary_from(rng, (table, row, *b)).0)
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::And,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::false_())
|
||||
}
|
||||
} else {
|
||||
// An OR for true requires at least one of its children to be true
|
||||
// An OR for false requires each of its children to be false
|
||||
if predicate_value {
|
||||
// Create a vector of random booleans
|
||||
let mut booleans = (0..rng.gen_range(0..=3))
|
||||
.map(|_| rng.gen_bool(0.5))
|
||||
.collect::<Vec<_>>();
|
||||
let len = booleans.len();
|
||||
// Make sure at least one of them is true
|
||||
if !booleans.is_empty() && booleans.iter().all(|b| !*b) {
|
||||
booleans[rng.gen_range(0..len)] = true;
|
||||
}
|
||||
|
||||
booleans
|
||||
.iter()
|
||||
.map(|b| SimplePredicate::arbitrary_from(rng, (table, row, *b)).0)
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::Or,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::true_())
|
||||
} else {
|
||||
(0..rng.gen_range(0..=3))
|
||||
.map(|_| SimplePredicate::arbitrary_from(rng, (table, row, false)).0)
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::Or,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::false_())
|
||||
}
|
||||
};
|
||||
Self(predicate)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::{Rng as _, SeedableRng as _};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
use crate::{
|
||||
generation::{pick, predicate::SimplePredicate, Arbitrary, ArbitraryFrom as _},
|
||||
model::{
|
||||
query::predicate::{expr_to_value, Predicate},
|
||||
table::{SimValue, Table},
|
||||
},
|
||||
};
|
||||
|
||||
fn get_seed() -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_true_binary_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.gen_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))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let row = pick(&values, &mut rng);
|
||||
let predicate = Predicate::true_binary(&mut rng, &table, row);
|
||||
let value = expr_to_value(&predicate.0, row, &table);
|
||||
assert!(
|
||||
value.as_ref().map_or(false, |value| value.into_bool()),
|
||||
"Predicate: {:#?}\nValue: {:#?}\nSeed: {}",
|
||||
predicate,
|
||||
value,
|
||||
seed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_false_binary_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.gen_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))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let row = pick(&values, &mut rng);
|
||||
let predicate = Predicate::false_binary(&mut rng, &table, row);
|
||||
let value = expr_to_value(&predicate.0, row, &table);
|
||||
assert!(
|
||||
!value.as_ref().map_or(false, |value| value.into_bool()),
|
||||
"Predicate: {:#?}\nValue: {:#?}\nSeed: {}",
|
||||
predicate,
|
||||
value,
|
||||
seed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_true_binary_simple_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let mut table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.gen_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))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
table.rows.extend(values.clone());
|
||||
let row = pick(&table.rows, &mut rng);
|
||||
let predicate = SimplePredicate::true_binary(&mut rng, &table, row);
|
||||
let result = values
|
||||
.iter()
|
||||
.map(|row| predicate.0.test(row, &table))
|
||||
.reduce(|accum, curr| accum || curr)
|
||||
.unwrap_or(false);
|
||||
assert!(result, "Predicate: {:#?}\nSeed: {}", predicate, seed)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_false_binary_simple_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let mut table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.gen_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))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
table.rows.extend(values.clone());
|
||||
let row = pick(&table.rows, &mut rng);
|
||||
let predicate = SimplePredicate::false_binary(&mut rng, &table, row);
|
||||
let result = values
|
||||
.iter()
|
||||
.map(|row| predicate.0.test(row, &table))
|
||||
.any(|res| !res);
|
||||
assert!(result, "Predicate: {:#?}\nSeed: {}", predicate, seed)
|
||||
}
|
||||
}
|
||||
}
|
||||
390
simulator/generation/predicate/mod.rs
Normal file
390
simulator/generation/predicate/mod.rs
Normal file
@@ -0,0 +1,390 @@
|
||||
use limbo_sqlite3_parser::ast::{self, Expr};
|
||||
use rand::{seq::SliceRandom as _, Rng};
|
||||
|
||||
use crate::model::{
|
||||
query::predicate::Predicate,
|
||||
table::{SimValue, Table},
|
||||
};
|
||||
|
||||
use super::{one_of, ArbitraryFrom};
|
||||
|
||||
mod binary;
|
||||
mod unary;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CompoundPredicate(Predicate);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SimplePredicate(Predicate);
|
||||
|
||||
impl<A: AsRef<[SimValue]>> ArbitraryFrom<(&Table, A, bool)> for SimplePredicate {
|
||||
fn arbitrary_from<R: Rng>(
|
||||
rng: &mut R,
|
||||
(table, row, predicate_value): (&Table, A, bool),
|
||||
) -> Self {
|
||||
let row = row.as_ref();
|
||||
// Pick an operator
|
||||
let choice = rng.gen_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),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
false => match choice {
|
||||
0 => SimplePredicate::false_binary(rng, table, row),
|
||||
1 => SimplePredicate::false_unary(rng, table, row),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, predicate_value): (&Table, bool)) -> Self {
|
||||
CompoundPredicate::from_table_binary(rng, table, predicate_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&Table> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, table: &Table) -> Self {
|
||||
let predicate_value = rng.gen_bool(0.5);
|
||||
Predicate::arbitrary_from(rng, (table, predicate_value))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<(&Table, bool)> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, predicate_value): (&Table, bool)) -> Self {
|
||||
CompoundPredicate::arbitrary_from(rng, (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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// Produce some true and false predicates
|
||||
let mut true_predicates = (1..=rng.gen_range(1..=4))
|
||||
.map(|_| Predicate::true_binary(rng, t, row))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let false_predicates = (0..=rng.gen_range(0..=3))
|
||||
.map(|_| Predicate::false_binary(rng, t, row))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Start building a top level predicate from a true predicate
|
||||
let mut result = true_predicates.pop().unwrap();
|
||||
|
||||
let mut predicates = true_predicates
|
||||
.iter()
|
||||
.map(|p| (true, p.clone()))
|
||||
.chain(false_predicates.iter().map(|p| (false, p.clone())))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
predicates.shuffle(rng);
|
||||
|
||||
while !predicates.is_empty() {
|
||||
// Create a new predicate from at least 1 and at most 3 predicates
|
||||
let context =
|
||||
predicates[0..rng.gen_range(0..=usize::min(3, predicates.len()))].to_vec();
|
||||
// Shift `predicates` to remove the predicates in the context
|
||||
predicates = predicates[context.len()..].to_vec();
|
||||
|
||||
// `result` is true, so we have the following three options to make a true predicate:
|
||||
// T or F
|
||||
// T or T
|
||||
// T and T
|
||||
|
||||
result = one_of(
|
||||
vec![
|
||||
// T or (X1 or X2 or ... or Xn)
|
||||
Box::new(|_| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(result.0.clone()),
|
||||
ast::Operator::Or,
|
||||
Box::new(
|
||||
context
|
||||
.iter()
|
||||
.map(|(_, p)| p.clone())
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::Or,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::false_())
|
||||
.0,
|
||||
),
|
||||
))
|
||||
}),
|
||||
// T or (T1 and T2 and ... and Tn)
|
||||
Box::new(|_| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(result.0.clone()),
|
||||
ast::Operator::Or,
|
||||
Box::new(
|
||||
context
|
||||
.iter()
|
||||
.map(|(_, p)| p.clone())
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::And,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::true_())
|
||||
.0,
|
||||
),
|
||||
))
|
||||
}),
|
||||
// T and T
|
||||
Box::new(|_| {
|
||||
// Check if all the predicates in the context are true
|
||||
if context.iter().all(|(b, _)| *b) {
|
||||
// T and (X1 or X2 or ... or Xn)
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(result.0.clone()),
|
||||
ast::Operator::And,
|
||||
Box::new(
|
||||
context
|
||||
.iter()
|
||||
.map(|(_, p)| p.clone())
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::And,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::true_())
|
||||
.0,
|
||||
),
|
||||
))
|
||||
}
|
||||
// Check if there is at least one true predicate
|
||||
else if context.iter().any(|(b, _)| *b) {
|
||||
// T and (X1 or X2 or ... or Xn)
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(result.0.clone()),
|
||||
ast::Operator::And,
|
||||
Box::new(
|
||||
context
|
||||
.iter()
|
||||
.map(|(_, p)| p.clone())
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::Or,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::false_())
|
||||
.0,
|
||||
),
|
||||
))
|
||||
// Predicate::And(vec![
|
||||
// result.clone(),
|
||||
// Predicate::Or(context.iter().map(|(_, p)| p.clone()).collect()),
|
||||
// ])
|
||||
} else {
|
||||
// T and (X1 or X2 or ... or Xn or TRUE)
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(result.0.clone()),
|
||||
ast::Operator::And,
|
||||
Box::new(
|
||||
context
|
||||
.iter()
|
||||
.map(|(_, p)| p.clone())
|
||||
.chain(std::iter::once(Predicate::true_()))
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::Or,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap() // Chain guarantees at least one value
|
||||
.0,
|
||||
),
|
||||
))
|
||||
}
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::{Rng as _, SeedableRng as _};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
use crate::{
|
||||
generation::{pick, predicate::SimplePredicate, Arbitrary, ArbitraryFrom as _},
|
||||
model::{
|
||||
query::predicate::{expr_to_value, Predicate},
|
||||
table::{SimValue, Table},
|
||||
},
|
||||
};
|
||||
|
||||
fn get_seed() -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_arbitrary_table_true_simple_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.gen_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))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let row = pick(&values, &mut rng);
|
||||
let predicate = SimplePredicate::arbitrary_from(&mut rng, (&table, row, true)).0;
|
||||
let value = expr_to_value(&predicate.0, row, &table);
|
||||
assert!(
|
||||
value.as_ref().map_or(false, |value| value.into_bool()),
|
||||
"Predicate: {:#?}\nValue: {:#?}\nSeed: {}",
|
||||
predicate,
|
||||
value,
|
||||
seed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_arbitrary_table_false_simple_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.gen_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))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let row = pick(&values, &mut rng);
|
||||
let predicate = SimplePredicate::arbitrary_from(&mut rng, (&table, row, false)).0;
|
||||
let value = expr_to_value(&predicate.0, row, &table);
|
||||
assert!(
|
||||
!value.as_ref().map_or(false, |value| value.into_bool()),
|
||||
"Predicate: {:#?}\nValue: {:#?}\nSeed: {}",
|
||||
predicate,
|
||||
value,
|
||||
seed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_arbitrary_row_table_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.gen_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))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let row = pick(&values, &mut rng);
|
||||
let predicate = Predicate::arbitrary_from(&mut rng, (&table, row));
|
||||
let value = expr_to_value(&predicate.0, row, &table);
|
||||
assert!(
|
||||
value.as_ref().map_or(false, |value| value.into_bool()),
|
||||
"Predicate: {:#?}\nValue: {:#?}\nSeed: {}",
|
||||
predicate,
|
||||
value,
|
||||
seed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_arbitrary_true_table_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let mut table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.gen_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))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
table.rows.extend(values.clone());
|
||||
let predicate = Predicate::arbitrary_from(&mut rng, (&table, true));
|
||||
let result = values
|
||||
.iter()
|
||||
.map(|row| predicate.test(row, &table))
|
||||
.reduce(|accum, curr| accum || curr)
|
||||
.unwrap_or(false);
|
||||
assert!(result, "Predicate: {:#?}\nSeed: {}", predicate, seed)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_arbitrary_false_table_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let mut table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.gen_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))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
table.rows.extend(values.clone());
|
||||
let predicate = Predicate::arbitrary_from(&mut rng, (&table, false));
|
||||
let result = values
|
||||
.iter()
|
||||
.map(|row| predicate.test(row, &table))
|
||||
.any(|res| !res);
|
||||
assert!(result, "Predicate: {:#?}\nSeed: {}", predicate, seed)
|
||||
}
|
||||
}
|
||||
}
|
||||
296
simulator/generation/predicate/unary.rs
Normal file
296
simulator/generation/predicate/unary.rs
Normal file
@@ -0,0 +1,296 @@
|
||||
//! 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 limbo_sqlite3_parser::ast::{self, Expr};
|
||||
|
||||
use crate::{
|
||||
generation::{backtrack, pick, predicate::SimplePredicate, ArbitraryFromMaybe},
|
||||
model::{
|
||||
query::predicate::Predicate,
|
||||
table::{SimValue, Table},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct TrueValue(pub SimValue);
|
||||
|
||||
impl ArbitraryFromMaybe<&SimValue> for TrueValue {
|
||||
fn arbitrary_from_maybe<R: rand::Rng>(_rng: &mut R, value: &SimValue) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// If the Value is a true value return it else you cannot return a true Value
|
||||
value.into_bool().then_some(Self(value.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
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(SimValue::TRUE));
|
||||
}
|
||||
|
||||
let value = pick(values, rng);
|
||||
Self::arbitrary_from_maybe(rng, *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>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// If the Value is a false value return it else you cannot return a false Value
|
||||
(!value.into_bool()).then_some(Self(value.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
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(SimValue::FALSE));
|
||||
}
|
||||
|
||||
let value = pick(values, rng);
|
||||
Self::arbitrary_from_maybe(rng, *value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BitNotValue(pub SimValue);
|
||||
|
||||
impl ArbitraryFromMaybe<(&SimValue, bool)> for BitNotValue {
|
||||
fn arbitrary_from_maybe<R: rand::Rng>(
|
||||
_rng: &mut R,
|
||||
(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.into_bool() == predicate).then_some(BitNotValue(value.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFromMaybe<(&Vec<&SimValue>, bool)> for BitNotValue {
|
||||
fn arbitrary_from_maybe<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
(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, (*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 [Table] for some values in the table
|
||||
pub fn true_unary<R: rand::Rng>(rng: &mut R, table: &Table, row: &[SimValue]) -> Self {
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..table.columns.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, column_value).map(|value| {
|
||||
assert!(value.0.into_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.into_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, column_value).map(|value| {
|
||||
assert!(!value.0.into_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 [Table] for a row in the table
|
||||
pub fn false_unary<R: rand::Rng>(rng: &mut R, table: &Table, row: &[SimValue]) -> Self {
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..table.columns.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::FALSE.into())));
|
||||
}
|
||||
let expr = backtrack(
|
||||
vec![
|
||||
(
|
||||
num_retries,
|
||||
Box::new(|rng| {
|
||||
FalseValue::arbitrary_from_maybe(rng, column_value).map(|value| {
|
||||
assert!(!value.0.into_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.into_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, column_value).map(|value| {
|
||||
assert!(value.0.into_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, 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);
|
||||
for _ in 0..10000 {
|
||||
let mut table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.gen_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))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
table.rows.extend(values.clone());
|
||||
let row = pick(&table.rows, &mut rng);
|
||||
let predicate = SimplePredicate::true_unary(&mut rng, &table, row);
|
||||
let result = values
|
||||
.iter()
|
||||
.map(|row| predicate.0.test(row, &table))
|
||||
.reduce(|accum, curr| accum || curr)
|
||||
.unwrap_or(false);
|
||||
assert!(result, "Predicate: {:#?}\nSeed: {}", predicate, seed)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_false_unary_simple_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let mut table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.gen_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))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
table.rows.extend(values.clone());
|
||||
let row = pick(&table.rows, &mut rng);
|
||||
let predicate = SimplePredicate::false_unary(&mut rng, &table, row);
|
||||
let result = values
|
||||
.iter()
|
||||
.map(|row| predicate.0.test(row, &table))
|
||||
.any(|res| !res);
|
||||
assert!(result, "Predicate: {:#?}\nSeed: {}", predicate, seed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
use limbo_core::LimboError;
|
||||
use limbo_sqlite3_parser::ast;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
model::{
|
||||
query::{
|
||||
select::{Distinctness, Predicate, ResultColumn},
|
||||
predicate::Predicate,
|
||||
select::{Distinctness, ResultColumn},
|
||||
Create, Delete, Drop, Insert, Query, Select,
|
||||
},
|
||||
table::Value,
|
||||
table::SimValue,
|
||||
},
|
||||
runner::env::SimulatorEnv,
|
||||
};
|
||||
@@ -128,14 +130,14 @@ pub(crate) enum Property {
|
||||
}
|
||||
|
||||
impl Property {
|
||||
pub(crate) fn name(&self) -> String {
|
||||
pub(crate) fn name(&self) -> &str {
|
||||
match self {
|
||||
Property::InsertValuesSelect { .. } => "Insert-Values-Select".to_string(),
|
||||
Property::DoubleCreateFailure { .. } => "Double-Create-Failure".to_string(),
|
||||
Property::SelectLimit { .. } => "Select-Limit".to_string(),
|
||||
Property::DeleteSelect { .. } => "Delete-Select".to_string(),
|
||||
Property::DropSelect { .. } => "Drop-Select".to_string(),
|
||||
Property::SelectSelectOptimizer { .. } => "Select-Select-Optimizer".to_string(),
|
||||
Property::InsertValuesSelect { .. } => "Insert-Values-Select",
|
||||
Property::DoubleCreateFailure { .. } => "Double-Create-Failure",
|
||||
Property::SelectLimit { .. } => "Select-Limit",
|
||||
Property::DeleteSelect { .. } => "Delete-Select",
|
||||
Property::DropSelect { .. } => "Drop-Select",
|
||||
Property::SelectSelectOptimizer { .. } => "Select-Select-Optimizer",
|
||||
}
|
||||
}
|
||||
/// interactions construct a list of interactions, which is an executable representation of the property.
|
||||
@@ -287,20 +289,6 @@ impl Property {
|
||||
}),
|
||||
});
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: format!(
|
||||
"select '{}' should return no values for table '{}'",
|
||||
predicate, table,
|
||||
),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let rows = stack.last().unwrap();
|
||||
match rows {
|
||||
Ok(rows) => Ok(rows.is_empty()),
|
||||
Err(err) => Err(LimboError::InternalError(err.to_string())),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let delete = Interaction::Query(Query::Delete(Delete {
|
||||
table: table.clone(),
|
||||
predicate: predicate.clone(),
|
||||
@@ -314,6 +302,17 @@ impl Property {
|
||||
distinct: Distinctness::All,
|
||||
}));
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: format!("`{}` should return no values for table `{}`", select, table,),
|
||||
func: Box::new(move |stack: &Vec<ResultSet>, _: &SimulatorEnv| {
|
||||
let rows = stack.last().unwrap();
|
||||
match rows {
|
||||
Ok(rows) => Ok(rows.is_empty()),
|
||||
Err(err) => Err(LimboError::InternalError(err.to_string())),
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let mut interactions = Vec::new();
|
||||
interactions.push(assumption);
|
||||
interactions.push(delete);
|
||||
@@ -382,7 +381,6 @@ impl Property {
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
let select1 = Interaction::Query(Query::Select(Select {
|
||||
table: table.clone(),
|
||||
result_columns: vec![ResultColumn::Expr(predicate.clone())],
|
||||
@@ -391,13 +389,14 @@ impl Property {
|
||||
distinct: Distinctness::All,
|
||||
}));
|
||||
|
||||
let select2 = Interaction::Query(Query::Select(Select {
|
||||
let select2_query = Query::Select(Select {
|
||||
table: table.clone(),
|
||||
result_columns: vec![ResultColumn::Star],
|
||||
predicate: predicate.clone(),
|
||||
limit: None,
|
||||
distinct: Distinctness::All,
|
||||
}));
|
||||
});
|
||||
let select2 = Interaction::Query(select2_query);
|
||||
|
||||
let assertion = Interaction::Assertion(Assertion {
|
||||
message: "select queries should return the same amount of results".to_string(),
|
||||
@@ -413,19 +412,14 @@ impl Property {
|
||||
));
|
||||
}
|
||||
// Count the 1s in the select query without the star
|
||||
let rows1 = rows1
|
||||
let rows1_count = rows1
|
||||
.iter()
|
||||
.filter(|vs| {
|
||||
let v = vs.first().unwrap();
|
||||
if let Value::Integer(i) = v {
|
||||
*i == 1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
v.into_bool()
|
||||
})
|
||||
.count();
|
||||
|
||||
Ok(rows1 == rows2.len())
|
||||
Ok(rows1_count == rows2.len())
|
||||
}
|
||||
_ => Ok(false),
|
||||
}
|
||||
@@ -495,7 +489,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
|
||||
@@ -696,10 +690,16 @@ fn property_select_select_optimizer<R: rand::Rng>(rng: &mut R, env: &SimulatorEn
|
||||
let table = pick(&env.tables, rng);
|
||||
// Generate a random predicate
|
||||
let predicate = Predicate::arbitrary_from(rng, table);
|
||||
// Transform into a Binary predicate to force values to be casted to a bool
|
||||
let expr = ast::Expr::Binary(
|
||||
Box::new(predicate.0),
|
||||
ast::Operator::And,
|
||||
Box::new(Predicate::true_().0),
|
||||
);
|
||||
|
||||
Property::SelectSelectOptimizer {
|
||||
table: table.name.clone(),
|
||||
predicate,
|
||||
predicate: Predicate(expr),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,28 +712,52 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
|
||||
frequency(
|
||||
vec![
|
||||
(
|
||||
f64::min(remaining_.read, remaining_.write),
|
||||
if !env.opts.disable_insert_values_select {
|
||||
f64::min(remaining_.read, remaining_.write)
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_insert_values_select(rng, env, &remaining_)),
|
||||
),
|
||||
(
|
||||
remaining_.create / 2.0,
|
||||
if !env.opts.disable_double_create_failure {
|
||||
remaining_.create / 2.0
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_double_create_failure(rng, env, &remaining_)),
|
||||
),
|
||||
(
|
||||
remaining_.read,
|
||||
if !env.opts.disable_select_limit {
|
||||
remaining_.read
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_select_limit(rng, env)),
|
||||
),
|
||||
(
|
||||
f64::min(remaining_.read, remaining_.write).min(remaining_.delete),
|
||||
if !env.opts.disable_delete_select {
|
||||
f64::min(remaining_.read, remaining_.write).min(remaining_.delete)
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_delete_select(rng, env, &remaining_)),
|
||||
),
|
||||
(
|
||||
// remaining_.drop,
|
||||
0.0,
|
||||
if !env.opts.disable_drop_select {
|
||||
// remaining_.drop
|
||||
0.0
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_drop_select(rng, env, &remaining_)),
|
||||
),
|
||||
(
|
||||
remaining_.read / 2.0,
|
||||
if !env.opts.disable_select_optimizer {
|
||||
remaining_.read / 2.0
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
Box::new(|rng: &mut R| property_select_select_optimizer(rng, env)),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::generation::table::{GTValue, LTValue};
|
||||
use crate::generation::{one_of, Arbitrary, ArbitraryFrom};
|
||||
|
||||
use crate::model::query::select::{Distinctness, Predicate, ResultColumn};
|
||||
use crate::generation::{Arbitrary, ArbitraryFrom};
|
||||
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::seq::SliceRandom as _;
|
||||
use rand::Rng;
|
||||
|
||||
use super::property::Remaining;
|
||||
use super::table::LikeValue;
|
||||
use super::{backtrack, frequency, pick, ArbitraryFromMaybe};
|
||||
use super::{backtrack, frequency, pick};
|
||||
|
||||
impl Arbitrary for Create {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
|
||||
@@ -41,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();
|
||||
@@ -81,6 +78,7 @@ impl ArbitraryFrom<&SimulatorEnv> for Insert {
|
||||
})
|
||||
};
|
||||
|
||||
// Backtrack here cannot return None
|
||||
backtrack(
|
||||
vec![
|
||||
(1, Box::new(|rng| gen_values(rng))),
|
||||
@@ -89,6 +87,7 @@ impl ArbitraryFrom<&SimulatorEnv> for Insert {
|
||||
],
|
||||
rng,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,348 +136,12 @@ impl ArbitraryFrom<(&SimulatorEnv, &Remaining)> for Query {
|
||||
}
|
||||
}
|
||||
|
||||
struct CompoundPredicate(Predicate);
|
||||
struct SimplePredicate(Predicate);
|
||||
|
||||
impl ArbitraryFrom<(&Table, bool)> for SimplePredicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, predicate_value): (&Table, bool)) -> Self {
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..table.columns.len());
|
||||
let column = &table.columns[column_index];
|
||||
let column_values = table
|
||||
.rows
|
||||
.iter()
|
||||
.map(|r| &r[column_index])
|
||||
.collect::<Vec<_>>();
|
||||
// Pick an operator
|
||||
let operator = match predicate_value {
|
||||
true => one_of(
|
||||
vec![
|
||||
Box::new(|rng| {
|
||||
Predicate::Eq(
|
||||
column.name.clone(),
|
||||
Value::arbitrary_from(rng, &column_values),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
Predicate::Gt(
|
||||
column.name.clone(),
|
||||
GTValue::arbitrary_from(rng, &column_values).0,
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
Predicate::Lt(
|
||||
column.name.clone(),
|
||||
LTValue::arbitrary_from(rng, &column_values).0,
|
||||
)
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
),
|
||||
false => one_of(
|
||||
vec![
|
||||
Box::new(|rng| {
|
||||
Predicate::Neq(
|
||||
column.name.clone(),
|
||||
Value::arbitrary_from(rng, &column.column_type),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
Predicate::Gt(
|
||||
column.name.clone(),
|
||||
LTValue::arbitrary_from(rng, &column_values).0,
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
Predicate::Lt(
|
||||
column.name.clone(),
|
||||
GTValue::arbitrary_from(rng, &column_values).0,
|
||||
)
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
),
|
||||
};
|
||||
|
||||
Self(operator)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, predicate_value): (&Table, bool)) -> Self {
|
||||
// Decide if you want to create an AND or an OR
|
||||
Self(if rng.gen_bool(0.7) {
|
||||
// An AND for true requires each of its children to be true
|
||||
// An AND for false requires at least one of its children to be false
|
||||
if predicate_value {
|
||||
Predicate::And(
|
||||
(0..rng.gen_range(0..=3))
|
||||
.map(|_| SimplePredicate::arbitrary_from(rng, (table, true)).0)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
// Create a vector of random booleans
|
||||
let mut booleans = (0..rng.gen_range(0..=3))
|
||||
.map(|_| rng.gen_bool(0.5))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let len = booleans.len();
|
||||
|
||||
// Make sure at least one of them is false
|
||||
if !booleans.is_empty() && booleans.iter().all(|b| *b) {
|
||||
booleans[rng.gen_range(0..len)] = false;
|
||||
}
|
||||
|
||||
Predicate::And(
|
||||
booleans
|
||||
.iter()
|
||||
.map(|b| SimplePredicate::arbitrary_from(rng, (table, *b)).0)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// An OR for true requires at least one of its children to be true
|
||||
// An OR for false requires each of its children to be false
|
||||
if predicate_value {
|
||||
// Create a vector of random booleans
|
||||
let mut booleans = (0..rng.gen_range(0..=3))
|
||||
.map(|_| rng.gen_bool(0.5))
|
||||
.collect::<Vec<_>>();
|
||||
let len = booleans.len();
|
||||
// Make sure at least one of them is true
|
||||
if !booleans.is_empty() && booleans.iter().all(|b| !*b) {
|
||||
booleans[rng.gen_range(0..len)] = true;
|
||||
}
|
||||
|
||||
Predicate::Or(
|
||||
booleans
|
||||
.iter()
|
||||
.map(|b| SimplePredicate::arbitrary_from(rng, (table, *b)).0)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
Predicate::Or(
|
||||
(0..rng.gen_range(0..=3))
|
||||
.map(|_| SimplePredicate::arbitrary_from(rng, (table, false)).0)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&Table> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, table: &Table) -> Self {
|
||||
let predicate_value = rng.gen_bool(0.5);
|
||||
CompoundPredicate::arbitrary_from(rng, (table, predicate_value)).0
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<(&str, &Value)> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (column_name, value): (&str, &Value)) -> Self {
|
||||
one_of(
|
||||
vec![
|
||||
Box::new(|_| Predicate::Eq(column_name.to_string(), (*value).clone())),
|
||||
Box::new(|rng| {
|
||||
Self::Gt(
|
||||
column_name.to_string(),
|
||||
GTValue::arbitrary_from(rng, value).0,
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
Self::Lt(
|
||||
column_name.to_string(),
|
||||
LTValue::arbitrary_from(rng, value).0,
|
||||
)
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a predicate that is true for the provided row in the given table
|
||||
fn produce_true_predicate<R: Rng>(rng: &mut R, (t, row): (&Table, &Vec<Value>)) -> Predicate {
|
||||
// Pick a column
|
||||
let column_index = rng.gen_range(0..t.columns.len());
|
||||
let column = &t.columns[column_index];
|
||||
let value = &row[column_index];
|
||||
backtrack(
|
||||
vec![
|
||||
(
|
||||
1,
|
||||
Box::new(|_| Some(Predicate::Eq(column.name.clone(), value.clone()))),
|
||||
),
|
||||
(
|
||||
1,
|
||||
Box::new(|rng| {
|
||||
let v = Value::arbitrary_from(rng, &column.column_type);
|
||||
if &v == value {
|
||||
None
|
||||
} else {
|
||||
Some(Predicate::Neq(column.name.clone(), v))
|
||||
}
|
||||
}),
|
||||
),
|
||||
(
|
||||
1,
|
||||
Box::new(|rng| {
|
||||
Some(Predicate::Gt(
|
||||
column.name.clone(),
|
||||
LTValue::arbitrary_from(rng, value).0,
|
||||
))
|
||||
}),
|
||||
),
|
||||
(
|
||||
1,
|
||||
Box::new(|rng| {
|
||||
Some(Predicate::Lt(
|
||||
column.name.clone(),
|
||||
GTValue::arbitrary_from(rng, value).0,
|
||||
))
|
||||
}),
|
||||
),
|
||||
(
|
||||
1,
|
||||
Box::new(|rng| {
|
||||
LikeValue::arbitrary_from_maybe(rng, value)
|
||||
.map(|like| Predicate::Like(column.name.clone(), like.0))
|
||||
}),
|
||||
),
|
||||
],
|
||||
rng,
|
||||
)
|
||||
}
|
||||
|
||||
/// Produces a predicate that is false for the provided row in the given table
|
||||
fn produce_false_predicate<R: Rng>(rng: &mut R, (t, row): (&Table, &Vec<Value>)) -> Predicate {
|
||||
// Pick a column
|
||||
let column_index = rng.gen_range(0..t.columns.len());
|
||||
let column = &t.columns[column_index];
|
||||
let value = &row[column_index];
|
||||
one_of(
|
||||
vec![
|
||||
Box::new(|_| Predicate::Neq(column.name.clone(), value.clone())),
|
||||
Box::new(|rng| {
|
||||
let v = loop {
|
||||
let v = Value::arbitrary_from(rng, &column.column_type);
|
||||
if &v != value {
|
||||
break v;
|
||||
}
|
||||
};
|
||||
Predicate::Eq(column.name.clone(), v)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
Predicate::Gt(column.name.clone(), GTValue::arbitrary_from(rng, value).0)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
Predicate::Lt(column.name.clone(), LTValue::arbitrary_from(rng, value).0)
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
)
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<(&Table, &Vec<Value>)> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (t, row): (&Table, &Vec<Value>)) -> 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.gen_range(1..=4))
|
||||
.map(|_| produce_true_predicate(rng, (t, row)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let false_predicates = (0..=rng.gen_range(0..=3))
|
||||
.map(|_| produce_false_predicate(rng, (t, row)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Start building a top level predicate from a true predicate
|
||||
let mut result = true_predicates.pop().unwrap();
|
||||
|
||||
let mut predicates = true_predicates
|
||||
.iter()
|
||||
.map(|p| (true, p.clone()))
|
||||
.chain(false_predicates.iter().map(|p| (false, p.clone())))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
predicates.shuffle(rng);
|
||||
|
||||
while !predicates.is_empty() {
|
||||
// Create a new predicate from at least 1 and at most 3 predicates
|
||||
let context =
|
||||
predicates[0..rng.gen_range(0..=usize::min(3, predicates.len()))].to_vec();
|
||||
// Shift `predicates` to remove the predicates in the context
|
||||
predicates = predicates[context.len()..].to_vec();
|
||||
|
||||
// `result` is true, so we have the following three options to make a true predicate:
|
||||
// T or F
|
||||
// T or T
|
||||
// T and T
|
||||
|
||||
result = one_of(
|
||||
vec![
|
||||
// T or (X1 or X2 or ... or Xn)
|
||||
Box::new(|_| {
|
||||
Predicate::Or(vec![
|
||||
result.clone(),
|
||||
Predicate::Or(context.iter().map(|(_, p)| p.clone()).collect()),
|
||||
])
|
||||
}),
|
||||
// T or (T1 and T2 and ... and Tn)
|
||||
Box::new(|_| {
|
||||
Predicate::Or(vec![
|
||||
result.clone(),
|
||||
Predicate::And(context.iter().map(|(_, p)| p.clone()).collect()),
|
||||
])
|
||||
}),
|
||||
// T and T
|
||||
Box::new(|_| {
|
||||
// Check if all the predicates in the context are true
|
||||
if context.iter().all(|(b, _)| *b) {
|
||||
// T and (X1 or X2 or ... or Xn)
|
||||
Predicate::And(vec![
|
||||
result.clone(),
|
||||
Predicate::And(context.iter().map(|(_, p)| p.clone()).collect()),
|
||||
])
|
||||
}
|
||||
// Check if there is at least one true predicate
|
||||
else if context.iter().any(|(b, _)| *b) {
|
||||
// T and (X1 or X2 or ... or Xn)
|
||||
Predicate::And(vec![
|
||||
result.clone(),
|
||||
Predicate::Or(context.iter().map(|(_, p)| p.clone()).collect()),
|
||||
])
|
||||
} else {
|
||||
// T and (X1 or X2 or ... or Xn or TRUE)
|
||||
Predicate::And(vec![
|
||||
result.clone(),
|
||||
Predicate::Or(
|
||||
context
|
||||
.iter()
|
||||
.map(|(_, p)| p.clone())
|
||||
.chain(std::iter::once(Predicate::true_()))
|
||||
.collect(),
|
||||
),
|
||||
])
|
||||
}
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for Update {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
|
||||
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);
|
||||
@@ -490,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();
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
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;
|
||||
|
||||
@@ -15,9 +18,20 @@ impl Arbitrary for Name {
|
||||
impl Arbitrary for Table {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
|
||||
let name = Name::arbitrary(rng).0;
|
||||
let columns = (1..=rng.gen_range(1..10))
|
||||
.map(|_| Column::arbitrary(rng))
|
||||
.collect();
|
||||
let columns = loop {
|
||||
let columns = (1..=rng.gen_range(1..10))
|
||||
.map(|_| Column::arbitrary(rng))
|
||||
.collect::<Vec<_>>();
|
||||
// TODO: see if there is a better way to detect duplicates here
|
||||
let mut set = HashSet::with_capacity(columns.len());
|
||||
set.extend(columns.iter());
|
||||
// Has repeated column name inside so generate again
|
||||
if set.len() != columns.len() {
|
||||
continue;
|
||||
}
|
||||
break columns;
|
||||
};
|
||||
|
||||
Table {
|
||||
rows: Vec::new(),
|
||||
name,
|
||||
@@ -45,62 +59,64 @@ 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);
|
||||
Self::arbitrary_from(rng, *value)
|
||||
// Get value less than all values
|
||||
let value = Value::exec_min(values.iter().map(|value| &value.0));
|
||||
Self::arbitrary_from(rng, &SimValue(value))
|
||||
}
|
||||
}
|
||||
|
||||
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 +129,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 +137,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,38 +145,40 @@ 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));
|
||||
}
|
||||
// Get value greater than all values
|
||||
let value = Value::exec_max(values.iter().map(|value| &value.0));
|
||||
|
||||
let value = pick(values, rng);
|
||||
Self::arbitrary_from(rng, *value)
|
||||
Self::arbitrary_from(rng, &SimValue(value))
|
||||
}
|
||||
}
|
||||
|
||||
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 +191,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 +199,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 +207,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) String);
|
||||
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 +241,9 @@ impl ArbitraryFromMaybe<&Value> for LikeValue {
|
||||
}
|
||||
let index = rng.gen_range(0..t.len());
|
||||
t.insert(index, '%');
|
||||
Some(Self(t.into_iter().collect()))
|
||||
Some(Self(SimValue(Value::build_text(
|
||||
t.into_iter().collect::<String>(),
|
||||
))))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use tracing_subscriber::field::MakeExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
@@ -359,6 +360,8 @@ fn run_simulator(
|
||||
) => {
|
||||
if e1 != e2 {
|
||||
tracing::error!(
|
||||
?shrunk,
|
||||
?result,
|
||||
"shrinking failed, the error was not properly reproduced"
|
||||
);
|
||||
if let Some(bugbase) = bugbase {
|
||||
@@ -391,7 +394,11 @@ fn run_simulator(
|
||||
unreachable!("shrinking should never be called on a correct simulation")
|
||||
}
|
||||
_ => {
|
||||
tracing::error!("shrinking failed, the error was not properly reproduced");
|
||||
tracing::error!(
|
||||
?shrunk,
|
||||
?result,
|
||||
"shrinking failed, the error was not properly reproduced"
|
||||
);
|
||||
if let Some(bugbase) = bugbase {
|
||||
bugbase
|
||||
.add_bug(seed, plans[0].clone(), Some(error.clone()), cli_opts)
|
||||
@@ -701,7 +708,8 @@ fn init_logger() {
|
||||
.with_ansi(true)
|
||||
.with_line_number(true)
|
||||
.without_time()
|
||||
.with_thread_ids(false),
|
||||
.with_thread_ids(false)
|
||||
.map_fmt_fields(|f| f.debug_alt()),
|
||||
)
|
||||
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
|
||||
.with(
|
||||
@@ -710,7 +718,8 @@ fn init_logger() {
|
||||
.with_ansi(false)
|
||||
.with_line_number(true)
|
||||
.without_time()
|
||||
.with_thread_ids(false),
|
||||
.with_thread_ids(false)
|
||||
.map_fmt_fields(|f| f.debug_alt()),
|
||||
)
|
||||
.try_init();
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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![]
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{model::table::Value, SimulatorEnv};
|
||||
use crate::{model::table::SimValue, SimulatorEnv};
|
||||
|
||||
use super::select::Predicate;
|
||||
use super::predicate::Predicate;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub(crate) struct Delete {
|
||||
@@ -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()
|
||||
|
||||
@@ -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![]
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -5,17 +5,19 @@ pub(crate) use create_index::CreateIndex;
|
||||
pub(crate) use delete::Delete;
|
||||
pub(crate) use drop::Drop;
|
||||
pub(crate) use insert::Insert;
|
||||
use limbo_sqlite3_parser::to_sql_string::ToSqlContext;
|
||||
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;
|
||||
pub mod delete;
|
||||
pub mod drop;
|
||||
pub mod insert;
|
||||
pub mod predicate;
|
||||
pub mod select;
|
||||
pub mod update;
|
||||
|
||||
@@ -59,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),
|
||||
@@ -85,3 +87,20 @@ impl Display for Query {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to print sql strings that already have all the context it needs
|
||||
struct EmptyContext;
|
||||
|
||||
impl ToSqlContext for EmptyContext {
|
||||
fn get_column_name(
|
||||
&self,
|
||||
_table_id: limbo_sqlite3_parser::ast::TableInternalId,
|
||||
_col_idx: usize,
|
||||
) -> &str {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn get_table_name(&self, _id: limbo_sqlite3_parser::ast::TableInternalId) -> &str {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
78
simulator/model/query/predicate.rs
Normal file
78
simulator/model/query/predicate.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use limbo_sqlite3_parser::{ast, to_sql_string::ToSqlString};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model::{
|
||||
query::EmptyContext,
|
||||
table::{SimValue, Table},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Predicate(pub ast::Expr);
|
||||
|
||||
impl Predicate {
|
||||
pub(crate) fn true_() -> Self {
|
||||
Self(ast::Expr::Literal(ast::Literal::Numeric("1".to_string())))
|
||||
}
|
||||
|
||||
pub(crate) fn false_() -> Self {
|
||||
Self(ast::Expr::Literal(ast::Literal::Numeric("0".to_string())))
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: In the future pass a Vec<Table> to support resolving a value from another table
|
||||
// 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
|
||||
pub 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))
|
||||
| ast::Expr::Id(ast::Id(col_name)) => {
|
||||
assert_eq!(row.len(), table.columns.len());
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.zip(row.iter())
|
||||
.find(|(column, _)| column.name == *col_name)
|
||||
.map(|(_, value)| value)
|
||||
.cloned()
|
||||
}
|
||||
ast::Expr::Literal(literal) => Some(literal.into()),
|
||||
ast::Expr::Binary(lhs, op, rhs) => {
|
||||
let lhs = expr_to_value(lhs, row, table)?;
|
||||
let rhs = expr_to_value(rhs, row, table)?;
|
||||
Some(lhs.binary_compare(&rhs, *op))
|
||||
}
|
||||
ast::Expr::Like {
|
||||
lhs,
|
||||
not,
|
||||
op,
|
||||
rhs,
|
||||
escape: _, // TODO: support escape
|
||||
} => {
|
||||
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: SimValue = if *not { !res } else { res }.into();
|
||||
Some(value)
|
||||
}
|
||||
ast::Expr::Unary(op, expr) => {
|
||||
let value = expr_to_value(expr, row, table)?;
|
||||
Some(value.unary_exec(*op))
|
||||
}
|
||||
_ => unreachable!("{:?}", expr),
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Predicate {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0.to_sql_string(&EmptyContext))
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use regex::{Regex, RegexBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
model::table::{Table, Value},
|
||||
SimulatorEnv,
|
||||
};
|
||||
use crate::{model::table::SimValue, SimulatorEnv};
|
||||
|
||||
use super::predicate::Predicate;
|
||||
|
||||
/// `SELECT` distinctness
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@@ -48,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
|
||||
@@ -80,123 +78,3 @@ impl Display for Select {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub(crate) enum Predicate {
|
||||
And(Vec<Predicate>), // p1 AND p2 AND p3... AND pn
|
||||
Or(Vec<Predicate>), // p1 OR p2 OR p3... OR pn
|
||||
Eq(String, Value), // column = Value
|
||||
Neq(String, Value), // column != Value
|
||||
Gt(String, Value), // column > Value
|
||||
Lt(String, Value), // column < Value
|
||||
Like(String, String), // column LIKE Value
|
||||
}
|
||||
|
||||
/// This function is a duplication of the exec_like function in core/vdbe/mod.rs at commit 9b9d5f9b4c9920e066ef1237c80878f4c3968524
|
||||
/// Any updates to the original function should be reflected here, otherwise the test will be incorrect.
|
||||
fn construct_like_regex(pattern: &str) -> Regex {
|
||||
let mut regex_pattern = String::with_capacity(pattern.len() * 2);
|
||||
|
||||
regex_pattern.push('^');
|
||||
|
||||
for c in pattern.chars() {
|
||||
match c {
|
||||
'\\' => regex_pattern.push_str("\\\\"),
|
||||
'%' => regex_pattern.push_str(".*"),
|
||||
'_' => regex_pattern.push('.'),
|
||||
ch => {
|
||||
if regex_syntax::is_meta_character(c) {
|
||||
regex_pattern.push('\\');
|
||||
}
|
||||
regex_pattern.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
regex_pattern.push('$');
|
||||
|
||||
RegexBuilder::new(®ex_pattern)
|
||||
.case_insensitive(true)
|
||||
.dot_matches_new_line(true)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn exec_like(pattern: &str, text: &str) -> bool {
|
||||
let re = construct_like_regex(pattern);
|
||||
re.is_match(text)
|
||||
}
|
||||
|
||||
impl Predicate {
|
||||
pub(crate) fn true_() -> Self {
|
||||
Self::And(vec![])
|
||||
}
|
||||
|
||||
pub(crate) fn false_() -> Self {
|
||||
Self::Or(vec![])
|
||||
}
|
||||
|
||||
pub(crate) fn test(&self, row: &[Value], table: &Table) -> bool {
|
||||
let get_value = |name: &str| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.zip(row.iter())
|
||||
.find(|(column, _)| column.name == name)
|
||||
.map(|(_, value)| value)
|
||||
};
|
||||
|
||||
match self {
|
||||
Predicate::And(vec) => vec.iter().all(|p| p.test(row, table)),
|
||||
Predicate::Or(vec) => vec.iter().any(|p| p.test(row, table)),
|
||||
Predicate::Eq(column, value) => get_value(column) == Some(value),
|
||||
Predicate::Neq(column, value) => get_value(column) != Some(value),
|
||||
Predicate::Gt(column, value) => get_value(column).map(|v| v > value).unwrap_or(false),
|
||||
Predicate::Lt(column, value) => get_value(column).map(|v| v < value).unwrap_or(false),
|
||||
Predicate::Like(column, value) => get_value(column)
|
||||
.map(|v| exec_like(v.to_string().as_str(), value.as_str()))
|
||||
.unwrap_or(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Predicate {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::And(predicates) => {
|
||||
if predicates.is_empty() {
|
||||
// todo: Make this TRUE when the bug is fixed
|
||||
write!(f, "TRUE")
|
||||
} else {
|
||||
write!(f, "(")?;
|
||||
for (i, p) in predicates.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, " AND ")?;
|
||||
}
|
||||
write!(f, "{}", p)?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
Self::Or(predicates) => {
|
||||
if predicates.is_empty() {
|
||||
write!(f, "FALSE")
|
||||
} else {
|
||||
write!(f, "(")?;
|
||||
for (i, p) in predicates.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, " OR ")?;
|
||||
}
|
||||
write!(f, "{}", p)?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
Self::Eq(name, value) => write!(f, "{} = {}", name, value),
|
||||
Self::Neq(name, value) => write!(f, "{} != {}", name, value),
|
||||
Self::Gt(name, value) => write!(f, "{} > {}", name, value),
|
||||
Self::Lt(name, value) => write!(f, "{} < {}", name, value),
|
||||
Self::Like(name, value) => write!(f, "{} LIKE '{}'", name, value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::select::Predicate;
|
||||
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()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::{fmt::Display, ops::Deref};
|
||||
use std::{fmt::Display, hash::Hash, ops::Deref};
|
||||
|
||||
use limbo_core::{numeric::Numeric, types};
|
||||
use limbo_sqlite3_parser::ast;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub(crate) struct Name(pub(crate) String);
|
||||
@@ -14,7 +16,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>,
|
||||
}
|
||||
@@ -27,6 +29,21 @@ pub(crate) struct Column {
|
||||
pub(crate) unique: bool,
|
||||
}
|
||||
|
||||
// Uniquely defined by name in this case
|
||||
impl Hash for Column {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Column {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Column {}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) enum ColumnType {
|
||||
Integer,
|
||||
@@ -61,35 +78,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!(
|
||||
@@ -100,14 +90,175 @@ 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),
|
||||
types::Value::Blob(b) => write!(f, "{}", to_sqlite_blob(b)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.0).try_into_bool().unwrap_or_default()
|
||||
}
|
||||
|
||||
// TODO: support more predicates
|
||||
/// Returns a Result of a Binary Operation
|
||||
///
|
||||
/// TODO: forget collations for now
|
||||
/// TODO: have the [ast::Operator::Equals], [ast::Operator::NotEquals], [ast::Operator::Greater],
|
||||
/// [ast::Operator::GreaterEquals], [ast::Operator::Less], [ast::Operator::LessEquals] function to be extracted
|
||||
/// into its functions in limbo_core so that it can be used here
|
||||
pub fn binary_compare(&self, other: &Self, operator: ast::Operator) -> SimValue {
|
||||
match operator {
|
||||
ast::Operator::Add => self.0.exec_add(&other.0).into(),
|
||||
ast::Operator::And => self.0.exec_and(&other.0).into(),
|
||||
ast::Operator::ArrowRight => todo!(),
|
||||
ast::Operator::ArrowRightShift => todo!(),
|
||||
ast::Operator::BitwiseAnd => self.0.exec_bit_and(&other.0).into(),
|
||||
ast::Operator::BitwiseOr => self.0.exec_bit_or(&other.0).into(),
|
||||
ast::Operator::BitwiseNot => todo!(), // TODO: Do not see any function usage of this operator in Core
|
||||
ast::Operator::Concat => self.0.exec_concat(&other.0).into(),
|
||||
ast::Operator::Equals => (self == other).into(),
|
||||
ast::Operator::Divide => self.0.exec_divide(&other.0).into(),
|
||||
ast::Operator::Greater => (self > other).into(),
|
||||
ast::Operator::GreaterEquals => (self >= other).into(),
|
||||
// TODO: Should attempt to extract `Is` and `IsNot` handling in a function in Core
|
||||
ast::Operator::Is => todo!(),
|
||||
ast::Operator::IsNot => todo!(),
|
||||
ast::Operator::LeftShift => self.0.exec_shift_left(&other.0).into(),
|
||||
ast::Operator::Less => (self < other).into(),
|
||||
ast::Operator::LessEquals => (self <= other).into(),
|
||||
ast::Operator::Modulus => self.0.exec_remainder(&other.0).into(),
|
||||
ast::Operator::Multiply => self.0.exec_multiply(&other.0).into(),
|
||||
ast::Operator::NotEquals => (self != other).into(),
|
||||
ast::Operator::Or => self.0.exec_or(&other.0).into(),
|
||||
ast::Operator::RightShift => self.0.exec_shift_right(&other.0).into(),
|
||||
ast::Operator::Subtract => self.0.exec_subtract(&other.0).into(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: support more operators. Copy the implementation for exec_glob
|
||||
pub fn like_compare(&self, other: &Self, operator: ast::LikeOperator) -> bool {
|
||||
match operator {
|
||||
ast::LikeOperator::Glob => todo!(),
|
||||
ast::LikeOperator::Like => {
|
||||
// TODO: support ESCAPE `expr` option in AST
|
||||
// TODO: regex cache
|
||||
types::Value::exec_like(
|
||||
None,
|
||||
other.0.to_string().as_str(),
|
||||
self.0.to_string().as_str(),
|
||||
)
|
||||
}
|
||||
ast::LikeOperator::Match => todo!(),
|
||||
ast::LikeOperator::Regexp => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
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 => {
|
||||
SimValue(types::Value::Integer(0))
|
||||
.binary_compare(self, ast::Operator::Subtract)
|
||||
.0
|
||||
}
|
||||
ast::UnaryOperator::Not => self.0.exec_boolean_not(),
|
||||
ast::UnaryOperator::Positive => self.0.clone(),
|
||||
};
|
||||
Self(new_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ast::Literal> for SimValue {
|
||||
fn from(value: ast::Literal) -> Self {
|
||||
Self::from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sanitaizes a string literal by removing single quote at front and back
|
||||
/// and escaping double single quotes
|
||||
fn sanitize_string(input: &str) -> String {
|
||||
input[1..input.len() - 1].replace("''", "'").to_string()
|
||||
}
|
||||
|
||||
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(),
|
||||
// TODO: see how to avoid sanitizing here
|
||||
ast::Literal::String(string) => types::Value::build_text(sanitize_string(&string)),
|
||||
ast::Literal::Blob(blob) => types::Value::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),
|
||||
};
|
||||
Self(new_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SimValue> for ast::Literal {
|
||||
fn from(value: SimValue) -> Self {
|
||||
Self::from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
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 SimValue {
|
||||
fn from(value: bool) -> Self {
|
||||
value.then_some(SimValue::TRUE).unwrap_or(SimValue::FALSE)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SimValue> for limbo_core::types::Value {
|
||||
fn from(value: SimValue) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SimValue> for limbo_core::types::Value {
|
||||
fn from(value: &SimValue) -> Self {
|
||||
value.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<limbo_core::types::Value> for SimValue {
|
||||
fn from(value: limbo_core::types::Value) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&limbo_core::types::Value> for SimValue {
|
||||
fn from(value: &limbo_core::types::Value) -> Self {
|
||||
Self(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,30 @@ pub struct SimulatorCLI {
|
||||
pub disable_create_index: bool,
|
||||
#[clap(long, help = "disable DROP Statement", default_value_t = false)]
|
||||
pub disable_drop: bool,
|
||||
#[clap(
|
||||
long,
|
||||
help = "disable Insert-Values-Select Property",
|
||||
default_value_t = false
|
||||
)]
|
||||
pub disable_insert_values_select: bool,
|
||||
#[clap(
|
||||
long,
|
||||
help = "disable Double-Create-Failure Property",
|
||||
default_value_t = false
|
||||
)]
|
||||
pub disable_double_create_failure: bool,
|
||||
#[clap(long, help = "disable Select-Limit Property", default_value_t = false)]
|
||||
pub disable_select_limit: bool,
|
||||
#[clap(long, help = "disable Delete-Select Property", default_value_t = false)]
|
||||
pub disable_delete_select: bool,
|
||||
#[clap(long, help = "disable Drop-Select Property", default_value_t = false)]
|
||||
pub disable_drop_select: bool,
|
||||
#[clap(
|
||||
long,
|
||||
help = "disable Select-Select-Optimizer Property",
|
||||
default_value_t = false
|
||||
)]
|
||||
pub disable_select_optimizer: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
|
||||
|
||||
@@ -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)
|
||||
})?;
|
||||
|
||||
@@ -110,6 +110,12 @@ impl SimulatorEnv {
|
||||
delete_percent,
|
||||
drop_percent,
|
||||
update_percent,
|
||||
disable_select_optimizer: cli_opts.disable_select_optimizer,
|
||||
disable_insert_values_select: cli_opts.disable_insert_values_select,
|
||||
disable_double_create_failure: cli_opts.disable_double_create_failure,
|
||||
disable_select_limit: cli_opts.disable_select_limit,
|
||||
disable_delete_select: cli_opts.disable_delete_select,
|
||||
disable_drop_select: cli_opts.disable_drop_select,
|
||||
page_size: 4096, // TODO: randomize this too
|
||||
max_interactions: rng.gen_range(cli_opts.minimum_tests..=cli_opts.maximum_tests),
|
||||
max_time_simulation: cli_opts.maximum_time,
|
||||
@@ -215,6 +221,14 @@ pub(crate) struct SimulatorOpts {
|
||||
pub(crate) delete_percent: f64,
|
||||
pub(crate) update_percent: f64,
|
||||
pub(crate) drop_percent: f64,
|
||||
|
||||
pub(crate) disable_select_optimizer: bool,
|
||||
pub(crate) disable_insert_values_select: bool,
|
||||
pub(crate) disable_double_create_failure: bool,
|
||||
pub(crate) disable_select_limit: bool,
|
||||
pub(crate) disable_delete_select: bool,
|
||||
pub(crate) disable_drop_select: bool,
|
||||
|
||||
pub(crate) max_interactions: usize,
|
||||
pub(crate) page_size: usize,
|
||||
pub(crate) max_time_simulation: usize,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use limbo_core::{LimboError, Result};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::generation::{
|
||||
pick_index,
|
||||
@@ -173,17 +174,15 @@ pub(crate) enum ExecutionContinuation {
|
||||
NextProperty,
|
||||
}
|
||||
|
||||
#[instrument(skip(env, interaction, stack), fields(interaction = %interaction))]
|
||||
pub(crate) fn execute_interaction(
|
||||
env: &mut SimulatorEnv,
|
||||
connection_index: usize,
|
||||
interaction: &Interaction,
|
||||
stack: &mut Vec<ResultSet>,
|
||||
) -> Result<ExecutionContinuation> {
|
||||
tracing::info!(
|
||||
"execute_interaction(connection_index={}, interaction={})",
|
||||
connection_index,
|
||||
interaction
|
||||
);
|
||||
// Leave this empty info! here to print the span of the execution
|
||||
tracing::info!("");
|
||||
match interaction {
|
||||
Interaction::Query(_) => {
|
||||
let conn = match &mut env.connections[connection_index] {
|
||||
@@ -192,7 +191,6 @@ pub(crate) fn execute_interaction(
|
||||
SimConnection::Disconnected => unreachable!(),
|
||||
};
|
||||
|
||||
tracing::debug!("{}", interaction);
|
||||
let results = interaction.execute_query(conn, &env.io);
|
||||
tracing::debug!("{:?}", results);
|
||||
stack.push(results);
|
||||
|
||||
@@ -31,30 +31,34 @@ impl SimulatorFile {
|
||||
self.fault.replace(fault);
|
||||
}
|
||||
|
||||
pub(crate) fn print_stats(&self) {
|
||||
tracing::info!("op calls faults");
|
||||
tracing::info!("--------- -------- --------");
|
||||
tracing::info!(
|
||||
"pread {:8} {:8}",
|
||||
*self.nr_pread_calls.borrow(),
|
||||
*self.nr_pread_faults.borrow()
|
||||
);
|
||||
tracing::info!(
|
||||
"pwrite {:8} {:8}",
|
||||
*self.nr_pwrite_calls.borrow(),
|
||||
*self.nr_pwrite_faults.borrow()
|
||||
);
|
||||
tracing::info!(
|
||||
"sync {:8} {:8}",
|
||||
*self.nr_sync_calls.borrow(),
|
||||
0 // No fault counter for sync
|
||||
);
|
||||
tracing::info!("--------- -------- --------");
|
||||
pub(crate) fn stats_table(&self) -> String {
|
||||
let sum_calls = *self.nr_pread_calls.borrow()
|
||||
+ *self.nr_pwrite_calls.borrow()
|
||||
+ *self.nr_sync_calls.borrow();
|
||||
let sum_faults = *self.nr_pread_faults.borrow() + *self.nr_pwrite_faults.borrow();
|
||||
tracing::info!("total {:8} {:8}", sum_calls, sum_faults);
|
||||
let stats_table = [
|
||||
"op calls faults".to_string(),
|
||||
"--------- -------- --------".to_string(),
|
||||
format!(
|
||||
"pread {:8} {:8}",
|
||||
*self.nr_pread_calls.borrow(),
|
||||
*self.nr_pread_faults.borrow()
|
||||
),
|
||||
format!(
|
||||
"pwrite {:8} {:8}",
|
||||
*self.nr_pwrite_calls.borrow(),
|
||||
*self.nr_pwrite_faults.borrow()
|
||||
),
|
||||
format!(
|
||||
"sync {:8} {:8}",
|
||||
*self.nr_sync_calls.borrow(),
|
||||
0 // No fault counter for sync
|
||||
),
|
||||
"--------- -------- --------".to_string(),
|
||||
format!("total {:8} {:8}", sum_calls, sum_faults),
|
||||
];
|
||||
let table = stats_table.join("\n");
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +45,7 @@ impl SimulatorIO {
|
||||
pub(crate) fn print_stats(&self) {
|
||||
tracing::info!("run_once faults: {}", self.nr_run_once_faults.borrow());
|
||||
for file in self.files.borrow().iter() {
|
||||
tracing::info!("");
|
||||
tracing::info!("===========================");
|
||||
file.print_stats();
|
||||
tracing::info!("\n===========================\n{}", file.stats_table());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ YYNOERRORRECOVERY = []
|
||||
YYCOVERAGE = []
|
||||
NDEBUG = []
|
||||
default = ["YYNOERRORRECOVERY", "NDEBUG"]
|
||||
serde = ["dep:serde", "indexmap/serde", "bitflags/serde"]
|
||||
|
||||
[dependencies]
|
||||
phf = { version = "0.11", features = ["uncased"] }
|
||||
|
||||
@@ -69,6 +69,7 @@ pub(crate) enum ExplainKind {
|
||||
/// SQL statement
|
||||
// https://sqlite.org/syntax/sql-stmt.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Stmt {
|
||||
/// `ALTER TABLE`: table name, body
|
||||
AlterTable(Box<(QualifiedName, AlterTableBody)>),
|
||||
@@ -193,6 +194,7 @@ pub enum Stmt {
|
||||
|
||||
/// `CREATE VIRTUAL TABLE`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct CreateVirtualTable {
|
||||
/// `IF NOT EXISTS`
|
||||
pub if_not_exists: bool,
|
||||
@@ -206,6 +208,7 @@ pub struct CreateVirtualTable {
|
||||
|
||||
/// `CREATE TRIGGER
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct CreateTrigger {
|
||||
/// `TEMPORARY`
|
||||
pub temporary: bool,
|
||||
@@ -229,6 +232,7 @@ pub struct CreateTrigger {
|
||||
|
||||
/// `INSERT`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Insert {
|
||||
/// CTE
|
||||
pub with: Option<With>,
|
||||
@@ -246,6 +250,7 @@ pub struct Insert {
|
||||
|
||||
/// `UPDATE` clause
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Update {
|
||||
/// CTE
|
||||
pub with: Option<With>,
|
||||
@@ -271,6 +276,7 @@ pub struct Update {
|
||||
|
||||
/// `DELETE`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Delete {
|
||||
/// CTE
|
||||
pub with: Option<With>,
|
||||
@@ -290,6 +296,7 @@ pub struct Delete {
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// Internal ID of a table reference.
|
||||
///
|
||||
/// Used by [Expr::Column] and [Expr::RowId] to refer to a table.
|
||||
@@ -332,6 +339,7 @@ impl std::fmt::Display for TableInternalId {
|
||||
/// SQL expression
|
||||
// https://sqlite.org/syntax/expr.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Expr {
|
||||
/// `BETWEEN`
|
||||
Between {
|
||||
@@ -570,6 +578,7 @@ impl Expr {
|
||||
|
||||
/// SQL literal
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Literal {
|
||||
/// Number
|
||||
Numeric(String),
|
||||
@@ -608,6 +617,7 @@ impl Literal {
|
||||
|
||||
/// Textual comparison operator in an expression
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum LikeOperator {
|
||||
/// `GLOB`
|
||||
Glob,
|
||||
@@ -640,6 +650,7 @@ impl LikeOperator {
|
||||
|
||||
/// SQL operators
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Operator {
|
||||
/// `+`
|
||||
Add,
|
||||
@@ -750,6 +761,7 @@ impl Operator {
|
||||
|
||||
/// Unary operators
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum UnaryOperator {
|
||||
/// bitwise negation (`~`)
|
||||
BitwiseNot,
|
||||
@@ -777,6 +789,7 @@ impl From<YYCODETYPE> for UnaryOperator {
|
||||
// https://sqlite.org/lang_select.html
|
||||
// https://sqlite.org/syntax/factored-select-stmt.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Select {
|
||||
/// CTE
|
||||
pub with: Option<With>,
|
||||
@@ -790,6 +803,7 @@ pub struct Select {
|
||||
|
||||
/// `SELECT` body
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct SelectBody {
|
||||
/// first select
|
||||
pub select: Box<OneSelect>,
|
||||
@@ -821,6 +835,7 @@ impl SelectBody {
|
||||
|
||||
/// Compound select
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct CompoundSelect {
|
||||
/// operator
|
||||
pub operator: CompoundOperator,
|
||||
@@ -831,6 +846,7 @@ pub struct CompoundSelect {
|
||||
/// Compound operators
|
||||
// https://sqlite.org/syntax/compound-operator.html
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum CompoundOperator {
|
||||
/// `UNION`
|
||||
Union,
|
||||
@@ -845,6 +861,7 @@ pub enum CompoundOperator {
|
||||
/// `SELECT` core
|
||||
// https://sqlite.org/syntax/select-core.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum OneSelect {
|
||||
/// `SELECT`
|
||||
Select(Box<SelectInner>),
|
||||
@@ -853,6 +870,7 @@ pub enum OneSelect {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// `SELECT` core
|
||||
pub struct SelectInner {
|
||||
/// `DISTINCT`
|
||||
@@ -872,6 +890,7 @@ pub struct SelectInner {
|
||||
/// `SELECT` ... `FROM` clause
|
||||
// https://sqlite.org/syntax/join-clause.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct FromClause {
|
||||
/// table
|
||||
pub select: Option<Box<SelectTable>>, // FIXME mandatory
|
||||
@@ -928,6 +947,7 @@ impl FromClause {
|
||||
|
||||
/// `SELECT` distinctness
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Distinctness {
|
||||
/// `DISTINCT`
|
||||
Distinct,
|
||||
@@ -938,6 +958,7 @@ pub enum Distinctness {
|
||||
/// `SELECT` or `RETURNING` result column
|
||||
// https://sqlite.org/syntax/result-column.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ResultColumn {
|
||||
/// expression
|
||||
Expr(Expr, Option<As>),
|
||||
@@ -949,6 +970,7 @@ pub enum ResultColumn {
|
||||
|
||||
/// Alias
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum As {
|
||||
/// `AS`
|
||||
As(Name),
|
||||
@@ -959,6 +981,7 @@ pub enum As {
|
||||
/// `JOIN` clause
|
||||
// https://sqlite.org/syntax/join-clause.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct JoinedSelectTable {
|
||||
/// operator
|
||||
pub operator: JoinOperator,
|
||||
@@ -971,6 +994,7 @@ pub struct JoinedSelectTable {
|
||||
/// Table or subquery
|
||||
// https://sqlite.org/syntax/table-or-subquery.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum SelectTable {
|
||||
/// table
|
||||
Table(QualifiedName, Option<As>, Option<Indexed>),
|
||||
@@ -985,6 +1009,7 @@ pub enum SelectTable {
|
||||
/// Join operators
|
||||
// https://sqlite.org/syntax/join-operator.html
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum JoinOperator {
|
||||
/// `,`
|
||||
Comma,
|
||||
@@ -1028,6 +1053,7 @@ impl JoinOperator {
|
||||
bitflags::bitflags! {
|
||||
/// `JOIN` types
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct JoinType: u8 {
|
||||
/// `INNER`
|
||||
const INNER = 0x01;
|
||||
@@ -1072,6 +1098,8 @@ impl TryFrom<&[u8]> for JoinType {
|
||||
|
||||
/// `JOIN` constraint
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
||||
pub enum JoinConstraint {
|
||||
/// `ON`
|
||||
On(Expr),
|
||||
@@ -1081,6 +1109,7 @@ pub enum JoinConstraint {
|
||||
|
||||
/// `GROUP BY`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct GroupBy {
|
||||
/// expressions
|
||||
pub exprs: Vec<Expr>,
|
||||
@@ -1090,6 +1119,7 @@ pub struct GroupBy {
|
||||
|
||||
/// identifier or one of several keywords or `INDEXED`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Id(pub String);
|
||||
|
||||
impl Id {
|
||||
@@ -1103,6 +1133,7 @@ impl Id {
|
||||
|
||||
/// identifier or string or `CROSS` or `FULL` or `INNER` or `LEFT` or `NATURAL` or `OUTER` or `RIGHT`.
|
||||
#[derive(Clone, Debug, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Name(pub String); // TODO distinction between Name and "Name"/[Name]/`Name`
|
||||
|
||||
impl Name {
|
||||
@@ -1199,6 +1230,7 @@ impl PartialEq<&str> for Name {
|
||||
|
||||
/// Qualified name
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct QualifiedName {
|
||||
/// schema
|
||||
pub db_name: Option<Name>,
|
||||
@@ -1245,6 +1277,7 @@ impl QualifiedName {
|
||||
|
||||
/// Ordered set of distinct column names
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct DistinctNames(IndexSet<Name>);
|
||||
|
||||
impl DistinctNames {
|
||||
@@ -1280,6 +1313,7 @@ impl Deref for DistinctNames {
|
||||
/// `ALTER TABLE` body
|
||||
// https://sqlite.org/lang_altertable.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum AlterTableBody {
|
||||
/// `RENAME TO`: new table name
|
||||
RenameTo(Name),
|
||||
@@ -1300,6 +1334,7 @@ pub enum AlterTableBody {
|
||||
// https://sqlite.org/lang_createtable.html
|
||||
// https://sqlite.org/syntax/create-table-stmt.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum CreateTableBody {
|
||||
/// columns and constraints
|
||||
ColumnsAndConstraints {
|
||||
@@ -1332,6 +1367,7 @@ impl CreateTableBody {
|
||||
/// Table column definition
|
||||
// https://sqlite.org/syntax/column-def.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ColumnDefinition {
|
||||
/// column name
|
||||
pub col_name: Name,
|
||||
@@ -1403,6 +1439,7 @@ impl ColumnDefinition {
|
||||
/// Named column constraint
|
||||
// https://sqlite.org/syntax/column-constraint.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct NamedColumnConstraint {
|
||||
/// constraint name
|
||||
pub name: Option<Name>,
|
||||
@@ -1413,6 +1450,7 @@ pub struct NamedColumnConstraint {
|
||||
/// Column constraint
|
||||
// https://sqlite.org/syntax/column-constraint.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ColumnConstraint {
|
||||
/// `PRIMARY KEY`
|
||||
PrimaryKey {
|
||||
@@ -1462,6 +1500,7 @@ pub enum ColumnConstraint {
|
||||
/// Named table constraint
|
||||
// https://sqlite.org/syntax/table-constraint.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct NamedTableConstraint {
|
||||
/// constraint name
|
||||
pub name: Option<Name>,
|
||||
@@ -1472,6 +1511,7 @@ pub struct NamedTableConstraint {
|
||||
/// Table constraint
|
||||
// https://sqlite.org/syntax/table-constraint.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum TableConstraint {
|
||||
/// `PRIMARY KEY`
|
||||
PrimaryKey {
|
||||
@@ -1505,6 +1545,7 @@ pub enum TableConstraint {
|
||||
bitflags::bitflags! {
|
||||
/// `CREATE TABLE` options
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TableOptions: u8 {
|
||||
/// None
|
||||
const NONE = 0;
|
||||
@@ -1517,6 +1558,7 @@ bitflags::bitflags! {
|
||||
|
||||
/// Sort orders
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum SortOrder {
|
||||
/// `ASC`
|
||||
Asc,
|
||||
@@ -1526,6 +1568,7 @@ pub enum SortOrder {
|
||||
|
||||
/// `NULLS FIRST` or `NULLS LAST`
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum NullsOrder {
|
||||
/// `NULLS FIRST`
|
||||
First,
|
||||
@@ -1536,6 +1579,7 @@ pub enum NullsOrder {
|
||||
/// `REFERENCES` clause
|
||||
// https://sqlite.org/syntax/foreign-key-clause.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ForeignKeyClause {
|
||||
/// foreign table name
|
||||
pub tbl_name: Name,
|
||||
@@ -1547,6 +1591,7 @@ pub struct ForeignKeyClause {
|
||||
|
||||
/// foreign-key reference args
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum RefArg {
|
||||
/// `ON DELETE`
|
||||
OnDelete(RefAct),
|
||||
@@ -1560,6 +1605,7 @@ pub enum RefArg {
|
||||
|
||||
/// foreign-key reference actions
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum RefAct {
|
||||
/// `SET NULL`
|
||||
SetNull,
|
||||
@@ -1575,6 +1621,7 @@ pub enum RefAct {
|
||||
|
||||
/// foreign-key defer clause
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct DeferSubclause {
|
||||
/// `DEFERRABLE`
|
||||
pub deferrable: bool,
|
||||
@@ -1584,6 +1631,7 @@ pub struct DeferSubclause {
|
||||
|
||||
/// `INITIALLY` `DEFERRED` / `IMMEDIATE`
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum InitDeferredPred {
|
||||
/// `INITIALLY DEFERRED`
|
||||
InitiallyDeferred,
|
||||
@@ -1594,6 +1642,7 @@ pub enum InitDeferredPred {
|
||||
/// Indexed column
|
||||
// https://sqlite.org/syntax/indexed-column.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct IndexedColumn {
|
||||
/// column name
|
||||
pub col_name: Name,
|
||||
@@ -1605,6 +1654,7 @@ pub struct IndexedColumn {
|
||||
|
||||
/// `INDEXED BY` / `NOT INDEXED`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Indexed {
|
||||
/// `INDEXED BY`: idx name
|
||||
IndexedBy(Name),
|
||||
@@ -1614,6 +1664,7 @@ pub enum Indexed {
|
||||
|
||||
/// Sorted column
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct SortedColumn {
|
||||
/// expression
|
||||
pub expr: Expr,
|
||||
@@ -1625,6 +1676,7 @@ pub struct SortedColumn {
|
||||
|
||||
/// `LIMIT`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Limit {
|
||||
/// count
|
||||
pub expr: Expr,
|
||||
@@ -1636,6 +1688,7 @@ pub struct Limit {
|
||||
// https://sqlite.org/lang_insert.html
|
||||
// https://sqlite.org/syntax/insert-stmt.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum InsertBody {
|
||||
/// `SELECT` or `VALUES`
|
||||
Select(Box<Select>, Option<Upsert>),
|
||||
@@ -1645,6 +1698,7 @@ pub enum InsertBody {
|
||||
|
||||
/// `UPDATE ... SET`
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Set {
|
||||
/// column name(s)
|
||||
pub col_names: DistinctNames,
|
||||
@@ -1655,6 +1709,7 @@ pub struct Set {
|
||||
/// `PRAGMA` body
|
||||
// https://sqlite.org/syntax/pragma-stmt.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum PragmaBody {
|
||||
/// `=`
|
||||
Equals(PragmaValue),
|
||||
@@ -1669,6 +1724,7 @@ pub type PragmaValue = Expr; // TODO
|
||||
// https://sqlite.org/pragma.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq, EnumIter, EnumString, strum::Display)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum PragmaName {
|
||||
/// set the autovacuum mode
|
||||
AutoVacuum,
|
||||
@@ -1694,6 +1750,7 @@ pub enum PragmaName {
|
||||
|
||||
/// `CREATE TRIGGER` time
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum TriggerTime {
|
||||
/// `BEFORE`
|
||||
Before, // default
|
||||
@@ -1705,6 +1762,7 @@ pub enum TriggerTime {
|
||||
|
||||
/// `CREATE TRIGGER` event
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum TriggerEvent {
|
||||
/// `DELETE`
|
||||
Delete,
|
||||
@@ -1720,6 +1778,7 @@ pub enum TriggerEvent {
|
||||
// https://sqlite.org/lang_createtrigger.html
|
||||
// https://sqlite.org/syntax/create-trigger-stmt.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum TriggerCmd {
|
||||
/// `UPDATE`
|
||||
Update(Box<TriggerCmdUpdate>),
|
||||
@@ -1733,6 +1792,7 @@ pub enum TriggerCmd {
|
||||
|
||||
/// `UPDATE` trigger command
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TriggerCmdUpdate {
|
||||
/// `OR`
|
||||
pub or_conflict: Option<ResolveType>,
|
||||
@@ -1748,6 +1808,7 @@ pub struct TriggerCmdUpdate {
|
||||
|
||||
/// `INSERT` trigger command
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TriggerCmdInsert {
|
||||
/// `OR`
|
||||
pub or_conflict: Option<ResolveType>,
|
||||
@@ -1765,6 +1826,7 @@ pub struct TriggerCmdInsert {
|
||||
|
||||
/// `DELETE` trigger command
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TriggerCmdDelete {
|
||||
/// table name
|
||||
pub tbl_name: Name,
|
||||
@@ -1774,6 +1836,7 @@ pub struct TriggerCmdDelete {
|
||||
|
||||
/// Conflict resolution types
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ResolveType {
|
||||
/// `ROLLBACK`
|
||||
Rollback,
|
||||
@@ -1803,6 +1866,7 @@ impl ResolveType {
|
||||
// https://sqlite.org/lang_with.html
|
||||
// https://sqlite.org/syntax/with-clause.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct With {
|
||||
/// `RECURSIVE`
|
||||
pub recursive: bool,
|
||||
@@ -1812,6 +1876,7 @@ pub struct With {
|
||||
|
||||
/// CTE materialization
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Materialized {
|
||||
/// No hint
|
||||
Any,
|
||||
@@ -1824,6 +1889,7 @@ pub enum Materialized {
|
||||
/// CTE
|
||||
// https://sqlite.org/syntax/common-table-expression.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct CommonTableExpr {
|
||||
/// table name
|
||||
pub tbl_name: Name,
|
||||
@@ -1849,6 +1915,7 @@ impl CommonTableExpr {
|
||||
/// Column type
|
||||
// https://sqlite.org/syntax/type-name.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Type {
|
||||
/// type name
|
||||
pub name: String, // TODO Validate: Ids+
|
||||
@@ -1859,6 +1926,7 @@ pub struct Type {
|
||||
/// Column type size limit(s)
|
||||
// https://sqlite.org/syntax/type-name.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum TypeSize {
|
||||
/// maximum size
|
||||
MaxSize(Box<Expr>),
|
||||
@@ -1868,6 +1936,7 @@ pub enum TypeSize {
|
||||
|
||||
/// Transaction types
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum TransactionType {
|
||||
/// `DEFERRED`
|
||||
Deferred, // default
|
||||
@@ -1881,6 +1950,7 @@ pub enum TransactionType {
|
||||
// https://sqlite.org/lang_upsert.html
|
||||
// https://sqlite.org/syntax/upsert-clause.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Upsert {
|
||||
/// conflict targets
|
||||
pub index: Option<Box<UpsertIndex>>,
|
||||
@@ -1892,6 +1962,7 @@ pub struct Upsert {
|
||||
|
||||
/// Upsert conflict targets
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct UpsertIndex {
|
||||
/// columns
|
||||
pub targets: Vec<SortedColumn>,
|
||||
@@ -1901,6 +1972,7 @@ pub struct UpsertIndex {
|
||||
|
||||
/// Upsert `DO` action
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum UpsertDo {
|
||||
/// `SET`
|
||||
Set {
|
||||
@@ -1915,6 +1987,7 @@ pub enum UpsertDo {
|
||||
|
||||
/// Function call tail
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct FunctionTail {
|
||||
/// `FILTER` clause
|
||||
pub filter_clause: Option<Box<Expr>>,
|
||||
@@ -1925,6 +1998,7 @@ pub struct FunctionTail {
|
||||
/// Function call `OVER` clause
|
||||
// https://sqlite.org/syntax/over-clause.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Over {
|
||||
/// Window definition
|
||||
Window(Window),
|
||||
@@ -1934,6 +2008,7 @@ pub enum Over {
|
||||
|
||||
/// `OVER` window definition
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct WindowDef {
|
||||
/// window name
|
||||
pub name: Name,
|
||||
@@ -1944,6 +2019,7 @@ pub struct WindowDef {
|
||||
/// Window definition
|
||||
// https://sqlite.org/syntax/window-defn.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Window {
|
||||
/// base window name
|
||||
pub base: Option<Name>,
|
||||
@@ -1958,6 +2034,7 @@ pub struct Window {
|
||||
/// Frame specification
|
||||
// https://sqlite.org/syntax/frame-spec.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct FrameClause {
|
||||
/// unit
|
||||
pub mode: FrameMode,
|
||||
@@ -1971,6 +2048,7 @@ pub struct FrameClause {
|
||||
|
||||
/// Frame modes
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum FrameMode {
|
||||
/// `GROUPS`
|
||||
Groups,
|
||||
@@ -1982,6 +2060,7 @@ pub enum FrameMode {
|
||||
|
||||
/// Frame bounds
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum FrameBound {
|
||||
/// `CURRENT ROW`
|
||||
CurrentRow,
|
||||
@@ -1997,6 +2076,7 @@ pub enum FrameBound {
|
||||
|
||||
/// Frame exclusions
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum FrameExclude {
|
||||
/// `NO OTHERS`
|
||||
NoOthers,
|
||||
|
||||
@@ -77,6 +77,7 @@ impl ToSqlString for ast::OneSelect {
|
||||
|
||||
impl ToSqlString for ast::SelectInner {
|
||||
fn to_sql_string<C: ToSqlContext>(&self, context: &C) -> String {
|
||||
dbg!(&self);
|
||||
let mut ret = Vec::with_capacity(2 + self.columns.len());
|
||||
ret.push("SELECT".to_string());
|
||||
if let Some(distinct) = self.distinctness {
|
||||
|
||||
Reference in New Issue
Block a user