mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 01:34:27 +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