From 5e8e3ecbeb71ea924ed9662251af2897285b8220 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Tue, 25 Oct 2022 20:47:46 +0800
Subject: [PATCH] Fix issues count bug (#21557)

fix #19349 , #19505

Co-authored-by: delvh <dev.lh@web.de>
---
 models/issues/comment.go |  2 +-
 models/issues/issue.go   | 12 ++--------
 models/repo.go           | 13 ++++-------
 models/repo/repo.go      | 49 ++++++++++++++++++----------------------
 4 files changed, 29 insertions(+), 47 deletions(-)

diff --git a/models/issues/comment.go b/models/issues/comment.go
index bdc574252c..6877991a93 100644
--- a/models/issues/comment.go
+++ b/models/issues/comment.go
@@ -886,7 +886,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
 			}
 		}
 	case CommentTypeReopen, CommentTypeClose:
-		if err = updateIssueClosedNum(ctx, opts.Issue); err != nil {
+		if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
 			return err
 		}
 	}
diff --git a/models/issues/issue.go b/models/issues/issue.go
index b1b140732b..ca48f425f2 100644
--- a/models/issues/issue.go
+++ b/models/issues/issue.go
@@ -725,7 +725,8 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
 		}
 	}
 
-	if err := updateIssueClosedNum(ctx, issue); err != nil {
+	// update repository's issue closed number
+	if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
 		return nil, err
 	}
 
@@ -2137,15 +2138,6 @@ func (issue *Issue) BlockingDependencies(ctx context.Context) (issueDeps []*Depe
 	return issueDeps, err
 }
 
-func updateIssueClosedNum(ctx context.Context, issue *Issue) (err error) {
-	if issue.IsPull {
-		err = repo_model.StatsCorrectNumClosed(ctx, issue.RepoID, true, "num_closed_pulls")
-	} else {
-		err = repo_model.StatsCorrectNumClosed(ctx, issue.RepoID, false, "num_closed_issues")
-	}
-	return err
-}
-
 // FindAndUpdateIssueMentions finds users mentioned in the given content string, and saves them in the database.
 func FindAndUpdateIssueMentions(ctx context.Context, issue *Issue, doer *user_model.User, content string) (mentions []*user_model.User, err error) {
 	rawMentions := references.FindAllMentionsMarkdown(content)
diff --git a/models/repo.go b/models/repo.go
index b544853a5d..569dafee5f 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -404,24 +404,19 @@ func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error {
 }
 
 func repoStatsCorrectNumIssues(ctx context.Context, id int64) error {
-	return repoStatsCorrectNum(ctx, id, false, "num_issues")
+	return repo_model.UpdateRepoIssueNumbers(ctx, id, false, false)
 }
 
 func repoStatsCorrectNumPulls(ctx context.Context, id int64) error {
-	return repoStatsCorrectNum(ctx, id, true, "num_pulls")
-}
-
-func repoStatsCorrectNum(ctx context.Context, id int64, isPull bool, field string) error {
-	_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET "+field+"=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_pull=?) WHERE id=?", id, isPull, id)
-	return err
+	return repo_model.UpdateRepoIssueNumbers(ctx, id, true, false)
 }
 
 func repoStatsCorrectNumClosedIssues(ctx context.Context, id int64) error {
-	return repo_model.StatsCorrectNumClosed(ctx, id, false, "num_closed_issues")
+	return repo_model.UpdateRepoIssueNumbers(ctx, id, false, true)
 }
 
 func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error {
-	return repo_model.StatsCorrectNumClosed(ctx, id, true, "num_closed_pulls")
+	return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true)
 }
 
 func statsQuery(args ...interface{}) func(context.Context) ([]map[string][]byte, error) {
diff --git a/models/repo/repo.go b/models/repo/repo.go
index 848138c824..77e0367a5a 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -765,35 +765,30 @@ func CountRepositories(ctx context.Context, opts CountRepositoryOptions) (int64,
 	return count, nil
 }
 
-// StatsCorrectNumClosed update repository's issue related numbers
-func StatsCorrectNumClosed(ctx context.Context, id int64, isPull bool, field string) error {
-	_, err := db.Exec(ctx, "UPDATE `repository` SET "+field+"=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, isPull, id)
-	return err
-}
-
-// UpdateRepoIssueNumbers update repository issue numbers
+// UpdateRepoIssueNumbers updates one of a repositories amount of (open|closed) (issues|PRs) with the current count
 func UpdateRepoIssueNumbers(ctx context.Context, repoID int64, isPull, isClosed bool) error {
-	e := db.GetEngine(ctx)
-	if isPull {
-		if _, err := e.ID(repoID).Decr("num_pulls").Update(new(Repository)); err != nil {
-			return err
-		}
-		if isClosed {
-			if _, err := e.ID(repoID).Decr("num_closed_pulls").Update(new(Repository)); err != nil {
-				return err
-			}
-		}
-	} else {
-		if _, err := e.ID(repoID).Decr("num_issues").Update(new(Repository)); err != nil {
-			return err
-		}
-		if isClosed {
-			if _, err := e.ID(repoID).Decr("num_closed_issues").Update(new(Repository)); err != nil {
-				return err
-			}
-		}
+	field := "num_"
+	if isClosed {
+		field += "closed_"
 	}
-	return nil
+	if isPull {
+		field += "pulls"
+	} else {
+		field += "issues"
+	}
+
+	subQuery := builder.Select("count(*)").
+		From("issue").Where(builder.Eq{
+		"repo_id": repoID,
+		"is_pull": isPull,
+	}.And(builder.If(isClosed, builder.Eq{"is_closed": isClosed})))
+
+	// builder.Update(cond) will generate SQL like UPDATE ... SET cond
+	query := builder.Update(builder.Eq{field: subQuery}).
+		From("repository").
+		Where(builder.Eq{"id": repoID})
+	_, err := db.Exec(ctx, query)
+	return err
 }
 
 // CountNullArchivedRepository counts the number of repositories with is_archived is null