use crate::model::{Query, QueryDiscriminants}; use rand::{ Rng, distr::{Distribution, weighted::WeightedIndex}, }; use sql_generation::{ generation::{Arbitrary, ArbitraryFrom, GenerationContext, query::SelectFree}, model::{ query::{Create, CreateIndex, Delete, Insert, Select, update::Update}, table::Table, }, }; use super::property::Remaining; fn random_create(rng: &mut R, conn_ctx: &impl GenerationContext) -> Query { let mut create = Create::arbitrary(rng, conn_ctx); while conn_ctx .tables() .iter() .any(|t| t.name == create.table.name) { create = Create::arbitrary(rng, conn_ctx); } Query::Create(create) } fn random_select(rng: &mut R, conn_ctx: &impl GenerationContext) -> Query { if rng.random_bool(0.7) { Query::Select(Select::arbitrary(rng, conn_ctx)) } else { // Random expression Query::Select(SelectFree::arbitrary(rng, conn_ctx).0) } } fn random_insert(rng: &mut R, conn_ctx: &impl GenerationContext) -> Query { assert!(!conn_ctx.tables().is_empty()); Query::Insert(Insert::arbitrary(rng, conn_ctx)) } fn random_delete(rng: &mut R, conn_ctx: &impl GenerationContext) -> Query { assert!(!conn_ctx.tables().is_empty()); Query::Delete(Delete::arbitrary(rng, conn_ctx)) } fn random_update(rng: &mut R, conn_ctx: &impl GenerationContext) -> Query { assert!(!conn_ctx.tables().is_empty()); Query::Update(Update::arbitrary(rng, conn_ctx)) } fn random_drop(rng: &mut R, conn_ctx: &impl GenerationContext) -> Query { assert!(!conn_ctx.tables().is_empty()); Query::Drop(sql_generation::model::query::Drop::arbitrary(rng, conn_ctx)) } fn random_create_index(rng: &mut R, conn_ctx: &impl GenerationContext) -> Query { assert!(!conn_ctx.tables().is_empty()); let mut create_index = CreateIndex::arbitrary(rng, conn_ctx); while conn_ctx .tables() .iter() .find(|t| t.name == create_index.table_name) .expect("table should exist") .indexes .iter() .any(|i| i == &create_index.index_name) { create_index = CreateIndex::arbitrary(rng, conn_ctx); } Query::CreateIndex(create_index) } /// Possible queries that can be generated given the table state /// /// Does not take into account transactional statements pub const fn possible_queries(tables: &[Table]) -> &'static [QueryDiscriminants] { if tables.is_empty() { &[QueryDiscriminants::Select, QueryDiscriminants::Create] } else { QueryDiscriminants::ALL_NO_TRANSACTION } } type QueryGenFunc = fn(&mut R, &G) -> Query; impl QueryDiscriminants { pub fn gen_function(&self) -> QueryGenFunc where R: rand::Rng, G: GenerationContext, { match self { QueryDiscriminants::Create => random_create, QueryDiscriminants::Select => random_select, QueryDiscriminants::Insert => random_insert, QueryDiscriminants::Delete => random_delete, QueryDiscriminants::Update => random_update, QueryDiscriminants::Drop => random_drop, QueryDiscriminants::CreateIndex => random_create_index, QueryDiscriminants::Begin | QueryDiscriminants::Commit | QueryDiscriminants::Rollback => { unreachable!("transactional queries should not be generated") } } } pub fn weight(&self, remaining: &Remaining) -> u32 { match self { QueryDiscriminants::Create => remaining.create, QueryDiscriminants::Select => remaining.select + remaining.select / 3, // remaining.select / 3 is for the random_expr generation QueryDiscriminants::Insert => remaining.insert, QueryDiscriminants::Delete => remaining.delete, QueryDiscriminants::Update => remaining.update, QueryDiscriminants::Drop => 0, QueryDiscriminants::CreateIndex => remaining.create_index, QueryDiscriminants::Begin | QueryDiscriminants::Commit | QueryDiscriminants::Rollback => { unreachable!("transactional queries should not be generated") } } } } impl ArbitraryFrom<&Remaining> for Query { fn arbitrary_from( rng: &mut R, context: &C, remaining: &Remaining, ) -> Self { let queries = possible_queries(context.tables()); let weights = WeightedIndex::new(queries.iter().map(|query| query.weight(remaining))).unwrap(); let idx = weights.sample(rng); let query_fn = queries[idx].gen_function(); let query = (query_fn)(rng, context); query } }