Skip to content

Commit

Permalink
fix(sql): run PRAGMA incremental_vacuum on a write connection
Browse files Browse the repository at this point in the history
Otherwise it always fails with SQLITE_READONLY:
```
WARNING src/sql.rs:769: Failed to run incremental vacuum: attempt to write a readonly database: Error code 8: Attempt to write a readonly database.
```
  • Loading branch information
link2xt committed Oct 22, 2024
1 parent 839b0e9 commit a55e33f
Showing 1 changed file with 42 additions and 18 deletions.
60 changes: 42 additions & 18 deletions src/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,36 @@ fn new_connection(path: &Path, passphrase: &str) -> Result<Connection> {
Ok(conn)
}

// Tries to clear the freelist to free some space on the disk.
//
// This only works if auto_vacuum is enabled.
async fn incremental_vacuum(context: &Context) -> Result<()> {
context
.sql
.call_write(move |conn| {
let mut stmt = conn
.prepare("PRAGMA incremental_vacuum")
.context("Failed to prepare incremental_vacuum statement")?;

// It is important to step the statement until it returns no more rows.
// Otherwise it will not free as many pages as it can:
// <https://stackoverflow.com/questions/53746807/sqlite-incremental-vacuum-removing-only-one-free-page>.
let mut rows = stmt
.query(())
.context("Failed to run incremental_vacuum statement")?;
let mut row_count = 0;
while let Some(_row) = rows
.next()
.context("Failed to step incremental_vacuum statement")?
{
row_count += 1;
}
info!(context, "Incremental vacuum freed {row_count} pages.");
Ok(())
})
.await
}

/// Cleanup the account to restore some storage and optimize the database.
pub async fn housekeeping(context: &Context) -> Result<()> {
// Setting `Config::LastHousekeeping` at the beginning avoids endless loops when things do not
Expand Down Expand Up @@ -713,24 +743,8 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
);
}

// Try to clear the freelist to free some space on the disk. This
// only works if auto_vacuum is enabled.
match context
.sql
.query_row_optional("PRAGMA incremental_vacuum", (), |_row| Ok(()))
.await
{
Err(err) => {
warn!(context, "Failed to run incremental vacuum: {err:#}.");
}
Ok(Some(())) => {
// Incremental vacuum returns a zero-column result if it did anything.
info!(context, "Successfully ran incremental vacuum.");
}
Ok(None) => {
// Incremental vacuum returned `SQLITE_DONE` immediately,
// there were no pages to remove.
}
if let Err(err) = incremental_vacuum(context).await {
warn!(context, "Failed to run incremental vacuum: {err:#}.");
}

context
Expand Down Expand Up @@ -1370,4 +1384,14 @@ mod tests {

Ok(())
}

/// Tests that incremental_vacuum does not fail.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_incremental_vacuum() -> Result<()> {
let t = TestContext::new().await;

incremental_vacuum(&t).await?;

Ok(())
}
}

0 comments on commit a55e33f

Please sign in to comment.