use std::path::{Path, PathBuf};
use std::time::Duration;
use rusqlite::Connection;
use super::context::StorageContext;
use super::migrations;
use crate::error::Result;
const DB_SUBDIR: &str = "session_db";
const DB_FILE: &str = "sessions.db";
const BUSY_TIMEOUT: Duration = Duration::from_secs(5);
pub fn db_path(workspace_dir: &Path) -> PathBuf {
workspace_dir.join(DB_SUBDIR).join(DB_FILE)
}
pub fn with_connection<T>(
workspace_dir: &Path,
f: impl FnOnce(&Connection) -> Result<T>,
) -> Result<T> {
let db_path = db_path(workspace_dir);
if let Some(parent) = db_path.parent() {
std::fs::create_dir_all(parent).storage_context(&format!(
"failed to create session_db directory: {}",
parent.display()
))?;
}
let conn = Connection::open(&db_path)
.storage_context(&format!("failed to open session DB: {}", db_path.display()))?;
prepare_connection(&conn)?;
migrations::apply(&conn)?;
f(&conn)
}
fn prepare_connection(conn: &Connection) -> Result<()> {
conn.busy_timeout(BUSY_TIMEOUT)
.storage_context("failed to set session DB busy_timeout")?;
conn.execute_batch(
"PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;",
)
.storage_context("failed to apply session DB pragmas")?;
Ok(())
}
pub fn with_transaction<T>(
workspace_dir: &Path,
f: impl FnOnce(&Connection) -> Result<T>,
) -> Result<T> {
with_connection(workspace_dir, |conn| {
conn.execute_batch("BEGIN IMMEDIATE")
.storage_context("begin session DB transaction")?;
match f(conn) {
Ok(value) => {
conn.execute_batch("COMMIT")
.storage_context("commit session DB transaction")?;
Ok(value)
}
Err(err) => {
if let Err(rollback_err) = conn.execute_batch("ROLLBACK") {
tracing::warn!(
"[session] rollback after error failed: {rollback_err} (original: {err})"
);
}
Err(err)
}
}
})
}
#[cfg(test)]
pub fn with_memory_connection<T>(f: impl FnOnce(&Connection) -> Result<T>) -> Result<T> {
let conn =
Connection::open_in_memory().storage_context("failed to open in-memory session DB")?;
prepare_connection(&conn)?;
migrations::apply(&conn)?;
f(&conn)
}