tests/fuzz: add compound_select_fuzz()

This commit is contained in:
Jussi Saurio 2025-05-24 12:46:04 +03:00
parent f6443ae742
commit 8ed5334ca7
2 changed files with 88 additions and 1 deletions

View file

@ -280,7 +280,7 @@ fn parse_from_clause_table<'a>(
let Plan::Select(mut subplan) =
prepare_select_plan(schema, *subselect, syms, Some(scope))?
else {
unreachable!();
crate::bail_parse_error!("Only non-compound SELECT queries are currently supported in FROM clause subqueries");
};
subplan.query_type = SelectQueryType::Subquery {
yield_reg: usize::MAX, // will be set later in bytecode emission

View file

@ -417,6 +417,93 @@ mod tests {
}
}
#[test]
pub fn compound_select_fuzz() {
let _ = env_logger::try_init();
let (mut rng, seed) = rng_from_time();
log::info!("compound_select_fuzz seed: {}", seed);
// Constants for fuzzing parameters
const MAX_TABLES: usize = 5;
const MIN_TABLES: usize = 1;
const MAX_ROWS_PER_TABLE: usize = 15;
const MIN_ROWS_PER_TABLE: usize = 5;
const NUM_FUZZ_ITERATIONS: usize = 1000;
// How many more SELECTs than tables can be in a UNION (e.g., if 2 tables, max 2+2=4 SELECTs)
const MAX_SELECTS_IN_UNION_EXTRA: usize = 2;
const MAX_LIMIT_VALUE: usize = 50;
let db = TempDatabase::new_empty();
let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
let mut table_names = Vec::new();
let num_tables = rng.random_range(MIN_TABLES..=MAX_TABLES);
for i in 0..num_tables {
let table_name = format!("t{}", i);
// Schema: c1 INTEGER, c2 INTEGER, c3 INTEGER for simplicity and UNION ALL compatibility
let create_table_sql = format!(
"CREATE TABLE {} (c1 INTEGER, c2 INTEGER, c3 INTEGER)",
table_name
);
limbo_exec_rows(&db, &limbo_conn, &create_table_sql);
sqlite_exec_rows(&sqlite_conn, &create_table_sql);
let num_rows_to_insert = rng.random_range(MIN_ROWS_PER_TABLE..=MAX_ROWS_PER_TABLE);
for _ in 0..num_rows_to_insert {
let c1_val: i64 = rng.random_range(-1000..1000);
let c2_val: i64 = rng.random_range(-1000..1000);
let c3_val: i64 = rng.random_range(-1000..1000);
let insert_sql = format!(
"INSERT INTO {} VALUES ({}, {}, {})",
table_name, c1_val, c2_val, c3_val
);
limbo_exec_rows(&db, &limbo_conn, &insert_sql);
sqlite_exec_rows(&sqlite_conn, &insert_sql);
}
table_names.push(table_name);
}
for iter_num in 0..NUM_FUZZ_ITERATIONS {
// Number of SELECT clauses to be UNION ALL'd
let num_selects_in_union =
rng.random_range(1..=(table_names.len() + MAX_SELECTS_IN_UNION_EXTRA));
let mut select_statements = Vec::new();
for _ in 0..num_selects_in_union {
// Randomly pick a table
let table_to_select_from = &table_names[rng.random_range(0..table_names.len())];
select_statements.push(format!("SELECT c1, c2, c3 FROM {}", table_to_select_from));
}
let mut query = select_statements.join(" UNION ALL ");
if rng.random_bool(0.8) {
let limit_val = rng.random_range(0..=MAX_LIMIT_VALUE); // LIMIT 0 is valid
query = format!("{} LIMIT {}", query, limit_val);
}
log::debug!(
"Iteration {}/{}: Query: {}",
iter_num + 1,
NUM_FUZZ_ITERATIONS,
query
);
let limbo_results = limbo_exec_rows(&db, &limbo_conn, &query);
let sqlite_results = sqlite_exec_rows(&sqlite_conn, &query);
assert_eq!(
limbo_results, sqlite_results,
"query: {}, limbo: {:?}, sqlite: {:?}, seed: {}",
query, limbo_results, sqlite_results, seed
);
}
}
#[test]
pub fn arithmetic_expression_fuzz() {
let _ = env_logger::try_init();