Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix migration v292 #30153

Merged
merged 16 commits into from
Mar 28, 2024
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,8 @@ var migrations = []Migration{
// v291 -> v292
NewMigration("Add Index to attachment.comment_id", v1_22.AddCommentIDIndexofAttachment),
// v292 -> v293
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),
}

Expand Down
84 changes: 4 additions & 80 deletions models/migrations/v1_22/v292.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,83 +3,7 @@

package v1_22 //nolint

import (
"code.gitea.io/gitea/models/project"
"code.gitea.io/gitea/modules/setting"

"xorm.io/builder"
"xorm.io/xorm"
)

// CheckProjectColumnsConsistency ensures there is exactly one default board per project present
func CheckProjectColumnsConsistency(x *xorm.Engine) error {
sess := x.NewSession()
defer sess.Close()

if err := sess.Begin(); err != nil {
return err
}

limit := setting.Database.IterateBufferSize
if limit <= 0 {
limit = 50
}

start := 0

for {
var projects []project.Project
if err := sess.SQL("SELECT DISTINCT `p`.`id`, `p`.`creator_id` FROM `project` `p` WHERE (SELECT COUNT(*) FROM `project_board` `pb` WHERE `pb`.`project_id` = `p`.`id` AND `pb`.`default` = ?) != 1", true).
Limit(limit, start).
Find(&projects); err != nil {
return err
}

if len(projects) == 0 {
break
}
start += len(projects)

for _, p := range projects {
var boards []project.Board
if err := sess.Where("project_id=? AND `default` = ?", p.ID, true).OrderBy("sorting").Find(&boards); err != nil {
return err
}

if len(boards) == 0 {
if _, err := sess.Insert(project.Board{
ProjectID: p.ID,
Default: true,
Title: "Uncategorized",
CreatorID: p.CreatorID,
}); err != nil {
return err
}
continue
}

var boardsToUpdate []int64
for id, b := range boards {
if id > 0 {
boardsToUpdate = append(boardsToUpdate, b.ID)
}
}

if _, err := sess.Where(builder.Eq{"project_id": p.ID}.And(builder.In("id", boardsToUpdate))).
Cols("`default`").Update(&project.Board{Default: false}); err != nil {
return err
}
}

if start%1000 == 0 {
if err := sess.Commit(); err != nil {
return err
}
if err := sess.Begin(); err != nil {
return err
}
}
}

return sess.Commit()
}
// NOTE: noop the original migration has bug which some projects will be skip, so
// these projects will have no default board.
// So that this migration will be skipped and go to v293.go
// This file is a placeholder so that readers can know what happened
111 changes: 111 additions & 0 deletions models/migrations/v1_22/v293.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_22 //nolint

import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"

"xorm.io/xorm"
)

// CheckProjectColumnsConsistency ensures there is exactly one default board per project present
func CheckProjectColumnsConsistency(x *xorm.Engine) error {
sess := x.NewSession()
defer sess.Close()

if err := sess.Begin(); err != nil {
return err
}

limit := setting.Database.IterateBufferSize
if limit <= 0 {
limit = 50
}

type Project struct {
ID int64
CreatorID int64
BoardID int64
}

type ProjectBoard struct {
ID int64 `xorm:"pk autoincr"`
Title string
Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board
Sorting int8 `xorm:"NOT NULL DEFAULT 0"`
Color string `xorm:"VARCHAR(7)"`

ProjectID int64 `xorm:"INDEX NOT NULL"`
CreatorID int64 `xorm:"NOT NULL"`

CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
lunny marked this conversation as resolved.
Show resolved Hide resolved

for {
if err := sess.Begin(); err != nil {
return err
}

// all these projects without defaults will be fixed in the same loop, so
// we just need to always get projects without defaults until no such project
var projects []*Project
if err := sess.Select("project.id as id, project.creator_id, project_board.id as board_id").
Join("LEFT", "project_board", "project_board.project_id = project.id AND project_board.`default`=?", true).
Where("project_board.id is NULL OR project_board.id = 0").
Limit(limit).
Find(&projects); err != nil {
return err
}
if len(projects) == 0 {
break
}

for _, p := range projects {
if _, err := sess.Insert(ProjectBoard{
ProjectID: p.ID,
Default: true,
Title: "Uncategorized",
CreatorID: p.CreatorID,
}); err != nil {
return err
}
}

if err := sess.Commit(); err != nil {
return err
}
}

return removeDuplicatedBoardDefault(x)
}

func removeDuplicatedBoardDefault(x *xorm.Engine) error {
type ProjectInfo struct {
ProjectID int64
DefaultNum int
}
var projects []ProjectInfo
if err := x.Select("project_id, count(*) AS default_num").
Table("project_board").
Where("`default` = ?", true).
GroupBy("project_id").
Having("count(*) > 1").
Find(&projects); err != nil {
return err
}

for _, project := range projects {
if _, err := x.Where("project_id=?", project.ProjectID).
Table("project_board").
Limit(project.DefaultNum - 1).
Update(map[string]bool{
"`default`": false,
}); err != nil {
return err
}
}
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ func Test_CheckProjectColumnsConsistency(t *testing.T) {
assert.Equal(t, int64(1), defaultBoard.ProjectID)
assert.True(t, defaultBoard.Default)

// check if multiple defaults were removed
// check if multiple defaults, previous were removed and last will be kept
lunny marked this conversation as resolved.
Show resolved Hide resolved
expectDefaultBoard, err := project.GetBoard(db.DefaultContext, 2)
assert.NoError(t, err)
assert.Equal(t, int64(2), expectDefaultBoard.ProjectID)
assert.True(t, expectDefaultBoard.Default)
assert.False(t, expectDefaultBoard.Default)

expectNonDefaultBoard, err := project.GetBoard(db.DefaultContext, 3)
assert.NoError(t, err)
assert.Equal(t, int64(2), expectNonDefaultBoard.ProjectID)
assert.False(t, expectNonDefaultBoard.Default)
assert.True(t, expectNonDefaultBoard.Default)
}
Loading