Skip to content

Commit

Permalink
Add Sync options to ignore constrains and indices (#2320)
Browse files Browse the repository at this point in the history
needed for woodpecker-ci/woodpecker#2117

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2320
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
  • Loading branch information
6543 authored and lunny committed Aug 9, 2023
1 parent ac84217 commit db7c264
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 16 deletions.
1 change: 1 addition & 0 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ type EngineInterface interface {
ShowSQL(show ...bool)
Sync(...interface{}) error
Sync2(...interface{}) error
SyncWithOptions(SyncOptions, ...interface{}) (*SyncResult, error)
StoreEngine(storeEngine string) *Session
TableInfo(bean interface{}) (*schemas.Table, error)
TableName(interface{}, ...bool) string
Expand Down
54 changes: 38 additions & 16 deletions sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import (

type SyncOptions struct {
WarnIfDatabaseColumnMissed bool
// IgnoreConstrains will not add, delete or update unique constrains
IgnoreConstrains bool
// IgnoreIndices will not add or delete indices
IgnoreIndices bool
}

type SyncResult struct{}
Expand Down Expand Up @@ -49,6 +53,8 @@ func (session *Session) Sync2(beans ...interface{}) error {
func (session *Session) Sync(beans ...interface{}) error {
_, err := session.SyncWithOptions(SyncOptions{
WarnIfDatabaseColumnMissed: false,
IgnoreConstrains: false,
IgnoreIndices: false,
}, beans...)
return err
}
Expand Down Expand Up @@ -103,15 +109,20 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{})
return nil, err
}

err = session.createUniques(bean)
if err != nil {
return nil, err
if !opts.IgnoreConstrains {
err = session.createUniques(bean)
if err != nil {
return nil, err
}
}

err = session.createIndexes(bean)
if err != nil {
return nil, err
if !opts.IgnoreIndices {
err = session.createIndexes(bean)
if err != nil {
return nil, err
}
}

continue
}

Expand Down Expand Up @@ -208,9 +219,12 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{})
}
}

// indices found in orig table
foundIndexNames := make(map[string]bool)
// indices to be added
addedNames := make(map[string]*schemas.Index)

// drop indices that exist in orig and new table schema but are not equal
for name, index := range table.Indexes {
var oriIndex *schemas.Index
for name2, index2 := range oriTable.Indexes {
Expand All @@ -221,24 +235,31 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{})
}
}

if oriIndex != nil {
if oriIndex.Type != index.Type {
sql := engine.dialect.DropIndexSQL(tbNameWithSchema, oriIndex)
_, err = session.exec(sql)
if err != nil {
return nil, err
}
oriIndex = nil
if oriIndex != nil && oriIndex.Type != index.Type {
sql := engine.dialect.DropIndexSQL(tbNameWithSchema, oriIndex)
_, err = session.exec(sql)
if err != nil {
return nil, err
}
oriIndex = nil
}

if oriIndex == nil {
addedNames[name] = index
}
}

// drop all indices that do not exist in new schema or have changed
for name2, index2 := range oriTable.Indexes {
if _, ok := foundIndexNames[name2]; !ok {
// ignore based on there type
if (index2.Type == schemas.IndexType && opts.IgnoreIndices) ||
(index2.Type == schemas.UniqueType && opts.IgnoreConstrains) {
// make sure we do not add a index with same name later
delete(addedNames, name2)
continue
}

sql := engine.dialect.DropIndexSQL(tbNameWithSchema, index2)
_, err = session.exec(sql)
if err != nil {
Expand All @@ -247,12 +268,13 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{})
}
}

// Add new indices because either they did not exist before or were dropped to update them
for name, index := range addedNames {
if index.Type == schemas.UniqueType {
if index.Type == schemas.UniqueType && !opts.IgnoreConstrains {
session.statement.RefTable = table
session.statement.SetTableName(tbNameWithSchema)
err = session.addUnique(tbNameWithSchema, name)
} else if index.Type == schemas.IndexType {
} else if index.Type == schemas.IndexType && !opts.IgnoreIndices {
session.statement.RefTable = table
session.statement.SetTableName(tbNameWithSchema)
err = session.addIndex(tbNameWithSchema, name)
Expand Down
99 changes: 99 additions & 0 deletions tests/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"github.com/stretchr/testify/assert"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)

Expand Down Expand Up @@ -645,3 +646,101 @@ func TestCollate(t *testing.T) {
})
assert.NoError(t, err)
}

type SyncWithOpts1 struct {
Id int64
Index int `xorm:"index"`
Unique int `xorm:"unique"`
Group1 int `xorm:"index(ttt)"`
Group2 int `xorm:"index(ttt)"`
UniGroup1 int `xorm:"unique(lll)"`
UniGroup2 int `xorm:"unique(lll)"`
}

func (*SyncWithOpts1) TableName() string {
return "sync_with_opts"
}

type SyncWithOpts2 struct {
Id int64
Index int `xorm:"index"`
Unique int `xorm:""`
Group1 int `xorm:"index(ttt)"`
Group2 int `xorm:"index(ttt)"`
UniGroup1 int `xorm:""`
UniGroup2 int `xorm:"unique(lll)"`
}

func (*SyncWithOpts2) TableName() string {
return "sync_with_opts"
}

type SyncWithOpts3 struct {
Id int64
Index int `xorm:""`
Unique int `xorm:"unique"`
Group1 int `xorm:""`
Group2 int `xorm:"index(ttt)"`
UniGroup1 int `xorm:"unique(lll)"`
UniGroup2 int `xorm:"unique(lll)"`
}

func (*SyncWithOpts3) TableName() string {
return "sync_with_opts"
}

func TestSyncWithOptions(t *testing.T) {
assert.NoError(t, PrepareEngine())

// ignore indices and constrains
result, err := testEngine.SyncWithOptions(xorm.SyncOptions{IgnoreIndices: true, IgnoreConstrains: true}, &SyncWithOpts1{})
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Len(t, getIndicesOfBeanFromDB(t, &SyncWithOpts1{}), 0)

// only ignore indices
result, err = testEngine.SyncWithOptions(xorm.SyncOptions{IgnoreConstrains: true}, &SyncWithOpts2{})
assert.NoError(t, err)
assert.NotNil(t, result)
indices := getIndicesOfBeanFromDB(t, &SyncWithOpts1{})
assert.Len(t, indices, 2)
assert.ElementsMatch(t, []string{"ttt", "index"}, getKeysFromMap(indices))

// only ignore constrains
result, err = testEngine.SyncWithOptions(xorm.SyncOptions{IgnoreIndices: true}, &SyncWithOpts3{})
assert.NoError(t, err)
assert.NotNil(t, result)
indices = getIndicesOfBeanFromDB(t, &SyncWithOpts1{})
assert.Len(t, indices, 4)
assert.ElementsMatch(t, []string{"ttt", "index", "unique", "lll"}, getKeysFromMap(indices))

tableInfoFromStruct, _ := testEngine.TableInfo(&SyncWithOpts1{})
assert.ElementsMatch(t, getKeysFromMap(tableInfoFromStruct.Indexes), getKeysFromMap(getIndicesOfBeanFromDB(t, &SyncWithOpts1{})))

}

func getIndicesOfBeanFromDB(t *testing.T, bean interface{}) map[string]*schemas.Index {
dbm, err := testEngine.DBMetas()
assert.NoError(t, err)

tName := testEngine.TableName(bean)
var tSchema *schemas.Table
for _, t := range dbm {
if t.Name == tName {
tSchema = t
break
}
}
if !assert.NotNil(t, tSchema) {
return nil
}
return tSchema.Indexes
}

func getKeysFromMap(m map[string]*schemas.Index) []string {
var ss []string
for k := range m {
ss = append(ss, k)
}
return ss
}

0 comments on commit db7c264

Please sign in to comment.