fix(mssql): expand legacy issue and comment long-text columns (#38120)

## Summary

This fixes pull request creation failures on upgraded MSSQL instances
where legacy `issue` and `comment` long-text columns are still limited
to `nvarchar(4000)`.

When a PR is created, Gitea stores a pull request push timeline comment
containing JSON with `commit_ids`. For PRs with many commits, that
payload can exceed 4000 characters and MSSQL rejects the insert with:

> String or binary data would be truncated in table 'comment', column
'content'

This change adds a migration that expands the affected legacy MSSQL
columns to `NVARCHAR(MAX)`.

The previous migration in models/migrations/v1_16/v191.go only applies
to MySQL, not MSSQL.

migration now skips columns already using NVARCHAR(MAX) / VARCHAR(MAX)

Closes #37893

## Changes

- add migration `338` for MSSQL-only long-text expansion
- expand:
  - `issue.content`
  - `comment.content`
  - `comment.patch`
- add an MSSQL regression test that starts from a legacy `VARCHAR(4000)`
schema and verifies inserts larger than 4000 characters succeed after
migration

## Why this approach

The current model already declares these fields as `LONGTEXT`, so the
bug is caused by stale upgraded MSSQL schemas rather than by PR creation
logic itself. Fixing the schema is the smallest and safest change, and
also prevents similar truncation issues for other long issue/comment
content.
This commit is contained in:
Lunny Xiao
2026-06-16 10:40:13 -07:00
committed by GitHub
parent b7bd222e87
commit 0be7543560
3 changed files with 126 additions and 0 deletions
+1
View File
@@ -415,6 +415,7 @@ 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, "Expand legacy MSSQL issue/comment long-text columns", v1_27.ExpandIssueAndCommentLongTextFieldsForMSSQL),
}
return preparedMigrations
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_27
import (
"fmt"
"strings"
"gitea.dev/models/db"
"gitea.dev/models/migrations/base"
"xorm.io/xorm/schemas"
)
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 _, 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)
}
}
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")
}
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_27
import (
"strings"
"testing"
"gitea.dev/models/migrations/migrationtest"
"gitea.dev/modules/setting"
"github.com/stretchr/testify/require"
)
type issueBeforeLongTextMSSQLMigration struct {
ID int64 `xorm:"pk autoincr"`
Content string `xorm:"VARCHAR(4000)"`
}
func (issueBeforeLongTextMSSQLMigration) TableName() string {
return "issue"
}
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")
}
x, deferrable := migrationtest.PrepareTestEnv(t, 0, new(issueBeforeLongTextMSSQLMigration), new(commentBeforeLongTextMSSQLMigration))
defer deferrable()
require.NoError(t, ExpandIssueAndCommentLongTextFieldsForMSSQL(x))
require.NoError(t, ExpandIssueAndCommentLongTextFieldsForMSSQL(x))
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)
}