package infra import ( "fmt" "time" ) // JobFail increments the attempt counter and transitions the job to: // - "failed" when attempts < max_attempts (eligible for retry) // - "dead" when attempts >= max_attempts (no more retries) // // errMsg is stored in the error column for debugging. func JobFail(q *JobQueue, jobID string, errMsg string) error { if q == nil { return fmt.Errorf("job_fail: queue must not be nil") } if jobID == "" { return fmt.Errorf("job_fail: jobID must not be empty") } now := time.Now().UTC().Format(time.RFC3339) // Atomically increment attempts and decide status. query := fmt.Sprintf(` UPDATE %s SET attempts = attempts + 1, status = CASE WHEN (attempts + 1) >= max_attempts THEN 'dead' ELSE 'failed' END, error = ?, completed_at = ? WHERE id = ? `, q.TableName) res, err := q.DB.Exec(query, errMsg, now, jobID) if err != nil { return fmt.Errorf("job_fail: update: %w", err) } n, _ := res.RowsAffected() if n == 0 { return fmt.Errorf("job_fail: job %q not found", jobID) } return nil }