mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Add unique index for project_issue to prevent duplicate data (#30190)
Fix #27639
This commit is contained in:
		| @@ -0,0 +1,9 @@ | |||||||
|  | - | ||||||
|  |   id: 1 | ||||||
|  |   project_id: 1 | ||||||
|  |   issue_id: 1 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 2 | ||||||
|  |   project_id: 1 | ||||||
|  |   issue_id: 1 | ||||||
| @@ -21,6 +21,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models/migrations/v1_20" | 	"code.gitea.io/gitea/models/migrations/v1_20" | ||||||
| 	"code.gitea.io/gitea/models/migrations/v1_21" | 	"code.gitea.io/gitea/models/migrations/v1_21" | ||||||
| 	"code.gitea.io/gitea/models/migrations/v1_22" | 	"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_6" | ||||||
| 	"code.gitea.io/gitea/models/migrations/v1_7" | 	"code.gitea.io/gitea/models/migrations/v1_7" | ||||||
| 	"code.gitea.io/gitea/models/migrations/v1_8" | 	"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), | 	NewMigration("Ensure every project has exactly one default column - No Op", noopMigration), | ||||||
| 	// v293 -> v294 | 	// v293 -> v294 | ||||||
| 	NewMigration("Ensure every project has exactly one default column", v1_22.CheckProjectColumnsConsistency), | 	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 | // GetCurrentDBVersion returns the current db version | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								models/migrations/v1_23/main_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								models/migrations/v1_23/main_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								models/migrations/v1_23/v294.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								models/migrations/v1_23/v294.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								models/migrations/v1_23/v294_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								models/migrations/v1_23/v294_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Lunny Xiao
					Lunny Xiao