Files
gitea-xorm-xorm/tests/sync_same_definition_indexes_test.go
T
dualfroz 0a2e162014 Match declared and existing indexes one to one during sync
Sync paired each declared index with the first existing index that had an
equal definition and did not mark that index as taken. Two declared indexes
with the same definition could therefore claim the same database index: the
other one was dropped as unmatched and never recreated, or a missing one was
never added.

Claim an existing index with the same name and an equal definition first,
then let the remaining declared indexes fall back to any unclaimed existing
index with an equal definition, so renamed indexes are still kept as before.
Iterate in name order so the result no longer depends on map order.
2026-09-29 15:57:45 +02:00

94 lines
2.7 KiB
Go

// Copyright 2026 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tests
import (
"testing"
"github.com/stretchr/testify/require"
"xorm.io/xorm/schemas"
)
// sameDefinitionIndexes declares two indexes with an identical definition.
// Sync must keep one database index per declared index instead of letting
// both declarations claim the same database index.
type sameDefinitionIndexes struct {
Id int64
RepoId int64
CreatedAt int64
}
func (*sameDefinitionIndexes) TableIndices() []*schemas.Index {
first := schemas.NewIndex("first", schemas.IndexType)
first.AddColumn("repo_id", "created_at")
second := schemas.NewIndex("second", schemas.IndexType)
second.AddColumn("repo_id", "created_at")
return []*schemas.Index{first, second}
}
func assertSameDefinitionIndexes(t *testing.T, bean *sameDefinitionIndexes) {
t.Helper()
tables, err := testEngine.DBMetas()
require.NoError(t, err)
require.Len(t, tables, 1)
require.Len(t, tables[0].Indexes, 2)
for _, expected := range bean.TableIndices() {
actual := tables[0].Indexes[expected.Name]
require.NotNil(t, actual, "missing index %s", expected.Name)
require.Equal(t, expected.Cols, actual.Cols)
}
}
func TestSyncSameDefinitionIndexes(t *testing.T) {
require.NoError(t, PrepareEngine())
bean := new(sameDefinitionIndexes)
require.NoError(t, testEngine.Sync(bean))
assertSameDefinitionIndexes(t, bean)
for i := 0; i < 10; i++ {
require.NoError(t, testEngine.Sync(bean))
assertSameDefinitionIndexes(t, bean)
}
for _, index := range bean.TableIndices() {
_, err := testEngine.Exec(testEngine.Dialect().DropIndexSQL(testEngine.TableName(bean), index))
require.NoError(t, err)
require.NoError(t, testEngine.Sync(bean))
assertSameDefinitionIndexes(t, bean)
}
}
type renamedIndexBefore struct {
Id int64
RepoId int64 `xorm:"index(old_name)"`
}
func (*renamedIndexBefore) TableName() string {
return "renamed_index"
}
type renamedIndexAfter struct {
Id int64
RepoId int64 `xorm:"index(new_name)"`
}
func (*renamedIndexAfter) TableName() string {
return "renamed_index"
}
// TestSyncRenamedIndexKeepsExisting guards the fallback: an index whose
// declared name changed but whose definition did not is kept as it is.
func TestSyncRenamedIndexKeepsExisting(t *testing.T) {
require.NoError(t, PrepareEngine())
require.NoError(t, testEngine.Sync(new(renamedIndexBefore)))
require.NoError(t, testEngine.Sync(new(renamedIndexAfter)))
tables, err := testEngine.DBMetas()
require.NoError(t, err)
require.Len(t, tables, 1)
require.Len(t, tables[0].Indexes, 1)
require.NotNil(t, tables[0].Indexes["old_name"])
}