fix(storage): PRAGMA foreign_keys=ON; atomic transaction in complete_subtask_and_check_parent; uncomplete_task moved to storage layer
Some checks failed
check / cargo check (macos-latest) (push) Has been cancelled
check / cargo check (ubuntu-22.04) (push) Has been cancelled
check / cargo check (windows-latest) (push) Has been cancelled
check / svelte build + lint (push) Has been cancelled

This commit is contained in:
2026-04-19 11:00:37 +01:00
parent f54d55d110
commit 8c9c9390d8
3 changed files with 42 additions and 23 deletions

View File

@@ -21,6 +21,11 @@ pub async fn init(db_path: &Path) -> Result<SqlitePool> {
.await
.map_err(|e| KonError::StorageError(format!("Database connect failed: {e}")))?;
sqlx::query("PRAGMA foreign_keys = ON")
.execute(&pool)
.await
.map_err(|e| KonError::StorageError(format!("foreign_keys pragma failed: {e}")))?;
run_migrations(&pool).await?;
Ok(pool)
@@ -343,10 +348,14 @@ pub async fn list_subtasks(pool: &SqlitePool, parent_id: &str) -> Result<Vec<Tas
}
/// Mark a subtask done. If all siblings are now done, auto-complete the parent.
/// Runs in a transaction so concurrent completions see consistent sibling counts.
pub async fn complete_subtask_and_check_parent(pool: &SqlitePool, subtask_id: &str) -> Result<()> {
let mut tx = pool.begin().await
.map_err(|e| KonError::StorageError(format!("Begin transaction failed: {e}")))?;
sqlx::query("UPDATE tasks SET done = 1, done_at = datetime('now') WHERE id = ?")
.bind(subtask_id)
.execute(pool)
.execute(&mut *tx)
.await
.map_err(|e| KonError::StorageError(format!("Complete subtask failed: {e}")))?;
@@ -354,27 +363,30 @@ pub async fn complete_subtask_and_check_parent(pool: &SqlitePool, subtask_id: &s
"SELECT parent_task_id FROM tasks WHERE id = ?"
)
.bind(subtask_id)
.fetch_one(pool)
.fetch_one(&mut *tx)
.await
.map_err(|e| KonError::StorageError(format!("Get parent_task_id failed: {e}")))?;
let Some(pid) = parent_id else { return Ok(()); };
if let Some(pid) = parent_id {
let pending: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM tasks WHERE parent_task_id = ? AND done = 0"
)
.bind(&pid)
.fetch_one(pool)
.fetch_one(&mut *tx)
.await
.map_err(|e| KonError::StorageError(format!("Count pending subtasks failed: {e}")))?;
if pending == 0 {
sqlx::query("UPDATE tasks SET done = 1, done_at = datetime('now') WHERE id = ?")
.bind(&pid)
.execute(pool)
.execute(&mut *tx)
.await
.map_err(|e| KonError::StorageError(format!("Auto-complete parent failed: {e}")))?;
}
}
tx.commit().await
.map_err(|e| KonError::StorageError(format!("Commit transaction failed: {e}")))?;
Ok(())
}
@@ -388,6 +400,15 @@ pub async fn complete_task(pool: &SqlitePool, id: &str) -> Result<()> {
Ok(())
}
pub async fn uncomplete_task(pool: &SqlitePool, id: &str) -> Result<()> {
sqlx::query("UPDATE tasks SET done = 0, done_at = NULL WHERE id = ?")
.bind(id)
.execute(pool)
.await
.map_err(|e| KonError::StorageError(format!("Uncomplete task failed: {e}")))?;
Ok(())
}
pub async fn delete_task(pool: &SqlitePool, id: &str) -> Result<()> {
sqlx::query("DELETE FROM tasks WHERE id = ?")
.bind(id)

View File

@@ -4,7 +4,7 @@ pub mod migrations;
pub use database::{
add_dictionary_entry, complete_subtask_and_check_parent, complete_task, count_transcripts,
delete_dictionary_entry, delete_task, delete_transcript, get_setting, get_task_by_id,
delete_dictionary_entry, delete_task, delete_transcript, get_setting, get_task_by_id, uncomplete_task,
get_transcript, init, insert_subtask, insert_task, insert_transcript, list_dictionary,
list_recent_errors, list_subtasks, list_tasks, list_transcripts, list_transcripts_paged,
log_error, search_transcripts, set_setting, update_transcript, DictionaryEntry, ErrorLogRow,

View File

@@ -15,6 +15,7 @@ use kon_storage::{
insert_task as db_insert_task,
list_subtasks as db_list_subtasks,
list_tasks as db_list_tasks,
uncomplete_task as db_uncomplete_task,
TaskRow,
};
@@ -113,12 +114,9 @@ pub async fn uncomplete_task_cmd(
state: tauri::State<'_, AppState>,
id: String,
) -> Result<(), String> {
sqlx::query("UPDATE tasks SET done = 0, done_at = NULL WHERE id = ?")
.bind(&id)
.execute(&state.db)
db_uncomplete_task(&state.db, &id)
.await
.map_err(|e| format!("Uncomplete task failed: {e}"))
.map(|_| ())
.map_err(|e| e.to_string())
}
#[tauri::command]