use std::hint::black_box;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use vibesql_executor::SelectExecutor;
use vibesql_parser::Parser;
use vibesql_storage::Database;
fn parse_select(sql: &str) -> vibesql_ast::SelectStmt {
match Parser::parse_sql(sql) {
Ok(vibesql_ast::Statement::Select(select_stmt)) => *select_stmt,
_ => panic!("Failed to parse SELECT statement: {}", sql),
}
}
fn setup_large_table(db: &mut Database, row_count: usize) {
let schema = vibesql_catalog::TableSchema::new(
"LARGE_TABLE".to_string(),
vec![
vibesql_catalog::ColumnSchema {
name: "ID".to_string(),
data_type: vibesql_types::DataType::Integer,
nullable: false,
default_value: None,
generated_expr: None,
is_exact_integer_type: false,
collation: None,
},
vibesql_catalog::ColumnSchema {
name: "VALUE".to_string(),
data_type: vibesql_types::DataType::Varchar { max_length: Some(50) },
nullable: true,
default_value: None,
generated_expr: None,
is_exact_integer_type: false,
collation: None,
},
vibesql_catalog::ColumnSchema {
name: "AMOUNT".to_string(),
data_type: vibesql_types::DataType::Integer,
nullable: true,
default_value: None,
generated_expr: None,
is_exact_integer_type: false,
collation: None,
},
],
);
db.create_table(schema).unwrap();
for i in 0..row_count {
let row = vibesql_storage::Row::new(vec![
vibesql_types::SqlValue::Integer(i as i64),
vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from(format!("row_{}", i))),
vibesql_types::SqlValue::Integer((i % 100) as i64),
]);
db.insert_row("LARGE_TABLE", row).unwrap();
}
}
fn bench_limit_early_termination(c: &mut Criterion) {
let mut group = c.benchmark_group("limit_early_termination");
for row_count in [1000, 10_000, 100_000] {
let mut db = Database::new();
setup_large_table(&mut db, row_count);
group.throughput(Throughput::Elements(10));
group.bench_with_input(
BenchmarkId::new("iterator_with_limit", row_count),
&row_count,
|b, _| {
b.iter(|| {
let stmt = parse_select("SELECT * FROM large_table LIMIT 10;");
let executor = SelectExecutor::new(&db);
black_box(executor.execute(&stmt).unwrap())
});
},
);
group.bench_with_input(
BenchmarkId::new("materialized_full_scan", row_count),
&row_count,
|b, _| {
b.iter(|| {
let stmt = parse_select("SELECT * FROM large_table;");
let executor = SelectExecutor::new(&db);
black_box(executor.execute(&stmt).unwrap())
});
},
);
}
group.finish();
}
fn bench_where_with_limit(c: &mut Criterion) {
let mut group = c.benchmark_group("where_with_limit");
for row_count in [1000, 10_000, 100_000] {
let mut db = Database::new();
setup_large_table(&mut db, row_count);
group.throughput(Throughput::Elements(10));
group.bench_with_input(
BenchmarkId::new("iterator_where_limit", row_count),
&row_count,
|b, _| {
b.iter(|| {
let stmt =
parse_select("SELECT * FROM large_table WHERE amount > 50 LIMIT 10;");
let executor = SelectExecutor::new(&db);
black_box(executor.execute(&stmt).unwrap())
});
},
);
group.bench_with_input(
BenchmarkId::new("materialized_where_no_limit", row_count),
&row_count,
|b, _| {
b.iter(|| {
let stmt = parse_select("SELECT * FROM large_table WHERE amount > 50;");
let executor = SelectExecutor::new(&db);
black_box(executor.execute(&stmt).unwrap())
});
},
);
}
group.finish();
}
fn bench_offset_with_limit(c: &mut Criterion) {
let mut group = c.benchmark_group("offset_with_limit");
for row_count in [1000, 10_000, 100_000] {
let mut db = Database::new();
setup_large_table(&mut db, row_count);
group.throughput(Throughput::Elements(10));
group.bench_with_input(
BenchmarkId::new("iterator_offset_limit", row_count),
&row_count,
|b, &count| {
b.iter(|| {
let stmt = parse_select(&format!(
"SELECT * FROM large_table OFFSET {} LIMIT 10;",
count / 2
));
let executor = SelectExecutor::new(&db);
black_box(executor.execute(&stmt).unwrap())
});
},
);
group.bench_with_input(
BenchmarkId::new("materialized_full_scan", row_count),
&row_count,
|b, _| {
b.iter(|| {
let stmt = parse_select("SELECT * FROM large_table;");
let executor = SelectExecutor::new(&db);
black_box(executor.execute(&stmt).unwrap())
});
},
);
}
group.finish();
}
fn bench_projection_with_limit(c: &mut Criterion) {
let mut group = c.benchmark_group("projection_with_limit");
for row_count in [1000, 10_000, 100_000] {
let mut db = Database::new();
setup_large_table(&mut db, row_count);
group.throughput(Throughput::Elements(10));
group.bench_with_input(
BenchmarkId::new("iterator_projection_limit", row_count),
&row_count,
|b, _| {
b.iter(|| {
let stmt = parse_select(
"SELECT id * 2, amount + 100, value FROM large_table LIMIT 10;",
);
let executor = SelectExecutor::new(&db);
black_box(executor.execute(&stmt).unwrap())
});
},
);
group.bench_with_input(
BenchmarkId::new("materialized_projection_no_limit", row_count),
&row_count,
|b, _| {
b.iter(|| {
let stmt = parse_select("SELECT id * 2, amount + 100, value FROM large_table;");
let executor = SelectExecutor::new(&db);
black_box(executor.execute(&stmt).unwrap())
});
},
);
}
group.finish();
}
fn bench_materialized_queries(c: &mut Criterion) {
let mut group = c.benchmark_group("materialized_queries");
for row_count in [1000, 10_000] {
let mut db = Database::new();
setup_large_table(&mut db, row_count);
group.throughput(Throughput::Elements(row_count as u64));
group.bench_with_input(BenchmarkId::new("order_by", row_count), &row_count, |b, _| {
b.iter(|| {
let stmt = parse_select("SELECT * FROM large_table ORDER BY amount;");
let executor = SelectExecutor::new(&db);
black_box(executor.execute(&stmt).unwrap())
});
});
group.bench_with_input(BenchmarkId::new("distinct", row_count), &row_count, |b, _| {
b.iter(|| {
let stmt = parse_select("SELECT DISTINCT amount FROM large_table;");
let executor = SelectExecutor::new(&db);
black_box(executor.execute(&stmt).unwrap())
});
});
}
group.finish();
}
criterion_group!(
benches,
bench_limit_early_termination,
bench_where_with_limit,
bench_offset_with_limit,
bench_projection_with_limit,
bench_materialized_queries,
);
criterion_main!(benches);