feat(storage): add parent_task_id migration for micro-stepping
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -194,7 +194,7 @@ pub async fn insert_task(
|
||||
|
||||
pub async fn list_tasks(pool: &SqlitePool) -> Result<Vec<TaskRow>> {
|
||||
let rows =
|
||||
sqlx::query("SELECT id, text, bucket, list_id, effort, done, done_at, created_at, source_transcript_id, priority, project, status, updated_at, sort_order, notes FROM tasks ORDER BY sort_order ASC, created_at DESC")
|
||||
sqlx::query("SELECT id, text, bucket, list_id, effort, done, done_at, created_at, source_transcript_id, priority, project, status, updated_at, sort_order, notes, parent_task_id FROM tasks ORDER BY sort_order ASC, created_at DESC")
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.map_err(|e| KonError::StorageError(format!("List tasks failed: {e}")))?;
|
||||
@@ -303,7 +303,7 @@ pub async fn list_tasks_by_status(
|
||||
limit: i64,
|
||||
) -> Result<Vec<TaskRow>> {
|
||||
let rows = sqlx::query(
|
||||
"SELECT id, text, bucket, list_id, effort, done, done_at, created_at, source_transcript_id, priority, project, status, updated_at, sort_order, notes
|
||||
"SELECT id, text, bucket, list_id, effort, done, done_at, created_at, source_transcript_id, priority, project, status, updated_at, sort_order, notes, parent_task_id
|
||||
FROM tasks WHERE status = ?
|
||||
ORDER BY sort_order ASC, created_at DESC
|
||||
LIMIT ?",
|
||||
@@ -317,6 +317,49 @@ pub async fn list_tasks_by_status(
|
||||
Ok(rows.into_iter().map(task_row_from).collect())
|
||||
}
|
||||
|
||||
// --- Subtask (micro-step) queries ---
|
||||
|
||||
/// List subtasks (children) of a parent task, ordered by sort_order.
|
||||
pub async fn list_subtasks(pool: &SqlitePool, parent_task_id: &str) -> Result<Vec<TaskRow>> {
|
||||
let rows = sqlx::query(
|
||||
"SELECT id, text, bucket, list_id, effort, done, done_at, created_at, source_transcript_id, priority, project, status, updated_at, sort_order, notes, parent_task_id
|
||||
FROM tasks WHERE parent_task_id = ?
|
||||
ORDER BY sort_order ASC",
|
||||
)
|
||||
.bind(parent_task_id)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.map_err(|e| KonError::StorageError(format!("List subtasks failed: {e}")))?;
|
||||
|
||||
Ok(rows.into_iter().map(task_row_from).collect())
|
||||
}
|
||||
|
||||
/// Check whether all subtasks of a parent are done.
|
||||
pub async fn all_subtasks_done(pool: &SqlitePool, parent_task_id: &str) -> Result<bool> {
|
||||
let count: i64 = sqlx::query_scalar(
|
||||
"SELECT COUNT(*) FROM tasks WHERE parent_task_id = ? AND done = 0",
|
||||
)
|
||||
.bind(parent_task_id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| KonError::StorageError(format!("Check subtasks failed: {e}")))?;
|
||||
|
||||
Ok(count == 0)
|
||||
}
|
||||
|
||||
/// Check whether a task has any subtasks.
|
||||
pub async fn has_subtasks(pool: &SqlitePool, task_id: &str) -> Result<bool> {
|
||||
let count: i64 = sqlx::query_scalar(
|
||||
"SELECT COUNT(*) FROM tasks WHERE parent_task_id = ?",
|
||||
)
|
||||
.bind(task_id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| KonError::StorageError(format!("Check has subtasks failed: {e}")))?;
|
||||
|
||||
Ok(count > 0)
|
||||
}
|
||||
|
||||
// --- FTS5 Search ---
|
||||
|
||||
/// Full-text search across transcripts using the FTS5 virtual table.
|
||||
@@ -455,6 +498,8 @@ pub struct TaskRow {
|
||||
pub updated_at: String,
|
||||
pub sort_order: i64,
|
||||
pub notes: String,
|
||||
// v3 field
|
||||
pub parent_task_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -484,6 +529,7 @@ fn task_row_from(r: sqlx::sqlite::SqliteRow) -> TaskRow {
|
||||
updated_at: r.get("updated_at"),
|
||||
sort_order: r.get("sort_order"),
|
||||
notes: r.get("notes"),
|
||||
parent_task_id: r.get("parent_task_id"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,11 @@ pub mod file_storage;
|
||||
pub mod migrations;
|
||||
|
||||
pub use database::{
|
||||
clear_timer_state, complete_task, delete_task, delete_transcript, get_segments,
|
||||
get_setting, get_timer_state, get_transcript, init, insert_segments, insert_task,
|
||||
insert_task_v2, insert_transcript, list_tasks, list_tasks_by_status, list_transcripts,
|
||||
log_error, reorder_tasks, save_timer_state, search_transcripts, set_setting,
|
||||
update_task_v2, InsertTranscriptParams, SegmentRow, TaskRow, TimerStateRow, TranscriptRow,
|
||||
all_subtasks_done, clear_timer_state, complete_task, delete_task, delete_transcript,
|
||||
get_segments, get_setting, get_timer_state, get_transcript, has_subtasks, init,
|
||||
insert_segments, insert_task, insert_task_v2, insert_transcript, list_subtasks, list_tasks,
|
||||
list_tasks_by_status, list_transcripts, log_error, reorder_tasks, save_timer_state,
|
||||
search_transcripts, set_setting, update_task_v2, InsertTranscriptParams, SegmentRow,
|
||||
TaskRow, TimerStateRow, TranscriptRow,
|
||||
};
|
||||
pub use file_storage::{app_data_dir, database_path, recordings_dir};
|
||||
|
||||
@@ -118,6 +118,10 @@ const MIGRATIONS: &[(i64, &str, &str)] = &[
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project)
|
||||
"#),
|
||||
(3, "phase 2b — parent_task_id for micro-stepping", r#"
|
||||
ALTER TABLE tasks ADD COLUMN parent_task_id TEXT;
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task_id)
|
||||
"#),
|
||||
];
|
||||
|
||||
/// Split SQL text into individual statements, respecting BEGIN...END blocks
|
||||
@@ -236,7 +240,7 @@ mod tests {
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(count, 2);
|
||||
assert_eq!(count, 3);
|
||||
|
||||
sqlx::query("INSERT INTO settings (key, value) VALUES ('test', 'value')")
|
||||
.execute(&pool)
|
||||
@@ -279,6 +283,6 @@ mod tests {
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(count, 2);
|
||||
assert_eq!(count, 3);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user