use std::time::Instant;
use libsql::Connection;
const SIZES: &[usize] = &[2_000, 50_000, 500_000];
const ARCHIVE_ROWS: usize = 500_000;
const ARCHIVE_REMOVED: usize = 333_000;
const TS: &str = "2026-01-01T00:00:00.000000Z";
const OLD: &str = "SELECT COUNT(*), MIN(seq_id), MAX(seq_id) FROM transaction_log";
const NEW: &str = "SELECT rows_removed FROM log_integrity WHERE id = 1";
async fn open(dir: &tempfile::TempDir, name: &str) -> Connection {
let conn = libsql::Builder::new_local(dir.path().join(name))
.build()
.await
.unwrap()
.connect()
.unwrap();
macrame::schema::run_migrations(&conn).await.unwrap();
conn
}
async fn fill(conn: &Connection, rows: usize) {
conn.execute("BEGIN", ()).await.unwrap();
for i in 0..rows {
conn.execute(
"INSERT INTO concepts (id, title, valid_from, recorded_at) \
VALUES (?1, 't', ?2, ?2)",
libsql::params![format!("c{i}"), TS],
)
.await
.unwrap();
}
conn.execute("COMMIT", ()).await.unwrap();
}
async fn time(conn: &Connection, sql: &str, n: usize) -> (f64, f64) {
let mut best = f64::MAX;
let mut total = 0.0;
for _ in 0..n {
let start = Instant::now();
let mut rows = conn.query(sql, ()).await.unwrap();
let row = rows
.next()
.await
.unwrap()
.expect("the query returned no row");
let _: i64 = row.get(0).unwrap();
let ms = start.elapsed().as_secs_f64() * 1e3;
best = best.min(ms);
total += ms;
}
(best, total / n as f64)
}
async fn archive_session(dir: &tempfile::TempDir, name: &str, with_trigger: bool) -> f64 {
let conn = open(dir, name).await;
fill(&conn, ARCHIVE_ROWS).await;
if !with_trigger {
conn.execute("DROP TRIGGER trg_txlog_mark_gap", ())
.await
.unwrap();
}
conn.execute("CREATE TABLE macrame_archive_session (marker INTEGER)", ())
.await
.unwrap();
let start = Instant::now();
conn.execute(
"DELETE FROM transaction_log WHERE seq_id <= ?1",
libsql::params![ARCHIVE_REMOVED as i64],
)
.await
.unwrap();
let ms = start.elapsed().as_secs_f64() * 1e3;
conn.execute("DROP TABLE macrame_archive_session", ())
.await
.unwrap();
if with_trigger {
let bit: i64 = conn
.query(NEW, ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap();
assert_eq!(bit, 1, "the session deleted rows and the bit did not move");
}
ms
}
#[tokio::main]
async fn main() {
let dir = tempfile::tempdir().unwrap();
println!("== 1. the guard's intactness test, per log size ==");
println!(
"{:>10} {:>18} {:>18}",
"log rows", "v15 COUNT(*)", "v16 one row"
);
for &size in SIZES {
let conn = open(&dir, &format!("read{size}.db")).await;
fill(&conn, size).await;
let _ = time(&conn, OLD, 3).await;
let _ = time(&conn, NEW, 3).await;
let iterations = if size >= 500_000 { 20 } else { 200 };
let (old_best, old_mean) = time(&conn, OLD, iterations).await;
let (new_best, new_mean) = time(&conn, NEW, iterations).await;
println!(
"{size:>10} {old_best:>8.4} / {old_mean:<7.4} {new_best:>8.4} / {new_mean:<7.4}",
);
}
println!("(best / mean, ms)");
println!();
println!("== 2. what the per-row trigger costs one archive session ==");
let without = archive_session(&dir, "arch_without.db", false).await;
let with = archive_session(&dir, "arch_with.db", true).await;
println!("{ARCHIVE_REMOVED} of {ARCHIVE_ROWS} rows deleted in one statement:");
println!(" without trg_txlog_mark_gap: {without:.1} ms");
println!(" with trg_txlog_mark_gap: {with:.1} ms");
println!(
" delta: {:.1} ms, {:.3} us per row deleted",
with - without,
(with - without) * 1e3 / ARCHIVE_REMOVED as f64
);
}