diff options
| author | Claudomator Agent <agent@claudomator> | 2026-03-09 07:32:26 +0000 |
|---|---|---|
| committer | Claudomator Agent <agent@claudomator> | 2026-03-09 07:32:26 +0000 |
| commit | 361040939eb428f990c97ab0ab983e5360761b27 (patch) | |
| tree | 2ba86e7e1081db54022a331be4ea2cfef841dc37 /internal/storage | |
| parent | 933af819ae3ee7ea0cf6b750815ab185043e19fc (diff) | |
storage: add missing indexes and ListRecentExecutions correctness tests
Add two schema indexes that were missing:
- idx_executions_start_time on executions(start_time): speeds up
ListRecentExecutions WHERE start_time >= ? ORDER BY start_time DESC
- idx_tasks_parent_task_id on tasks(parent_task_id): speeds up
ListSubtasks WHERE parent_task_id = ?
Both use CREATE INDEX IF NOT EXISTS so they are safe to apply on
existing databases without a migration version bump.
Add TestListRecentExecutions_LargeDataset (100 rows, two tasks) covering:
- returns all rows in descending start_time order
- respects the limit parameter
- filters correctly by since time
- filters correctly by task_id
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/storage')
| -rw-r--r-- | internal/storage/db.go | 2 | ||||
| -rw-r--r-- | internal/storage/db_test.go | 88 |
2 files changed, 90 insertions, 0 deletions
diff --git a/internal/storage/db.go b/internal/storage/db.go index fb754f5..b6af2c8 100644 --- a/internal/storage/db.go +++ b/internal/storage/db.go @@ -66,8 +66,10 @@ func (s *DB) migrate() error { ); CREATE INDEX IF NOT EXISTS idx_tasks_state ON tasks(state); + CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id ON tasks(parent_task_id); CREATE INDEX IF NOT EXISTS idx_executions_status ON executions(status); CREATE INDEX IF NOT EXISTS idx_executions_task_id ON executions(task_id); + CREATE INDEX IF NOT EXISTS idx_executions_start_time ON executions(start_time); ` if _, err := s.db.Exec(schema); err != nil { return err diff --git a/internal/storage/db_test.go b/internal/storage/db_test.go index e76c00a..8b10817 100644 --- a/internal/storage/db_test.go +++ b/internal/storage/db_test.go @@ -581,6 +581,94 @@ func TestStorage_GetLatestExecution(t *testing.T) { } } +func TestListRecentExecutions_LargeDataset(t *testing.T) { + db := testDB(t) + now := time.Now().UTC().Truncate(time.Second) + + // Create two tasks so we can also test the taskID filter. + taskA := makeTestTask("re-task-a", now) + taskB := makeTestTask("re-task-b", now) + db.CreateTask(taskA) + db.CreateTask(taskB) + + // Insert 100 executions spread across the two tasks, 1 minute apart. + for i := 0; i < 100; i++ { + tid := "re-task-a" + if i%2 == 0 { + tid = "re-task-b" + } + start := now.Add(time.Duration(i) * time.Minute) + db.CreateExecution(&Execution{ + ID: fmt.Sprintf("re-exec-%03d", i), + TaskID: tid, + StartTime: start, + EndTime: start.Add(30 * time.Second), + Status: "COMPLETED", + CostUSD: float64(i) * 0.01, + }) + } + + t.Run("returns all executions since epoch", func(t *testing.T) { + results, err := db.ListRecentExecutions(now.Add(-time.Hour), 200, "") + if err != nil { + t.Fatalf("ListRecentExecutions: %v", err) + } + if len(results) != 100 { + t.Errorf("want 100, got %d", len(results)) + } + // Verify descending order by start_time. + for i := 1; i < len(results); i++ { + if results[i-1].StartedAt.Before(results[i].StartedAt) { + t.Errorf("results not in descending order at index %d/%d", i-1, i) + break + } + } + }) + + t.Run("respects limit", func(t *testing.T) { + results, err := db.ListRecentExecutions(now.Add(-time.Hour), 10, "") + if err != nil { + t.Fatalf("ListRecentExecutions: %v", err) + } + if len(results) != 10 { + t.Errorf("want 10, got %d", len(results)) + } + }) + + t.Run("filters by since time", func(t *testing.T) { + // Only executions starting at index 50+ (minute 50 onward). + since := now.Add(50 * time.Minute) + results, err := db.ListRecentExecutions(since, 200, "") + if err != nil { + t.Fatalf("ListRecentExecutions: %v", err) + } + if len(results) != 50 { + t.Errorf("want 50 (indices 50–99), got %d", len(results)) + } + for _, r := range results { + if r.StartedAt.Before(since) { + t.Errorf("result %q has start_time %v before since %v", r.ID, r.StartedAt, since) + } + } + }) + + t.Run("filters by task_id", func(t *testing.T) { + // task-a gets odd indices (1,3,5,...,99) = 50 executions. + results, err := db.ListRecentExecutions(now.Add(-time.Hour), 200, "re-task-a") + if err != nil { + t.Fatalf("ListRecentExecutions: %v", err) + } + if len(results) != 50 { + t.Errorf("want 50 for task-a, got %d", len(results)) + } + for _, r := range results { + if r.TaskID != "re-task-a" { + t.Errorf("unexpected task_id %q in results", r.TaskID) + } + } + }) +} + func TestGetTask_BackwardCompatibility(t *testing.T) { db := testDB(t) now := time.Now().UTC().Truncate(time.Second) |
