use libsql::Builder;
async fn rowids(conn: &libsql::Connection, table: &str, col: &str) -> Vec<i64> {
let mut rows = conn
.query(&format!("SELECT {col} FROM {table} ORDER BY {col}"), ())
.await
.unwrap();
let mut v = Vec::new();
while let Some(r) = rows.next().await.unwrap() {
v.push(r.get(0).unwrap());
}
v
}
async fn scalar(conn: &libsql::Connection, sql: &str) -> i64 {
conn.query(sql, ())
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap()
.get(0)
.unwrap()
}
#[tokio::main]
async fn main() {
let dir = std::env::temp_dir().join(format!("vacuum_rowid_probe_{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let cases: &[(&str, &str, &str)] = &[
(
"one column, implicit rowid",
"one_col",
"id TEXT PRIMARY KEY",
),
(
"several columns, implicit rowid",
"multi_col",
"id TEXT PRIMARY KEY, a TEXT, b INTEGER",
),
("no primary key at all", "no_pk", "id TEXT NOT NULL, a TEXT"),
(
"UNIQUE but no primary key",
"uniq_no_pk",
"id TEXT UNIQUE, a TEXT",
),
(
"no constraints, one secondary index",
"sec_index",
"id TEXT NOT NULL, a TEXT",
),
(
"explicit INTEGER PRIMARY KEY (the v8 shape)",
"explicit_pk",
"rowid_pk INTEGER PRIMARY KEY, id TEXT NOT NULL UNIQUE",
),
];
for (label, table, cols) in cases {
let path = dir.join(format!("{table}.db"));
let db = Builder::new_local(&path).build().await.unwrap();
let conn = db.connect().unwrap();
conn.execute(&format!("CREATE TABLE {table} ({cols})"), ())
.await
.unwrap();
if *table == "sec_index" {
conn.execute(&format!("CREATE INDEX ix_{table} ON {table} (a)"), ())
.await
.unwrap();
}
for pk in [1i64, 2, 3, 4, 5, 9, 100, 1_000_000] {
let sql = if *table == "explicit_pk" {
format!("INSERT INTO {table} (rowid_pk, id) VALUES (?1, ?2)")
} else {
format!("INSERT INTO {table} (rowid, id) VALUES (?1, ?2)")
};
conn.execute(&sql, libsql::params![pk, format!("c{pk:07}")])
.await
.unwrap();
}
conn.execute(&format!("DELETE FROM {table} WHERE rowid IN (2, 3)"), ())
.await
.unwrap();
let col = if *table == "explicit_pk" {
"rowid_pk"
} else {
"rowid"
};
let before = rowids(&conn, table, col).await;
let pages_before = scalar(&conn, "PRAGMA page_count").await;
let free_before = scalar(&conn, "PRAGMA freelist_count").await;
conn.execute("VACUUM", ()).await.unwrap();
let after = rowids(&conn, table, col).await;
let pages_after = scalar(&conn, "PRAGMA page_count").await;
println!("{label}");
println!(" before: {before:?} ({pages_before} pages, {free_before} free)");
println!(" after: {after:?} ({pages_after} pages)");
println!(
" -> VACUUM {}\n",
if before == after {
"PRESERVED the numbering"
} else {
"RENUMBERED"
}
);
}
let _ = std::fs::remove_dir_all(&dir);
}