use rudb::Database;
use rudb_cli::Settings;
use rudb_cli::format::render;
const SQL: &str = include_str!("../../rudb/testdata/clickbench.sql");
const ANSWERS: &str = include_str!("../../rudb/testdata/clickbench-answers.txt");
const FIXTURE: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../rudb/testdata/hits.parquet");
struct Answer {
name: String,
mode: String,
rows: usize,
names: Vec<String>,
types: Vec<String>,
body: Vec<Vec<String>>,
}
fn queries() -> Vec<(String, String)> {
let mut found = Vec::new();
let mut name = String::new();
let mut sql = String::new();
for line in SQL.lines() {
let line = line.trim();
if let Some(rest) = line.strip_prefix("--") {
let rest = rest.trim();
if rest.starts_with('q') && rest[1..].chars().all(|c| c.is_ascii_digit()) {
name = rest.to_string();
}
continue;
}
if line.is_empty() {
continue;
}
if !sql.is_empty() {
sql.push(' ');
}
sql.push_str(line);
if let Some(statement) = sql.strip_suffix(';') {
if !name.is_empty() {
found.push((name.clone(), statement.to_string()));
}
sql.clear();
}
}
found
}
fn answers() -> Vec<Answer> {
let mut found: Vec<Answer> = Vec::new();
for line in ANSWERS.lines() {
if let Some(rest) = line.strip_prefix("-- q") {
let mut words = rest.split_whitespace();
let number = words.next().expect("a query number");
let mode = words.next().expect("a mode").to_string();
let rows = words.next().expect("a row count").parse().expect("a number");
found.push(Answer {
name: format!("q{number}"),
mode,
rows,
names: Vec::new(),
types: Vec::new(),
body: Vec::new(),
});
continue;
}
if line.starts_with("--") || line.is_empty() {
continue;
}
let cells: Vec<String> = line.split('\t').map(str::to_string).collect();
let answer = found.last_mut().expect("a row before any query was named");
if answer.names.is_empty() {
answer.names = cells;
} else if answer.types.is_empty() {
answer.types = cells;
} else {
answer.body.push(cells);
}
}
found
}
fn cells(rendered: &str) -> Vec<Vec<String>> {
let mut rows: Vec<Vec<String>> = Vec::new();
for line in rendered.lines() {
let Some(inner) = line.strip_prefix('│') else { continue };
let inner = inner.strip_suffix('│').unwrap_or(inner);
rows.push(inner.split('│').map(|cell| cell.trim().to_string()).collect());
}
rows
}
fn loaded() -> Database {
let database = Database::new();
let sql = format!("CREATE TABLE hits AS SELECT * FROM '{FIXTURE}'");
database.execute(&sql).expect("the benchmark fixture loads");
assert_eq!(database.table_len("hits").expect("hits exists"), 10_000);
database
}
#[test]
fn the_answers_line_up_with_the_queries() {
let queries = queries();
let answers = answers();
assert_eq!(queries.len(), 43, "the query file does not hold forty three queries");
let asked: Vec<&str> = queries.iter().map(|(name, _)| name.as_str()).collect();
let answered: Vec<&str> = answers.iter().map(|answer| answer.name.as_str()).collect();
assert_eq!(asked, answered, "the answers file is not the queries file, in order");
}
#[test]
fn every_query_answers_what_duckdb_answers() {
let database = loaded();
let answers = answers();
let mut wrong = Vec::new();
for ((name, sql), answer) in queries().into_iter().zip(&answers) {
let result = match database.query(&sql) {
Ok(result) => result,
Err(error) => {
wrong.push(format!("{name}: {error}"));
continue;
}
};
let got = cells(&render(&result, &Settings::default()));
let (head, body) = got.split_at(2.min(got.len()));
if head.len() != 2 {
wrong.push(format!("{name}: no table came out at all"));
continue;
}
if head[0] != answer.names {
wrong.push(format!(
"{name}: columns are {:?} and duckdb calls them {:?}",
head[0], answer.names
));
continue;
}
if head[1] != answer.types {
wrong.push(format!(
"{name}: types are {:?} and duckdb says {:?}",
head[1], answer.types
));
continue;
}
if body.len() != answer.rows {
wrong.push(format!("{name}: {} rows where duckdb has {}", body.len(), answer.rows));
continue;
}
match answer.mode.as_str() {
"exact" => {
if body != answer.body {
wrong.push(format!(
"{name}: rows are {:?} and duckdb has {:?}",
body, answer.body
));
}
}
"last" => {
let last: Vec<Vec<String>> =
body.iter().map(|row| vec![row.last().cloned().unwrap_or_default()]).collect();
if last != answer.body {
wrong.push(format!(
"{name}: the ordered column is {:?} and duckdb has {:?}",
last, answer.body
));
}
}
"count" => {}
other => wrong.push(format!("{name}: the answers file says mode {other}")),
}
}
assert!(
wrong.is_empty(),
"queries that do not answer what duckdb answers:\n{}",
wrong.join("\n")
);
}