internal/relui: log previous state when retrying tasks

This logs the task state and workflow state when retrying a task for a
workflow, so as not to lose history.

Eventually, we should store the full history for workflows and tasks.

Moves the transaction rollback to a defer, which is safe even if commit
or rollback is called first.

Fixes golang/go#53165

Change-Id: I6a9c42beb9352656b044dbe6e5adf6493d32873d
Reviewed-on: https://go-review.googlesource.com/c/build/+/411194
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Run-TryBot: Alex Rakoczy <alex@golang.org>
diff --git a/internal/relui/db/workflows.sql.go b/internal/relui/db/workflows.sql.go
index 11a780d..41e10a7 100644
--- a/internal/relui/db/workflows.sql.go
+++ b/internal/relui/db/workflows.sql.go
@@ -120,7 +120,8 @@
     result     = DEFAULT,
     error      = DEFAULT,
     updated_at = $3
-WHERE workflow_id = $1 AND name = $2
+WHERE workflow_id = $1
+  AND name = $2
 RETURNING workflow_id, name, finished, result, error, created_at, updated_at
 `
 
@@ -147,9 +148,9 @@
 
 const resetWorkflow = `-- name: ResetWorkflow :one
 UPDATE workflows
-SET finished = false,
-    output = DEFAULT,
-    error = DEFAULT,
+SET finished   = false,
+    output     = DEFAULT,
+    error      = DEFAULT,
     updated_at = $2
 WHERE id = $1
 RETURNING id, params, name, created_at, updated_at, finished, output, error
@@ -176,6 +177,34 @@
 	return i, err
 }
 
+const task = `-- name: Task :one
+SELECT tasks.workflow_id, tasks.name, tasks.finished, tasks.result, tasks.error, tasks.created_at, tasks.updated_at
+FROM tasks
+WHERE workflow_id = $1
+  AND name = $2
+LIMIT 1
+`
+
+type TaskParams struct {
+	WorkflowID uuid.UUID
+	Name       string
+}
+
+func (q *Queries) Task(ctx context.Context, arg TaskParams) (Task, error) {
+	row := q.db.QueryRow(ctx, task, arg.WorkflowID, arg.Name)
+	var i Task
+	err := row.Scan(
+		&i.WorkflowID,
+		&i.Name,
+		&i.Finished,
+		&i.Result,
+		&i.Error,
+		&i.CreatedAt,
+		&i.UpdatedAt,
+	)
+	return i, err
+}
+
 const taskLogs = `-- name: TaskLogs :many
 SELECT task_logs.id, task_logs.workflow_id, task_logs.task_name, task_logs.body, task_logs.created_at, task_logs.updated_at
 FROM task_logs
diff --git a/internal/relui/queries/workflows.sql b/internal/relui/queries/workflows.sql
index 4a21cac..755a7d9 100644
--- a/internal/relui/queries/workflows.sql
+++ b/internal/relui/queries/workflows.sql
@@ -45,6 +45,13 @@
 WHERE workflow_id = $1
 ORDER BY created_at;
 
+-- name: Task :one
+SELECT tasks.*
+FROM tasks
+WHERE workflow_id = $1
+  AND name = $2
+LIMIT 1;
+
 -- name: CreateTaskLog :one
 INSERT INTO task_logs (workflow_id, task_name, body)
 VALUES ($1, $2, $3)
@@ -82,14 +89,15 @@
     result     = DEFAULT,
     error      = DEFAULT,
     updated_at = $3
-WHERE workflow_id = $1 AND name = $2
+WHERE workflow_id = $1
+  AND name = $2
 RETURNING *;
 
 -- name: ResetWorkflow :one
 UPDATE workflows
-SET finished = false,
-    output = DEFAULT,
-    error = DEFAULT,
+SET finished   = false,
+    output     = DEFAULT,
+    error      = DEFAULT,
     updated_at = $2
 WHERE id = $1
 RETURNING *;
diff --git a/internal/relui/web.go b/internal/relui/web.go
index 807ee0d..e946ac2 100644
--- a/internal/relui/web.go
+++ b/internal/relui/web.go
@@ -281,19 +281,27 @@
 	if err != nil {
 		return fmt.Errorf("tx.Begin(): %w", err)
 	}
-	q := db.New(s.db)
-	q = q.WithTx(tx)
+	defer tx.Rollback(ctx)
+	q := db.New(s.db).WithTx(tx)
+	wf, err := q.Workflow(ctx, id)
+	if err != nil {
+		return fmt.Errorf("q.Workflow: %w", err)
+	}
+	task, err := q.Task(ctx, db.TaskParams{WorkflowID: id, Name: name})
+	if err != nil {
+		return fmt.Errorf("q.Task: %w", err)
+	}
 	if _, err := q.ResetTask(ctx, db.ResetTaskParams{WorkflowID: id, Name: name, UpdatedAt: time.Now()}); err != nil {
-		tx.Rollback(ctx)
 		return fmt.Errorf("q.ResetTask: %w", err)
 	}
 	if _, err := q.ResetWorkflow(ctx, db.ResetWorkflowParams{ID: id, UpdatedAt: time.Now()}); err != nil {
-		tx.Rollback(ctx)
 		return fmt.Errorf("q.ResetWorkflow: %w", err)
 	}
 	if err := tx.Commit(ctx); err != nil {
-		tx.Rollback(ctx)
 		return fmt.Errorf("tx.Commit: %w", err)
 	}
+	l := s.w.l.Logger(id, name)
+	l.Printf("task reset. Previous state: %#v", task)
+	l.Printf("workflow reset. Previous state: %#v", wf)
 	return nil
 }