mirror of
https://github.com/chatmail/core.git
synced 2026-05-02 21:06:31 +03:00
fix(sql): run PRAGMA incremental_vacuum on a write connection
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. ```
This commit is contained in:
58
src/sql.rs
58
src/sql.rs
@@ -681,6 +681,36 @@ fn new_connection(path: &Path, passphrase: &str) -> Result<Connection> {
|
|||||||
Ok(conn)
|
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.
|
/// Cleanup the account to restore some storage and optimize the database.
|
||||||
pub async fn housekeeping(context: &Context) -> Result<()> {
|
pub async fn housekeeping(context: &Context) -> Result<()> {
|
||||||
// Setting `Config::LastHousekeeping` at the beginning avoids endless loops when things do not
|
// Setting `Config::LastHousekeeping` at the beginning avoids endless loops when things do not
|
||||||
@@ -713,25 +743,9 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to clear the freelist to free some space on the disk. This
|
if let Err(err) = incremental_vacuum(context).await {
|
||||||
// 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:#}.");
|
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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
@@ -1370,4 +1384,14 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user