mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 17:05:36 +01:00
Merge 'Decouple SQL generation from Simulator crate' from Pedro Muniz
Decouple Sql generation code from simulator code, so that it can potentially be reused for fuzzing on other crates and to create a `GenerationContext` trait so that it becomes easier to create `Simulation Profiles`. Ideally in further PRs, I want to expand the `GenerationContext` trait so we can guide the generation with context from the simulation profile. Depends on #2789 . Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #2793
This commit is contained in:
35
Cargo.lock
generated
35
Cargo.lock
generated
@@ -119,6 +119,15 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anarchist-readable-name-generator-lib"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09a645c34bad5551ed4b2496536985efdc4373b097c0e57abf2eb14774538278"
|
||||
dependencies = [
|
||||
"rand 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@@ -2125,7 +2134,6 @@ dependencies = [
|
||||
name = "limbo_sim"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"anarchist-readable-name-generator-lib",
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
@@ -2135,17 +2143,18 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"notify",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand 0.9.2",
|
||||
"rand_chacha 0.9.0",
|
||||
"regex",
|
||||
"regex-syntax 0.8.5",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sql_generation",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"turso_core",
|
||||
"turso_sqlite3_parser",
|
||||
"turso_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3462,6 +3471,22 @@ version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d372029cb5195f9ab4e4b9aef550787dce78b124fcaee8d82519925defcd6f0d"
|
||||
|
||||
[[package]]
|
||||
name = "sql_generation"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"anarchist-readable-name-generator-lib 0.2.0",
|
||||
"anyhow",
|
||||
"hex",
|
||||
"itertools 0.14.0",
|
||||
"rand 0.9.2",
|
||||
"rand_chacha 0.9.0",
|
||||
"serde",
|
||||
"tracing",
|
||||
"turso_core",
|
||||
"turso_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparser_bench"
|
||||
version = "0.1.0"
|
||||
@@ -4159,7 +4184,7 @@ dependencies = [
|
||||
name = "turso_stress"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"anarchist-readable-name-generator-lib",
|
||||
"anarchist-readable-name-generator-lib 0.1.2",
|
||||
"antithesis_sdk",
|
||||
"clap",
|
||||
"hex",
|
||||
|
||||
@@ -29,6 +29,7 @@ members = [
|
||||
"parser",
|
||||
"sync/engine",
|
||||
"sync/javascript",
|
||||
"sql_generation",
|
||||
]
|
||||
exclude = ["perf/latency/limbo"]
|
||||
|
||||
@@ -56,6 +57,7 @@ limbo_regexp = { path = "extensions/regexp", version = "0.1.4" }
|
||||
turso_sqlite3_parser = { path = "vendored/sqlite3-parser", version = "0.1.4" }
|
||||
limbo_uuid = { path = "extensions/uuid", version = "0.1.4" }
|
||||
turso_parser = { path = "parser" }
|
||||
sql_generation = { path = "sql_generation" }
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
strum_macros = "0.26"
|
||||
serde = "1.0"
|
||||
@@ -63,6 +65,9 @@ serde_json = "1.0"
|
||||
anyhow = "1.0.98"
|
||||
mimalloc = { version = "0.1.47", default-features = false }
|
||||
rusqlite = { version = "0.37.0", features = ["bundled"] }
|
||||
itertools = "0.14.0"
|
||||
rand = "0.9.2"
|
||||
tracing = "0.1.41"
|
||||
|
||||
[profile.release]
|
||||
debug = "line-tables-only"
|
||||
|
||||
@@ -3,6 +3,8 @@ pub mod fmt;
|
||||
|
||||
use strum_macros::{EnumIter, EnumString};
|
||||
|
||||
use crate::ast::fmt::ToTokens;
|
||||
|
||||
/// `?` or `$` Prepared statement arg placeholder(s)
|
||||
#[derive(Default)]
|
||||
pub struct ParameterInfo {
|
||||
@@ -982,6 +984,41 @@ impl std::fmt::Display for QualifiedName {
|
||||
}
|
||||
}
|
||||
|
||||
impl QualifiedName {
|
||||
/// Constructor
|
||||
pub fn single(name: Name) -> Self {
|
||||
Self {
|
||||
db_name: None,
|
||||
name,
|
||||
alias: None,
|
||||
}
|
||||
}
|
||||
/// Constructor
|
||||
pub fn fullname(db_name: Name, name: Name) -> Self {
|
||||
Self {
|
||||
db_name: Some(db_name),
|
||||
name,
|
||||
alias: None,
|
||||
}
|
||||
}
|
||||
/// Constructor
|
||||
pub fn xfullname(db_name: Name, name: Name, alias: Name) -> Self {
|
||||
Self {
|
||||
db_name: Some(db_name),
|
||||
name,
|
||||
alias: Some(alias),
|
||||
}
|
||||
}
|
||||
/// Constructor
|
||||
pub fn alias(name: Name, alias: Name) -> Self {
|
||||
Self {
|
||||
db_name: None,
|
||||
name,
|
||||
alias: Some(alias),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `ALTER TABLE` body
|
||||
// https://sqlite.org/lang_altertable.html
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -1153,7 +1190,7 @@ bitflags::bitflags! {
|
||||
}
|
||||
|
||||
/// Sort orders
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum SortOrder {
|
||||
/// `ASC`
|
||||
@@ -1162,6 +1199,12 @@ pub enum SortOrder {
|
||||
Desc,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for SortOrder {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.to_fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// `NULLS FIRST` or `NULLS LAST`
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
||||
@@ -16,15 +16,14 @@ path = "main.rs"
|
||||
|
||||
[dependencies]
|
||||
turso_core = { path = "../core", features = ["simulator"]}
|
||||
rand = "0.8.5"
|
||||
rand_chacha = "0.3.1"
|
||||
rand = { workspace = true }
|
||||
rand_chacha = "0.9.0"
|
||||
log = "0.4.20"
|
||||
env_logger = "0.10.1"
|
||||
regex = "1.11.1"
|
||||
regex-syntax = { version = "0.8.5", default-features = false, features = [
|
||||
"unicode",
|
||||
] }
|
||||
anarchist-readable-name-generator-lib = "=0.1.2"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
@@ -32,9 +31,10 @@ notify = "8.0.0"
|
||||
rusqlite.workspace = true
|
||||
dirs = "6.0.0"
|
||||
chrono = { version = "0.4.40", features = ["serde"] }
|
||||
tracing = "0.1.41"
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
anyhow.workspace = true
|
||||
turso_sqlite3_parser = { workspace = true, features = ["serde"]}
|
||||
hex = "0.4.3"
|
||||
itertools = "0.14.0"
|
||||
sql_generation = { workspace = true }
|
||||
turso_parser = { workspace = true }
|
||||
|
||||
@@ -1,65 +1,10 @@
|
||||
use std::{iter::Sum, ops::SubAssign};
|
||||
use sql_generation::generation::GenerationContext;
|
||||
|
||||
use anarchist_readable_name_generator_lib::readable_name_custom;
|
||||
use rand::{distributions::uniform::SampleUniform, Rng};
|
||||
use crate::runner::env::{SimulatorEnv, SimulatorTables};
|
||||
|
||||
use crate::runner::env::SimulatorTables;
|
||||
|
||||
mod expr;
|
||||
pub mod plan;
|
||||
mod predicate;
|
||||
pub mod property;
|
||||
pub mod query;
|
||||
pub mod table;
|
||||
|
||||
type ArbitraryFromFunc<'a, R, T> = Box<dyn Fn(&mut R) -> T + 'a>;
|
||||
type Choice<'a, R, T> = (usize, Box<dyn Fn(&mut R) -> Option<T> + 'a>);
|
||||
|
||||
/// Arbitrary trait for generating random values
|
||||
/// An implementation of arbitrary is assumed to be a uniform sampling of
|
||||
/// the possible values of the type, with a bias towards smaller values for
|
||||
/// practicality.
|
||||
pub trait Arbitrary {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self;
|
||||
}
|
||||
|
||||
/// ArbitrarySized trait for generating random values of a specific size
|
||||
/// An implementation of arbitrary_sized is assumed to be a uniform sampling of
|
||||
/// the possible values of the type, with a bias towards smaller values for
|
||||
/// practicality, but with the additional constraint that the generated value
|
||||
/// must fit in the given size. This is useful for generating values that are
|
||||
/// constrained by a specific size, such as integers or strings.
|
||||
pub trait ArbitrarySized {
|
||||
fn arbitrary_sized<R: Rng>(rng: &mut R, size: usize) -> Self;
|
||||
}
|
||||
|
||||
/// ArbitraryFrom trait for generating random values from a given value
|
||||
/// ArbitraryFrom allows for constructing relations, where the generated
|
||||
/// value is dependent on the given value. These relations could be constraints
|
||||
/// such as generating an integer within an interval, or a value that fits in a table,
|
||||
/// or a predicate satisfying a given table row.
|
||||
pub trait ArbitraryFrom<T> {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, t: T) -> Self;
|
||||
}
|
||||
|
||||
/// ArbitrarySizedFrom trait for generating random values from a given value
|
||||
/// ArbitrarySizedFrom allows for constructing relations, where the generated
|
||||
/// value is dependent on the given value and a size constraint. These relations
|
||||
/// could be constraints such as generating an integer within an interval,
|
||||
/// or a value that fits in a table, or a predicate satisfying a given table row,
|
||||
/// but with the additional constraint that the generated value must fit in the given size.
|
||||
/// This is useful for generating values that are constrained by a specific size,
|
||||
/// such as integers or strings, while still being dependent on the given value.
|
||||
pub trait ArbitrarySizedFrom<T> {
|
||||
fn arbitrary_sized_from<R: Rng>(rng: &mut R, t: T, size: usize) -> Self;
|
||||
}
|
||||
|
||||
/// ArbitraryFromMaybe trait for fallibally generating random values from a given value
|
||||
pub trait ArbitraryFromMaybe<T> {
|
||||
fn arbitrary_from_maybe<R: Rng>(rng: &mut R, t: T) -> Option<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// Shadow trait for types that can be "shadowed" in the simulator environment.
|
||||
/// Shadowing is a process of applying a transformation to the simulator environment
|
||||
@@ -75,108 +20,14 @@ pub(crate) trait Shadow {
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result;
|
||||
}
|
||||
|
||||
/// Frequency is a helper function for composing different generators with different frequency
|
||||
/// of occurrences.
|
||||
/// The type signature for the `N` parameter is a bit complex, but it
|
||||
/// roughly corresponds to a type that can be summed, compared, subtracted and sampled, which are
|
||||
/// the operations we require for the implementation.
|
||||
// todo: switch to a simpler type signature that can accommodate all integer and float types, which
|
||||
// should be enough for our purposes.
|
||||
pub(crate) fn frequency<
|
||||
T,
|
||||
R: Rng,
|
||||
N: Sum + PartialOrd + Copy + Default + SampleUniform + SubAssign,
|
||||
>(
|
||||
choices: Vec<(N, ArbitraryFromFunc<R, T>)>,
|
||||
rng: &mut R,
|
||||
) -> T {
|
||||
let total = choices.iter().map(|(weight, _)| *weight).sum::<N>();
|
||||
let mut choice = rng.gen_range(N::default()..total);
|
||||
|
||||
for (weight, f) in choices {
|
||||
if choice < weight {
|
||||
return f(rng);
|
||||
}
|
||||
choice -= weight;
|
||||
impl GenerationContext for SimulatorEnv {
|
||||
fn tables(&self) -> &Vec<sql_generation::model::table::Table> {
|
||||
&self.tables.tables
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
/// one_of is a helper function for composing different generators with equal probability of occurrence.
|
||||
pub(crate) fn one_of<T, R: Rng>(choices: Vec<ArbitraryFromFunc<R, T>>, rng: &mut R) -> T {
|
||||
let index = rng.gen_range(0..choices.len());
|
||||
choices[index](rng)
|
||||
}
|
||||
|
||||
/// backtrack is a helper function for composing different "failable" generators.
|
||||
/// 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<T, R: Rng>(mut choices: Vec<Choice<R, T>>, rng: &mut R) -> Option<T> {
|
||||
loop {
|
||||
// If there are no more choices left, we give up
|
||||
let choices_ = choices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, (retries, _))| *retries > 0)
|
||||
.collect::<Vec<_>>();
|
||||
if choices_.is_empty() {
|
||||
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 result.is_some() {
|
||||
return result;
|
||||
} else {
|
||||
choices[choice_index].0 -= 1;
|
||||
fn opts(&self) -> sql_generation::generation::Opts {
|
||||
sql_generation::generation::Opts {
|
||||
indexes: self.opts.experimental_indexes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// pick is a helper function for uniformly picking a random element from a slice
|
||||
pub(crate) fn pick<'a, T, R: Rng>(choices: &'a [T], rng: &mut R) -> &'a T {
|
||||
let index = rng.gen_range(0..choices.len());
|
||||
&choices[index]
|
||||
}
|
||||
|
||||
/// pick_index is typically used for picking an index from a slice to later refer to the element
|
||||
/// at that index.
|
||||
pub(crate) fn pick_index<R: Rng>(choices: usize, rng: &mut R) -> usize {
|
||||
rng.gen_range(0..choices)
|
||||
}
|
||||
|
||||
/// pick_n_unique is a helper function for uniformly picking N unique elements from a range.
|
||||
/// The elements themselves are usize, typically representing indices.
|
||||
pub(crate) fn pick_n_unique<R: Rng>(
|
||||
range: std::ops::Range<usize>,
|
||||
n: usize,
|
||||
rng: &mut R,
|
||||
) -> Vec<usize> {
|
||||
use rand::seq::SliceRandom;
|
||||
let mut items: Vec<usize> = range.collect();
|
||||
items.shuffle(rng);
|
||||
items.into_iter().take(n).collect()
|
||||
}
|
||||
|
||||
/// gen_random_text uses `anarchist_readable_name_generator_lib` to generate random
|
||||
/// readable names for tables, columns, text values etc.
|
||||
pub(crate) fn gen_random_text<T: Rng>(rng: &mut T) -> String {
|
||||
let big_text = rng.gen_ratio(1, 1000);
|
||||
if big_text {
|
||||
// let max_size: u64 = 2 * 1024 * 1024 * 1024;
|
||||
let max_size: u64 = 2 * 1024;
|
||||
let size = rng.gen_range(1024..max_size);
|
||||
let mut name = String::with_capacity(size as usize);
|
||||
for i in 0..size {
|
||||
name.push(((i % 26) as u8 + b'A') as char);
|
||||
}
|
||||
name
|
||||
} else {
|
||||
let name = readable_name_custom("_", rng);
|
||||
name.replace("-", "_")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,18 @@ use std::{
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use sql_generation::{
|
||||
generation::{frequency, query::SelectFree, Arbitrary, ArbitraryFrom},
|
||||
model::{
|
||||
query::{update::Update, Create, CreateIndex, Delete, Drop, Insert, Select},
|
||||
table::SimValue,
|
||||
},
|
||||
};
|
||||
use turso_core::{Connection, Result, StepResult};
|
||||
|
||||
use crate::{
|
||||
generation::{query::SelectFree, Shadow},
|
||||
model::{
|
||||
query::{update::Update, Create, CreateIndex, Delete, Drop, Insert, Query, Select},
|
||||
table::SimValue,
|
||||
},
|
||||
generation::Shadow,
|
||||
model::Query,
|
||||
runner::{
|
||||
env::{SimConnection, SimulationType, SimulatorTables},
|
||||
io::SimulatorIO,
|
||||
@@ -23,8 +27,6 @@ use crate::{
|
||||
SimulatorEnv,
|
||||
};
|
||||
|
||||
use crate::generation::{frequency, Arbitrary, ArbitraryFrom};
|
||||
|
||||
use super::property::{remaining, Property};
|
||||
|
||||
pub(crate) type ResultSet = Result<Vec<Vec<SimValue>>>;
|
||||
@@ -661,7 +663,7 @@ impl Interaction {
|
||||
.iter()
|
||||
.any(|file| file.sync_completion.borrow().is_some())
|
||||
};
|
||||
let inject_fault = env.rng.gen_bool(current_prob);
|
||||
let inject_fault = env.rng.random_bool(current_prob);
|
||||
// TODO: avoid for now injecting faults when syncing
|
||||
if inject_fault && !syncing {
|
||||
env.io.inject_fault(true);
|
||||
@@ -811,7 +813,7 @@ fn random_fault<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
|
||||
} else {
|
||||
vec![Fault::Disconnect, Fault::ReopenDatabase]
|
||||
};
|
||||
let fault = faults[rng.gen_range(0..faults.len())].clone();
|
||||
let fault = faults[rng.random_range(0..faults.len())].clone();
|
||||
Interactions::Fault(fault)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use turso_core::{types, LimboError};
|
||||
use turso_sqlite3_parser::ast::{self};
|
||||
|
||||
use crate::{
|
||||
generation::Shadow as _,
|
||||
use sql_generation::{
|
||||
generation::{frequency, pick, pick_index, ArbitraryFrom},
|
||||
model::{
|
||||
query::{
|
||||
predicate::Predicate,
|
||||
select::{
|
||||
CompoundOperator, CompoundSelect, Distinctness, ResultColumn, SelectBody,
|
||||
SelectInner,
|
||||
},
|
||||
select::{CompoundOperator, CompoundSelect, ResultColumn, SelectBody, SelectInner},
|
||||
transaction::{Begin, Commit, Rollback},
|
||||
update::Update,
|
||||
Create, Delete, Drop, Insert, Query, Select,
|
||||
Create, Delete, Drop, Insert, Select,
|
||||
},
|
||||
table::SimValue,
|
||||
},
|
||||
runner::env::SimulatorEnv,
|
||||
};
|
||||
use turso_core::{types, LimboError};
|
||||
use turso_parser::ast::{self, Distinctness};
|
||||
|
||||
use super::{
|
||||
frequency, pick, pick_index,
|
||||
plan::{Assertion, Interaction, InteractionStats, ResultSet},
|
||||
ArbitraryFrom,
|
||||
};
|
||||
use crate::{generation::Shadow as _, model::Query, runner::env::SimulatorEnv};
|
||||
|
||||
use super::plan::{Assertion, Interaction, InteractionStats, ResultSet};
|
||||
|
||||
/// Properties are representations of executable specifications
|
||||
/// about the database behavior.
|
||||
@@ -1073,7 +1066,7 @@ fn property_insert_values_select<R: rand::Rng>(
|
||||
// Get a random table
|
||||
let table = pick(&env.tables, rng);
|
||||
// Generate rows to insert
|
||||
let rows = (0..rng.gen_range(1..=5))
|
||||
let rows = (0..rng.random_range(1..=5))
|
||||
.map(|_| Vec::<SimValue>::arbitrary_from(rng, table))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -1088,10 +1081,10 @@ fn property_insert_values_select<R: rand::Rng>(
|
||||
};
|
||||
|
||||
// Choose if we want queries to be executed in an interactive transaction
|
||||
let interactive = if rng.gen_bool(0.5) {
|
||||
let interactive = if rng.random_bool(0.5) {
|
||||
Some(InteractiveQueryInfo {
|
||||
start_with_immediate: rng.gen_bool(0.5),
|
||||
end_with_commit: rng.gen_bool(0.5),
|
||||
start_with_immediate: rng.random_bool(0.5),
|
||||
end_with_commit: rng.random_bool(0.5),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -1107,7 +1100,7 @@ fn property_insert_values_select<R: rand::Rng>(
|
||||
immediate: interactive.start_with_immediate,
|
||||
}));
|
||||
}
|
||||
for _ in 0..rng.gen_range(0..3) {
|
||||
for _ in 0..rng.random_range(0..3) {
|
||||
let query = Query::arbitrary_from(rng, (env, remaining));
|
||||
match &query {
|
||||
Query::Delete(Delete {
|
||||
@@ -1198,7 +1191,7 @@ fn property_select_limit<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Prope
|
||||
table.name.clone(),
|
||||
vec![ResultColumn::Star],
|
||||
Predicate::arbitrary_from(rng, table),
|
||||
Some(rng.gen_range(1..=5)),
|
||||
Some(rng.random_range(1..=5)),
|
||||
Distinctness::All,
|
||||
);
|
||||
Property::SelectLimit { select }
|
||||
@@ -1221,7 +1214,7 @@ fn property_double_create_failure<R: rand::Rng>(
|
||||
// The interactions in the middle has the following constraints;
|
||||
// - [x] There will be no errors in the middle interactions.(best effort)
|
||||
// - [ ] Table `t` will not be renamed or dropped.(todo: add this constraint once ALTER or DROP is implemented)
|
||||
for _ in 0..rng.gen_range(0..3) {
|
||||
for _ in 0..rng.random_range(0..3) {
|
||||
let query = Query::arbitrary_from(rng, (env, remaining));
|
||||
if let Query::Create(Create { table: t }) = &query {
|
||||
// There will be no errors in the middle interactions.
|
||||
@@ -1254,7 +1247,7 @@ fn property_delete_select<R: rand::Rng>(
|
||||
// - [x] There will be no errors in the middle interactions. (this constraint is impossible to check, so this is just best effort)
|
||||
// - [x] A row that holds for the predicate will not be inserted.
|
||||
// - [ ] The table `t` will not be renamed, dropped, or altered. (todo: add this constraint once ALTER or DROP is implemented)
|
||||
for _ in 0..rng.gen_range(0..3) {
|
||||
for _ in 0..rng.random_range(0..3) {
|
||||
let query = Query::arbitrary_from(rng, (env, remaining));
|
||||
match &query {
|
||||
Query::Insert(Insert::Values { table: t, values }) => {
|
||||
@@ -1309,7 +1302,7 @@ fn property_drop_select<R: rand::Rng>(
|
||||
let mut queries = Vec::new();
|
||||
// - [x] There will be no errors in the middle interactions. (this constraint is impossible to check, so this is just best effort)
|
||||
// - [-] The table `t` will not be created, no table will be renamed to `t`. (todo: update this constraint once ALTER is implemented)
|
||||
for _ in 0..rng.gen_range(0..3) {
|
||||
for _ in 0..rng.random_range(0..3) {
|
||||
let query = Query::arbitrary_from(rng, (env, remaining));
|
||||
if let Query::Create(Create { table: t }) = &query {
|
||||
// - The table `t` will not be created
|
||||
|
||||
@@ -1,330 +1,11 @@
|
||||
use crate::generation::{Arbitrary, ArbitraryFrom, ArbitrarySizedFrom, Shadow};
|
||||
use crate::model::query::predicate::Predicate;
|
||||
use crate::model::query::select::{
|
||||
CompoundOperator, CompoundSelect, Distinctness, FromClause, OrderBy, ResultColumn, SelectBody,
|
||||
SelectInner,
|
||||
};
|
||||
use crate::model::query::update::Update;
|
||||
use crate::model::query::{Create, Delete, Drop, Insert, Query, Select};
|
||||
use crate::model::table::{JoinTable, JoinType, JoinedTable, SimValue, Table, TableContext};
|
||||
use crate::SimulatorEnv;
|
||||
use itertools::Itertools;
|
||||
use crate::{model::Query, SimulatorEnv};
|
||||
use rand::Rng;
|
||||
use turso_sqlite3_parser::ast::{Expr, SortOrder};
|
||||
use sql_generation::{
|
||||
generation::{frequency, Arbitrary, ArbitraryFrom},
|
||||
model::query::{update::Update, Create, Delete, Insert, Select},
|
||||
};
|
||||
|
||||
use super::property::Remaining;
|
||||
use super::{backtrack, frequency, pick};
|
||||
|
||||
impl Arbitrary for Create {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
|
||||
Create {
|
||||
table: Table::arbitrary(rng),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&Vec<Table>> for FromClause {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, tables: &Vec<Table>) -> Self {
|
||||
let num_joins = match rng.gen_range(0..=100) {
|
||||
0..=90 => 0,
|
||||
91..=97 => 1,
|
||||
98..=100 => 2,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut tables = tables.clone();
|
||||
let mut table = pick(&tables, rng).clone();
|
||||
|
||||
tables.retain(|t| t.name != table.name);
|
||||
|
||||
let name = table.name.clone();
|
||||
|
||||
let mut table_context = JoinTable {
|
||||
tables: Vec::new(),
|
||||
rows: Vec::new(),
|
||||
};
|
||||
|
||||
let joins: Vec<_> = (0..num_joins)
|
||||
.filter_map(|_| {
|
||||
if tables.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let join_table = pick(&tables, rng).clone();
|
||||
let joined_table_name = join_table.name.clone();
|
||||
|
||||
tables.retain(|t| t.name != join_table.name);
|
||||
table_context.rows = table_context
|
||||
.rows
|
||||
.iter()
|
||||
.cartesian_product(join_table.rows.iter())
|
||||
.map(|(t_row, j_row)| {
|
||||
let mut row = t_row.clone();
|
||||
row.extend(j_row.clone());
|
||||
row
|
||||
})
|
||||
.collect();
|
||||
// TODO: inneficient. use a Deque to push_front?
|
||||
table_context.tables.insert(0, join_table);
|
||||
for row in &mut table.rows {
|
||||
assert_eq!(
|
||||
row.len(),
|
||||
table.columns.len(),
|
||||
"Row length does not match column length after join"
|
||||
);
|
||||
}
|
||||
|
||||
let predicate = Predicate::arbitrary_from(rng, &table);
|
||||
Some(JoinedTable {
|
||||
table: joined_table_name,
|
||||
join_type: JoinType::Inner,
|
||||
on: predicate,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
FromClause { table: name, joins }
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for SelectInner {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
|
||||
let from = FromClause::arbitrary_from(rng, &env.tables);
|
||||
let mut tables = env.tables.clone();
|
||||
// todo: this is a temporary hack because env is not separated from the tables
|
||||
let join_table = from
|
||||
.shadow(&mut tables)
|
||||
.expect("Failed to shadow FromClause");
|
||||
let cuml_col_count = join_table.columns().count();
|
||||
|
||||
let order_by = 'order_by: {
|
||||
if rng.gen_bool(0.3) {
|
||||
let order_by_table_candidates = from
|
||||
.joins
|
||||
.iter()
|
||||
.map(|j| j.table.clone())
|
||||
.chain(std::iter::once(from.table.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
let order_by_col_count =
|
||||
(rng.gen::<f64>() * rng.gen::<f64>() * (cuml_col_count as f64)) as usize; // skew towards 0
|
||||
if order_by_col_count == 0 {
|
||||
break 'order_by None;
|
||||
}
|
||||
let mut col_names = std::collections::HashSet::new();
|
||||
let mut order_by_cols = Vec::new();
|
||||
while order_by_cols.len() < order_by_col_count {
|
||||
let table = pick(&order_by_table_candidates, rng);
|
||||
let table = tables.iter().find(|t| t.name == *table).unwrap();
|
||||
let col = pick(&table.columns, rng);
|
||||
let col_name = format!("{}.{}", table.name, col.name);
|
||||
if col_names.insert(col_name.clone()) {
|
||||
order_by_cols.push((
|
||||
col_name,
|
||||
if rng.gen_bool(0.5) {
|
||||
SortOrder::Asc
|
||||
} else {
|
||||
SortOrder::Desc
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
Some(OrderBy {
|
||||
columns: order_by_cols,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
SelectInner {
|
||||
distinctness: if env.opts.experimental_indexes {
|
||||
Distinctness::arbitrary(rng)
|
||||
} else {
|
||||
Distinctness::All
|
||||
},
|
||||
columns: vec![ResultColumn::Star],
|
||||
from: Some(from),
|
||||
where_clause: Predicate::arbitrary_from(rng, &join_table),
|
||||
order_by,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitrarySizedFrom<&SimulatorEnv> for SelectInner {
|
||||
fn arbitrary_sized_from<R: Rng>(
|
||||
rng: &mut R,
|
||||
env: &SimulatorEnv,
|
||||
num_result_columns: usize,
|
||||
) -> Self {
|
||||
let mut select_inner = SelectInner::arbitrary_from(rng, env);
|
||||
let select_from = &select_inner.from.as_ref().unwrap();
|
||||
let table_names = select_from
|
||||
.joins
|
||||
.iter()
|
||||
.map(|j| j.table.clone())
|
||||
.chain(std::iter::once(select_from.table.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let flat_columns_names = table_names
|
||||
.iter()
|
||||
.flat_map(|t| {
|
||||
env.tables
|
||||
.iter()
|
||||
.find(|table| table.name == *t)
|
||||
.unwrap()
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| format!("{}.{}", t.clone(), c.name))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let selected_columns = pick_unique(&flat_columns_names, num_result_columns, rng);
|
||||
let mut columns = Vec::new();
|
||||
for column_name in selected_columns {
|
||||
columns.push(ResultColumn::Column(column_name.clone()));
|
||||
}
|
||||
select_inner.columns = columns;
|
||||
select_inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for Distinctness {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
|
||||
match rng.gen_range(0..=5) {
|
||||
0..4 => Distinctness::All,
|
||||
_ => Distinctness::Distinct,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Arbitrary for CompoundOperator {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
|
||||
match rng.gen_range(0..=1) {
|
||||
0 => CompoundOperator::Union,
|
||||
1 => CompoundOperator::UnionAll,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SelectFree is a wrapper around Select that allows for arbitrary generation
|
||||
/// of selects without requiring a specific environment, which is useful for generating
|
||||
/// arbitrary expressions without referring to the tables.
|
||||
pub(crate) struct SelectFree(pub(crate) Select);
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for SelectFree {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
|
||||
let expr = Predicate(Expr::arbitrary_sized_from(rng, env, 8));
|
||||
let select = Select::expr(expr);
|
||||
Self(select)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for Select {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
|
||||
// Generate a number of selects based on the query size
|
||||
// If experimental indexes are enabled, we can have selects with compounds
|
||||
// Otherwise, we just have a single select with no compounds
|
||||
let num_compound_selects = if env.opts.experimental_indexes {
|
||||
match rng.gen_range(0..=100) {
|
||||
0..=95 => 0,
|
||||
96..=99 => 1,
|
||||
100 => 2,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let min_column_count_across_tables =
|
||||
env.tables.iter().map(|t| t.columns.len()).min().unwrap();
|
||||
|
||||
let num_result_columns = rng.gen_range(1..=min_column_count_across_tables);
|
||||
|
||||
let mut first = SelectInner::arbitrary_sized_from(rng, env, num_result_columns);
|
||||
|
||||
let mut rest: Vec<SelectInner> = (0..num_compound_selects)
|
||||
.map(|_| SelectInner::arbitrary_sized_from(rng, env, num_result_columns))
|
||||
.collect();
|
||||
|
||||
if !rest.is_empty() {
|
||||
// ORDER BY is not supported in compound selects yet
|
||||
first.order_by = None;
|
||||
for s in &mut rest {
|
||||
s.order_by = None;
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
body: SelectBody {
|
||||
select: Box::new(first),
|
||||
compounds: rest
|
||||
.into_iter()
|
||||
.map(|s| CompoundSelect {
|
||||
operator: CompoundOperator::arbitrary(rng),
|
||||
select: Box::new(s),
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
limit: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for Insert {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
|
||||
let gen_values = |rng: &mut R| {
|
||||
let table = pick(&env.tables, 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(rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
Some(Insert::Values {
|
||||
table: table.name.clone(),
|
||||
values,
|
||||
})
|
||||
};
|
||||
|
||||
let _gen_select = |rng: &mut R| {
|
||||
// Find a non-empty table
|
||||
let select_table = env.tables.iter().find(|t| !t.rows.is_empty())?;
|
||||
let row = pick(&select_table.rows, rng);
|
||||
let predicate = Predicate::arbitrary_from(rng, (select_table, row));
|
||||
// Pick another table to insert into
|
||||
let select = Select::simple(select_table.name.clone(), predicate);
|
||||
let table = pick(&env.tables, rng);
|
||||
Some(Insert::Select {
|
||||
table: table.name.clone(),
|
||||
select: Box::new(select),
|
||||
})
|
||||
};
|
||||
|
||||
// TODO: Add back gen_select when https://github.com/tursodatabase/turso/issues/2129 is fixed.
|
||||
// Backtrack here cannot return None
|
||||
backtrack(vec![(1, Box::new(gen_values))], rng).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for Delete {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
|
||||
let table = pick(&env.tables, rng);
|
||||
Self {
|
||||
table: table.name.clone(),
|
||||
predicate: Predicate::arbitrary_from(rng, table),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for Drop {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
|
||||
let table = pick(&env.tables, rng);
|
||||
Self {
|
||||
table: table.name.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<(&SimulatorEnv, &Remaining)> for Query {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (env, remaining): (&SimulatorEnv, &Remaining)) -> Self {
|
||||
@@ -355,43 +36,3 @@ impl ArbitraryFrom<(&SimulatorEnv, &Remaining)> for Query {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn pick_unique<T: ToOwned + PartialEq>(
|
||||
items: &[T],
|
||||
count: usize,
|
||||
rng: &mut impl rand::Rng,
|
||||
) -> Vec<T::Owned>
|
||||
where
|
||||
<T as ToOwned>::Owned: PartialEq,
|
||||
{
|
||||
let mut picked: Vec<T::Owned> = Vec::new();
|
||||
while picked.len() < count {
|
||||
let item = pick(items, rng);
|
||||
if !picked.contains(&item.to_owned()) {
|
||||
picked.push(item.to_owned());
|
||||
}
|
||||
}
|
||||
picked
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for Update {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
|
||||
let table = pick(&env.tables, rng);
|
||||
let num_cols = rng.gen_range(1..=table.columns.len());
|
||||
let columns = pick_unique(&table.columns, num_cols, rng);
|
||||
let set_values: Vec<(String, SimValue)> = columns
|
||||
.iter()
|
||||
.map(|column| {
|
||||
(
|
||||
column.name.clone(),
|
||||
SimValue::arbitrary_from(rng, &column.column_type),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
Update {
|
||||
table: table.name.clone(),
|
||||
set_values,
|
||||
predicate: Predicate::arbitrary_from(rng, table),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
use anyhow::anyhow;
|
||||
use clap::Parser;
|
||||
use generation::plan::{Interaction, InteractionPlan, InteractionPlanState};
|
||||
use generation::ArbitraryFrom;
|
||||
use notify::event::{DataChange, ModifyKind};
|
||||
use notify::{EventKind, RecursiveMode, Watcher};
|
||||
use rand::prelude::*;
|
||||
@@ -11,6 +10,7 @@ use runner::cli::{SimulatorCLI, SimulatorCommand};
|
||||
use runner::env::SimulatorEnv;
|
||||
use runner::execution::{execute_plans, Execution, ExecutionHistory, ExecutionResult};
|
||||
use runner::{differential, watch};
|
||||
use sql_generation::generation::ArbitraryFrom;
|
||||
use std::any::Any;
|
||||
use std::backtrace::Backtrace;
|
||||
use std::fs::OpenOptions;
|
||||
@@ -507,7 +507,7 @@ fn setup_simulation(
|
||||
(seed, env, plans)
|
||||
} else {
|
||||
let seed = cli_opts.seed.unwrap_or_else(|| {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = rand::rng();
|
||||
rng.next_u64()
|
||||
});
|
||||
tracing::info!("seed={}", seed);
|
||||
|
||||
@@ -1,4 +1,417 @@
|
||||
pub mod query;
|
||||
pub mod table;
|
||||
use std::{collections::HashSet, fmt::Display};
|
||||
|
||||
pub(crate) const FAULT_ERROR_MSG: &str = "Injected fault";
|
||||
use anyhow::Context;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sql_generation::model::{
|
||||
query::{
|
||||
select::{CompoundOperator, FromClause, ResultColumn, SelectInner},
|
||||
transaction::{Begin, Commit, Rollback},
|
||||
update::Update,
|
||||
Create, CreateIndex, Delete, Drop, EmptyContext, Insert, Select,
|
||||
},
|
||||
table::{JoinTable, JoinType, SimValue, Table, TableContext},
|
||||
};
|
||||
use turso_parser::ast::{fmt::ToTokens, Distinctness};
|
||||
|
||||
use crate::{generation::Shadow, runner::env::SimulatorTables};
|
||||
|
||||
// This type represents the potential queries on the database.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Query {
|
||||
Create(Create),
|
||||
Select(Select),
|
||||
Insert(Insert),
|
||||
Delete(Delete),
|
||||
Update(Update),
|
||||
Drop(Drop),
|
||||
CreateIndex(CreateIndex),
|
||||
Begin(Begin),
|
||||
Commit(Commit),
|
||||
Rollback(Rollback),
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub fn dependencies(&self) -> HashSet<String> {
|
||||
match self {
|
||||
Query::Select(select) => select.dependencies(),
|
||||
Query::Create(_) => HashSet::new(),
|
||||
Query::Insert(Insert::Select { table, .. })
|
||||
| Query::Insert(Insert::Values { table, .. })
|
||||
| Query::Delete(Delete { table, .. })
|
||||
| Query::Update(Update { table, .. })
|
||||
| Query::Drop(Drop { table, .. }) => HashSet::from_iter([table.clone()]),
|
||||
Query::CreateIndex(CreateIndex { table_name, .. }) => {
|
||||
HashSet::from_iter([table_name.clone()])
|
||||
}
|
||||
Query::Begin(_) | Query::Commit(_) | Query::Rollback(_) => HashSet::new(),
|
||||
}
|
||||
}
|
||||
pub fn uses(&self) -> Vec<String> {
|
||||
match self {
|
||||
Query::Create(Create { table }) => vec![table.name.clone()],
|
||||
Query::Select(select) => select.dependencies().into_iter().collect(),
|
||||
Query::Insert(Insert::Select { table, .. })
|
||||
| Query::Insert(Insert::Values { table, .. })
|
||||
| Query::Delete(Delete { table, .. })
|
||||
| Query::Update(Update { table, .. })
|
||||
| Query::Drop(Drop { table, .. }) => vec![table.clone()],
|
||||
Query::CreateIndex(CreateIndex { table_name, .. }) => vec![table_name.clone()],
|
||||
Query::Begin(..) | Query::Commit(..) | Query::Rollback(..) => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Query {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Create(create) => write!(f, "{create}"),
|
||||
Self::Select(select) => write!(f, "{select}"),
|
||||
Self::Insert(insert) => write!(f, "{insert}"),
|
||||
Self::Delete(delete) => write!(f, "{delete}"),
|
||||
Self::Update(update) => write!(f, "{update}"),
|
||||
Self::Drop(drop) => write!(f, "{drop}"),
|
||||
Self::CreateIndex(create_index) => write!(f, "{create_index}"),
|
||||
Self::Begin(begin) => write!(f, "{begin}"),
|
||||
Self::Commit(commit) => write!(f, "{commit}"),
|
||||
Self::Rollback(rollback) => write!(f, "{rollback}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Query {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
match self {
|
||||
Query::Create(create) => create.shadow(env),
|
||||
Query::Insert(insert) => insert.shadow(env),
|
||||
Query::Delete(delete) => delete.shadow(env),
|
||||
Query::Select(select) => select.shadow(env),
|
||||
Query::Update(update) => update.shadow(env),
|
||||
Query::Drop(drop) => drop.shadow(env),
|
||||
Query::CreateIndex(create_index) => Ok(create_index.shadow(env)),
|
||||
Query::Begin(begin) => Ok(begin.shadow(env)),
|
||||
Query::Commit(commit) => Ok(commit.shadow(env)),
|
||||
Query::Rollback(rollback) => Ok(rollback.shadow(env)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Create {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
if !tables.iter().any(|t| t.name == self.table.name) {
|
||||
tables.push(self.table.clone());
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"Table {} already exists. CREATE TABLE statement ignored.",
|
||||
self.table.name
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for CreateIndex {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Vec<Vec<SimValue>> {
|
||||
env.tables
|
||||
.iter_mut()
|
||||
.find(|t| t.name == self.table_name)
|
||||
.unwrap()
|
||||
.indexes
|
||||
.push(self.index_name.clone());
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Delete {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
let table = tables.tables.iter_mut().find(|t| t.name == self.table);
|
||||
|
||||
if let Some(table) = table {
|
||||
// If the table exists, we can delete from it
|
||||
let t2 = table.clone();
|
||||
table.rows.retain_mut(|r| !self.predicate.test(r, &t2));
|
||||
} else {
|
||||
// If the table does not exist, we return an error
|
||||
return Err(anyhow::anyhow!(
|
||||
"Table {} does not exist. DELETE statement ignored.",
|
||||
self.table
|
||||
));
|
||||
}
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Drop {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
if !tables.iter().any(|t| t.name == self.table) {
|
||||
// If the table does not exist, we return an error
|
||||
return Err(anyhow::anyhow!(
|
||||
"Table {} does not exist. DROP statement ignored.",
|
||||
self.table
|
||||
));
|
||||
}
|
||||
|
||||
tables.tables.retain(|t| t.name != self.table);
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Insert {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
match self {
|
||||
Insert::Values { table, values } => {
|
||||
if let Some(t) = tables.tables.iter_mut().find(|t| &t.name == table) {
|
||||
t.rows.extend(values.clone());
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Table {} does not exist. INSERT statement ignored.",
|
||||
table
|
||||
));
|
||||
}
|
||||
}
|
||||
Insert::Select { table, select } => {
|
||||
let rows = select.shadow(tables)?;
|
||||
if let Some(t) = tables.tables.iter_mut().find(|t| &t.name == table) {
|
||||
t.rows.extend(rows);
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Table {} does not exist. INSERT statement ignored.",
|
||||
table
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for FromClause {
|
||||
type Result = anyhow::Result<JoinTable>;
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
let tables = &mut env.tables;
|
||||
|
||||
let first_table = tables
|
||||
.iter()
|
||||
.find(|t| t.name == self.table)
|
||||
.context("Table not found")?;
|
||||
|
||||
let mut join_table = JoinTable {
|
||||
tables: vec![first_table.clone()],
|
||||
rows: Vec::new(),
|
||||
};
|
||||
|
||||
for join in &self.joins {
|
||||
let joined_table = tables
|
||||
.iter()
|
||||
.find(|t| t.name == join.table)
|
||||
.context("Joined table not found")?;
|
||||
|
||||
join_table.tables.push(joined_table.clone());
|
||||
|
||||
match join.join_type {
|
||||
JoinType::Inner => {
|
||||
// Implement inner join logic
|
||||
let join_rows = joined_table
|
||||
.rows
|
||||
.iter()
|
||||
.filter(|row| join.on.test(row, joined_table))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
// take a cartesian product of the rows
|
||||
let all_row_pairs = join_table
|
||||
.rows
|
||||
.clone()
|
||||
.into_iter()
|
||||
.cartesian_product(join_rows.iter());
|
||||
|
||||
for (row1, row2) in all_row_pairs {
|
||||
let row = row1.iter().chain(row2.iter()).cloned().collect::<Vec<_>>();
|
||||
|
||||
let is_in = join.on.test(&row, &join_table);
|
||||
|
||||
if is_in {
|
||||
join_table.rows.push(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
Ok(join_table)
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for SelectInner {
|
||||
type Result = anyhow::Result<JoinTable>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
if let Some(from) = &self.from {
|
||||
let mut join_table = from.shadow(env)?;
|
||||
let col_count = join_table.columns().count();
|
||||
for row in &mut join_table.rows {
|
||||
assert_eq!(
|
||||
row.len(),
|
||||
col_count,
|
||||
"Row length does not match column length after join"
|
||||
);
|
||||
}
|
||||
let join_clone = join_table.clone();
|
||||
|
||||
join_table
|
||||
.rows
|
||||
.retain(|row| self.where_clause.test(row, &join_clone));
|
||||
|
||||
if self.distinctness == Distinctness::Distinct {
|
||||
join_table.rows.sort_unstable();
|
||||
join_table.rows.dedup();
|
||||
}
|
||||
|
||||
Ok(join_table)
|
||||
} else {
|
||||
assert!(self
|
||||
.columns
|
||||
.iter()
|
||||
.all(|col| matches!(col, ResultColumn::Expr(_))));
|
||||
|
||||
// If `WHERE` is false, just return an empty table
|
||||
if !self.where_clause.test(&[], &Table::anonymous(vec![])) {
|
||||
return Ok(JoinTable {
|
||||
tables: Vec::new(),
|
||||
rows: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
// Compute the results of the column expressions and make a row
|
||||
let mut row = Vec::new();
|
||||
for col in &self.columns {
|
||||
match col {
|
||||
ResultColumn::Expr(expr) => {
|
||||
let value = expr.eval(&[], &Table::anonymous(vec![]));
|
||||
if let Some(value) = value {
|
||||
row.push(value);
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Failed to evaluate expression in free select ({})",
|
||||
expr.0.format_with_context(&EmptyContext {}).unwrap()
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Only expressions are allowed in free selects"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(JoinTable {
|
||||
tables: Vec::new(),
|
||||
rows: vec![row],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Select {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
let first_result = self.body.select.shadow(env)?;
|
||||
|
||||
let mut rows = first_result.rows;
|
||||
|
||||
for compound in self.body.compounds.iter() {
|
||||
let compound_results = compound.select.shadow(env)?;
|
||||
|
||||
match compound.operator {
|
||||
CompoundOperator::Union => {
|
||||
// Union means we need to combine the results, removing duplicates
|
||||
let mut new_rows = compound_results.rows;
|
||||
new_rows.extend(rows.clone());
|
||||
new_rows.sort_unstable();
|
||||
new_rows.dedup();
|
||||
rows = new_rows;
|
||||
}
|
||||
CompoundOperator::UnionAll => {
|
||||
// Union all means we just concatenate the results
|
||||
rows.extend(compound_results.rows.into_iter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(rows)
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Begin {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
tables.snapshot = Some(tables.tables.clone());
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Commit {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
tables.snapshot = None;
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Rollback {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
if let Some(tables_) = tables.snapshot.take() {
|
||||
tables.tables = tables_;
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Update {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
let table = tables.tables.iter_mut().find(|t| t.name == self.table);
|
||||
|
||||
let table = if let Some(table) = table {
|
||||
table
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Table {} does not exist. UPDATE statement ignored.",
|
||||
self.table
|
||||
));
|
||||
};
|
||||
|
||||
let t2 = table.clone();
|
||||
for row in table
|
||||
.rows
|
||||
.iter_mut()
|
||||
.filter(|r| self.predicate.test(r, &t2))
|
||||
{
|
||||
for (column, set_value) in &self.set_values {
|
||||
if let Some((idx, _)) = table
|
||||
.columns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, c)| &c.name == column)
|
||||
{
|
||||
row[idx] = set_value.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
generation::Shadow,
|
||||
model::table::{SimValue, Table},
|
||||
runner::env::SimulatorTables,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct Create {
|
||||
pub(crate) table: Table,
|
||||
}
|
||||
|
||||
impl Shadow for Create {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
if !tables.iter().any(|t| t.name == self.table.name) {
|
||||
tables.push(self.table.clone());
|
||||
Ok(vec![])
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"Table {} already exists. CREATE TABLE statement ignored.",
|
||||
self.table.name
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Create {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "CREATE TABLE {} (", self.table.name)?;
|
||||
|
||||
for (i, column) in self.table.columns.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, ",")?;
|
||||
}
|
||||
write!(f, "{} {}", column.name, column.column_type)?;
|
||||
}
|
||||
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
use crate::{
|
||||
generation::{gen_random_text, pick, pick_n_unique, ArbitraryFrom, Shadow},
|
||||
model::table::SimValue,
|
||||
runner::env::{SimulatorEnv, SimulatorTables},
|
||||
};
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub enum SortOrder {
|
||||
Asc,
|
||||
Desc,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SortOrder {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SortOrder::Asc => write!(f, "ASC"),
|
||||
SortOrder::Desc => write!(f, "DESC"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct CreateIndex {
|
||||
pub(crate) index_name: String,
|
||||
pub(crate) table_name: String,
|
||||
pub(crate) columns: Vec<(String, SortOrder)>,
|
||||
}
|
||||
|
||||
impl Shadow for CreateIndex {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Vec<Vec<SimValue>> {
|
||||
env.tables
|
||||
.iter_mut()
|
||||
.find(|t| t.name == self.table_name)
|
||||
.unwrap()
|
||||
.indexes
|
||||
.push(self.index_name.clone());
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CreateIndex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"CREATE INDEX {} ON {} ({})",
|
||||
self.index_name,
|
||||
self.table_name,
|
||||
self.columns
|
||||
.iter()
|
||||
.map(|(name, order)| format!("{name} {order}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for CreateIndex {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
|
||||
assert!(
|
||||
!env.tables.is_empty(),
|
||||
"Cannot create an index when no tables exist in the environment."
|
||||
);
|
||||
|
||||
let table = pick(&env.tables, rng);
|
||||
|
||||
if table.columns.is_empty() {
|
||||
panic!(
|
||||
"Cannot create an index on table '{}' as it has no columns.",
|
||||
table.name
|
||||
);
|
||||
}
|
||||
|
||||
let num_columns_to_pick = rng.gen_range(1..=table.columns.len());
|
||||
let picked_column_indices = pick_n_unique(0..table.columns.len(), num_columns_to_pick, rng);
|
||||
|
||||
let columns = picked_column_indices
|
||||
.into_iter()
|
||||
.map(|i| {
|
||||
let column = &table.columns[i];
|
||||
(
|
||||
column.name.clone(),
|
||||
if rng.gen_bool(0.5) {
|
||||
SortOrder::Asc
|
||||
} else {
|
||||
SortOrder::Desc
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(String, SortOrder)>>();
|
||||
|
||||
let index_name = format!(
|
||||
"idx_{}_{}",
|
||||
table.name,
|
||||
gen_random_text(rng).chars().take(8).collect::<String>()
|
||||
);
|
||||
|
||||
CreateIndex {
|
||||
index_name,
|
||||
table_name: table.name.clone(),
|
||||
columns,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{generation::Shadow, model::table::SimValue, runner::env::SimulatorTables};
|
||||
|
||||
use super::predicate::Predicate;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub(crate) struct Delete {
|
||||
pub(crate) table: String,
|
||||
pub(crate) predicate: Predicate,
|
||||
}
|
||||
|
||||
impl Shadow for Delete {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
let table = tables.tables.iter_mut().find(|t| t.name == self.table);
|
||||
|
||||
if let Some(table) = table {
|
||||
// If the table exists, we can delete from it
|
||||
let t2 = table.clone();
|
||||
table.rows.retain_mut(|r| !self.predicate.test(r, &t2));
|
||||
} else {
|
||||
// If the table does not exist, we return an error
|
||||
return Err(anyhow::anyhow!(
|
||||
"Table {} does not exist. DELETE statement ignored.",
|
||||
self.table
|
||||
));
|
||||
}
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Delete {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "DELETE FROM {} WHERE {}", self.table, self.predicate)
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{generation::Shadow, model::table::SimValue, runner::env::SimulatorTables};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub(crate) struct Drop {
|
||||
pub(crate) table: String,
|
||||
}
|
||||
|
||||
impl Shadow for Drop {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
if !tables.iter().any(|t| t.name == self.table) {
|
||||
// If the table does not exist, we return an error
|
||||
return Err(anyhow::anyhow!(
|
||||
"Table {} does not exist. DROP statement ignored.",
|
||||
self.table
|
||||
));
|
||||
}
|
||||
|
||||
tables.tables.retain(|t| t.name != self.table);
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Drop {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "DROP TABLE {}", self.table)
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
use std::{collections::HashSet, fmt::Display};
|
||||
|
||||
pub(crate) use create::Create;
|
||||
pub(crate) use create_index::CreateIndex;
|
||||
pub(crate) use delete::Delete;
|
||||
pub(crate) use drop::Drop;
|
||||
pub(crate) use insert::Insert;
|
||||
pub(crate) use select::Select;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use turso_sqlite3_parser::to_sql_string::ToSqlContext;
|
||||
use update::Update;
|
||||
|
||||
use crate::{
|
||||
generation::Shadow,
|
||||
model::{
|
||||
query::transaction::{Begin, Commit, Rollback},
|
||||
table::SimValue,
|
||||
},
|
||||
runner::env::SimulatorTables,
|
||||
};
|
||||
|
||||
pub mod create;
|
||||
pub mod create_index;
|
||||
pub mod delete;
|
||||
pub mod drop;
|
||||
pub mod insert;
|
||||
pub mod predicate;
|
||||
pub mod select;
|
||||
pub mod transaction;
|
||||
pub mod update;
|
||||
|
||||
// This type represents the potential queries on the database.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) enum Query {
|
||||
Create(Create),
|
||||
Select(Select),
|
||||
Insert(Insert),
|
||||
Delete(Delete),
|
||||
Update(Update),
|
||||
Drop(Drop),
|
||||
CreateIndex(CreateIndex),
|
||||
Begin(Begin),
|
||||
Commit(Commit),
|
||||
Rollback(Rollback),
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub(crate) fn dependencies(&self) -> HashSet<String> {
|
||||
match self {
|
||||
Query::Select(select) => select.dependencies(),
|
||||
Query::Create(_) => HashSet::new(),
|
||||
Query::Insert(Insert::Select { table, .. })
|
||||
| Query::Insert(Insert::Values { table, .. })
|
||||
| Query::Delete(Delete { table, .. })
|
||||
| Query::Update(Update { table, .. })
|
||||
| Query::Drop(Drop { table, .. }) => HashSet::from_iter([table.clone()]),
|
||||
Query::CreateIndex(CreateIndex { table_name, .. }) => {
|
||||
HashSet::from_iter([table_name.clone()])
|
||||
}
|
||||
Query::Begin(_) | Query::Commit(_) | Query::Rollback(_) => HashSet::new(),
|
||||
}
|
||||
}
|
||||
pub(crate) fn uses(&self) -> Vec<String> {
|
||||
match self {
|
||||
Query::Create(Create { table }) => vec![table.name.clone()],
|
||||
Query::Select(select) => select.dependencies().into_iter().collect(),
|
||||
Query::Insert(Insert::Select { table, .. })
|
||||
| Query::Insert(Insert::Values { table, .. })
|
||||
| Query::Delete(Delete { table, .. })
|
||||
| Query::Update(Update { table, .. })
|
||||
| Query::Drop(Drop { table, .. }) => vec![table.clone()],
|
||||
Query::CreateIndex(CreateIndex { table_name, .. }) => vec![table_name.clone()],
|
||||
Query::Begin(..) | Query::Commit(..) | Query::Rollback(..) => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Query {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
match self {
|
||||
Query::Create(create) => create.shadow(env),
|
||||
Query::Insert(insert) => insert.shadow(env),
|
||||
Query::Delete(delete) => delete.shadow(env),
|
||||
Query::Select(select) => select.shadow(env),
|
||||
Query::Update(update) => update.shadow(env),
|
||||
Query::Drop(drop) => drop.shadow(env),
|
||||
Query::CreateIndex(create_index) => Ok(create_index.shadow(env)),
|
||||
Query::Begin(begin) => Ok(begin.shadow(env)),
|
||||
Query::Commit(commit) => Ok(commit.shadow(env)),
|
||||
Query::Rollback(rollback) => Ok(rollback.shadow(env)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Query {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Create(create) => write!(f, "{create}"),
|
||||
Self::Select(select) => write!(f, "{select}"),
|
||||
Self::Insert(insert) => write!(f, "{insert}"),
|
||||
Self::Delete(delete) => write!(f, "{delete}"),
|
||||
Self::Update(update) => write!(f, "{update}"),
|
||||
Self::Drop(drop) => write!(f, "{drop}"),
|
||||
Self::CreateIndex(create_index) => write!(f, "{create_index}"),
|
||||
Self::Begin(begin) => write!(f, "{begin}"),
|
||||
Self::Commit(commit) => write!(f, "{commit}"),
|
||||
Self::Rollback(rollback) => write!(f, "{rollback}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to print sql strings that already have all the context it needs
|
||||
pub(crate) struct EmptyContext;
|
||||
|
||||
impl ToSqlContext for EmptyContext {
|
||||
fn get_column_name(
|
||||
&self,
|
||||
_table_id: turso_sqlite3_parser::ast::TableInternalId,
|
||||
_col_idx: usize,
|
||||
) -> String {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn get_table_name(&self, _id: turso_sqlite3_parser::ast::TableInternalId) -> &str {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
@@ -1,497 +0,0 @@
|
||||
use std::{collections::HashSet, fmt::Display};
|
||||
|
||||
use anyhow::Context;
|
||||
pub use ast::Distinctness;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use turso_sqlite3_parser::ast::{self, fmt::ToTokens, SortOrder};
|
||||
|
||||
use crate::{
|
||||
generation::Shadow,
|
||||
model::{
|
||||
query::EmptyContext,
|
||||
table::{JoinTable, JoinType, JoinedTable, SimValue, Table, TableContext},
|
||||
},
|
||||
runner::env::SimulatorTables,
|
||||
};
|
||||
|
||||
use super::predicate::Predicate;
|
||||
|
||||
/// `SELECT` or `RETURNING` result column
|
||||
// https://sqlite.org/syntax/result-column.html
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ResultColumn {
|
||||
/// expression
|
||||
Expr(Predicate),
|
||||
/// `*`
|
||||
Star,
|
||||
/// column name
|
||||
Column(String),
|
||||
}
|
||||
|
||||
impl Display for ResultColumn {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ResultColumn::Expr(expr) => write!(f, "({expr})"),
|
||||
ResultColumn::Star => write!(f, "*"),
|
||||
ResultColumn::Column(name) => write!(f, "{name}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub(crate) struct Select {
|
||||
pub(crate) body: SelectBody,
|
||||
pub(crate) limit: Option<usize>,
|
||||
}
|
||||
|
||||
impl Select {
|
||||
pub fn simple(table: String, where_clause: Predicate) -> Self {
|
||||
Self::single(
|
||||
table,
|
||||
vec![ResultColumn::Star],
|
||||
where_clause,
|
||||
None,
|
||||
Distinctness::All,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn expr(expr: Predicate) -> Self {
|
||||
Select {
|
||||
body: SelectBody {
|
||||
select: Box::new(SelectInner {
|
||||
distinctness: Distinctness::All,
|
||||
columns: vec![ResultColumn::Expr(expr)],
|
||||
from: None,
|
||||
where_clause: Predicate::true_(),
|
||||
order_by: None,
|
||||
}),
|
||||
compounds: Vec::new(),
|
||||
},
|
||||
limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn single(
|
||||
table: String,
|
||||
result_columns: Vec<ResultColumn>,
|
||||
where_clause: Predicate,
|
||||
limit: Option<usize>,
|
||||
distinct: Distinctness,
|
||||
) -> Self {
|
||||
Select {
|
||||
body: SelectBody {
|
||||
select: Box::new(SelectInner {
|
||||
distinctness: distinct,
|
||||
columns: result_columns,
|
||||
from: Some(FromClause {
|
||||
table,
|
||||
joins: Vec::new(),
|
||||
}),
|
||||
where_clause,
|
||||
order_by: None,
|
||||
}),
|
||||
compounds: Vec::new(),
|
||||
},
|
||||
limit,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compound(left: Select, right: Select, operator: CompoundOperator) -> Self {
|
||||
let mut body = left.body;
|
||||
body.compounds.push(CompoundSelect {
|
||||
operator,
|
||||
select: Box::new(right.body.select.as_ref().clone()),
|
||||
});
|
||||
Select {
|
||||
body,
|
||||
limit: left.limit.or(right.limit),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dependencies(&self) -> HashSet<String> {
|
||||
if self.body.select.from.is_none() {
|
||||
return HashSet::new();
|
||||
}
|
||||
let from = self.body.select.from.as_ref().unwrap();
|
||||
let mut tables = HashSet::new();
|
||||
tables.insert(from.table.clone());
|
||||
|
||||
tables.extend(from.dependencies());
|
||||
|
||||
for compound in &self.body.compounds {
|
||||
tables.extend(
|
||||
compound
|
||||
.select
|
||||
.from
|
||||
.as_ref()
|
||||
.map(|f| f.dependencies())
|
||||
.unwrap_or(vec![])
|
||||
.into_iter(),
|
||||
);
|
||||
}
|
||||
|
||||
tables
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SelectBody {
|
||||
/// first select
|
||||
pub select: Box<SelectInner>,
|
||||
/// compounds
|
||||
pub compounds: Vec<CompoundSelect>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct OrderBy {
|
||||
pub columns: Vec<(String, SortOrder)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SelectInner {
|
||||
/// `DISTINCT`
|
||||
pub distinctness: Distinctness,
|
||||
/// columns
|
||||
pub columns: Vec<ResultColumn>,
|
||||
/// `FROM` clause
|
||||
pub from: Option<FromClause>,
|
||||
/// `WHERE` clause
|
||||
pub where_clause: Predicate,
|
||||
/// `ORDER BY` clause
|
||||
pub order_by: Option<OrderBy>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum CompoundOperator {
|
||||
/// `UNION`
|
||||
Union,
|
||||
/// `UNION ALL`
|
||||
UnionAll,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CompoundSelect {
|
||||
/// operator
|
||||
pub operator: CompoundOperator,
|
||||
/// select
|
||||
pub select: Box<SelectInner>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct FromClause {
|
||||
/// table
|
||||
pub table: String,
|
||||
/// `JOIN`ed tables
|
||||
pub joins: Vec<JoinedTable>,
|
||||
}
|
||||
|
||||
impl FromClause {
|
||||
fn to_sql_ast(&self) -> ast::FromClause {
|
||||
ast::FromClause {
|
||||
select: Some(Box::new(ast::SelectTable::Table(
|
||||
ast::QualifiedName::single(ast::Name::from_str(&self.table)),
|
||||
None,
|
||||
None,
|
||||
))),
|
||||
joins: if self.joins.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
self.joins
|
||||
.iter()
|
||||
.map(|join| ast::JoinedSelectTable {
|
||||
operator: match join.join_type {
|
||||
JoinType::Inner => {
|
||||
ast::JoinOperator::TypedJoin(Some(ast::JoinType::INNER))
|
||||
}
|
||||
JoinType::Left => {
|
||||
ast::JoinOperator::TypedJoin(Some(ast::JoinType::LEFT))
|
||||
}
|
||||
JoinType::Right => {
|
||||
ast::JoinOperator::TypedJoin(Some(ast::JoinType::RIGHT))
|
||||
}
|
||||
JoinType::Full => {
|
||||
ast::JoinOperator::TypedJoin(Some(ast::JoinType::OUTER))
|
||||
}
|
||||
JoinType::Cross => {
|
||||
ast::JoinOperator::TypedJoin(Some(ast::JoinType::CROSS))
|
||||
}
|
||||
},
|
||||
table: ast::SelectTable::Table(
|
||||
ast::QualifiedName::single(ast::Name::from_str(&join.table)),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
constraint: Some(ast::JoinConstraint::On(join.on.0.clone())),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
},
|
||||
op: None, // FIXME: this is a temporary fix, we should remove this field
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dependencies(&self) -> Vec<String> {
|
||||
let mut deps = vec![self.table.clone()];
|
||||
for join in &self.joins {
|
||||
deps.push(join.table.clone());
|
||||
}
|
||||
deps
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for FromClause {
|
||||
type Result = anyhow::Result<JoinTable>;
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
let tables = &mut env.tables;
|
||||
|
||||
let first_table = tables
|
||||
.iter()
|
||||
.find(|t| t.name == self.table)
|
||||
.context("Table not found")?;
|
||||
|
||||
let mut join_table = JoinTable {
|
||||
tables: vec![first_table.clone()],
|
||||
rows: Vec::new(),
|
||||
};
|
||||
|
||||
for join in &self.joins {
|
||||
let joined_table = tables
|
||||
.iter()
|
||||
.find(|t| t.name == join.table)
|
||||
.context("Joined table not found")?;
|
||||
|
||||
join_table.tables.push(joined_table.clone());
|
||||
|
||||
match join.join_type {
|
||||
JoinType::Inner => {
|
||||
// Implement inner join logic
|
||||
let join_rows = joined_table
|
||||
.rows
|
||||
.iter()
|
||||
.filter(|row| join.on.test(row, joined_table))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
// take a cartesian product of the rows
|
||||
let all_row_pairs = join_table
|
||||
.rows
|
||||
.clone()
|
||||
.into_iter()
|
||||
.cartesian_product(join_rows.iter());
|
||||
|
||||
for (row1, row2) in all_row_pairs {
|
||||
let row = row1.iter().chain(row2.iter()).cloned().collect::<Vec<_>>();
|
||||
|
||||
let is_in = join.on.test(&row, &join_table);
|
||||
|
||||
if is_in {
|
||||
join_table.rows.push(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
Ok(join_table)
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for SelectInner {
|
||||
type Result = anyhow::Result<JoinTable>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
if let Some(from) = &self.from {
|
||||
let mut join_table = from.shadow(env)?;
|
||||
let col_count = join_table.columns().count();
|
||||
for row in &mut join_table.rows {
|
||||
assert_eq!(
|
||||
row.len(),
|
||||
col_count,
|
||||
"Row length does not match column length after join"
|
||||
);
|
||||
}
|
||||
let join_clone = join_table.clone();
|
||||
|
||||
join_table
|
||||
.rows
|
||||
.retain(|row| self.where_clause.test(row, &join_clone));
|
||||
|
||||
if self.distinctness == Distinctness::Distinct {
|
||||
join_table.rows.sort_unstable();
|
||||
join_table.rows.dedup();
|
||||
}
|
||||
|
||||
Ok(join_table)
|
||||
} else {
|
||||
assert!(self
|
||||
.columns
|
||||
.iter()
|
||||
.all(|col| matches!(col, ResultColumn::Expr(_))));
|
||||
|
||||
// If `WHERE` is false, just return an empty table
|
||||
if !self.where_clause.test(&[], &Table::anonymous(vec![])) {
|
||||
return Ok(JoinTable {
|
||||
tables: Vec::new(),
|
||||
rows: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
// Compute the results of the column expressions and make a row
|
||||
let mut row = Vec::new();
|
||||
for col in &self.columns {
|
||||
match col {
|
||||
ResultColumn::Expr(expr) => {
|
||||
let value = expr.eval(&[], &Table::anonymous(vec![]));
|
||||
if let Some(value) = value {
|
||||
row.push(value);
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Failed to evaluate expression in free select ({})",
|
||||
expr.0.format_with_context(&EmptyContext {}).unwrap()
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Only expressions are allowed in free selects"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(JoinTable {
|
||||
tables: Vec::new(),
|
||||
rows: vec![row],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Select {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, env: &mut SimulatorTables) -> Self::Result {
|
||||
let first_result = self.body.select.shadow(env)?;
|
||||
|
||||
let mut rows = first_result.rows;
|
||||
|
||||
for compound in self.body.compounds.iter() {
|
||||
let compound_results = compound.select.shadow(env)?;
|
||||
|
||||
match compound.operator {
|
||||
CompoundOperator::Union => {
|
||||
// Union means we need to combine the results, removing duplicates
|
||||
let mut new_rows = compound_results.rows;
|
||||
new_rows.extend(rows.clone());
|
||||
new_rows.sort_unstable();
|
||||
new_rows.dedup();
|
||||
rows = new_rows;
|
||||
}
|
||||
CompoundOperator::UnionAll => {
|
||||
// Union all means we just concatenate the results
|
||||
rows.extend(compound_results.rows.into_iter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(rows)
|
||||
}
|
||||
}
|
||||
|
||||
impl Select {
|
||||
pub fn to_sql_ast(&self) -> ast::Select {
|
||||
ast::Select {
|
||||
with: None,
|
||||
body: ast::SelectBody {
|
||||
select: Box::new(ast::OneSelect::Select(Box::new(ast::SelectInner {
|
||||
distinctness: if self.body.select.distinctness == Distinctness::Distinct {
|
||||
Some(ast::Distinctness::Distinct)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
columns: self
|
||||
.body
|
||||
.select
|
||||
.columns
|
||||
.iter()
|
||||
.map(|col| match col {
|
||||
ResultColumn::Expr(expr) => {
|
||||
ast::ResultColumn::Expr(expr.0.clone(), None)
|
||||
}
|
||||
ResultColumn::Star => ast::ResultColumn::Star,
|
||||
ResultColumn::Column(name) => ast::ResultColumn::Expr(
|
||||
ast::Expr::Id(ast::Name::Ident(name.clone())),
|
||||
None,
|
||||
),
|
||||
})
|
||||
.collect(),
|
||||
from: self.body.select.from.as_ref().map(|f| f.to_sql_ast()),
|
||||
where_clause: Some(self.body.select.where_clause.0.clone()),
|
||||
group_by: None,
|
||||
window_clause: None,
|
||||
}))),
|
||||
compounds: Some(
|
||||
self.body
|
||||
.compounds
|
||||
.iter()
|
||||
.map(|compound| ast::CompoundSelect {
|
||||
operator: match compound.operator {
|
||||
CompoundOperator::Union => ast::CompoundOperator::Union,
|
||||
CompoundOperator::UnionAll => ast::CompoundOperator::UnionAll,
|
||||
},
|
||||
select: Box::new(ast::OneSelect::Select(Box::new(ast::SelectInner {
|
||||
distinctness: Some(compound.select.distinctness),
|
||||
columns: compound
|
||||
.select
|
||||
.columns
|
||||
.iter()
|
||||
.map(|col| match col {
|
||||
ResultColumn::Expr(expr) => {
|
||||
ast::ResultColumn::Expr(expr.0.clone(), None)
|
||||
}
|
||||
ResultColumn::Star => ast::ResultColumn::Star,
|
||||
ResultColumn::Column(name) => ast::ResultColumn::Expr(
|
||||
ast::Expr::Id(ast::Name::Ident(name.clone())),
|
||||
None,
|
||||
),
|
||||
})
|
||||
.collect(),
|
||||
from: compound.select.from.as_ref().map(|f| f.to_sql_ast()),
|
||||
where_clause: Some(compound.select.where_clause.0.clone()),
|
||||
group_by: None,
|
||||
window_clause: None,
|
||||
}))),
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
},
|
||||
order_by: self.body.select.order_by.as_ref().map(|o| {
|
||||
o.columns
|
||||
.iter()
|
||||
.map(|(name, order)| ast::SortedColumn {
|
||||
expr: ast::Expr::Id(ast::Name::Ident(name.clone())),
|
||||
order: match order {
|
||||
SortOrder::Asc => Some(ast::SortOrder::Asc),
|
||||
SortOrder::Desc => Some(ast::SortOrder::Desc),
|
||||
},
|
||||
nulls: None,
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
limit: self.limit.map(|l| {
|
||||
Box::new(ast::Limit {
|
||||
expr: ast::Expr::Literal(ast::Literal::Numeric(l.to_string())),
|
||||
offset: None,
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for Select {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.to_sql_ast().to_fmt_with_context(f, &EmptyContext {})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod select_tests {
|
||||
|
||||
#[test]
|
||||
fn test_select_display() {}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{generation::Shadow, model::table::SimValue, runner::env::SimulatorTables};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct Begin {
|
||||
pub(crate) immediate: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct Commit;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct Rollback;
|
||||
|
||||
impl Shadow for Begin {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
tables.snapshot = Some(tables.tables.clone());
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Commit {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
tables.snapshot = None;
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Rollback {
|
||||
type Result = Vec<Vec<SimValue>>;
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
if let Some(tables_) = tables.snapshot.take() {
|
||||
tables.tables = tables_;
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Begin {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "BEGIN {}", if self.immediate { "IMMEDIATE" } else { "" })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Commit {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "COMMIT")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Rollback {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "ROLLBACK")
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{generation::Shadow, model::table::SimValue, runner::env::SimulatorTables};
|
||||
|
||||
use super::predicate::Predicate;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub(crate) struct Update {
|
||||
pub(crate) table: String,
|
||||
pub(crate) set_values: Vec<(String, SimValue)>, // Pair of value for set expressions => SET name=value
|
||||
pub(crate) predicate: Predicate,
|
||||
}
|
||||
|
||||
impl Update {
|
||||
pub fn table(&self) -> &str {
|
||||
&self.table
|
||||
}
|
||||
}
|
||||
|
||||
impl Shadow for Update {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
let table = tables.tables.iter_mut().find(|t| t.name == self.table);
|
||||
|
||||
let table = if let Some(table) = table {
|
||||
table
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Table {} does not exist. UPDATE statement ignored.",
|
||||
self.table
|
||||
));
|
||||
};
|
||||
|
||||
let t2 = table.clone();
|
||||
for row in table
|
||||
.rows
|
||||
.iter_mut()
|
||||
.filter(|r| self.predicate.test(r, &t2))
|
||||
{
|
||||
for (column, set_value) in &self.set_values {
|
||||
if let Some((idx, _)) = table
|
||||
.columns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, c)| &c.name == column)
|
||||
{
|
||||
row[idx] = set_value.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Update {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "UPDATE {} SET ", self.table)?;
|
||||
for (i, (name, value)) in self.set_values.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{name} = {value}")?;
|
||||
}
|
||||
write!(f, " WHERE {}", self.predicate)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ impl SimulatorClock {
|
||||
let nanos = self
|
||||
.rng
|
||||
.borrow_mut()
|
||||
.gen_range(self.min_tick..self.max_tick);
|
||||
.random_range(self.min_tick..self.max_tick);
|
||||
let nanos = std::time::Duration::from_micros(nanos);
|
||||
*time += nanos;
|
||||
*time
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sql_generation::{generation::pick_index, model::table::SimValue};
|
||||
use turso_core::Value;
|
||||
|
||||
use crate::{
|
||||
generation::{
|
||||
pick_index,
|
||||
plan::{Interaction, InteractionPlanState, ResultSet},
|
||||
Shadow as _,
|
||||
},
|
||||
model::{query::Query, table::SimValue},
|
||||
model::Query,
|
||||
runner::execution::ExecutionContinuation,
|
||||
InteractionPlan,
|
||||
};
|
||||
|
||||
@@ -3,9 +3,10 @@ use std::{
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use sql_generation::generation::pick_index;
|
||||
|
||||
use crate::{
|
||||
generation::{pick_index, plan::InteractionPlanState},
|
||||
runner::execution::ExecutionContinuation,
|
||||
generation::plan::InteractionPlanState, runner::execution::ExecutionContinuation,
|
||||
InteractionPlan,
|
||||
};
|
||||
|
||||
|
||||
@@ -7,10 +7,9 @@ use std::sync::Arc;
|
||||
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use sql_generation::model::table::Table;
|
||||
use turso_core::Database;
|
||||
|
||||
use crate::model::table::Table;
|
||||
|
||||
use crate::runner::io::SimulatorIO;
|
||||
|
||||
use super::cli::SimulatorCLI;
|
||||
@@ -173,29 +172,29 @@ impl SimulatorEnv {
|
||||
let mut delete_percent = 0.0;
|
||||
let mut update_percent = 0.0;
|
||||
|
||||
let read_percent = rng.gen_range(0.0..=total);
|
||||
let read_percent = rng.random_range(0.0..=total);
|
||||
let write_percent = total - read_percent;
|
||||
|
||||
if !cli_opts.disable_create {
|
||||
// Create percent should be 5-15% of the write percent
|
||||
create_percent = rng.gen_range(0.05..=0.15) * write_percent;
|
||||
create_percent = rng.random_range(0.05..=0.15) * write_percent;
|
||||
}
|
||||
if !cli_opts.disable_create_index {
|
||||
// Create indexpercent should be 2-5% of the write percent
|
||||
create_index_percent = rng.gen_range(0.02..=0.05) * write_percent;
|
||||
create_index_percent = rng.random_range(0.02..=0.05) * write_percent;
|
||||
}
|
||||
if !cli_opts.disable_drop {
|
||||
// Drop percent should be 2-5% of the write percent
|
||||
drop_percent = rng.gen_range(0.02..=0.05) * write_percent;
|
||||
drop_percent = rng.random_range(0.02..=0.05) * write_percent;
|
||||
}
|
||||
if !cli_opts.disable_delete {
|
||||
// Delete percent should be 10-20% of the write percent
|
||||
delete_percent = rng.gen_range(0.1..=0.2) * write_percent;
|
||||
delete_percent = rng.random_range(0.1..=0.2) * write_percent;
|
||||
}
|
||||
if !cli_opts.disable_update {
|
||||
// Update percent should be 10-20% of the write percent
|
||||
// TODO: freestyling the percentage
|
||||
update_percent = rng.gen_range(0.1..=0.2) * write_percent;
|
||||
update_percent = rng.random_range(0.1..=0.2) * write_percent;
|
||||
}
|
||||
|
||||
let write_percent = write_percent
|
||||
@@ -220,10 +219,10 @@ impl SimulatorEnv {
|
||||
|
||||
let opts = SimulatorOpts {
|
||||
seed,
|
||||
ticks: rng.gen_range(cli_opts.minimum_tests..=cli_opts.maximum_tests),
|
||||
ticks: rng.random_range(cli_opts.minimum_tests..=cli_opts.maximum_tests),
|
||||
max_connections: 1, // TODO: for now let's use one connection as we didn't implement
|
||||
// correct transactions processing
|
||||
max_tables: rng.gen_range(0..128),
|
||||
max_tables: rng.random_range(0..128),
|
||||
create_percent,
|
||||
create_index_percent,
|
||||
read_percent,
|
||||
@@ -243,7 +242,7 @@ impl SimulatorEnv {
|
||||
disable_fsync_no_wait: cli_opts.disable_fsync_no_wait,
|
||||
disable_faulty_query: cli_opts.disable_faulty_query,
|
||||
page_size: 4096, // TODO: randomize this too
|
||||
max_interactions: rng.gen_range(cli_opts.minimum_tests..=cli_opts.maximum_tests),
|
||||
max_interactions: rng.random_range(cli_opts.minimum_tests..=cli_opts.maximum_tests),
|
||||
max_time_simulation: cli_opts.maximum_time,
|
||||
disable_reopen_database: cli_opts.disable_reopen_database,
|
||||
latency_probability: cli_opts.latency_probability,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sql_generation::generation::pick_index;
|
||||
use tracing::instrument;
|
||||
use turso_core::{Connection, LimboError, Result, StepResult};
|
||||
|
||||
use crate::generation::{
|
||||
pick_index,
|
||||
plan::{Interaction, InteractionPlan, InteractionPlanState, ResultSet},
|
||||
Shadow as _,
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ use rand_chacha::ChaCha8Rng;
|
||||
use tracing::{instrument, Level};
|
||||
use turso_core::{File, Result};
|
||||
|
||||
use crate::{model::FAULT_ERROR_MSG, runner::clock::SimulatorClock};
|
||||
use crate::runner::{clock::SimulatorClock, FAULT_ERROR_MSG};
|
||||
pub(crate) struct SimulatorFile {
|
||||
pub path: String,
|
||||
pub(crate) inner: Arc<dyn File>,
|
||||
@@ -100,10 +100,10 @@ impl SimulatorFile {
|
||||
fn generate_latency_duration(&self) -> Option<turso_core::Instant> {
|
||||
let mut rng = self.rng.borrow_mut();
|
||||
// Chance to introduce some latency
|
||||
rng.gen_bool(self.latency_probability as f64 / 100.0)
|
||||
rng.random_bool(self.latency_probability as f64 / 100.0)
|
||||
.then(|| {
|
||||
let now = self.clock.now();
|
||||
let sum = now + std::time::Duration::from_millis(rng.gen_range(5..20));
|
||||
let sum = now + std::time::Duration::from_millis(rng.random_range(5..20));
|
||||
sum.into()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,3 +9,5 @@ pub mod execution;
|
||||
pub mod file;
|
||||
pub mod io;
|
||||
pub mod watch;
|
||||
|
||||
pub const FAULT_ERROR_MSG: &str = "Injected Fault";
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sql_generation::generation::pick_index;
|
||||
|
||||
use crate::{
|
||||
generation::{
|
||||
pick_index,
|
||||
plan::{Interaction, InteractionPlanState},
|
||||
},
|
||||
generation::plan::{Interaction, InteractionPlanState},
|
||||
runner::execution::ExecutionContinuation,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::model::query::Query;
|
||||
use crate::{
|
||||
generation::{
|
||||
plan::{Interaction, InteractionPlan, Interactions},
|
||||
property::Property,
|
||||
},
|
||||
model::Query,
|
||||
run_simulation,
|
||||
runner::execution::Execution,
|
||||
SandboxedResult, SimulatorEnv,
|
||||
|
||||
24
sql_generation/Cargo.toml
Normal file
24
sql_generation/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "sql_generation"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
hex = "0.4.3"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
turso_core = { workspace = true, features = ["simulator"] }
|
||||
turso_parser = { workspace = true, features = ["serde"] }
|
||||
rand = { workspace = true }
|
||||
anarchist-readable-name-generator-lib = "0.2.0"
|
||||
itertools = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand_chacha = "0.9.0"
|
||||
@@ -1,14 +1,13 @@
|
||||
use turso_sqlite3_parser::ast::{
|
||||
use turso_parser::ast::{
|
||||
self, Expr, LikeOperator, Name, Operator, QualifiedName, Type, UnaryOperator,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
generation::{
|
||||
frequency, gen_random_text, one_of, pick, pick_index, Arbitrary, ArbitraryFrom,
|
||||
ArbitrarySizedFrom,
|
||||
ArbitrarySizedFrom, GenerationContext,
|
||||
},
|
||||
model::table::SimValue,
|
||||
SimulatorEnv,
|
||||
};
|
||||
|
||||
impl<T> Arbitrary for Box<T>
|
||||
@@ -34,7 +33,7 @@ where
|
||||
T: Arbitrary,
|
||||
{
|
||||
fn arbitrary<R: rand::Rng>(rng: &mut R) -> Self {
|
||||
rng.gen_bool(0.5).then_some(T::arbitrary(rng))
|
||||
rng.random_bool(0.5).then_some(T::arbitrary(rng))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +42,7 @@ where
|
||||
T: ArbitrarySizedFrom<A>,
|
||||
{
|
||||
fn arbitrary_sized_from<R: rand::Rng>(rng: &mut R, t: A, size: usize) -> Self {
|
||||
rng.gen_bool(0.5)
|
||||
rng.random_bool(0.5)
|
||||
.then_some(T::arbitrary_sized_from(rng, t, size))
|
||||
}
|
||||
}
|
||||
@@ -53,14 +52,14 @@ where
|
||||
T: ArbitraryFrom<A>,
|
||||
{
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, t: A) -> Self {
|
||||
let size = rng.gen_range(0..5);
|
||||
let size = rng.random_range(0..5);
|
||||
(0..size).map(|_| T::arbitrary_from(rng, t)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
// Freestyling generation
|
||||
impl ArbitrarySizedFrom<&SimulatorEnv> for Expr {
|
||||
fn arbitrary_sized_from<R: rand::Rng>(rng: &mut R, t: &SimulatorEnv, size: usize) -> Self {
|
||||
impl<C: GenerationContext> ArbitrarySizedFrom<&C> for Expr {
|
||||
fn arbitrary_sized_from<R: rand::Rng>(rng: &mut R, t: &C, size: usize) -> Self {
|
||||
frequency(
|
||||
vec![
|
||||
(
|
||||
@@ -200,36 +199,23 @@ impl Arbitrary for Type {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
impl<C: GenerationContext> ArbitraryFrom<&C> for QualifiedName {
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, t: &C) -> Self {
|
||||
// TODO: for now just generate table name
|
||||
let table_idx = pick_index(t.tables.len(), rng);
|
||||
let table = &t.tables[table_idx];
|
||||
let table_idx = pick_index(t.tables().len(), rng);
|
||||
let table = &t.tables()[table_idx];
|
||||
// TODO: for now forego alias
|
||||
Self::single(Name::from_str(&table.name))
|
||||
Self {
|
||||
db_name: None,
|
||||
name: Name::new(&table.name),
|
||||
alias: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for LikeOperator {
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, _t: &SimulatorEnv) -> Self {
|
||||
let choice = rng.gen_range(0..4);
|
||||
impl<C: GenerationContext> ArbitraryFrom<&C> for LikeOperator {
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, _t: &C) -> Self {
|
||||
let choice = rng.random_range(0..4);
|
||||
match choice {
|
||||
0 => LikeOperator::Glob,
|
||||
1 => LikeOperator::Like,
|
||||
@@ -241,17 +227,17 @@ impl ArbitraryFrom<&SimulatorEnv> for LikeOperator {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
impl<C: GenerationContext> ArbitraryFrom<&C> for ast::Literal {
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, _t: &C) -> Self {
|
||||
loop {
|
||||
let choice = rng.gen_range(0..5);
|
||||
let choice = rng.random_range(0..5);
|
||||
let lit = match choice {
|
||||
0 => ast::Literal::Numeric({
|
||||
let integer = rng.gen_bool(0.5);
|
||||
let integer = rng.random_bool(0.5);
|
||||
if integer {
|
||||
rng.gen_range(i64::MIN..i64::MAX).to_string()
|
||||
rng.random_range(i64::MIN..i64::MAX).to_string()
|
||||
} else {
|
||||
rng.gen_range(-1e10..1e10).to_string()
|
||||
rng.random_range(-1e10..1e10).to_string()
|
||||
}
|
||||
}),
|
||||
1 => ast::Literal::String(format!("'{}'", gen_random_text(rng))),
|
||||
@@ -279,9 +265,9 @@ impl ArbitraryFrom<&Vec<&SimValue>> for ast::Expr {
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&SimulatorEnv> for UnaryOperator {
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, _t: &SimulatorEnv) -> Self {
|
||||
let choice = rng.gen_range(0..4);
|
||||
impl<C: GenerationContext> ArbitraryFrom<&C> for UnaryOperator {
|
||||
fn arbitrary_from<R: rand::Rng>(rng: &mut R, _t: &C) -> Self {
|
||||
let choice = rng.random_range(0..4);
|
||||
match choice {
|
||||
0 => Self::BitwiseNot,
|
||||
1 => Self::Negative,
|
||||
188
sql_generation/generation/mod.rs
Normal file
188
sql_generation/generation/mod.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
use std::{iter::Sum, ops::SubAssign};
|
||||
|
||||
use anarchist_readable_name_generator_lib::readable_name_custom;
|
||||
use rand::{distr::uniform::SampleUniform, Rng};
|
||||
|
||||
use crate::model::table::Table;
|
||||
|
||||
pub mod expr;
|
||||
pub mod predicate;
|
||||
pub mod query;
|
||||
pub mod table;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Opts {
|
||||
/// Indexes enabled
|
||||
pub indexes: bool,
|
||||
}
|
||||
|
||||
/// Trait used to provide context to generation functions
|
||||
pub trait GenerationContext {
|
||||
fn tables(&self) -> &Vec<Table>;
|
||||
fn opts(&self) -> Opts;
|
||||
}
|
||||
|
||||
type ArbitraryFromFunc<'a, R, T> = Box<dyn Fn(&mut R) -> T + 'a>;
|
||||
type Choice<'a, R, T> = (usize, Box<dyn Fn(&mut R) -> Option<T> + 'a>);
|
||||
|
||||
/// Arbitrary trait for generating random values
|
||||
/// An implementation of arbitrary is assumed to be a uniform sampling of
|
||||
/// the possible values of the type, with a bias towards smaller values for
|
||||
/// practicality.
|
||||
pub trait Arbitrary {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self;
|
||||
}
|
||||
|
||||
/// ArbitrarySized trait for generating random values of a specific size
|
||||
/// An implementation of arbitrary_sized is assumed to be a uniform sampling of
|
||||
/// the possible values of the type, with a bias towards smaller values for
|
||||
/// practicality, but with the additional constraint that the generated value
|
||||
/// must fit in the given size. This is useful for generating values that are
|
||||
/// constrained by a specific size, such as integers or strings.
|
||||
pub trait ArbitrarySized {
|
||||
fn arbitrary_sized<R: Rng>(rng: &mut R, size: usize) -> Self;
|
||||
}
|
||||
|
||||
/// ArbitraryFrom trait for generating random values from a given value
|
||||
/// ArbitraryFrom allows for constructing relations, where the generated
|
||||
/// value is dependent on the given value. These relations could be constraints
|
||||
/// such as generating an integer within an interval, or a value that fits in a table,
|
||||
/// or a predicate satisfying a given table row.
|
||||
pub trait ArbitraryFrom<T> {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, t: T) -> Self;
|
||||
}
|
||||
|
||||
/// ArbitrarySizedFrom trait for generating random values from a given value
|
||||
/// ArbitrarySizedFrom allows for constructing relations, where the generated
|
||||
/// value is dependent on the given value and a size constraint. These relations
|
||||
/// could be constraints such as generating an integer within an interval,
|
||||
/// or a value that fits in a table, or a predicate satisfying a given table row,
|
||||
/// but with the additional constraint that the generated value must fit in the given size.
|
||||
/// This is useful for generating values that are constrained by a specific size,
|
||||
/// such as integers or strings, while still being dependent on the given value.
|
||||
pub trait ArbitrarySizedFrom<T> {
|
||||
fn arbitrary_sized_from<R: Rng>(rng: &mut R, t: T, size: usize) -> Self;
|
||||
}
|
||||
|
||||
/// ArbitraryFromMaybe trait for fallibally generating random values from a given value
|
||||
pub trait ArbitraryFromMaybe<T> {
|
||||
fn arbitrary_from_maybe<R: Rng>(rng: &mut R, t: T) -> Option<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// Frequency is a helper function for composing different generators with different frequency
|
||||
/// of occurrences.
|
||||
/// The type signature for the `N` parameter is a bit complex, but it
|
||||
/// roughly corresponds to a type that can be summed, compared, subtracted and sampled, which are
|
||||
/// the operations we require for the implementation.
|
||||
// todo: switch to a simpler type signature that can accommodate all integer and float types, which
|
||||
// should be enough for our purposes.
|
||||
pub fn frequency<T, R: Rng, N: Sum + PartialOrd + Copy + Default + SampleUniform + SubAssign>(
|
||||
choices: Vec<(N, ArbitraryFromFunc<R, T>)>,
|
||||
rng: &mut R,
|
||||
) -> T {
|
||||
let total = choices.iter().map(|(weight, _)| *weight).sum::<N>();
|
||||
let mut choice = rng.random_range(N::default()..total);
|
||||
|
||||
for (weight, f) in choices {
|
||||
if choice < weight {
|
||||
return f(rng);
|
||||
}
|
||||
choice -= weight;
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
/// one_of is a helper function for composing different generators with equal probability of occurrence.
|
||||
pub fn one_of<T, R: Rng>(choices: Vec<ArbitraryFromFunc<R, T>>, rng: &mut R) -> T {
|
||||
let index = rng.random_range(0..choices.len());
|
||||
choices[index](rng)
|
||||
}
|
||||
|
||||
/// backtrack is a helper function for composing different "failable" generators.
|
||||
/// The function takes a list of functions that return an Option<T>, along with number of retries
|
||||
/// to make before giving up.
|
||||
pub fn backtrack<T, R: Rng>(mut choices: Vec<Choice<R, T>>, rng: &mut R) -> Option<T> {
|
||||
loop {
|
||||
// If there are no more choices left, we give up
|
||||
let choices_ = choices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, (retries, _))| *retries > 0)
|
||||
.collect::<Vec<_>>();
|
||||
if choices_.is_empty() {
|
||||
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 result.is_some() {
|
||||
return result;
|
||||
} else {
|
||||
choices[choice_index].0 -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// pick is a helper function for uniformly picking a random element from a slice
|
||||
pub fn pick<'a, T, R: Rng>(choices: &'a [T], rng: &mut R) -> &'a T {
|
||||
let index = rng.random_range(0..choices.len());
|
||||
&choices[index]
|
||||
}
|
||||
|
||||
/// pick_index is typically used for picking an index from a slice to later refer to the element
|
||||
/// at that index.
|
||||
pub fn pick_index<R: Rng>(choices: usize, rng: &mut R) -> usize {
|
||||
rng.random_range(0..choices)
|
||||
}
|
||||
|
||||
/// pick_n_unique is a helper function for uniformly picking N unique elements from a range.
|
||||
/// The elements themselves are usize, typically representing indices.
|
||||
pub fn pick_n_unique<R: Rng>(range: std::ops::Range<usize>, n: usize, rng: &mut R) -> Vec<usize> {
|
||||
use rand::seq::SliceRandom;
|
||||
let mut items: Vec<usize> = range.collect();
|
||||
items.shuffle(rng);
|
||||
items.into_iter().take(n).collect()
|
||||
}
|
||||
|
||||
/// gen_random_text uses `anarchist_readable_name_generator_lib` to generate random
|
||||
/// readable names for tables, columns, text values etc.
|
||||
pub fn gen_random_text<T: Rng>(rng: &mut T) -> String {
|
||||
let big_text = rng.random_ratio(1, 1000);
|
||||
if big_text {
|
||||
// let max_size: u64 = 2 * 1024 * 1024 * 1024;
|
||||
let max_size: u64 = 2 * 1024;
|
||||
let size = rng.random_range(1024..max_size);
|
||||
let mut name = String::with_capacity(size as usize);
|
||||
for i in 0..size {
|
||||
name.push(((i % 26) as u8 + b'A') as char);
|
||||
}
|
||||
name
|
||||
} else {
|
||||
let name = readable_name_custom("_", rng);
|
||||
name.replace("-", "_")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pick_unique<T: ToOwned + PartialEq>(
|
||||
items: &[T],
|
||||
count: usize,
|
||||
rng: &mut impl rand::Rng,
|
||||
) -> Vec<T::Owned>
|
||||
where
|
||||
<T as ToOwned>::Owned: PartialEq,
|
||||
{
|
||||
let mut picked: Vec<T::Owned> = Vec::new();
|
||||
while picked.len() < count {
|
||||
let item = pick(items, rng);
|
||||
if !picked.contains(&item.to_owned()) {
|
||||
picked.push(item.to_owned());
|
||||
}
|
||||
}
|
||||
picked
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Contains code for generation for [ast::Expr::Binary] Predicate
|
||||
|
||||
use turso_sqlite3_parser::ast::{self, Expr};
|
||||
use turso_parser::ast::{self, Expr};
|
||||
|
||||
use crate::{
|
||||
generation::{
|
||||
@@ -56,7 +56,7 @@ impl Predicate {
|
||||
/// Produces a true [ast::Expr::Binary] [Predicate] that is true for the provided row in the given table
|
||||
pub fn true_binary<R: rand::Rng>(rng: &mut R, t: &Table, row: &[SimValue]) -> Predicate {
|
||||
// Pick a column
|
||||
let column_index = rng.gen_range(0..t.columns.len());
|
||||
let column_index = rng.random_range(0..t.columns.len());
|
||||
let mut column = t.columns[column_index].clone();
|
||||
let value = &row[column_index];
|
||||
|
||||
@@ -82,8 +82,8 @@ impl Predicate {
|
||||
Box::new(|_| {
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
Box::new(Expr::Literal(value.into())),
|
||||
@@ -99,8 +99,8 @@ impl Predicate {
|
||||
} else {
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
Box::new(Expr::Literal(v.into())),
|
||||
@@ -114,8 +114,8 @@ impl Predicate {
|
||||
let lt_value = LTValue::arbitrary_from(rng, value).0;
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
@@ -128,8 +128,8 @@ impl Predicate {
|
||||
let gt_value = GTValue::arbitrary_from(rng, value).0;
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
@@ -143,8 +143,8 @@ impl Predicate {
|
||||
LikeValue::arbitrary_from_maybe(rng, value).map(|like| {
|
||||
Expr::Like {
|
||||
lhs: Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
not: false, // TODO: also generate this value eventually
|
||||
op: ast::LikeOperator::Like,
|
||||
@@ -164,7 +164,7 @@ impl Predicate {
|
||||
/// Produces an [ast::Expr::Binary] [Predicate] that is false for the provided row in the given table
|
||||
pub fn false_binary<R: rand::Rng>(rng: &mut R, t: &Table, row: &[SimValue]) -> Predicate {
|
||||
// Pick a column
|
||||
let column_index = rng.gen_range(0..t.columns.len());
|
||||
let column_index = rng.random_range(0..t.columns.len());
|
||||
let mut column = t.columns[column_index].clone();
|
||||
let mut table_name = t.name.clone();
|
||||
let value = &row[column_index];
|
||||
@@ -188,8 +188,8 @@ impl Predicate {
|
||||
Box::new(|_| {
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
Box::new(Expr::Literal(value.into())),
|
||||
@@ -204,8 +204,8 @@ impl Predicate {
|
||||
};
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
Box::new(Expr::Literal(v.into())),
|
||||
@@ -215,8 +215,8 @@ impl Predicate {
|
||||
let gt_value = GTValue::arbitrary_from(rng, value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
@@ -226,8 +226,8 @@ impl Predicate {
|
||||
let lt_value = LTValue::arbitrary_from(rng, value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(&table_name),
|
||||
ast::Name::from_str(&column.name),
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
@@ -249,7 +249,7 @@ impl SimplePredicate {
|
||||
) -> Self {
|
||||
// Pick a random column
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
let column_index = rng.gen_range(0..columns.len());
|
||||
let column_index = rng.random_range(0..columns.len());
|
||||
let column = columns[column_index];
|
||||
let column_value = &row[column_index];
|
||||
let table_name = column.table_name;
|
||||
@@ -263,8 +263,8 @@ impl SimplePredicate {
|
||||
Box::new(|_rng| {
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(table_name),
|
||||
ast::Name::from_str(&column.column.name),
|
||||
ast::Name::new(table_name),
|
||||
ast::Name::new(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
Box::new(Expr::Literal(column_value.into())),
|
||||
@@ -274,8 +274,8 @@ impl SimplePredicate {
|
||||
let lt_value = LTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name::from_str(table_name),
|
||||
ast::Name::from_str(&column.column.name),
|
||||
ast::Name::new(table_name),
|
||||
ast::Name::new(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
@@ -285,8 +285,8 @@ impl SimplePredicate {
|
||||
let gt_value = GTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name::from_str(table_name),
|
||||
ast::Name::from_str(&column.column.name),
|
||||
ast::Name::new(table_name),
|
||||
ast::Name::new(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
@@ -306,7 +306,7 @@ impl SimplePredicate {
|
||||
) -> Self {
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..columns.len());
|
||||
let column_index = rng.random_range(0..columns.len());
|
||||
let column = columns[column_index];
|
||||
let column_value = &row[column_index];
|
||||
let table_name = column.table_name;
|
||||
@@ -320,8 +320,8 @@ impl SimplePredicate {
|
||||
Box::new(|_rng| {
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name::from_str(table_name),
|
||||
ast::Name::from_str(&column.column.name),
|
||||
ast::Name::new(table_name),
|
||||
ast::Name::new(&column.column.name),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
Box::new(Expr::Literal(column_value.into())),
|
||||
@@ -331,8 +331,8 @@ impl SimplePredicate {
|
||||
let gt_value = GTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(table_name),
|
||||
ast::Name::from_str(&column.column.name),
|
||||
ast::Name::new(table_name),
|
||||
ast::Name::new(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
@@ -342,8 +342,8 @@ impl SimplePredicate {
|
||||
let lt_value = LTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::from_str(table_name),
|
||||
ast::Name::from_str(&column.column.name),
|
||||
ast::Name::new(table_name),
|
||||
ast::Name::new(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
@@ -376,11 +376,11 @@ impl CompoundPredicate {
|
||||
}
|
||||
let row = pick(rows, rng);
|
||||
|
||||
let predicate = if rng.gen_bool(0.7) {
|
||||
let predicate = if rng.random_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(1..=3))
|
||||
(0..rng.random_range(1..=3))
|
||||
.map(|_| SimplePredicate::arbitrary_from(rng, (table, row, true)).0)
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
@@ -392,15 +392,15 @@ impl CompoundPredicate {
|
||||
.unwrap_or(Predicate::true_())
|
||||
} else {
|
||||
// Create a vector of random booleans
|
||||
let mut booleans = (0..rng.gen_range(1..=3))
|
||||
.map(|_| rng.gen_bool(0.5))
|
||||
let mut booleans = (0..rng.random_range(1..=3))
|
||||
.map(|_| rng.random_bool(0.5))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let len = booleans.len();
|
||||
|
||||
// Make sure at least one of them is false
|
||||
if booleans.iter().all(|b| *b) {
|
||||
booleans[rng.gen_range(0..len)] = false;
|
||||
booleans[rng.random_range(0..len)] = false;
|
||||
}
|
||||
|
||||
booleans
|
||||
@@ -420,13 +420,13 @@ impl CompoundPredicate {
|
||||
// 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(1..=3))
|
||||
.map(|_| rng.gen_bool(0.5))
|
||||
let mut booleans = (0..rng.random_range(1..=3))
|
||||
.map(|_| rng.random_bool(0.5))
|
||||
.collect::<Vec<_>>();
|
||||
let len = booleans.len();
|
||||
// Make sure at least one of them is true
|
||||
if booleans.iter().all(|b| !*b) {
|
||||
booleans[rng.gen_range(0..len)] = true;
|
||||
booleans[rng.random_range(0..len)] = true;
|
||||
}
|
||||
|
||||
booleans
|
||||
@@ -441,7 +441,7 @@ impl CompoundPredicate {
|
||||
})
|
||||
.unwrap_or(Predicate::true_())
|
||||
} else {
|
||||
(0..rng.gen_range(1..=3))
|
||||
(0..rng.random_range(1..=3))
|
||||
.map(|_| SimplePredicate::arbitrary_from(rng, (table, row, false)).0)
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
@@ -483,7 +483,7 @@ mod tests {
|
||||
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 num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
@@ -509,7 +509,7 @@ mod tests {
|
||||
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 num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
@@ -535,7 +535,7 @@ mod tests {
|
||||
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 num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
@@ -563,7 +563,7 @@ mod tests {
|
||||
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 num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
@@ -1,5 +1,5 @@
|
||||
use rand::{seq::SliceRandom as _, Rng};
|
||||
use turso_sqlite3_parser::ast::{self, Expr};
|
||||
use turso_parser::ast::{self, Expr};
|
||||
|
||||
use crate::model::{
|
||||
query::predicate::Predicate,
|
||||
@@ -8,8 +8,8 @@ use crate::model::{
|
||||
|
||||
use super::{one_of, ArbitraryFrom};
|
||||
|
||||
mod binary;
|
||||
mod unary;
|
||||
pub mod binary;
|
||||
pub mod unary;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CompoundPredicate(Predicate);
|
||||
@@ -21,7 +21,7 @@ impl<A: AsRef<[SimValue]>, T: TableContext> ArbitraryFrom<(&T, A, bool)> for Sim
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, row, predicate_value): (&T, A, bool)) -> Self {
|
||||
let row = row.as_ref();
|
||||
// Pick an operator
|
||||
let choice = rng.gen_range(0..2);
|
||||
let choice = rng.random_range(0..2);
|
||||
// Pick an operator
|
||||
match predicate_value {
|
||||
true => match choice {
|
||||
@@ -46,7 +46,7 @@ impl<T: TableContext> ArbitraryFrom<(&T, bool)> for CompoundPredicate {
|
||||
|
||||
impl<T: TableContext> ArbitraryFrom<&T> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, table: &T) -> Self {
|
||||
let predicate_value = rng.gen_bool(0.5);
|
||||
let predicate_value = rng.random_bool(0.5);
|
||||
Predicate::arbitrary_from(rng, (table, predicate_value)).parens()
|
||||
}
|
||||
}
|
||||
@@ -70,11 +70,11 @@ impl ArbitraryFrom<(&Table, &Vec<SimValue>)> for Predicate {
|
||||
// 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))
|
||||
let mut true_predicates = (1..=rng.random_range(1..=4))
|
||||
.map(|_| Predicate::true_binary(rng, t, row))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let false_predicates = (0..=rng.gen_range(0..=3))
|
||||
let false_predicates = (0..=rng.random_range(0..=3))
|
||||
.map(|_| Predicate::false_binary(rng, t, row))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -92,7 +92,7 @@ impl ArbitraryFrom<(&Table, &Vec<SimValue>)> for Predicate {
|
||||
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();
|
||||
predicates[0..rng.random_range(0..=usize::min(3, predicates.len()))].to_vec();
|
||||
// Shift `predicates` to remove the predicates in the context
|
||||
predicates = predicates[context.len()..].to_vec();
|
||||
|
||||
@@ -251,7 +251,7 @@ mod tests {
|
||||
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 num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
@@ -277,7 +277,7 @@ mod tests {
|
||||
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 num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
@@ -303,7 +303,7 @@ mod tests {
|
||||
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 num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
@@ -329,7 +329,7 @@ mod tests {
|
||||
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 num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
@@ -356,7 +356,7 @@ mod tests {
|
||||
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 num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
@@ -2,7 +2,7 @@
|
||||
//! TODO: for now just generating [ast::Literal], but want to also generate Columns and any
|
||||
//! arbitrary [ast::Expr]
|
||||
|
||||
use turso_sqlite3_parser::ast::{self, Expr};
|
||||
use turso_parser::ast::{self, Expr};
|
||||
|
||||
use crate::{
|
||||
generation::{backtrack, pick, predicate::SimplePredicate, ArbitraryFromMaybe},
|
||||
@@ -64,6 +64,7 @@ impl ArbitraryFromMaybe<&Vec<&SimValue>> for FalseValue {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct BitNotValue(pub SimValue);
|
||||
|
||||
impl ArbitraryFromMaybe<(&SimValue, bool)> for BitNotValue {
|
||||
@@ -107,7 +108,7 @@ impl SimplePredicate {
|
||||
) -> Self {
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..columns.len());
|
||||
let column_index = rng.random_range(0..columns.len());
|
||||
let column_value = &row[column_index];
|
||||
let num_retries = row.len();
|
||||
// Avoid creation of NULLs
|
||||
@@ -173,7 +174,7 @@ impl SimplePredicate {
|
||||
) -> Self {
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
// Pick a random column
|
||||
let column_index = rng.gen_range(0..columns.len());
|
||||
let column_index = rng.random_range(0..columns.len());
|
||||
let column_value = &row[column_index];
|
||||
let num_retries = row.len();
|
||||
// Avoid creation of NULLs
|
||||
@@ -255,7 +256,7 @@ mod tests {
|
||||
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 num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
@@ -283,7 +284,7 @@ mod tests {
|
||||
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 num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
391
sql_generation/generation/query.rs
Normal file
391
sql_generation/generation/query.rs
Normal file
@@ -0,0 +1,391 @@
|
||||
use crate::generation::{
|
||||
gen_random_text, pick_n_unique, pick_unique, Arbitrary, ArbitraryFrom, ArbitrarySizedFrom,
|
||||
GenerationContext,
|
||||
};
|
||||
use crate::model::query::predicate::Predicate;
|
||||
use crate::model::query::select::{
|
||||
CompoundOperator, CompoundSelect, Distinctness, FromClause, OrderBy, ResultColumn, SelectBody,
|
||||
SelectInner,
|
||||
};
|
||||
use crate::model::query::update::Update;
|
||||
use crate::model::query::{Create, CreateIndex, Delete, Drop, Insert, Select};
|
||||
use crate::model::table::{JoinTable, JoinType, JoinedTable, SimValue, Table, TableContext};
|
||||
use itertools::Itertools;
|
||||
use rand::Rng;
|
||||
use turso_parser::ast::{Expr, SortOrder};
|
||||
|
||||
use super::{backtrack, pick};
|
||||
|
||||
impl Arbitrary for Create {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
|
||||
Create {
|
||||
table: Table::arbitrary(rng),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<&Vec<Table>> for FromClause {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, tables: &Vec<Table>) -> Self {
|
||||
let num_joins = match rng.random_range(0..=100) {
|
||||
0..=90 => 0,
|
||||
91..=97 => 1,
|
||||
98..=100 => 2,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut tables = tables.clone();
|
||||
let mut table = pick(&tables, rng).clone();
|
||||
|
||||
tables.retain(|t| t.name != table.name);
|
||||
|
||||
let name = table.name.clone();
|
||||
|
||||
let mut table_context = JoinTable {
|
||||
tables: Vec::new(),
|
||||
rows: Vec::new(),
|
||||
};
|
||||
|
||||
let joins: Vec<_> = (0..num_joins)
|
||||
.filter_map(|_| {
|
||||
if tables.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let join_table = pick(&tables, rng).clone();
|
||||
let joined_table_name = join_table.name.clone();
|
||||
|
||||
tables.retain(|t| t.name != join_table.name);
|
||||
table_context.rows = table_context
|
||||
.rows
|
||||
.iter()
|
||||
.cartesian_product(join_table.rows.iter())
|
||||
.map(|(t_row, j_row)| {
|
||||
let mut row = t_row.clone();
|
||||
row.extend(j_row.clone());
|
||||
row
|
||||
})
|
||||
.collect();
|
||||
// TODO: inneficient. use a Deque to push_front?
|
||||
table_context.tables.insert(0, join_table);
|
||||
for row in &mut table.rows {
|
||||
assert_eq!(
|
||||
row.len(),
|
||||
table.columns.len(),
|
||||
"Row length does not match column length after join"
|
||||
);
|
||||
}
|
||||
|
||||
let predicate = Predicate::arbitrary_from(rng, &table);
|
||||
Some(JoinedTable {
|
||||
table: joined_table_name,
|
||||
join_type: JoinType::Inner,
|
||||
on: predicate,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
FromClause { table: name, joins }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GenerationContext> ArbitraryFrom<&C> for SelectInner {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
|
||||
let from = FromClause::arbitrary_from(rng, env.tables());
|
||||
let tables = env.tables().clone();
|
||||
let join_table = from.into_join_table(&tables);
|
||||
let cuml_col_count = join_table.columns().count();
|
||||
|
||||
let order_by = 'order_by: {
|
||||
if rng.random_bool(0.3) {
|
||||
let order_by_table_candidates = from
|
||||
.joins
|
||||
.iter()
|
||||
.map(|j| j.table.clone())
|
||||
.chain(std::iter::once(from.table.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
let order_by_col_count =
|
||||
(rng.random::<f64>() * rng.random::<f64>() * (cuml_col_count as f64)) as usize; // skew towards 0
|
||||
if order_by_col_count == 0 {
|
||||
break 'order_by None;
|
||||
}
|
||||
let mut col_names = std::collections::HashSet::new();
|
||||
let mut order_by_cols = Vec::new();
|
||||
while order_by_cols.len() < order_by_col_count {
|
||||
let table = pick(&order_by_table_candidates, rng);
|
||||
let table = tables.iter().find(|t| t.name == *table).unwrap();
|
||||
let col = pick(&table.columns, rng);
|
||||
let col_name = format!("{}.{}", table.name, col.name);
|
||||
if col_names.insert(col_name.clone()) {
|
||||
order_by_cols.push((
|
||||
col_name,
|
||||
if rng.random_bool(0.5) {
|
||||
SortOrder::Asc
|
||||
} else {
|
||||
SortOrder::Desc
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
Some(OrderBy {
|
||||
columns: order_by_cols,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
SelectInner {
|
||||
distinctness: if env.opts().indexes {
|
||||
Distinctness::arbitrary(rng)
|
||||
} else {
|
||||
Distinctness::All
|
||||
},
|
||||
columns: vec![ResultColumn::Star],
|
||||
from: Some(from),
|
||||
where_clause: Predicate::arbitrary_from(rng, &join_table),
|
||||
order_by,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GenerationContext> ArbitrarySizedFrom<&C> for SelectInner {
|
||||
fn arbitrary_sized_from<R: Rng>(rng: &mut R, env: &C, num_result_columns: usize) -> Self {
|
||||
let mut select_inner = SelectInner::arbitrary_from(rng, env);
|
||||
let select_from = &select_inner.from.as_ref().unwrap();
|
||||
let table_names = select_from
|
||||
.joins
|
||||
.iter()
|
||||
.map(|j| j.table.clone())
|
||||
.chain(std::iter::once(select_from.table.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let flat_columns_names = table_names
|
||||
.iter()
|
||||
.flat_map(|t| {
|
||||
env.tables()
|
||||
.iter()
|
||||
.find(|table| table.name == *t)
|
||||
.unwrap()
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| format!("{}.{}", t.clone(), c.name))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let selected_columns = pick_unique(&flat_columns_names, num_result_columns, rng);
|
||||
let mut columns = Vec::new();
|
||||
for column_name in selected_columns {
|
||||
columns.push(ResultColumn::Column(column_name.clone()));
|
||||
}
|
||||
select_inner.columns = columns;
|
||||
select_inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for Distinctness {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
|
||||
match rng.random_range(0..=5) {
|
||||
0..4 => Distinctness::All,
|
||||
_ => Distinctness::Distinct,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Arbitrary for CompoundOperator {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
|
||||
match rng.random_range(0..=1) {
|
||||
0 => CompoundOperator::Union,
|
||||
1 => CompoundOperator::UnionAll,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SelectFree is a wrapper around Select that allows for arbitrary generation
|
||||
/// of selects without requiring a specific environment, which is useful for generating
|
||||
/// arbitrary expressions without referring to the tables.
|
||||
pub struct SelectFree(pub Select);
|
||||
|
||||
impl<C: GenerationContext> ArbitraryFrom<&C> for SelectFree {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
|
||||
let expr = Predicate(Expr::arbitrary_sized_from(rng, env, 8));
|
||||
let select = Select::expr(expr);
|
||||
Self(select)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GenerationContext> ArbitraryFrom<&C> for Select {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
|
||||
// Generate a number of selects based on the query size
|
||||
// If experimental indexes are enabled, we can have selects with compounds
|
||||
// Otherwise, we just have a single select with no compounds
|
||||
let num_compound_selects = if env.opts().indexes {
|
||||
match rng.random_range(0..=100) {
|
||||
0..=95 => 0,
|
||||
96..=99 => 1,
|
||||
100 => 2,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let min_column_count_across_tables =
|
||||
env.tables().iter().map(|t| t.columns.len()).min().unwrap();
|
||||
|
||||
let num_result_columns = rng.random_range(1..=min_column_count_across_tables);
|
||||
|
||||
let mut first = SelectInner::arbitrary_sized_from(rng, env, num_result_columns);
|
||||
|
||||
let mut rest: Vec<SelectInner> = (0..num_compound_selects)
|
||||
.map(|_| SelectInner::arbitrary_sized_from(rng, env, num_result_columns))
|
||||
.collect();
|
||||
|
||||
if !rest.is_empty() {
|
||||
// ORDER BY is not supported in compound selects yet
|
||||
first.order_by = None;
|
||||
for s in &mut rest {
|
||||
s.order_by = None;
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
body: SelectBody {
|
||||
select: Box::new(first),
|
||||
compounds: rest
|
||||
.into_iter()
|
||||
.map(|s| CompoundSelect {
|
||||
operator: CompoundOperator::arbitrary(rng),
|
||||
select: Box::new(s),
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
limit: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GenerationContext> ArbitraryFrom<&C> for Insert {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
|
||||
let gen_values = |rng: &mut R| {
|
||||
let table = pick(env.tables(), rng);
|
||||
let num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| SimValue::arbitrary_from(rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
Some(Insert::Values {
|
||||
table: table.name.clone(),
|
||||
values,
|
||||
})
|
||||
};
|
||||
|
||||
let _gen_select = |rng: &mut R| {
|
||||
// Find a non-empty table
|
||||
let select_table = env.tables().iter().find(|t| !t.rows.is_empty())?;
|
||||
let row = pick(&select_table.rows, rng);
|
||||
let predicate = Predicate::arbitrary_from(rng, (select_table, row));
|
||||
// Pick another table to insert into
|
||||
let select = Select::simple(select_table.name.clone(), predicate);
|
||||
let table = pick(env.tables(), rng);
|
||||
Some(Insert::Select {
|
||||
table: table.name.clone(),
|
||||
select: Box::new(select),
|
||||
})
|
||||
};
|
||||
|
||||
// TODO: Add back gen_select when https://github.com/tursodatabase/turso/issues/2129 is fixed.
|
||||
// Backtrack here cannot return None
|
||||
backtrack(vec![(1, Box::new(gen_values))], rng).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GenerationContext> ArbitraryFrom<&C> for Delete {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
|
||||
let table = pick(env.tables(), rng);
|
||||
Self {
|
||||
table: table.name.clone(),
|
||||
predicate: Predicate::arbitrary_from(rng, table),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GenerationContext> ArbitraryFrom<&C> for Drop {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
|
||||
let table = pick(env.tables(), rng);
|
||||
Self {
|
||||
table: table.name.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GenerationContext> ArbitraryFrom<&C> for CreateIndex {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
|
||||
assert!(
|
||||
!env.tables().is_empty(),
|
||||
"Cannot create an index when no tables exist in the environment."
|
||||
);
|
||||
|
||||
let table = pick(env.tables(), rng);
|
||||
|
||||
if table.columns.is_empty() {
|
||||
panic!(
|
||||
"Cannot create an index on table '{}' as it has no columns.",
|
||||
table.name
|
||||
);
|
||||
}
|
||||
|
||||
let num_columns_to_pick = rng.random_range(1..=table.columns.len());
|
||||
let picked_column_indices = pick_n_unique(0..table.columns.len(), num_columns_to_pick, rng);
|
||||
|
||||
let columns = picked_column_indices
|
||||
.into_iter()
|
||||
.map(|i| {
|
||||
let column = &table.columns[i];
|
||||
(
|
||||
column.name.clone(),
|
||||
if rng.random_bool(0.5) {
|
||||
SortOrder::Asc
|
||||
} else {
|
||||
SortOrder::Desc
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(String, SortOrder)>>();
|
||||
|
||||
let index_name = format!(
|
||||
"idx_{}_{}",
|
||||
table.name,
|
||||
gen_random_text(rng).chars().take(8).collect::<String>()
|
||||
);
|
||||
|
||||
CreateIndex {
|
||||
index_name,
|
||||
table_name: table.name.clone(),
|
||||
columns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GenerationContext> ArbitraryFrom<&C> for Update {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, env: &C) -> Self {
|
||||
let table = pick(env.tables(), rng);
|
||||
let num_cols = rng.random_range(1..=table.columns.len());
|
||||
let columns = pick_unique(&table.columns, num_cols, rng);
|
||||
let set_values: Vec<(String, SimValue)> = columns
|
||||
.iter()
|
||||
.map(|column| {
|
||||
(
|
||||
column.name.clone(),
|
||||
SimValue::arbitrary_from(rng, &column.column_type),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
Update {
|
||||
table: table.name.clone(),
|
||||
set_values,
|
||||
predicate: Predicate::arbitrary_from(rng, table),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,11 @@ impl Arbitrary for Table {
|
||||
fn arbitrary<R: Rng>(rng: &mut R) -> Self {
|
||||
let name = Name::arbitrary(rng).0;
|
||||
let columns = loop {
|
||||
let large_table = rng.gen_bool(0.1);
|
||||
let large_table = rng.random_bool(0.1);
|
||||
let column_size = if large_table {
|
||||
rng.gen_range(64..125) // todo: make this higher (128+)
|
||||
rng.random_range(64..125) // todo: make this higher (128+)
|
||||
} else {
|
||||
rng.gen_range(1..=10)
|
||||
rng.random_range(1..=10)
|
||||
};
|
||||
let columns = (1..=column_size)
|
||||
.map(|_| Column::arbitrary(rng))
|
||||
@@ -90,8 +90,8 @@ impl ArbitraryFrom<&Vec<&SimValue>> for SimValue {
|
||||
impl ArbitraryFrom<&ColumnType> for SimValue {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, column_type: &ColumnType) -> Self {
|
||||
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::Integer => Value::Integer(rng.random_range(i64::MIN..i64::MAX)),
|
||||
ColumnType::Float => Value::Float(rng.random_range(-1e10..1e10)),
|
||||
ColumnType::Text => Value::build_text(gen_random_text(rng)),
|
||||
ColumnType::Blob => Value::Blob(gen_random_text(rng).as_bytes().to_vec()),
|
||||
};
|
||||
@@ -99,7 +99,7 @@ impl ArbitraryFrom<&ColumnType> for SimValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct LTValue(pub(crate) SimValue);
|
||||
pub struct LTValue(pub SimValue);
|
||||
|
||||
impl ArbitraryFrom<&Vec<&SimValue>> for LTValue {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Self {
|
||||
@@ -116,21 +116,21 @@ impl ArbitraryFrom<&Vec<&SimValue>> for LTValue {
|
||||
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::Integer(i) => Value::Integer(rng.random_range(i64::MIN..*i - 1)),
|
||||
Value::Float(f) => Value::Float(f - rng.random_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 = value.to_string();
|
||||
if rng.gen_bool(0.01) {
|
||||
if rng.random_bool(0.01) {
|
||||
t.pop();
|
||||
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());
|
||||
let index = rng.random_range(0..t.len());
|
||||
t[index] -= 1;
|
||||
// Mutate the rest of the string
|
||||
for val in t.iter_mut().skip(index + 1) {
|
||||
*val = rng.gen_range('a' as u32..='z' as u32);
|
||||
*val = rng.random_range('a' as u32..='z' as u32);
|
||||
}
|
||||
let t = t
|
||||
.into_iter()
|
||||
@@ -142,15 +142,15 @@ impl ArbitraryFrom<&SimValue> for LTValue {
|
||||
Value::Blob(b) => {
|
||||
// Either shorten the blob, or make at least one byte smaller and mutate the rest
|
||||
let mut b = b.clone();
|
||||
if rng.gen_bool(0.01) {
|
||||
if rng.random_bool(0.01) {
|
||||
b.pop();
|
||||
Value::Blob(b)
|
||||
} else {
|
||||
let index = rng.gen_range(0..b.len());
|
||||
let index = rng.random_range(0..b.len());
|
||||
b[index] -= 1;
|
||||
// Mutate the rest of the blob
|
||||
for val in b.iter_mut().skip(index + 1) {
|
||||
*val = rng.gen_range(0..=255);
|
||||
*val = rng.random_range(0..=255);
|
||||
}
|
||||
Value::Blob(b)
|
||||
}
|
||||
@@ -161,7 +161,7 @@ impl ArbitraryFrom<&SimValue> for LTValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct GTValue(pub(crate) SimValue);
|
||||
pub struct GTValue(pub SimValue);
|
||||
|
||||
impl ArbitraryFrom<&Vec<&SimValue>> for GTValue {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Self {
|
||||
@@ -178,21 +178,21 @@ impl ArbitraryFrom<&Vec<&SimValue>> for GTValue {
|
||||
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::Integer(i) => Value::Integer(rng.random_range(*i..i64::MAX)),
|
||||
Value::Float(f) => Value::Float(rng.random_range(*f..1e10)),
|
||||
value @ Value::Text(..) => {
|
||||
// Either lengthen the string, or make at least one character smaller and mutate the rest
|
||||
let mut t = value.to_string();
|
||||
if rng.gen_bool(0.01) {
|
||||
t.push(rng.gen_range(0..=255) as u8 as char);
|
||||
if rng.random_bool(0.01) {
|
||||
t.push(rng.random_range(0..=255) as u8 as char);
|
||||
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());
|
||||
let index = rng.random_range(0..t.len());
|
||||
t[index] += 1;
|
||||
// Mutate the rest of the string
|
||||
for val in t.iter_mut().skip(index + 1) {
|
||||
*val = rng.gen_range('a' as u32..='z' as u32);
|
||||
*val = rng.random_range('a' as u32..='z' as u32);
|
||||
}
|
||||
let t = t
|
||||
.into_iter()
|
||||
@@ -204,15 +204,15 @@ impl ArbitraryFrom<&SimValue> for GTValue {
|
||||
Value::Blob(b) => {
|
||||
// Either lengthen the blob, or make at least one byte smaller and mutate the rest
|
||||
let mut b = b.clone();
|
||||
if rng.gen_bool(0.01) {
|
||||
b.push(rng.gen_range(0..=255));
|
||||
if rng.random_bool(0.01) {
|
||||
b.push(rng.random_range(0..=255));
|
||||
Value::Blob(b)
|
||||
} else {
|
||||
let index = rng.gen_range(0..b.len());
|
||||
let index = rng.random_range(0..b.len());
|
||||
b[index] += 1;
|
||||
// Mutate the rest of the blob
|
||||
for val in b.iter_mut().skip(index + 1) {
|
||||
*val = rng.gen_range(0..=255);
|
||||
*val = rng.random_range(0..=255);
|
||||
}
|
||||
Value::Blob(b)
|
||||
}
|
||||
@@ -223,7 +223,7 @@ impl ArbitraryFrom<&SimValue> for GTValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct LikeValue(pub(crate) SimValue);
|
||||
pub struct LikeValue(pub SimValue);
|
||||
|
||||
impl ArbitraryFromMaybe<&SimValue> for LikeValue {
|
||||
fn arbitrary_from_maybe<R: Rng>(rng: &mut R, value: &SimValue) -> Option<Self> {
|
||||
@@ -235,18 +235,18 @@ impl ArbitraryFromMaybe<&SimValue> for LikeValue {
|
||||
// insert one `%` for the whole substring
|
||||
let mut i = 0;
|
||||
while i < t.len() {
|
||||
if rng.gen_bool(0.1) {
|
||||
if rng.random_bool(0.1) {
|
||||
t[i] = '_';
|
||||
} else if rng.gen_bool(0.05) {
|
||||
} else if rng.random_bool(0.05) {
|
||||
t[i] = '%';
|
||||
// skip a list of characters
|
||||
for _ in 0..rng.gen_range(0..=3.min(t.len() - i - 1)) {
|
||||
for _ in 0..rng.random_range(0..=3.min(t.len() - i - 1)) {
|
||||
t.remove(i + 1);
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
let index = rng.gen_range(0..t.len());
|
||||
let index = rng.random_range(0..t.len());
|
||||
t.insert(index, '%');
|
||||
Some(Self(SimValue(Value::build_text(
|
||||
t.into_iter().collect::<String>(),
|
||||
2
sql_generation/lib.rs
Normal file
2
sql_generation/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod generation;
|
||||
pub mod model;
|
||||
2
sql_generation/model/mod.rs
Normal file
2
sql_generation/model/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod query;
|
||||
pub mod table;
|
||||
25
sql_generation/model/query/create.rs
Normal file
25
sql_generation/model/query/create.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model::table::Table;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Create {
|
||||
pub table: Table,
|
||||
}
|
||||
|
||||
impl Display for Create {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "CREATE TABLE {} (", self.table.name)?;
|
||||
|
||||
for (i, column) in self.table.columns.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, ",")?;
|
||||
}
|
||||
write!(f, "{} {}", column.name, column.column_type)?;
|
||||
}
|
||||
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
25
sql_generation/model/query/create_index.rs
Normal file
25
sql_generation/model/query/create_index.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use turso_parser::ast::SortOrder;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct CreateIndex {
|
||||
pub index_name: String,
|
||||
pub table_name: String,
|
||||
pub columns: Vec<(String, SortOrder)>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CreateIndex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"CREATE INDEX {} ON {} ({})",
|
||||
self.index_name,
|
||||
self.table_name,
|
||||
self.columns
|
||||
.iter()
|
||||
.map(|(name, order)| format!("{name} {order}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
17
sql_generation/model/query/delete.rs
Normal file
17
sql_generation/model/query/delete.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::predicate::Predicate;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Delete {
|
||||
pub table: String,
|
||||
pub predicate: Predicate,
|
||||
}
|
||||
|
||||
impl Display for Delete {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "DELETE FROM {} WHERE {}", self.table, self.predicate)
|
||||
}
|
||||
}
|
||||
14
sql_generation/model/query/drop.rs
Normal file
14
sql_generation/model/query/drop.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Drop {
|
||||
pub table: String,
|
||||
}
|
||||
|
||||
impl Display for Drop {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "DROP TABLE {}", self.table)
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@ use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{generation::Shadow, model::table::SimValue, runner::env::SimulatorTables};
|
||||
use crate::model::table::SimValue;
|
||||
|
||||
use super::select::Select;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub(crate) enum Insert {
|
||||
pub enum Insert {
|
||||
Values {
|
||||
table: String,
|
||||
values: Vec<Vec<SimValue>>,
|
||||
@@ -18,40 +18,8 @@ pub(crate) enum Insert {
|
||||
},
|
||||
}
|
||||
|
||||
impl Shadow for Insert {
|
||||
type Result = anyhow::Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
fn shadow(&self, tables: &mut SimulatorTables) -> Self::Result {
|
||||
match self {
|
||||
Insert::Values { table, values } => {
|
||||
if let Some(t) = tables.tables.iter_mut().find(|t| &t.name == table) {
|
||||
t.rows.extend(values.clone());
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Table {} does not exist. INSERT statement ignored.",
|
||||
table
|
||||
));
|
||||
}
|
||||
}
|
||||
Insert::Select { table, select } => {
|
||||
let rows = select.shadow(tables)?;
|
||||
if let Some(t) = tables.tables.iter_mut().find(|t| &t.name == table) {
|
||||
t.rows.extend(rows);
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Table {} does not exist. INSERT statement ignored.",
|
||||
table
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl Insert {
|
||||
pub(crate) fn table(&self) -> &str {
|
||||
pub fn table(&self) -> &str {
|
||||
match self {
|
||||
Insert::Values { table, .. } | Insert::Select { table, .. } => table,
|
||||
}
|
||||
34
sql_generation/model/query/mod.rs
Normal file
34
sql_generation/model/query/mod.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
pub use create::Create;
|
||||
pub use create_index::CreateIndex;
|
||||
pub use delete::Delete;
|
||||
pub use drop::Drop;
|
||||
pub use insert::Insert;
|
||||
pub use select::Select;
|
||||
use turso_parser::ast::fmt::ToSqlContext;
|
||||
|
||||
pub mod create;
|
||||
pub mod create_index;
|
||||
pub mod delete;
|
||||
pub mod drop;
|
||||
pub mod insert;
|
||||
pub mod predicate;
|
||||
pub mod select;
|
||||
pub mod transaction;
|
||||
pub mod update;
|
||||
|
||||
/// Used to print sql strings that already have all the context it needs
|
||||
pub struct EmptyContext;
|
||||
|
||||
impl ToSqlContext for EmptyContext {
|
||||
fn get_column_name(
|
||||
&self,
|
||||
_table_id: turso_parser::ast::TableInternalId,
|
||||
_col_idx: usize,
|
||||
) -> String {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn get_table_name(&self, _id: turso_parser::ast::TableInternalId) -> &str {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use turso_sqlite3_parser::ast::{self, fmt::ToTokens};
|
||||
use turso_parser::ast::{self, fmt::ToTokens};
|
||||
|
||||
use crate::model::table::{SimValue, Table, TableContext};
|
||||
|
||||
@@ -9,27 +9,28 @@ use crate::model::table::{SimValue, Table, TableContext};
|
||||
pub struct Predicate(pub ast::Expr);
|
||||
|
||||
impl Predicate {
|
||||
pub(crate) fn true_() -> Self {
|
||||
pub fn true_() -> Self {
|
||||
Self(ast::Expr::Literal(ast::Literal::Keyword(
|
||||
"TRUE".to_string(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn false_() -> Self {
|
||||
pub fn false_() -> Self {
|
||||
Self(ast::Expr::Literal(ast::Literal::Keyword(
|
||||
"FALSE".to_string(),
|
||||
)))
|
||||
}
|
||||
pub(crate) fn null() -> Self {
|
||||
pub fn null() -> Self {
|
||||
Self(ast::Expr::Literal(ast::Literal::Null))
|
||||
}
|
||||
|
||||
pub(crate) fn not(predicate: Predicate) -> Self {
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn not(predicate: Predicate) -> Self {
|
||||
let expr = ast::Expr::Unary(ast::UnaryOperator::Not, Box::new(predicate.0));
|
||||
Self(expr).parens()
|
||||
}
|
||||
|
||||
pub(crate) fn and(predicates: Vec<Predicate>) -> Self {
|
||||
pub fn and(predicates: Vec<Predicate>) -> Self {
|
||||
if predicates.is_empty() {
|
||||
Self::true_()
|
||||
} else if predicates.len() == 1 {
|
||||
@@ -44,7 +45,7 @@ impl Predicate {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn or(predicates: Vec<Predicate>) -> Self {
|
||||
pub fn or(predicates: Vec<Predicate>) -> Self {
|
||||
if predicates.is_empty() {
|
||||
Self::false_()
|
||||
} else if predicates.len() == 1 {
|
||||
@@ -59,26 +60,26 @@ impl Predicate {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn eq(lhs: Predicate, rhs: Predicate) -> Self {
|
||||
pub fn eq(lhs: Predicate, rhs: Predicate) -> Self {
|
||||
let expr = ast::Expr::Binary(Box::new(lhs.0), ast::Operator::Equals, Box::new(rhs.0));
|
||||
Self(expr).parens()
|
||||
}
|
||||
|
||||
pub(crate) fn is(lhs: Predicate, rhs: Predicate) -> Self {
|
||||
pub fn is(lhs: Predicate, rhs: Predicate) -> Self {
|
||||
let expr = ast::Expr::Binary(Box::new(lhs.0), ast::Operator::Is, Box::new(rhs.0));
|
||||
Self(expr).parens()
|
||||
}
|
||||
|
||||
pub(crate) fn parens(self) -> Self {
|
||||
pub fn parens(self) -> Self {
|
||||
let expr = ast::Expr::Parenthesized(vec![self.0]);
|
||||
Self(expr)
|
||||
}
|
||||
|
||||
pub(crate) fn eval(&self, row: &[SimValue], table: &Table) -> Option<SimValue> {
|
||||
pub fn eval(&self, row: &[SimValue], table: &Table) -> Option<SimValue> {
|
||||
expr_to_value(&self.0, row, table)
|
||||
}
|
||||
|
||||
pub(crate) fn test<T: TableContext>(&self, row: &[SimValue], table: &T) -> bool {
|
||||
pub fn test<T: TableContext>(&self, row: &[SimValue], table: &T) -> bool {
|
||||
let value = expr_to_value(&self.0, row, table);
|
||||
value.is_some_and(|value| value.as_bool())
|
||||
}
|
||||
378
sql_generation/model/query/select.rs
Normal file
378
sql_generation/model/query/select.rs
Normal file
@@ -0,0 +1,378 @@
|
||||
use std::{collections::HashSet, fmt::Display};
|
||||
|
||||
pub use ast::Distinctness;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use turso_parser::ast::{self, fmt::ToTokens, SortOrder};
|
||||
|
||||
use crate::model::{
|
||||
query::EmptyContext,
|
||||
table::{JoinTable, JoinType, JoinedTable, Table},
|
||||
};
|
||||
|
||||
use super::predicate::Predicate;
|
||||
|
||||
/// `SELECT` or `RETURNING` result column
|
||||
// https://sqlite.org/syntax/result-column.html
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum ResultColumn {
|
||||
/// expression
|
||||
Expr(Predicate),
|
||||
/// `*`
|
||||
Star,
|
||||
/// column name
|
||||
Column(String),
|
||||
}
|
||||
|
||||
impl Display for ResultColumn {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ResultColumn::Expr(expr) => write!(f, "({expr})"),
|
||||
ResultColumn::Star => write!(f, "*"),
|
||||
ResultColumn::Column(name) => write!(f, "{name}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Select {
|
||||
pub body: SelectBody,
|
||||
pub limit: Option<usize>,
|
||||
}
|
||||
|
||||
impl Select {
|
||||
pub fn simple(table: String, where_clause: Predicate) -> Self {
|
||||
Self::single(
|
||||
table,
|
||||
vec![ResultColumn::Star],
|
||||
where_clause,
|
||||
None,
|
||||
Distinctness::All,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn expr(expr: Predicate) -> Self {
|
||||
Select {
|
||||
body: SelectBody {
|
||||
select: Box::new(SelectInner {
|
||||
distinctness: Distinctness::All,
|
||||
columns: vec![ResultColumn::Expr(expr)],
|
||||
from: None,
|
||||
where_clause: Predicate::true_(),
|
||||
order_by: None,
|
||||
}),
|
||||
compounds: Vec::new(),
|
||||
},
|
||||
limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn single(
|
||||
table: String,
|
||||
result_columns: Vec<ResultColumn>,
|
||||
where_clause: Predicate,
|
||||
limit: Option<usize>,
|
||||
distinct: Distinctness,
|
||||
) -> Self {
|
||||
Select {
|
||||
body: SelectBody {
|
||||
select: Box::new(SelectInner {
|
||||
distinctness: distinct,
|
||||
columns: result_columns,
|
||||
from: Some(FromClause {
|
||||
table,
|
||||
joins: Vec::new(),
|
||||
}),
|
||||
where_clause,
|
||||
order_by: None,
|
||||
}),
|
||||
compounds: Vec::new(),
|
||||
},
|
||||
limit,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compound(left: Select, right: Select, operator: CompoundOperator) -> Self {
|
||||
let mut body = left.body;
|
||||
body.compounds.push(CompoundSelect {
|
||||
operator,
|
||||
select: Box::new(right.body.select.as_ref().clone()),
|
||||
});
|
||||
Select {
|
||||
body,
|
||||
limit: left.limit.or(right.limit),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dependencies(&self) -> HashSet<String> {
|
||||
if self.body.select.from.is_none() {
|
||||
return HashSet::new();
|
||||
}
|
||||
let from = self.body.select.from.as_ref().unwrap();
|
||||
let mut tables = HashSet::new();
|
||||
tables.insert(from.table.clone());
|
||||
|
||||
tables.extend(from.dependencies());
|
||||
|
||||
for compound in &self.body.compounds {
|
||||
tables.extend(
|
||||
compound
|
||||
.select
|
||||
.from
|
||||
.as_ref()
|
||||
.map(|f| f.dependencies())
|
||||
.unwrap_or(vec![])
|
||||
.into_iter(),
|
||||
);
|
||||
}
|
||||
|
||||
tables
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SelectBody {
|
||||
/// first select
|
||||
pub select: Box<SelectInner>,
|
||||
/// compounds
|
||||
pub compounds: Vec<CompoundSelect>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct OrderBy {
|
||||
pub columns: Vec<(String, SortOrder)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SelectInner {
|
||||
/// `DISTINCT`
|
||||
pub distinctness: Distinctness,
|
||||
/// columns
|
||||
pub columns: Vec<ResultColumn>,
|
||||
/// `FROM` clause
|
||||
pub from: Option<FromClause>,
|
||||
/// `WHERE` clause
|
||||
pub where_clause: Predicate,
|
||||
/// `ORDER BY` clause
|
||||
pub order_by: Option<OrderBy>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum CompoundOperator {
|
||||
/// `UNION`
|
||||
Union,
|
||||
/// `UNION ALL`
|
||||
UnionAll,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CompoundSelect {
|
||||
/// operator
|
||||
pub operator: CompoundOperator,
|
||||
/// select
|
||||
pub select: Box<SelectInner>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct FromClause {
|
||||
/// table
|
||||
pub table: String,
|
||||
/// `JOIN`ed tables
|
||||
pub joins: Vec<JoinedTable>,
|
||||
}
|
||||
|
||||
impl FromClause {
|
||||
fn to_sql_ast(&self) -> ast::FromClause {
|
||||
ast::FromClause {
|
||||
select: Box::new(ast::SelectTable::Table(
|
||||
ast::QualifiedName::single(ast::Name::new(&self.table)),
|
||||
None,
|
||||
None,
|
||||
)),
|
||||
joins: self
|
||||
.joins
|
||||
.iter()
|
||||
.map(|join| ast::JoinedSelectTable {
|
||||
operator: match join.join_type {
|
||||
JoinType::Inner => ast::JoinOperator::TypedJoin(Some(ast::JoinType::INNER)),
|
||||
JoinType::Left => ast::JoinOperator::TypedJoin(Some(ast::JoinType::LEFT)),
|
||||
JoinType::Right => ast::JoinOperator::TypedJoin(Some(ast::JoinType::RIGHT)),
|
||||
JoinType::Full => ast::JoinOperator::TypedJoin(Some(ast::JoinType::OUTER)),
|
||||
JoinType::Cross => ast::JoinOperator::TypedJoin(Some(ast::JoinType::CROSS)),
|
||||
},
|
||||
table: Box::new(ast::SelectTable::Table(
|
||||
ast::QualifiedName::single(ast::Name::new(&join.table)),
|
||||
None,
|
||||
None,
|
||||
)),
|
||||
constraint: Some(ast::JoinConstraint::On(Box::new(join.on.0.clone()))),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dependencies(&self) -> Vec<String> {
|
||||
let mut deps = vec![self.table.clone()];
|
||||
for join in &self.joins {
|
||||
deps.push(join.table.clone());
|
||||
}
|
||||
deps
|
||||
}
|
||||
|
||||
pub fn into_join_table(&self, tables: &[Table]) -> JoinTable {
|
||||
let first_table = tables
|
||||
.iter()
|
||||
.find(|t| t.name == self.table)
|
||||
.expect("Table not found");
|
||||
|
||||
let mut join_table = JoinTable {
|
||||
tables: vec![first_table.clone()],
|
||||
rows: Vec::new(),
|
||||
};
|
||||
|
||||
for join in &self.joins {
|
||||
let joined_table = tables
|
||||
.iter()
|
||||
.find(|t| t.name == join.table)
|
||||
.expect("Joined table not found");
|
||||
|
||||
join_table.tables.push(joined_table.clone());
|
||||
|
||||
match join.join_type {
|
||||
JoinType::Inner => {
|
||||
// Implement inner join logic
|
||||
let join_rows = joined_table
|
||||
.rows
|
||||
.iter()
|
||||
.filter(|row| join.on.test(row, joined_table))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
// take a cartesian product of the rows
|
||||
let all_row_pairs = join_table
|
||||
.rows
|
||||
.clone()
|
||||
.into_iter()
|
||||
.cartesian_product(join_rows.iter());
|
||||
|
||||
for (row1, row2) in all_row_pairs {
|
||||
let row = row1.iter().chain(row2.iter()).cloned().collect::<Vec<_>>();
|
||||
|
||||
let is_in = join.on.test(&row, &join_table);
|
||||
|
||||
if is_in {
|
||||
join_table.rows.push(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
join_table
|
||||
}
|
||||
}
|
||||
|
||||
impl Select {
|
||||
pub fn to_sql_ast(&self) -> ast::Select {
|
||||
ast::Select {
|
||||
with: None,
|
||||
body: ast::SelectBody {
|
||||
select: ast::OneSelect::Select {
|
||||
distinctness: if self.body.select.distinctness == Distinctness::Distinct {
|
||||
Some(ast::Distinctness::Distinct)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
columns: self
|
||||
.body
|
||||
.select
|
||||
.columns
|
||||
.iter()
|
||||
.map(|col| match col {
|
||||
ResultColumn::Expr(expr) => {
|
||||
ast::ResultColumn::Expr(expr.0.clone().into_boxed(), None)
|
||||
}
|
||||
ResultColumn::Star => ast::ResultColumn::Star,
|
||||
ResultColumn::Column(name) => ast::ResultColumn::Expr(
|
||||
ast::Expr::Id(ast::Name::Ident(name.clone())).into_boxed(),
|
||||
None,
|
||||
),
|
||||
})
|
||||
.collect(),
|
||||
from: self.body.select.from.as_ref().map(|f| f.to_sql_ast()),
|
||||
where_clause: Some(self.body.select.where_clause.0.clone().into_boxed()),
|
||||
group_by: None,
|
||||
window_clause: Vec::new(),
|
||||
},
|
||||
compounds: self
|
||||
.body
|
||||
.compounds
|
||||
.iter()
|
||||
.map(|compound| ast::CompoundSelect {
|
||||
operator: match compound.operator {
|
||||
CompoundOperator::Union => ast::CompoundOperator::Union,
|
||||
CompoundOperator::UnionAll => ast::CompoundOperator::UnionAll,
|
||||
},
|
||||
select: ast::OneSelect::Select {
|
||||
distinctness: Some(compound.select.distinctness),
|
||||
columns: compound
|
||||
.select
|
||||
.columns
|
||||
.iter()
|
||||
.map(|col| match col {
|
||||
ResultColumn::Expr(expr) => {
|
||||
ast::ResultColumn::Expr(expr.0.clone().into_boxed(), None)
|
||||
}
|
||||
ResultColumn::Star => ast::ResultColumn::Star,
|
||||
ResultColumn::Column(name) => ast::ResultColumn::Expr(
|
||||
ast::Expr::Id(ast::Name::Ident(name.clone())).into_boxed(),
|
||||
None,
|
||||
),
|
||||
})
|
||||
.collect(),
|
||||
from: compound.select.from.as_ref().map(|f| f.to_sql_ast()),
|
||||
where_clause: Some(compound.select.where_clause.0.clone().into_boxed()),
|
||||
group_by: None,
|
||||
window_clause: Vec::new(),
|
||||
},
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
order_by: self
|
||||
.body
|
||||
.select
|
||||
.order_by
|
||||
.as_ref()
|
||||
.map(|o| {
|
||||
o.columns
|
||||
.iter()
|
||||
.map(|(name, order)| ast::SortedColumn {
|
||||
expr: ast::Expr::Id(ast::Name::Ident(name.clone())).into_boxed(),
|
||||
order: match order {
|
||||
SortOrder::Asc => Some(ast::SortOrder::Asc),
|
||||
SortOrder::Desc => Some(ast::SortOrder::Desc),
|
||||
},
|
||||
nulls: None,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
limit: self.limit.map(|l| ast::Limit {
|
||||
expr: ast::Expr::Literal(ast::Literal::Numeric(l.to_string())).into_boxed(),
|
||||
offset: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Select {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.to_sql_ast().to_fmt_with_context(f, &EmptyContext {})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod select_tests {
|
||||
|
||||
#[test]
|
||||
fn test_select_display() {}
|
||||
}
|
||||
32
sql_generation/model/query/transaction.rs
Normal file
32
sql_generation/model/query/transaction.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Begin {
|
||||
pub immediate: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Commit;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Rollback;
|
||||
|
||||
impl Display for Begin {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "BEGIN {}", if self.immediate { "IMMEDIATE" } else { "" })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Commit {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "COMMIT")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Rollback {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "ROLLBACK")
|
||||
}
|
||||
}
|
||||
34
sql_generation/model/query/update.rs
Normal file
34
sql_generation/model/query/update.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model::table::SimValue;
|
||||
|
||||
use super::predicate::Predicate;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Update {
|
||||
pub table: String,
|
||||
pub set_values: Vec<(String, SimValue)>, // Pair of value for set expressions => SET name=value
|
||||
pub predicate: Predicate,
|
||||
}
|
||||
|
||||
impl Update {
|
||||
pub fn table(&self) -> &str {
|
||||
&self.table
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Update {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "UPDATE {} SET ", self.table)?;
|
||||
for (i, (name, value)) in self.set_values.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{name} = {value}")?;
|
||||
}
|
||||
write!(f, " WHERE {}", self.predicate)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,11 @@ use std::{fmt::Display, hash::Hash, ops::Deref};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use turso_core::{numeric::Numeric, types};
|
||||
use turso_sqlite3_parser::ast;
|
||||
use turso_parser::ast;
|
||||
|
||||
use crate::model::query::predicate::Predicate;
|
||||
|
||||
pub(crate) struct Name(pub(crate) String);
|
||||
pub struct Name(pub String);
|
||||
|
||||
impl Deref for Name {
|
||||
type Target = str;
|
||||
@@ -41,11 +41,11 @@ impl TableContext for Table {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct Table {
|
||||
pub(crate) name: String,
|
||||
pub(crate) columns: Vec<Column>,
|
||||
pub(crate) rows: Vec<Vec<SimValue>>,
|
||||
pub(crate) indexes: Vec<String>,
|
||||
pub struct Table {
|
||||
pub name: String,
|
||||
pub columns: Vec<Column>,
|
||||
pub rows: Vec<Vec<SimValue>>,
|
||||
pub indexes: Vec<String>,
|
||||
}
|
||||
|
||||
impl Table {
|
||||
@@ -60,11 +60,11 @@ impl Table {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct Column {
|
||||
pub(crate) name: String,
|
||||
pub(crate) column_type: ColumnType,
|
||||
pub(crate) primary: bool,
|
||||
pub(crate) unique: bool,
|
||||
pub struct Column {
|
||||
pub name: String,
|
||||
pub column_type: ColumnType,
|
||||
pub primary: bool,
|
||||
pub unique: bool,
|
||||
}
|
||||
|
||||
// Uniquely defined by name in this case
|
||||
@@ -83,7 +83,7 @@ impl PartialEq for Column {
|
||||
impl Eq for Column {}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) enum ColumnType {
|
||||
pub enum ColumnType {
|
||||
Integer,
|
||||
Float,
|
||||
Text,
|
||||
@@ -136,23 +136,8 @@ pub struct JoinTable {
|
||||
pub rows: Vec<Vec<SimValue>>,
|
||||
}
|
||||
|
||||
fn float_to_string<S>(float: &f64, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&format!("{float}"))
|
||||
}
|
||||
|
||||
fn string_to_float<'de, D>(deserializer: D) -> Result<f64, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
s.parse().map_err(serde::de::Error::custom)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)]
|
||||
pub(crate) struct SimValue(pub turso_core::Value);
|
||||
pub struct SimValue(pub turso_core::Value);
|
||||
|
||||
fn to_sqlite_blob(bytes: &[u8]) -> String {
|
||||
format!(
|
||||
Reference in New Issue
Block a user