0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-06-10 18:46:01 +02:00

Simplify v332 migration and add cross-DB test

Tested against the CI image versions (postgres:14, bitnamilegacy/mysql:8.0,
mcr.microsoft.com/mssql/server:2019-latest) plus SQLite. Both the original
implementation (per-dialect SQL) and a naive `base.ModifyColumn` with
`DefaultIsEmpty: false` were tried. Findings:

- DefaultIsEmpty: false fails on MSSQL with "Incorrect syntax near the
  keyword 'DEFAULT'" because MSSQL's ALTER COLUMN does not accept inline
  DEFAULT (it lives in a separate constraint object).
- DefaultIsEmpty: true succeeds on MSSQL (existing default constraint
  unaffected) and Postgres (DEFAULT constraint is independent of TYPE)
  but drops the DEFAULT on MySQL because MODIFY COLUMN rewrites all
  column attributes.

Settled on the minimal cross-DB form: base.ModifyColumn with
DefaultIsEmpty: true to widen the type, then a MySQL-only follow-up
`ALTER ... SET DEFAULT 0` to restore the default that MODIFY COLUMN drops.

The new test seeds rows at the int8 boundary (0 and 127), runs the
migration, asserts the column type widened, the rows preserved, and that
inserting a value > 127 succeeds afterward.

Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
This commit is contained in:
silverwind 2026-04-27 12:55:08 +02:00 committed by beardev-in
parent efe43882d5
commit d6ae7c1c50
No known key found for this signature in database
GPG Key ID: 625E0CB70AFF32B9
2 changed files with 107 additions and 6 deletions

View File

@ -10,17 +10,32 @@ import (
"xorm.io/xorm/schemas"
)
// WidenProjectBoardSorting changes project_board.sorting from int8 (TINYINT) to int (INTEGER)
// so the public API can expose a regular int and lift the 127 column upper bound.
// WidenProjectBoardSorting changes project_board.sorting from int8 (TINYINT/SMALLINT)
// to int. The previous int8 type capped projects at 127 columns and forced the API
// to truncate user-supplied sort values. SQLite uses dynamic typing so the schema
// type is cosmetic; existing rows already store the wider value.
//
// `base.ModifyColumn` is called with DefaultIsEmpty: true because MSSQL's ALTER
// COLUMN syntax does not accept inline DEFAULT (it would error on the keyword).
// On MySQL, MODIFY COLUMN without an explicit DEFAULT drops the existing default,
// so it has to be reapplied. On Postgres and MSSQL the DEFAULT constraint is
// maintained separately from the column type and is preserved automatically.
func WidenProjectBoardSorting(x *xorm.Engine) error {
if x.Dialect().URI().DBType == schemas.SQLITE {
return nil
}
return base.ModifyColumn(x, "project_board", &schemas.Column{
if err := base.ModifyColumn(x, "project_board", &schemas.Column{
Name: "sorting",
SQLType: schemas.SQLType{Name: "INT"},
Nullable: false,
Default: "0",
DefaultIsEmpty: false,
})
DefaultIsEmpty: true,
}); err != nil {
return err
}
if x.Dialect().URI().DBType == schemas.MYSQL {
if _, err := x.Exec("ALTER TABLE `project_board` ALTER `sorting` SET DEFAULT 0"); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,86 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_27
import (
"testing"
"code.gitea.io/gitea/models/migrations/base"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_WidenProjectBoardSorting(t *testing.T) {
// Pre-migration shape of project_board (only the column we care about plus the
// minimum needed to pass NOT NULL constraints during INSERT).
type projectBoard struct {
ID int64 `xorm:"pk autoincr"`
Title string
Sorting int8 `xorm:"NOT NULL DEFAULT 0"`
ProjectID int64 `xorm:"INDEX NOT NULL"`
CreatorID int64 `xorm:"NOT NULL"`
}
x, deferrable := base.PrepareTestEnv(t, 0, new(projectBoard))
defer deferrable()
// Seed two rows: one at the int8 lower bound and one at the upper bound,
// proving the migration preserves edge values without truncation.
_, err := x.Insert(
&projectBoard{Title: "first", Sorting: 0, ProjectID: 1, CreatorID: 1},
&projectBoard{Title: "boundary", Sorting: 127, ProjectID: 1, CreatorID: 1},
)
require.NoError(t, err)
require.NoError(t, WidenProjectBoardSorting(x))
// Verify column type widened (skipped on SQLite where the migration is a no-op).
if !setting.Database.Type.IsSQLite3() {
table := base.LoadTableSchemasMap(t, x)["project_board"]
require.NotNil(t, table)
col := table.GetColumn("sorting")
require.NotNil(t, col)
// Each dialect spells INT differently; verify the type is one of the wider
// names rather than TINYINT/INT2.
assert.Contains(t,
[]string{"INT", "INTEGER", "INT4"},
col.SQLType.Name,
"sorting column should have widened to int",
)
assert.False(t, col.Nullable, "sorting column should remain NOT NULL")
assert.Equal(t, "0", col.Default, "sorting column should keep DEFAULT 0")
}
// Existing rows must be preserved verbatim.
type projectBoardWide struct {
ID int64 `xorm:"pk autoincr"`
Title string
Sorting int `xorm:"NOT NULL DEFAULT 0"`
ProjectID int64 `xorm:"INDEX NOT NULL"`
CreatorID int64 `xorm:"NOT NULL"`
}
rows := make([]*projectBoardWide, 0, 2)
require.NoError(t, x.Table("project_board").Asc("id").Find(&rows))
require.Len(t, rows, 2)
assert.Equal(t, 0, rows[0].Sorting)
assert.Equal(t, 127, rows[1].Sorting)
// Inserting a value > 127 must succeed after widening (would have failed with
// TINYINT/INT2 either by truncation or out-of-range error).
_, err = x.Table("project_board").Insert(&projectBoardWide{
Title: "wide",
Sorting: 30000,
ProjectID: 1,
CreatorID: 1,
})
require.NoError(t, err)
var got projectBoardWide
has, err := x.Table("project_board").Where("title=?", "wide").Get(&got)
require.NoError(t, err)
require.True(t, has)
assert.Equal(t, 30000, got.Sorting, "value should round-trip without truncation")
}