mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-02 06:39:32 +02:00
Merge remote-tracking branch 'origin/main' into fix/action-dashboard-index-created-unix
# Conflicts: # models/migrations/migrations.go # models/migrations/v1_27/v338.go # models/migrations/v1_27/v338_test.go
This commit is contained in:
@@ -415,7 +415,8 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(335, "Add reusable workflow fields and action_run_attempt_job_id_index table for ActionRunJob", v1_27.AddReusableWorkflowFieldsToActionRunJob),
|
||||
newMigration(336, "Add ActionRunJobSummary table", v1_27.AddActionRunJobSummaryTable),
|
||||
newMigration(337, "Add visibility to team", v1_27.AddVisibilityToTeam),
|
||||
newMigration(338, "Extend action c_u index to include created_unix for faster dashboard feed queries", v1_27.AddCreatedUnixToActionUserIsDeletedIndex),
|
||||
newMigration(338, "Expand legacy MSSQL issue/comment long-text columns", v1_27.ExpandIssueAndCommentLongTextFieldsForMSSQL),
|
||||
newMigration(339, "Extend action c_u index to include created_unix for faster dashboard feed queries", v1_27.AddCreatedUnixToActionUserIsDeletedIndex),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
@@ -4,37 +4,70 @@
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/migrations/base"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// AddCreatedUnixToActionUserIsDeletedIndex extends the c_u composite index on
|
||||
// the action table to include created_unix, enabling efficient ORDER BY on the
|
||||
// dashboard feed query without a full sort of all matching rows.
|
||||
func AddCreatedUnixToActionUserIsDeletedIndex(x db.EngineMigration) error {
|
||||
// xorm Sync cannot reliably update an index when another index already
|
||||
// covers the same columns in a different order (Equal() is order-insensitive).
|
||||
// Drop the old c_u index explicitly, then recreate it with the new column set.
|
||||
indexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
type issueWithLongTextContent struct {
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
}
|
||||
|
||||
func (issueWithLongTextContent) TableName() string {
|
||||
return "issue"
|
||||
}
|
||||
|
||||
type commentWithLongTextFields struct {
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
PatchQuoted string `xorm:"LONGTEXT patch"`
|
||||
}
|
||||
|
||||
func (commentWithLongTextFields) TableName() string {
|
||||
return "comment"
|
||||
}
|
||||
|
||||
func isMSSQLMaxTextColumn(column *schemas.Column) bool {
|
||||
if column.Length != -1 {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(column.SQLType.Name, schemas.Varchar) || strings.EqualFold(column.SQLType.Name, schemas.NVarchar)
|
||||
}
|
||||
|
||||
func modifyLongTextColumnsForMSSQL(x db.EngineMigration, bean any, columnNames ...string) error {
|
||||
table, err := x.TableInfo(bean)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, idx := range indexes {
|
||||
if idx.Name == "c_u" {
|
||||
if _, err := x.Exec(x.Dialect().DropIndexSQL("action", idx)); err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
|
||||
for _, columnName := range columnNames {
|
||||
column := table.GetColumn(columnName)
|
||||
if column == nil {
|
||||
return fmt.Errorf("column %s does not exist in table %s", columnName, table.Name)
|
||||
}
|
||||
if isMSSQLMaxTextColumn(column) {
|
||||
continue
|
||||
}
|
||||
if err := base.ModifyColumn(x, table.Name, column); err != nil {
|
||||
return fmt.Errorf("modify %s.%s: %w", table.Name, columnName, err)
|
||||
}
|
||||
}
|
||||
|
||||
newIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||
newIndex.AddColumn("user_id", "is_deleted", "created_unix")
|
||||
if _, err := x.Exec(x.Dialect().CreateIndexSQL("action", newIndex)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExpandIssueAndCommentLongTextFieldsForMSSQL expands legacy MSSQL nvarchar(4000)
|
||||
// columns to nvarchar(max) so PR push comments and long issue content are not truncated.
|
||||
func ExpandIssueAndCommentLongTextFieldsForMSSQL(x db.EngineMigration) error {
|
||||
if x.Dialect().URI().DBType != schemas.MSSQL {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := modifyLongTextColumnsForMSSQL(x, new(issueWithLongTextContent), "content"); err != nil {
|
||||
return err
|
||||
}
|
||||
return modifyLongTextColumnsForMSSQL(x, new(commentWithLongTextFields), "content", "patch")
|
||||
}
|
||||
|
||||
@@ -4,69 +4,49 @@
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/migrations/migrationtest"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type actionBeforeV338 struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"INDEX"`
|
||||
OpType int
|
||||
ActUserID int64
|
||||
RepoID int64
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Content string `xorm:"TEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
type issueBeforeLongTextMSSQLMigration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Content string `xorm:"VARCHAR(4000)"`
|
||||
}
|
||||
|
||||
func (actionBeforeV338) TableName() string { return "action" }
|
||||
|
||||
func (actionBeforeV338) TableIndices() []*schemas.Index {
|
||||
repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
|
||||
repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
|
||||
|
||||
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
||||
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||
|
||||
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||
|
||||
// old 2-column index, before the migration
|
||||
cuIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||
cuIndex.AddColumn("user_id", "is_deleted")
|
||||
|
||||
actUserUserIndex := schemas.NewIndex("au_c_u", schemas.IndexType)
|
||||
actUserUserIndex.AddColumn("act_user_id", "created_unix", "user_id")
|
||||
|
||||
return []*schemas.Index{actUserIndex, repoIndex, cudIndex, cuIndex, actUserUserIndex}
|
||||
func (issueBeforeLongTextMSSQLMigration) TableName() string {
|
||||
return "issue"
|
||||
}
|
||||
|
||||
func Test_AddCreatedUnixToActionUserIsDeletedIndex(t *testing.T) {
|
||||
x, deferable := migrationtest.PrepareTestEnv(t, 0, new(actionBeforeV338))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
type commentBeforeLongTextMSSQLMigration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Content string `xorm:"VARCHAR(4000)"`
|
||||
Patch string `xorm:"VARCHAR(4000) patch"`
|
||||
}
|
||||
|
||||
func (commentBeforeLongTextMSSQLMigration) TableName() string {
|
||||
return "comment"
|
||||
}
|
||||
|
||||
func Test_ExpandIssueAndCommentLongTextFieldsForMSSQL(t *testing.T) {
|
||||
if !setting.Database.Type.IsMSSQL() {
|
||||
t.Skip("Only MSSQL needs to expand legacy nvarchar(4000) long-text columns")
|
||||
}
|
||||
|
||||
indexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted"}, false), "old c_u index should exist before migration")
|
||||
assert.False(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted", "created_unix"}, false), "new c_u index should not exist before migration")
|
||||
x, deferrable := migrationtest.PrepareTestEnv(t, 0, new(issueBeforeLongTextMSSQLMigration), new(commentBeforeLongTextMSSQLMigration))
|
||||
defer deferrable()
|
||||
|
||||
require.NoError(t, AddCreatedUnixToActionUserIsDeletedIndex(x))
|
||||
require.NoError(t, ExpandIssueAndCommentLongTextFieldsForMSSQL(x))
|
||||
require.NoError(t, ExpandIssueAndCommentLongTextFieldsForMSSQL(x))
|
||||
|
||||
indexes, err = x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
longText := strings.Repeat("x", 5000)
|
||||
_, err := x.Insert(&issueBeforeLongTextMSSQLMigration{Content: longText})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = x.Insert(&commentBeforeLongTextMSSQLMigration{Content: longText, Patch: longText})
|
||||
require.NoError(t, err)
|
||||
assert.False(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted"}, false), "old 2-column c_u index should be gone after migration")
|
||||
assert.True(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted", "created_unix"}, false), "new 3-column c_u index must exist after migration")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// AddCreatedUnixToActionUserIsDeletedIndex extends the c_u composite index on
|
||||
// the action table to include created_unix, enabling efficient ORDER BY on the
|
||||
// dashboard feed query without a full sort of all matching rows.
|
||||
func AddCreatedUnixToActionUserIsDeletedIndex(x db.EngineMigration) error {
|
||||
// xorm Sync cannot reliably update an index when another index already
|
||||
// covers the same columns in a different order (Equal() is order-insensitive).
|
||||
// Drop the old c_u index explicitly, then recreate it with the new column set.
|
||||
indexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, idx := range indexes {
|
||||
if idx.Name == "c_u" {
|
||||
if _, err := x.Exec(x.Dialect().DropIndexSQL("action", idx)); err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
newIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||
newIndex.AddColumn("user_id", "is_deleted", "created_unix")
|
||||
if _, err := x.Exec(x.Dialect().CreateIndexSQL("action", newIndex)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/migrations/migrationtest"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type actionBeforeV339 struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"INDEX"`
|
||||
OpType int
|
||||
ActUserID int64
|
||||
RepoID int64
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Content string `xorm:"TEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
}
|
||||
|
||||
func (actionBeforeV339) TableName() string { return "action" }
|
||||
|
||||
func (actionBeforeV339) TableIndices() []*schemas.Index {
|
||||
repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
|
||||
repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
|
||||
|
||||
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
||||
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||
|
||||
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||
|
||||
// old 2-column index, before the migration
|
||||
cuIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||
cuIndex.AddColumn("user_id", "is_deleted")
|
||||
|
||||
actUserUserIndex := schemas.NewIndex("au_c_u", schemas.IndexType)
|
||||
actUserUserIndex.AddColumn("act_user_id", "created_unix", "user_id")
|
||||
|
||||
return []*schemas.Index{actUserIndex, repoIndex, cudIndex, cuIndex, actUserUserIndex}
|
||||
}
|
||||
|
||||
func Test_AddCreatedUnixToActionUserIsDeletedIndex(t *testing.T) {
|
||||
x, deferable := migrationtest.PrepareTestEnv(t, 0, new(actionBeforeV339))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
indexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted"}, false), "old c_u index should exist before migration")
|
||||
assert.False(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted", "created_unix"}, false), "new c_u index should not exist before migration")
|
||||
|
||||
require.NoError(t, AddCreatedUnixToActionUserIsDeletedIndex(x))
|
||||
|
||||
indexes, err = x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted"}, false), "old 2-column c_u index should be gone after migration")
|
||||
assert.True(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted", "created_unix"}, false), "new 3-column c_u index must exist after migration")
|
||||
}
|
||||
Reference in New Issue
Block a user