diff --git a/Cargo.toml b/Cargo.toml index b7ed7ac2cb..30cd74c937 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -366,6 +366,24 @@ path = "benches/sqlite/describe.rs" harness = false required-features = ["sqlite"] +[[bench]] +name = "sqlite-connection" +path = "benches/sqlite/connection.rs" +harness = false +required-features = ["sqlite", "runtime-tokio"] + +[[bench]] +name = "postgres-connection" +path = "benches/postgres/connection.rs" +harness = false +required-features = ["postgres", "runtime-tokio"] + +[[bench]] +name = "mysql-connection" +path = "benches/mysql/connection.rs" +harness = false +required-features = ["mysql-rsa", "runtime-tokio"] + # # MySQL # diff --git a/benches/mysql/connection.rs b/benches/mysql/connection.rs new file mode 100644 index 0000000000..53eaf5b8f4 --- /dev/null +++ b/benches/mysql/connection.rs @@ -0,0 +1,144 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use sqlx::mysql::{MySqlConnection, MySqlPoolOptions}; +use sqlx::{Connection, Executor}; +use std::cell::RefCell; + +const LARGE_RESULT_ROWS: i64 = 10_000; + +fn db_url() -> String { + dotenvy::var("DATABASE_URL").expect("DATABASE_URL must be set") +} + +async fn setup_conn() -> MySqlConnection { + let mut conn = MySqlConnection::connect(&db_url()).await.unwrap(); + + conn.execute( + "CREATE TEMPORARY TABLE bench_data ( + id BIGINT NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + data BLOB NOT NULL, + val1 BIGINT NOT NULL, + val2 DOUBLE NOT NULL + )", + ) + .await + .unwrap(); + + // MySQL's recursive CTE default limit is 1000; raise it for this session. + conn.execute("SET cte_max_recursion_depth = 10000") + .await + .unwrap(); + + sqlx::query( + "INSERT INTO bench_data (id, name, data, val1, val2) + WITH RECURSIVE gen(n) AS ( + SELECT 1 + UNION ALL + SELECT n + 1 FROM gen WHERE n < ? + ) + SELECT n, CONCAT('row_', n), UNHEX(LPAD('', 32, '0')), n, n FROM gen", + ) + .bind(LARGE_RESULT_ROWS) + .execute(&mut conn) + .await + .unwrap(); + + conn +} + +// ── async helpers ──────────────────────────────────────────────────────────── + +async fn do_ping(conn: &RefCell) { + conn.borrow_mut().ping().await.unwrap(); +} + +async fn do_query_small(conn: &RefCell) { + let mut guard = conn.borrow_mut(); + let _: (i64, String, Vec, i64, f64) = + sqlx::query_as("SELECT id, name, data, val1, val2 FROM bench_data WHERE id = 1") + .fetch_one(&mut *guard) + .await + .unwrap(); +} + +async fn do_query_large(conn: &RefCell) { + let mut guard = conn.borrow_mut(); + let _: Vec<(i64, String, Vec, i64, f64)> = + sqlx::query_as("SELECT id, name, data, val1, val2 FROM bench_data") + .fetch_all(&mut *guard) + .await + .unwrap(); +} + +async fn do_pool_checkout(pool: &sqlx::MySqlPool) { + let _conn = pool.acquire().await.unwrap(); +} + +// ── criterion functions ─────────────────────────────────────────────────────── + +fn bench_new_connection(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let url = db_url(); + c.bench_function("new_connection", |b| { + b.to_async(&runtime).iter(|| async { + drop(MySqlConnection::connect(&url).await.unwrap()); + }); + }); +} + +fn bench_pool_checkout(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let pool = runtime + .block_on( + MySqlPoolOptions::new() + .min_connections(1) + .max_connections(1) + .connect(&db_url()), + ) + .unwrap(); + + c.bench_function("pool_checkout", |b| { + b.to_async(&runtime).iter(|| do_pool_checkout(&pool)); + }); +} + +fn bench_ping(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let conn = RefCell::new( + runtime + .block_on(MySqlConnection::connect(&db_url())) + .unwrap(), + ); + + c.bench_function("ping", |b| { + b.to_async(&runtime).iter(|| do_ping(&conn)); + }); +} + +fn bench_query_small(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let conn = RefCell::new(runtime.block_on(setup_conn())); + + c.bench_function("query_small_result", |b| { + b.to_async(&runtime).iter(|| do_query_small(&conn)); + }); +} + +fn bench_query_large(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let conn = RefCell::new(runtime.block_on(setup_conn())); + + c.bench_function("query_large_result", |b| { + b.to_async(&runtime).iter(|| do_query_large(&conn)); + }); +} + +criterion_group!( + benches, + bench_new_connection, + bench_pool_checkout, + bench_ping, + bench_query_small, + bench_query_large, +); +criterion_main!(benches); diff --git a/benches/postgres/connection.rs b/benches/postgres/connection.rs new file mode 100644 index 0000000000..b786ec885e --- /dev/null +++ b/benches/postgres/connection.rs @@ -0,0 +1,136 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use sqlx::postgres::{PgConnection, PgPoolOptions}; +use sqlx::{Connection, Executor}; +use std::cell::RefCell; + +const LARGE_RESULT_ROWS: i64 = 10_000; + +fn db_url() -> String { + dotenvy::var("DATABASE_URL").expect("DATABASE_URL must be set") +} + +async fn setup_conn() -> PgConnection { + let mut conn = PgConnection::connect(&db_url()).await.unwrap(); + + conn.execute( + "CREATE TEMP TABLE bench_data ( + id BIGINT PRIMARY KEY, + name TEXT NOT NULL, + data BYTEA NOT NULL, + val1 BIGINT NOT NULL, + val2 FLOAT8 NOT NULL + )", + ) + .await + .unwrap(); + + sqlx::query( + "INSERT INTO bench_data (id, name, data, val1, val2) + SELECT + n, + 'row_' || n, + decode(lpad('', 32, '0'), 'hex'), + n, + n::float8 + FROM generate_series(1, $1) AS n", + ) + .bind(LARGE_RESULT_ROWS) + .execute(&mut conn) + .await + .unwrap(); + + conn +} + +// ── async helpers ──────────────────────────────────────────────────────────── + +async fn do_ping(conn: &RefCell) { + conn.borrow_mut().ping().await.unwrap(); +} + +async fn do_query_small(conn: &RefCell) { + let mut guard = conn.borrow_mut(); + let _: (i64, String, Vec, i64, f64) = + sqlx::query_as("SELECT id, name, data, val1, val2 FROM bench_data WHERE id = 1") + .fetch_one(&mut *guard) + .await + .unwrap(); +} + +async fn do_query_large(conn: &RefCell) { + let mut guard = conn.borrow_mut(); + let _: Vec<(i64, String, Vec, i64, f64)> = + sqlx::query_as("SELECT id, name, data, val1, val2 FROM bench_data") + .fetch_all(&mut *guard) + .await + .unwrap(); +} + +async fn do_pool_checkout(pool: &sqlx::PgPool) { + let _conn = pool.acquire().await.unwrap(); +} + +// ── criterion functions ─────────────────────────────────────────────────────── + +fn bench_new_connection(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let url = db_url(); + c.bench_function("new_connection", |b| { + b.to_async(&runtime).iter(|| async { + drop(PgConnection::connect(&url).await.unwrap()); + }); + }); +} + +fn bench_pool_checkout(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let pool = runtime + .block_on( + PgPoolOptions::new() + .min_connections(1) + .max_connections(1) + .connect(&db_url()), + ) + .unwrap(); + + c.bench_function("pool_checkout", |b| { + b.to_async(&runtime).iter(|| do_pool_checkout(&pool)); + }); +} + +fn bench_ping(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let conn = RefCell::new(runtime.block_on(PgConnection::connect(&db_url())).unwrap()); + + c.bench_function("ping", |b| { + b.to_async(&runtime).iter(|| do_ping(&conn)); + }); +} + +fn bench_query_small(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let conn = RefCell::new(runtime.block_on(setup_conn())); + + c.bench_function("query_small_result", |b| { + b.to_async(&runtime).iter(|| do_query_small(&conn)); + }); +} + +fn bench_query_large(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let conn = RefCell::new(runtime.block_on(setup_conn())); + + c.bench_function("query_large_result", |b| { + b.to_async(&runtime).iter(|| do_query_large(&conn)); + }); +} + +criterion_group!( + benches, + bench_new_connection, + bench_pool_checkout, + bench_ping, + bench_query_small, + bench_query_large, +); +criterion_main!(benches); diff --git a/benches/sqlite/connection.rs b/benches/sqlite/connection.rs new file mode 100644 index 0000000000..7caf7dda2f --- /dev/null +++ b/benches/sqlite/connection.rs @@ -0,0 +1,132 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use sqlx::sqlite::{SqliteConnection, SqlitePoolOptions}; +use sqlx::{Connection, Executor}; +use std::cell::RefCell; + +const DB_URL: &str = "sqlite::memory:"; +const LARGE_RESULT_ROWS: i64 = 10_000; + +async fn setup_conn() -> SqliteConnection { + let mut conn = SqliteConnection::connect(DB_URL).await.unwrap(); + + conn.execute( + "CREATE TEMP TABLE bench_data ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + data BLOB NOT NULL, + val1 INTEGER NOT NULL, + val2 REAL NOT NULL + )", + ) + .await + .unwrap(); + + // Populate with LARGE_RESULT_ROWS rows using a recursive CTE. + sqlx::query( + "INSERT INTO bench_data (id, name, data, val1, val2) + WITH RECURSIVE gen(n) AS ( + VALUES(1) + UNION ALL + SELECT n + 1 FROM gen WHERE n < ? + ) + SELECT n, 'row_' || n, zeroblob(16), n, CAST(n AS REAL) FROM gen", + ) + .bind(LARGE_RESULT_ROWS) + .execute(&mut conn) + .await + .unwrap(); + + conn +} + +// ── async helpers ──────────────────────────────────────────────────────────── + +async fn do_ping(conn: &RefCell) { + conn.borrow_mut().ping().await.unwrap(); +} + +async fn do_query_small(conn: &RefCell) { + let mut guard = conn.borrow_mut(); + let _: (i64, String, Vec, i64, f64) = + sqlx::query_as("SELECT id, name, data, val1, val2 FROM bench_data WHERE id = 1") + .fetch_one(&mut *guard) + .await + .unwrap(); +} + +async fn do_query_large(conn: &RefCell) { + let mut guard = conn.borrow_mut(); + let _: Vec<(i64, String, Vec, i64, f64)> = + sqlx::query_as("SELECT id, name, data, val1, val2 FROM bench_data") + .fetch_all(&mut *guard) + .await + .unwrap(); +} + +async fn do_pool_checkout(pool: &sqlx::SqlitePool) { + let _conn = pool.acquire().await.unwrap(); +} + +// ── criterion functions ─────────────────────────────────────────────────────── + +fn bench_new_connection(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + c.bench_function("new_connection", |b| { + b.to_async(&runtime).iter(|| async { + drop(SqliteConnection::connect(DB_URL).await.unwrap()); + }); + }); +} + +fn bench_pool_checkout(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let pool = runtime + .block_on( + SqlitePoolOptions::new() + .min_connections(1) + .max_connections(1) + .connect(DB_URL), + ) + .unwrap(); + + c.bench_function("pool_checkout", |b| { + b.to_async(&runtime).iter(|| do_pool_checkout(&pool)); + }); +} + +fn bench_ping(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let conn = RefCell::new(runtime.block_on(SqliteConnection::connect(DB_URL)).unwrap()); + + c.bench_function("ping", |b| { + b.to_async(&runtime).iter(|| do_ping(&conn)); + }); +} + +fn bench_query_small(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let conn = RefCell::new(runtime.block_on(setup_conn())); + + c.bench_function("query_small_result", |b| { + b.to_async(&runtime).iter(|| do_query_small(&conn)); + }); +} + +fn bench_query_large(c: &mut Criterion) { + let runtime = tokio::runtime::Runtime::new().unwrap(); + let conn = RefCell::new(runtime.block_on(setup_conn())); + + c.bench_function("query_large_result", |b| { + b.to_async(&runtime).iter(|| do_query_large(&conn)); + }); +} + +criterion_group!( + benches, + bench_new_connection, + bench_pool_checkout, + bench_ping, + bench_query_small, + bench_query_large, +); +criterion_main!(benches);