summaryrefslogtreecommitdiffstats
path: root/models/migrations
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2024-04-02 16:02:05 +0800
committerGitHub <noreply@github.com>2024-04-02 16:02:05 +0800
commitb4825670596fe745cebdcc63a8ead4388602d42c (patch)
tree748ef7d37374d1c3968db520ce635d133a044a2c /models/migrations
parent8a5c597c1d53e7652f1f3fc59e64b46a04c5e20b (diff)
downloadgitea-b4825670596fe745cebdcc63a8ead4388602d42c.tar.gz
gitea-b4825670596fe745cebdcc63a8ead4388602d42c.zip
Add unique index for project_issue to prevent duplicate data (#30190)
Fix #27639
Diffstat (limited to 'models/migrations')
-rw-r--r--models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml9
-rw-r--r--models/migrations/migrations.go5
-rw-r--r--models/migrations/v1_23/main_test.go14
-rw-r--r--models/migrations/v1_23/v294.go53
-rw-r--r--models/migrations/v1_23/v294_test.go52
5 files changed, 133 insertions, 0 deletions
diff --git a/models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml b/models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml
new file mode 100644
index 0000000000..6feaeb39f0
--- /dev/null
+++ b/models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml
@@ -0,0 +1,9 @@
+-
+ id: 1
+ project_id: 1
+ issue_id: 1
+
+-
+ id: 2
+ project_id: 1
+ issue_id: 1
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 0daa799ff6..387cd96a53 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/models/migrations/v1_20"
"code.gitea.io/gitea/models/migrations/v1_21"
"code.gitea.io/gitea/models/migrations/v1_22"
+ "code.gitea.io/gitea/models/migrations/v1_23"
"code.gitea.io/gitea/models/migrations/v1_6"
"code.gitea.io/gitea/models/migrations/v1_7"
"code.gitea.io/gitea/models/migrations/v1_8"
@@ -572,6 +573,10 @@ var migrations = []Migration{
NewMigration("Ensure every project has exactly one default column - No Op", noopMigration),
// v293 -> v294
NewMigration("Ensure every project has exactly one default column", v1_22.CheckProjectColumnsConsistency),
+
+ // Gitea 1.22.0 ends at 294
+
+ NewMigration("Add unique index for project issue table", v1_23.AddUniqueIndexForProjectIssue),
}
// GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v1_23/main_test.go b/models/migrations/v1_23/main_test.go
new file mode 100644
index 0000000000..b7948bd4dd
--- /dev/null
+++ b/models/migrations/v1_23/main_test.go
@@ -0,0 +1,14 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_23 //nolint
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+)
+
+func TestMain(m *testing.M) {
+ base.MainTest(m)
+}
diff --git a/models/migrations/v1_23/v294.go b/models/migrations/v1_23/v294.go
new file mode 100644
index 0000000000..f2a54f6d23
--- /dev/null
+++ b/models/migrations/v1_23/v294.go
@@ -0,0 +1,53 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_23 //nolint
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+ "xorm.io/xorm/schemas"
+)
+
+// AddUniqueIndexForProjectIssue adds unique indexes for project issue table
+func AddUniqueIndexForProjectIssue(x *xorm.Engine) error {
+ // remove possible duplicated records in table project_issue
+ type result struct {
+ IssueID int64
+ ProjectID int64
+ Cnt int
+ }
+ var results []result
+ if err := x.Select("issue_id, project_id, count(*) as cnt").
+ Table("project_issue").
+ GroupBy("issue_id, project_id").
+ Having("count(*) > 1").
+ Find(&results); err != nil {
+ return err
+ }
+ for _, r := range results {
+ if x.Dialect().URI().DBType == schemas.MSSQL {
+ if _, err := x.Exec(fmt.Sprintf("delete from project_issue where id in (SELECT top %d id FROM project_issue WHERE issue_id = ? and project_id = ?)", r.Cnt-1), r.IssueID, r.ProjectID); err != nil {
+ return err
+ }
+ } else {
+ var ids []int64
+ if err := x.SQL("SELECT id FROM project_issue WHERE issue_id = ? and project_id = ? limit ?", r.IssueID, r.ProjectID, r.Cnt-1).Find(&ids); err != nil {
+ return err
+ }
+ if _, err := x.Table("project_issue").In("id", ids).Delete(); err != nil {
+ return err
+ }
+ }
+ }
+
+ // add unique index for project_issue table
+ type ProjectIssue struct { //revive:disable-line:exported
+ ID int64 `xorm:"pk autoincr"`
+ IssueID int64 `xorm:"INDEX unique(s)"`
+ ProjectID int64 `xorm:"INDEX unique(s)"`
+ }
+
+ return x.Sync(new(ProjectIssue))
+}
diff --git a/models/migrations/v1_23/v294_test.go b/models/migrations/v1_23/v294_test.go
new file mode 100644
index 0000000000..d9a44ad866
--- /dev/null
+++ b/models/migrations/v1_23/v294_test.go
@@ -0,0 +1,52 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_23 //nolint
+
+import (
+ "slices"
+ "testing"
+
+ "code.gitea.io/gitea/models/migrations/base"
+
+ "github.com/stretchr/testify/assert"
+ "xorm.io/xorm/schemas"
+)
+
+func Test_AddUniqueIndexForProjectIssue(t *testing.T) {
+ type ProjectIssue struct { //revive:disable-line:exported
+ ID int64 `xorm:"pk autoincr"`
+ IssueID int64 `xorm:"INDEX"`
+ ProjectID int64 `xorm:"INDEX"`
+ }
+
+ // Prepare and load the testing database
+ x, deferable := base.PrepareTestEnv(t, 0, new(ProjectIssue))
+ defer deferable()
+ if x == nil || t.Failed() {
+ return
+ }
+
+ cnt, err := x.Table("project_issue").Where("project_id=1 AND issue_id=1").Count()
+ assert.NoError(t, err)
+ assert.EqualValues(t, 2, cnt)
+
+ assert.NoError(t, AddUniqueIndexForProjectIssue(x))
+
+ cnt, err = x.Table("project_issue").Where("project_id=1 AND issue_id=1").Count()
+ assert.NoError(t, err)
+ assert.EqualValues(t, 1, cnt)
+
+ tables, err := x.DBMetas()
+ assert.NoError(t, err)
+ assert.EqualValues(t, 1, len(tables))
+ found := false
+ for _, index := range tables[0].Indexes {
+ if index.Type == schemas.UniqueType {
+ found = true
+ slices.Equal(index.Cols, []string{"project_id", "issue_id"})
+ break
+ }
+ }
+ assert.True(t, found)
+}