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:
Nicolas
2026-06-17 17:11:46 +02:00
32 changed files with 1098 additions and 345 deletions
+2 -1
View File
@@ -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
}
+53 -20
View File
@@ -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")
}
+29 -49
View File
@@ -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")
}
+40
View File
@@ -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
}
+72
View File
@@ -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")
}