diff --git a/models/issues/issue_user.go b/models/issues/issue_user.go index 24bb74648d..6b59e0725e 100644 --- a/models/issues/issue_user.go +++ b/models/issues/issue_user.go @@ -14,8 +14,8 @@ import ( // IssueUser represents an issue-user relation. type IssueUser struct { ID int64 `xorm:"pk autoincr"` - UID int64 `xorm:"INDEX"` // User ID. - IssueID int64 `xorm:"INDEX"` + UID int64 `xorm:"INDEX unique(uid_to_issue)"` // User ID. + IssueID int64 `xorm:"INDEX unique(uid_to_issue)"` IsRead bool IsMentioned bool } diff --git a/models/migrations/fixtures/Test_AddCombinedIndexToIssueUser/issue_user.yml b/models/migrations/fixtures/Test_AddCombinedIndexToIssueUser/issue_user.yml new file mode 100644 index 0000000000..7bbb6f2f30 --- /dev/null +++ b/models/migrations/fixtures/Test_AddCombinedIndexToIssueUser/issue_user.yml @@ -0,0 +1,20 @@ +- + id: 1 + uid: 1 + issue_id: 1 + is_read: true + is_mentioned: false + +- + id: 2 + uid: 2 + issue_id: 1 + is_read: true + is_mentioned: false + +- + id: 3 + uid: 2 + issue_id: 1 # duplicated with id 2 + is_read: false + is_mentioned: true diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 28e3be503b..578cbca035 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -550,6 +550,8 @@ var migrations = []Migration{ NewMigration("Add auth_token table", v1_22.CreateAuthTokenTable), // v282 -> v283 NewMigration("Add Index to pull_auto_merge.doer_id", v1_22.AddIndexToPullAutoMergeDoerID), + // v283 -> v284 + NewMigration("Add combined Index to issue_user.uid and issue_id", v1_22.AddCombinedIndexToIssueUser), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_22/main_test.go b/models/migrations/v1_22/main_test.go new file mode 100644 index 0000000000..efd8dbaa8c --- /dev/null +++ b/models/migrations/v1_22/main_test.go @@ -0,0 +1,14 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" +) + +func TestMain(m *testing.M) { + base.MainTest(m) +} diff --git a/models/migrations/v1_22/v283.go b/models/migrations/v1_22/v283.go new file mode 100644 index 0000000000..b2b94845d9 --- /dev/null +++ b/models/migrations/v1_22/v283.go @@ -0,0 +1,34 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "xorm.io/xorm" +) + +func AddCombinedIndexToIssueUser(x *xorm.Engine) error { + type OldIssueUser struct { + IssueID int64 + UID int64 + Cnt int64 + } + + var duplicatedIssueUsers []OldIssueUser + if err := x.SQL("select * from (select issue_id, uid, count(1) as cnt from issue_user group by issue_id, uid) a where a.cnt > 1"). + Find(&duplicatedIssueUsers); err != nil { + return err + } + for _, issueUser := range duplicatedIssueUsers { + if _, err := x.Exec("delete from issue_user where id in (SELECT id FROM issue_user WHERE issue_id = ? and uid = ? limit ?)", issueUser.IssueID, issueUser.UID, issueUser.Cnt-1); err != nil { + return err + } + } + + type IssueUser struct { + UID int64 `xorm:"INDEX unique(uid_to_issue)"` // User ID. + IssueID int64 `xorm:"INDEX unique(uid_to_issue)"` + } + + return x.Sync(&IssueUser{}) +} diff --git a/models/migrations/v1_22/v283_test.go b/models/migrations/v1_22/v283_test.go new file mode 100644 index 0000000000..864f47f840 --- /dev/null +++ b/models/migrations/v1_22/v283_test.go @@ -0,0 +1,28 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" +) + +func Test_AddCombinedIndexToIssueUser(t *testing.T) { + type IssueUser struct { + UID int64 `xorm:"INDEX unique(uid_to_issue)"` // User ID. + IssueID int64 `xorm:"INDEX unique(uid_to_issue)"` + } + + // Prepare and load the testing database + x, deferable := base.PrepareTestEnv(t, 0, new(IssueUser)) + defer deferable() + if x == nil || t.Failed() { + return + } + + if err := AddCombinedIndexToIssueUser(x); err != nil { + t.Fatal(err) + } +}