mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-27 21:14:21 +01:00
Move result row to `ProgramState` to mimic what SQLite does where `Vdbe` struct has a `pResultRow` member. This makes it easier to deal with result lifetime, but more importantly, eventually lazily parse values at the edges of the API.
281 lines
9.1 KiB
Rust
281 lines
9.1 KiB
Rust
pub mod grammar_generator;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::rc::Rc;
|
|
|
|
use rand::SeedableRng;
|
|
use rand_chacha::ChaCha8Rng;
|
|
use rusqlite::params;
|
|
|
|
use crate::{
|
|
common::TempDatabase,
|
|
fuzz::grammar_generator::{rand_int, rand_str, GrammarGenerator},
|
|
};
|
|
|
|
fn rng_from_time() -> (ChaCha8Rng, u64) {
|
|
let seed = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_secs();
|
|
let rng = ChaCha8Rng::seed_from_u64(seed);
|
|
(rng, seed)
|
|
}
|
|
|
|
fn sqlite_exec_row(conn: &rusqlite::Connection, query: &str) -> Vec<rusqlite::types::Value> {
|
|
let mut stmt = conn.prepare(&query).unwrap();
|
|
let mut rows = stmt.query(params![]).unwrap();
|
|
let mut columns = Vec::new();
|
|
let row = rows.next().unwrap().unwrap();
|
|
for i in 0.. {
|
|
let column: rusqlite::types::Value = match row.get(i) {
|
|
Ok(column) => column,
|
|
Err(rusqlite::Error::InvalidColumnIndex(_)) => break,
|
|
Err(err) => panic!("unexpected rusqlite error: {}", err),
|
|
};
|
|
columns.push(column);
|
|
}
|
|
assert!(rows.next().unwrap().is_none());
|
|
|
|
columns
|
|
}
|
|
|
|
fn limbo_exec_row(
|
|
conn: &Rc<limbo_core::Connection>,
|
|
query: &str,
|
|
) -> Vec<rusqlite::types::Value> {
|
|
let mut stmt = conn.prepare(query).unwrap();
|
|
let result = stmt.step().unwrap();
|
|
let row = loop {
|
|
match result {
|
|
limbo_core::StepResult::Row => {
|
|
let row = stmt.row().unwrap();
|
|
break row;
|
|
}
|
|
limbo_core::StepResult::IO => continue,
|
|
r => panic!("unexpected result {:?}: expecting single row", r),
|
|
}
|
|
};
|
|
row.values
|
|
.iter()
|
|
.map(|x| match x.to_value() {
|
|
limbo_core::Value::Null => rusqlite::types::Value::Null,
|
|
limbo_core::Value::Integer(x) => rusqlite::types::Value::Integer(x),
|
|
limbo_core::Value::Float(x) => rusqlite::types::Value::Real(x),
|
|
limbo_core::Value::Text(x) => rusqlite::types::Value::Text(x.to_string()),
|
|
limbo_core::Value::Blob(x) => rusqlite::types::Value::Blob(x.to_vec()),
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
pub fn arithmetic_expression_fuzz_ex1() {
|
|
let db = TempDatabase::new_empty();
|
|
let limbo_conn = db.connect_limbo();
|
|
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
|
|
|
|
for query in [
|
|
"SELECT ~1 >> 1536",
|
|
"SELECT ~ + 3 << - ~ (~ (8)) - + -1 - 3 >> 3 + -6 * (-7 * 9 >> - 2)",
|
|
] {
|
|
let limbo = limbo_exec_row(&limbo_conn, query);
|
|
let sqlite = sqlite_exec_row(&sqlite_conn, query);
|
|
assert_eq!(
|
|
limbo, sqlite,
|
|
"query: {}, limbo: {:?}, sqlite: {:?}",
|
|
query, limbo, sqlite
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
pub fn arithmetic_expression_fuzz() {
|
|
let _ = env_logger::try_init();
|
|
let g = GrammarGenerator::new();
|
|
let (expr, expr_builder) = g.create_handle();
|
|
let (bin_op, bin_op_builder) = g.create_handle();
|
|
let (unary_op, unary_op_builder) = g.create_handle();
|
|
let (paren, paren_builder) = g.create_handle();
|
|
|
|
paren_builder
|
|
.concat("")
|
|
.push_str("(")
|
|
.push(expr)
|
|
.push_str(")")
|
|
.build();
|
|
|
|
unary_op_builder
|
|
.concat(" ")
|
|
.push(g.create().choice().options_str(["~", "+", "-"]).build())
|
|
.push(expr)
|
|
.build();
|
|
|
|
bin_op_builder
|
|
.concat(" ")
|
|
.push(expr)
|
|
.push(
|
|
g.create()
|
|
.choice()
|
|
.options_str(["+", "-", "*", "/", "%", "&", "|", "<<", ">>"])
|
|
.build(),
|
|
)
|
|
.push(expr)
|
|
.build();
|
|
|
|
expr_builder
|
|
.choice()
|
|
.option_w(unary_op, 1.0)
|
|
.option_w(bin_op, 1.0)
|
|
.option_w(paren, 1.0)
|
|
.option_symbol_w(rand_int(-10..10), 1.0)
|
|
.build();
|
|
|
|
let sql = g.create().concat(" ").push_str("SELECT").push(expr).build();
|
|
|
|
let db = TempDatabase::new_empty();
|
|
let limbo_conn = db.connect_limbo();
|
|
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
|
|
|
|
let (mut rng, seed) = rng_from_time();
|
|
log::info!("seed: {}", seed);
|
|
for _ in 0..1024 {
|
|
let query = g.generate(&mut rng, sql, 50);
|
|
let limbo = limbo_exec_row(&limbo_conn, &query);
|
|
let sqlite = sqlite_exec_row(&sqlite_conn, &query);
|
|
assert_eq!(
|
|
limbo, sqlite,
|
|
"query: {}, limbo: {:?}, sqlite: {:?}",
|
|
query, limbo, sqlite
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
pub fn logical_expression_fuzz_ex1() {
|
|
let _ = env_logger::try_init();
|
|
let db = TempDatabase::new_empty();
|
|
let limbo_conn = db.connect_limbo();
|
|
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
|
|
|
|
for query in [
|
|
"SELECT FALSE",
|
|
"SELECT NOT FALSE",
|
|
"SELECT ((NULL) IS NOT TRUE <= ((NOT (FALSE))))",
|
|
"SELECT ifnull(0, NOT 0)",
|
|
"SELECT like('a%', 'a') = 1",
|
|
] {
|
|
let limbo = limbo_exec_row(&limbo_conn, query);
|
|
let sqlite = sqlite_exec_row(&sqlite_conn, query);
|
|
assert_eq!(
|
|
limbo, sqlite,
|
|
"query: {}, limbo: {:?}, sqlite: {:?}",
|
|
query, limbo, sqlite
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
pub fn logical_expression_fuzz_run() {
|
|
let _ = env_logger::try_init();
|
|
let g = GrammarGenerator::new();
|
|
let (expr, expr_builder) = g.create_handle();
|
|
let (bin_op, bin_op_builder) = g.create_handle();
|
|
let (unary_infix_op, unary_infix_op_builder) = g.create_handle();
|
|
let (scalar, scalar_builder) = g.create_handle();
|
|
let (paren, paren_builder) = g.create_handle();
|
|
|
|
paren_builder
|
|
.concat("")
|
|
.push_str("(")
|
|
.push(expr)
|
|
.push_str(")")
|
|
.build();
|
|
|
|
unary_infix_op_builder
|
|
.concat(" ")
|
|
.push(g.create().choice().options_str(["NOT"]).build())
|
|
.push(expr)
|
|
.build();
|
|
|
|
bin_op_builder
|
|
.concat(" ")
|
|
.push(expr)
|
|
.push(
|
|
g.create()
|
|
.choice()
|
|
.options_str(["AND", "OR", "IS", "IS NOT", "=", "<>", ">", "<", ">=", "<="])
|
|
.build(),
|
|
)
|
|
.push(expr)
|
|
.build();
|
|
|
|
scalar_builder
|
|
.choice()
|
|
.option(
|
|
g.create()
|
|
.concat("")
|
|
.push_str("like('")
|
|
.push_symbol(rand_str("", 2))
|
|
.push_str("', '")
|
|
.push_symbol(rand_str("", 2))
|
|
.push_str("')")
|
|
.build(),
|
|
)
|
|
.option(
|
|
g.create()
|
|
.concat("")
|
|
.push_str("ifnull(")
|
|
.push(expr)
|
|
.push_str(",")
|
|
.push(expr)
|
|
.push_str(")")
|
|
.build(),
|
|
)
|
|
.option(
|
|
g.create()
|
|
.concat("")
|
|
.push_str("iif(")
|
|
.push(expr)
|
|
.push_str(",")
|
|
.push(expr)
|
|
.push_str(",")
|
|
.push(expr)
|
|
.push_str(")")
|
|
.build(),
|
|
)
|
|
.build();
|
|
|
|
expr_builder
|
|
.choice()
|
|
.option_w(unary_infix_op, 1.0)
|
|
.option_w(bin_op, 1.0)
|
|
.option_w(paren, 1.0)
|
|
.option_w(scalar, 1.0)
|
|
// unfortunatelly, sqlite behaves weirdly when IS operator is used with TRUE/FALSE constants
|
|
// e.g. 8 IS TRUE == 1 (although 8 = TRUE == 0)
|
|
// so, we do not use TRUE/FALSE constants as they will produce diff with sqlite results
|
|
.options_str(["1", "0", "NULL", "2.0", "1.5", "-0.5", "-2.0", "(1 / 0)"])
|
|
.build();
|
|
|
|
let sql = g.create().concat(" ").push_str("SELECT").push(expr).build();
|
|
|
|
let db = TempDatabase::new_empty();
|
|
let limbo_conn = db.connect_limbo();
|
|
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
|
|
|
|
let (mut rng, seed) = rng_from_time();
|
|
log::info!("seed: {}", seed);
|
|
for _ in 0..1024 {
|
|
let query = g.generate(&mut rng, sql, 50);
|
|
log::info!("query: {}", query);
|
|
let limbo = limbo_exec_row(&limbo_conn, &query);
|
|
let sqlite = sqlite_exec_row(&sqlite_conn, &query);
|
|
assert_eq!(
|
|
limbo, sqlite,
|
|
"query: {}, limbo: {:?}, sqlite: {:?}",
|
|
query, limbo, sqlite
|
|
);
|
|
}
|
|
}
|
|
}
|