use std::hint::black_box;
use std::sync::{Arc, RwLock};
use alopex_core::kv::memory::MemoryKV;
use alopex_sql::ast::Statement;
use alopex_sql::catalog::MemoryCatalog;
use alopex_sql::dialect::AlopexDialect;
use alopex_sql::executor::Executor;
use alopex_sql::parser::Parser;
use alopex_sql::planner::Planner;
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
struct Schema {
catalog: Arc<RwLock<MemoryCatalog>>,
}
impl Schema {
fn new(ddl: &str) -> Self {
let store = Arc::new(MemoryKV::new());
let catalog = Arc::new(RwLock::new(MemoryCatalog::new()));
let mut executor = Executor::new(store, Arc::clone(&catalog));
for stmt in Parser::parse_sql(&AlopexDialect, ddl).expect("parse ddl") {
let plan = {
let guard = catalog.read().unwrap();
Planner::new(&*guard).plan(&stmt).expect("plan ddl")
};
executor.execute(plan).expect("execute ddl");
}
Self { catalog }
}
fn parse(&self, sql: &str) -> Vec<Statement> {
Parser::parse_sql(&AlopexDialect, sql).expect("parse query")
}
fn plan(&self, stmts: &[Statement]) {
let guard = self.catalog.read().unwrap();
for stmt in stmts {
black_box(Planner::new(&*guard).plan(stmt).expect("plan query"));
}
}
}
fn wide_table_ddl(name: &str, column_count: usize) -> String {
let columns = (0..column_count)
.map(|i| format!("c{i} INT"))
.collect::<Vec<_>>()
.join(", ");
format!("CREATE TABLE {name} ({columns});")
}
fn bench_wide_schema(c: &mut Criterion) {
let mut group = c.benchmark_group("resolve/wide_schema");
for &width in &[50usize, 100, 200, 400, 800] {
let schema = Schema::new(&wide_table_ddl("wide", width));
let projection = (0..width)
.map(|i| format!("c{i}"))
.collect::<Vec<_>>()
.join(", ");
let sql = format!("SELECT {projection} FROM wide;");
let parsed = schema.parse(&sql);
group.bench_with_input(BenchmarkId::from_parameter(width), &parsed, |b, parsed| {
b.iter(|| schema.plan(parsed));
});
}
group.finish();
}
fn bench_nesting_depth(c: &mut Criterion) {
let mut ddl = String::new();
for i in 0..16 {
ddl.push_str(&format!("CREATE TABLE t{i} (id INT, v{i} INT);\n"));
}
let schema = Schema::new(&ddl);
let mut group = c.benchmark_group("resolve/nesting_depth");
for &depth in &[2usize, 4, 8, 12] {
let mut sql = String::from("SELECT t0.v0 FROM t0 WHERE t0.id > 0");
for level in 1..depth {
sql.push_str(&format!(
" AND t0.id IN (SELECT t{level}.id FROM t{level} WHERE t{level}.id = t0.id"
));
}
for _ in 1..depth {
sql.push(')');
}
sql.push(';');
let parsed = schema.parse(&sql);
group.bench_with_input(BenchmarkId::from_parameter(depth), &parsed, |b, parsed| {
b.iter(|| schema.plan(parsed));
});
}
group.finish();
}
fn bench_natural_join(c: &mut Criterion) {
let mut group = c.benchmark_group("resolve/natural_join");
for &width in &[25usize, 50, 100, 200] {
let columns = (0..width)
.map(|i| format!("c{i} INT"))
.collect::<Vec<_>>()
.join(", ");
let ddl = format!("CREATE TABLE l ({columns}); CREATE TABLE r ({columns});");
let schema = Schema::new(&ddl);
let parsed = schema.parse("SELECT c0 FROM l NATURAL JOIN r;");
group.bench_with_input(BenchmarkId::from_parameter(width), &parsed, |b, parsed| {
b.iter(|| schema.plan(parsed));
});
}
group.finish();
}
fn bench_single_table(c: &mut Criterion) {
let schema = Schema::new(&wide_table_ddl("wide", 16));
let parsed = schema.parse("SELECT c0, c1, c2 FROM wide WHERE c0 = 1;");
c.bench_function("resolve/single_table", |b| {
b.iter(|| schema.plan(&parsed));
});
}
criterion_group!(
benches,
bench_wide_schema,
bench_nesting_depth,
bench_natural_join,
bench_single_table
);
criterion_main!(benches);