#[cfg(all(feature = "tokio-postgres", feature = "query"))]
#[tokio::main]
async fn main() -> drizzle::Result<()> {
use std::time::Instant;
use drizzle::core::expr::eq;
use drizzle::postgres::prelude::*;
use drizzle::postgres::tokio::Drizzle;
#[PostgresTable(name = "orders")]
struct Order {
#[column(serial, primary)]
id: i32,
ship_name: String,
}
#[PostgresTable(name = "order_details")]
struct Detail {
quantity: i32,
#[column(references = Order::id)]
order_id: i32,
}
#[derive(PostgresSchema)]
struct Schema {
order: Order,
detail: Detail,
}
const ADMIN_URL: &str = "host=localhost user=postgres password=postgres dbname=postgres";
const URL: &str = "host=localhost user=postgres password=postgres dbname=perf_check";
let (admin, conn) = tokio_postgres::connect(ADMIN_URL, tokio_postgres::NoTls)
.await
.expect("connect admin");
tokio::spawn(async move {
let _ = conn.await;
});
admin
.execute("DROP DATABASE IF EXISTS perf_check", &[])
.await
.expect("drop perf_check db");
admin
.execute("CREATE DATABASE perf_check", &[])
.await
.expect("create perf_check db");
let (client, conn) = tokio_postgres::connect(URL, tokio_postgres::NoTls)
.await
.expect("connect perf_check");
tokio::spawn(async move {
let _ = conn.await;
});
client
.batch_execute(
"CREATE TABLE orders (id serial PRIMARY KEY, ship_name text NOT NULL);
CREATE TABLE order_details (quantity int NOT NULL, order_id int NOT NULL REFERENCES orders(id));
INSERT INTO orders (ship_name) SELECT 'ship-' || g FROM generate_series(1, 50000) g;
INSERT INTO order_details (quantity, order_id)
SELECT (o.id + p) % 40 + 1, o.id FROM orders o, generate_series(1, 3) p;
CREATE INDEX idx_details_order ON order_details(order_id);
ANALYZE;",
)
.await
.expect("seed");
let (db, Schema { order, detail: _ }) = Drizzle::new(client, Schema::new());
const ITERS: i32 = 500;
let warm = |i: i32| (i % 50000) + 1;
for i in 0..10 {
let _rows: Vec<SelectOrder> = db
.select(())
.from(order)
.r#where(eq(order.id, warm(i)))
.all()
.await?;
}
let start = Instant::now();
for i in 0..ITERS {
let _rows: Vec<SelectOrder> = db
.select(())
.from(order)
.r#where(eq(order.id, warm(i)))
.all()
.await?;
}
let drizzle_us = start.elapsed().as_micros() as f64 / f64::from(ITERS);
let id_ph = order.id.placeholder("id");
let prepared = db
.select(())
.from(order)
.r#where(eq(order.id, id_ph))
.prepare()
.into_owned();
for i in 0..10 {
let _rows: Vec<SelectOrder> = prepared.all(db.conn(), [id_ph.bind(warm(i))]).await?;
}
let start = Instant::now();
for i in 0..ITERS {
let _rows: Vec<SelectOrder> = prepared.all(db.conn(), [id_ph.bind(warm(i))]).await?;
}
let drizzle_prepared_us = start.elapsed().as_micros() as f64 / f64::from(ITERS);
let raw_sql = "SELECT id, ship_name FROM orders WHERE id = $1";
let stmt = db.conn().prepare(raw_sql).await.expect("prepare");
let start = Instant::now();
for i in 0..ITERS {
let _rows = db
.conn()
.query(&stmt, &[&warm(i)])
.await
.expect("raw prepared");
}
let raw_prepared_us = start.elapsed().as_micros() as f64 / f64::from(ITERS);
let start = Instant::now();
for i in 0..ITERS {
let _rows = db
.conn()
.query(raw_sql, &[&warm(i)])
.await
.expect("raw text");
}
let raw_text_us = start.elapsed().as_micros() as f64 / f64::from(ITERS);
println!("by-id select, mean per query over {ITERS} iters:");
println!(" drizzle builder (cached): {drizzle_us:8.1} us");
println!(" drizzle prepared stmt: {drizzle_prepared_us:8.1} us");
println!(" raw prepared: {raw_prepared_us:8.1} us");
println!(" raw one-shot text: {raw_text_us:8.1} us");
for page in 0..50u32 {
let _rows: Vec<SelectOrder> = db
.select(())
.from(order)
.order_by([asc(order.id)])
.limit(50)
.offset(page as usize * 50)
.all()
.await?;
}
let row = db
.conn()
.query_one(
"SELECT count(*) FROM pg_prepared_statements WHERE statement LIKE '%LIMIT%'",
&[],
)
.await
.expect("pg_prepared_statements");
let cached: i64 = row.get(0);
println!(
"paginated select over 50 distinct offsets -> {cached} prepared LIMIT statement(s) on the connection"
);
for _ in 0..3 {
let _ = db
.query(order)
.with(order.details())
.order_by(asc(order.id))
.limit(50)
.offset(25_000)
.find_many()
.await?;
}
const QITERS: u32 = 20;
let start = Instant::now();
let mut last_len = 0;
for _ in 0..QITERS {
let rows = db
.query(order)
.with(order.details())
.order_by(asc(order.id))
.limit(50)
.offset(25_000)
.find_many()
.await?;
last_len = rows.len();
}
let query_ms = start.elapsed().as_secs_f64() * 1000.0 / f64::from(QITERS);
println!(
"relational find_many (LIMIT 50 OFFSET 25000, 50k orders x3 details): {query_ms:.2} ms/query ({last_len} rows)"
);
Ok(())
}
#[cfg(not(all(feature = "tokio-postgres", feature = "query")))]
fn main() {
eprintln!("requires --features \"tokio-postgres,query\"");
}