feat(schema): schema diffing (#21374)
This commit is contained in:
parent
8ddea56bb2
commit
51b63f7ffb
126
schema/diff/diff.go
Normal file
126
schema/diff/diff.go
Normal file
@ -0,0 +1,126 @@
|
||||
package diff
|
||||
|
||||
import "cosmossdk.io/schema"
|
||||
|
||||
// ModuleSchemaDiff represents the difference between two module schemas.
|
||||
type ModuleSchemaDiff struct {
|
||||
// AddedObjectTypes is a list of object types that were added.
|
||||
AddedObjectTypes []schema.ObjectType
|
||||
|
||||
// ChangedObjectTypes is a list of object types that were changed.
|
||||
ChangedObjectTypes []ObjectTypeDiff
|
||||
|
||||
// RemovedObjectTypes is a list of object types that were removed.
|
||||
RemovedObjectTypes []schema.ObjectType
|
||||
|
||||
// AddedEnumTypes is a list of enum types that were added.
|
||||
AddedEnumTypes []schema.EnumType
|
||||
|
||||
// ChangedEnumTypes is a list of enum types that were changed.
|
||||
ChangedEnumTypes []EnumTypeDiff
|
||||
|
||||
// RemovedEnumTypes is a list of enum types that were removed.
|
||||
RemovedEnumTypes []schema.EnumType
|
||||
}
|
||||
|
||||
// CompareModuleSchemas compares an old and a new module schemas and returns the difference between them.
|
||||
// If the schemas are equivalent, the Empty method of the returned ModuleSchemaDiff will return true.
|
||||
//
|
||||
// Indexer implementations can use these diffs to perform automatic schema migration.
|
||||
// The specific supported changes that a specific indexer supports are defined by that indexer implementation.
|
||||
// However, as a general rule, it is suggested that indexers support the following changes to module schemas:
|
||||
// - Adding object types
|
||||
// - Adding enum types
|
||||
// - Adding nullable value fields to object types
|
||||
// - Adding enum values to enum types
|
||||
//
|
||||
// These changes are officially considered "compatible" changes, and the HasCompatibleChanges method of the returned
|
||||
// ModuleSchemaDiff will return true if only compatible changes are present.
|
||||
// Module authors can use the above guidelines as a reference point for what changes are generally
|
||||
// considered safe to make to a module schema without breaking existing indexers.
|
||||
func CompareModuleSchemas(oldSchema, newSchema schema.ModuleSchema) ModuleSchemaDiff {
|
||||
diff := ModuleSchemaDiff{}
|
||||
|
||||
oldSchema.ObjectTypes(func(oldObj schema.ObjectType) bool {
|
||||
newTyp, found := newSchema.LookupType(oldObj.Name)
|
||||
newObj, typeMatch := newTyp.(schema.ObjectType)
|
||||
if !found || !typeMatch {
|
||||
diff.RemovedObjectTypes = append(diff.RemovedObjectTypes, oldObj)
|
||||
return true
|
||||
}
|
||||
objDiff := compareObjectType(oldObj, newObj)
|
||||
if !objDiff.Empty() {
|
||||
diff.ChangedObjectTypes = append(diff.ChangedObjectTypes, objDiff)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
newSchema.ObjectTypes(func(newObj schema.ObjectType) bool {
|
||||
oldTyp, found := oldSchema.LookupType(newObj.TypeName())
|
||||
_, typeMatch := oldTyp.(schema.ObjectType)
|
||||
if !found || !typeMatch {
|
||||
diff.AddedObjectTypes = append(diff.AddedObjectTypes, newObj)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
oldSchema.EnumTypes(func(oldEnum schema.EnumType) bool {
|
||||
newTyp, found := newSchema.LookupType(oldEnum.Name)
|
||||
newEnum, typeMatch := newTyp.(schema.EnumType)
|
||||
if !found || !typeMatch {
|
||||
diff.RemovedEnumTypes = append(diff.RemovedEnumTypes, oldEnum)
|
||||
return true
|
||||
}
|
||||
enumDiff := compareEnumType(oldEnum, newEnum)
|
||||
if !enumDiff.Empty() {
|
||||
diff.ChangedEnumTypes = append(diff.ChangedEnumTypes, enumDiff)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
newSchema.EnumTypes(func(newEnum schema.EnumType) bool {
|
||||
oldTyp, found := oldSchema.LookupType(newEnum.TypeName())
|
||||
_, typeMatch := oldTyp.(schema.EnumType)
|
||||
if !found || !typeMatch {
|
||||
diff.AddedEnumTypes = append(diff.AddedEnumTypes, newEnum)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
func (m ModuleSchemaDiff) Empty() bool {
|
||||
return len(m.AddedObjectTypes) == 0 &&
|
||||
len(m.ChangedObjectTypes) == 0 &&
|
||||
len(m.RemovedObjectTypes) == 0 &&
|
||||
len(m.AddedEnumTypes) == 0 &&
|
||||
len(m.ChangedEnumTypes) == 0 &&
|
||||
len(m.RemovedEnumTypes) == 0
|
||||
}
|
||||
|
||||
// HasCompatibleChanges returns true if the diff contains only compatible changes.
|
||||
// Compatible changes are changes that are generally safe to make to a module schema without breaking existing indexers
|
||||
// and indexers should aim to automatically migrate to such changes.
|
||||
// See the CompareModuleSchemas function for a list of changes that are considered compatible.
|
||||
func (m ModuleSchemaDiff) HasCompatibleChanges() bool {
|
||||
// object and enum types can be added but not removed
|
||||
// changed object and enum types must have compatible changes
|
||||
if len(m.RemovedObjectTypes) != 0 || len(m.RemovedEnumTypes) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, objectType := range m.ChangedObjectTypes {
|
||||
if !objectType.HasCompatibleChanges() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, enumType := range m.ChangedEnumTypes {
|
||||
if !enumType.HasCompatibleChanges() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
334
schema/diff/diff_test.go
Normal file
334
schema/diff/diff_test.go
Normal file
@ -0,0 +1,334 @@
|
||||
package diff
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"cosmossdk.io/schema"
|
||||
)
|
||||
|
||||
func TestCompareModuleSchemas(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
oldSchema schema.ModuleSchema
|
||||
newSchema schema.ModuleSchema
|
||||
diff ModuleSchemaDiff
|
||||
hasCompatibleChanges bool
|
||||
empty bool
|
||||
}{
|
||||
{
|
||||
name: "no change",
|
||||
oldSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}},
|
||||
}),
|
||||
newSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}},
|
||||
}),
|
||||
diff: ModuleSchemaDiff{},
|
||||
hasCompatibleChanges: true,
|
||||
empty: true,
|
||||
},
|
||||
{
|
||||
name: "object type added",
|
||||
oldSchema: mustModuleSchema(t),
|
||||
newSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}},
|
||||
}),
|
||||
diff: ModuleSchemaDiff{
|
||||
AddedObjectTypes: []schema.ObjectType{
|
||||
{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasCompatibleChanges: true,
|
||||
},
|
||||
{
|
||||
name: "object type removed",
|
||||
oldSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}},
|
||||
}),
|
||||
newSchema: mustModuleSchema(t),
|
||||
diff: ModuleSchemaDiff{
|
||||
RemovedObjectTypes: []schema.ObjectType{
|
||||
{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasCompatibleChanges: false,
|
||||
},
|
||||
{
|
||||
name: "object type changed, key field added",
|
||||
oldSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}},
|
||||
}),
|
||||
newSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}, {Name: "key2", Kind: schema.StringKind}},
|
||||
}),
|
||||
diff: ModuleSchemaDiff{
|
||||
ChangedObjectTypes: []ObjectTypeDiff{
|
||||
{
|
||||
Name: "object1",
|
||||
KeyFieldsDiff: FieldsDiff{
|
||||
Added: []schema.Field{
|
||||
{Name: "key2", Kind: schema.StringKind},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasCompatibleChanges: false,
|
||||
},
|
||||
{
|
||||
name: "object type changed, nullable value field added",
|
||||
oldSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}},
|
||||
}),
|
||||
newSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}},
|
||||
ValueFields: []schema.Field{{Name: "value1", Kind: schema.StringKind, Nullable: true}},
|
||||
}),
|
||||
diff: ModuleSchemaDiff{
|
||||
ChangedObjectTypes: []ObjectTypeDiff{
|
||||
{
|
||||
Name: "object1",
|
||||
ValueFieldsDiff: FieldsDiff{
|
||||
Added: []schema.Field{{Name: "value1", Kind: schema.StringKind, Nullable: true}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasCompatibleChanges: true,
|
||||
},
|
||||
{
|
||||
name: "object type changed, non-nullable value field added",
|
||||
oldSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}},
|
||||
}),
|
||||
newSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}},
|
||||
ValueFields: []schema.Field{{Name: "value1", Kind: schema.StringKind}},
|
||||
}),
|
||||
diff: ModuleSchemaDiff{
|
||||
ChangedObjectTypes: []ObjectTypeDiff{
|
||||
{
|
||||
Name: "object1",
|
||||
ValueFieldsDiff: FieldsDiff{
|
||||
Added: []schema.Field{{Name: "value1", Kind: schema.StringKind}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasCompatibleChanges: false,
|
||||
},
|
||||
{
|
||||
name: "object type changed, fields reordered",
|
||||
oldSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.StringKind}, {Name: "key2", Kind: schema.StringKind}},
|
||||
}),
|
||||
newSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key2", Kind: schema.StringKind}, {Name: "key1", Kind: schema.StringKind}},
|
||||
}),
|
||||
diff: ModuleSchemaDiff{
|
||||
ChangedObjectTypes: []ObjectTypeDiff{
|
||||
{
|
||||
Name: "object1",
|
||||
KeyFieldsDiff: FieldsDiff{
|
||||
OldOrder: []string{"key1", "key2"},
|
||||
NewOrder: []string{"key2", "key1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasCompatibleChanges: false,
|
||||
},
|
||||
{
|
||||
name: "enum type added, nullable value field added",
|
||||
oldSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.Int32Kind}},
|
||||
}),
|
||||
newSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.Int32Kind}},
|
||||
ValueFields: []schema.Field{
|
||||
{
|
||||
Name: "value1",
|
||||
Kind: schema.EnumKind,
|
||||
EnumType: schema.EnumType{Name: "enum1", Values: []string{"a", "b"}},
|
||||
Nullable: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
diff: ModuleSchemaDiff{
|
||||
ChangedObjectTypes: []ObjectTypeDiff{
|
||||
{
|
||||
Name: "object1",
|
||||
ValueFieldsDiff: FieldsDiff{
|
||||
Added: []schema.Field{
|
||||
{
|
||||
Name: "value1",
|
||||
Kind: schema.EnumKind,
|
||||
EnumType: schema.EnumType{Name: "enum1", Values: []string{"a", "b"}},
|
||||
Nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AddedEnumTypes: []schema.EnumType{
|
||||
{Name: "enum1", Values: []string{"a", "b"}},
|
||||
},
|
||||
},
|
||||
hasCompatibleChanges: true,
|
||||
},
|
||||
{
|
||||
name: "enum type removed",
|
||||
oldSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.Int32Kind}},
|
||||
ValueFields: []schema.Field{
|
||||
{
|
||||
Name: "value1",
|
||||
Kind: schema.EnumKind,
|
||||
EnumType: schema.EnumType{Name: "enum1", Values: []string{"a", "b"}},
|
||||
},
|
||||
},
|
||||
}),
|
||||
newSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.Int32Kind}},
|
||||
}),
|
||||
diff: ModuleSchemaDiff{
|
||||
ChangedObjectTypes: []ObjectTypeDiff{
|
||||
{
|
||||
Name: "object1",
|
||||
ValueFieldsDiff: FieldsDiff{
|
||||
Removed: []schema.Field{
|
||||
{
|
||||
Name: "value1",
|
||||
Kind: schema.EnumKind,
|
||||
EnumType: schema.EnumType{Name: "enum1", Values: []string{"a", "b"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RemovedEnumTypes: []schema.EnumType{
|
||||
{Name: "enum1", Values: []string{"a", "b"}},
|
||||
},
|
||||
},
|
||||
hasCompatibleChanges: false,
|
||||
},
|
||||
{
|
||||
name: "enum value added",
|
||||
oldSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.EnumKind, EnumType: schema.EnumType{Name: "enum1", Values: []string{"a"}}}},
|
||||
}),
|
||||
newSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.EnumKind, EnumType: schema.EnumType{Name: "enum1", Values: []string{"a", "b"}}}},
|
||||
}),
|
||||
diff: ModuleSchemaDiff{
|
||||
ChangedEnumTypes: []EnumTypeDiff{
|
||||
{
|
||||
Name: "enum1",
|
||||
AddedValues: []string{"b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasCompatibleChanges: true,
|
||||
},
|
||||
{
|
||||
name: "enum value removed",
|
||||
oldSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.EnumKind, EnumType: schema.EnumType{Name: "enum1", Values: []string{"a", "b", "c"}}}},
|
||||
}),
|
||||
newSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "object1",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.EnumKind, EnumType: schema.EnumType{Name: "enum1", Values: []string{"a", "b"}}}},
|
||||
}),
|
||||
diff: ModuleSchemaDiff{
|
||||
ChangedEnumTypes: []EnumTypeDiff{
|
||||
{
|
||||
Name: "enum1",
|
||||
RemovedValues: []string{"c"},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasCompatibleChanges: false,
|
||||
},
|
||||
{
|
||||
name: "object type and enum type name switched",
|
||||
oldSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "foo",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.EnumKind, EnumType: schema.EnumType{Name: "bar", Values: []string{"a"}}}},
|
||||
}),
|
||||
newSchema: mustModuleSchema(t, schema.ObjectType{
|
||||
Name: "bar",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.EnumKind, EnumType: schema.EnumType{Name: "foo", Values: []string{"a"}}}},
|
||||
}),
|
||||
diff: ModuleSchemaDiff{
|
||||
RemovedObjectTypes: []schema.ObjectType{
|
||||
{
|
||||
Name: "foo",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.EnumKind, EnumType: schema.EnumType{Name: "bar", Values: []string{"a"}}}},
|
||||
}},
|
||||
AddedObjectTypes: []schema.ObjectType{
|
||||
{
|
||||
Name: "bar",
|
||||
KeyFields: []schema.Field{{Name: "key1", Kind: schema.EnumKind, EnumType: schema.EnumType{Name: "foo", Values: []string{"a"}}}},
|
||||
},
|
||||
},
|
||||
RemovedEnumTypes: []schema.EnumType{
|
||||
{Name: "bar", Values: []string{"a"}},
|
||||
},
|
||||
AddedEnumTypes: []schema.EnumType{
|
||||
{Name: "foo", Values: []string{"a"}},
|
||||
},
|
||||
},
|
||||
hasCompatibleChanges: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := CompareModuleSchemas(tc.oldSchema, tc.newSchema)
|
||||
if !reflect.DeepEqual(got, tc.diff) {
|
||||
t.Errorf("CompareModuleSchemas() = %v, want %v", got, tc.diff)
|
||||
}
|
||||
hasCompatibleChanges := got.HasCompatibleChanges()
|
||||
if hasCompatibleChanges != tc.hasCompatibleChanges {
|
||||
t.Errorf("HasCompatibleChanges() = %v, want %v", hasCompatibleChanges, tc.hasCompatibleChanges)
|
||||
}
|
||||
if tc.empty != got.Empty() {
|
||||
t.Errorf("Empty() = %v, want %v", got.Empty(), tc.empty)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustModuleSchema(t *testing.T, objectTypes ...schema.ObjectType) schema.ModuleSchema {
|
||||
s, err := schema.NewModuleSchema(objectTypes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
4
schema/diff/doc.go
Normal file
4
schema/diff/doc.go
Normal file
@ -0,0 +1,4 @@
|
||||
// Package diff provides the CompareModuleSchemas function which compares module schemas
|
||||
// and their types and generates a structured diff. This diff can be used to check
|
||||
// compatibility between different versions of a module schema.
|
||||
package diff
|
||||
53
schema/diff/enum_diff.go
Normal file
53
schema/diff/enum_diff.go
Normal file
@ -0,0 +1,53 @@
|
||||
package diff
|
||||
|
||||
import "cosmossdk.io/schema"
|
||||
|
||||
// EnumTypeDiff represents the difference between two enum types.
|
||||
type EnumTypeDiff struct {
|
||||
// Name is the name of the enum type.
|
||||
Name string
|
||||
|
||||
// AddedValues is a list of values that were added.
|
||||
AddedValues []string
|
||||
|
||||
// RemovedValues is a list of values that were removed.
|
||||
RemovedValues []string
|
||||
}
|
||||
|
||||
func compareEnumType(oldEnum, newEnum schema.EnumType) EnumTypeDiff {
|
||||
diff := EnumTypeDiff{
|
||||
Name: oldEnum.TypeName(),
|
||||
}
|
||||
|
||||
newValues := make(map[string]struct{})
|
||||
for _, v := range newEnum.Values {
|
||||
newValues[v] = struct{}{}
|
||||
}
|
||||
|
||||
oldValues := make(map[string]struct{})
|
||||
for _, v := range oldEnum.Values {
|
||||
oldValues[v] = struct{}{}
|
||||
if _, ok := newValues[v]; !ok {
|
||||
diff.RemovedValues = append(diff.RemovedValues, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range newEnum.Values {
|
||||
if _, ok := oldValues[v]; !ok {
|
||||
diff.AddedValues = append(diff.AddedValues, v)
|
||||
}
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
// Empty returns true if the enum type diff has no changes.
|
||||
func (e EnumTypeDiff) Empty() bool {
|
||||
return len(e.AddedValues) == 0 && len(e.RemovedValues) == 0
|
||||
}
|
||||
|
||||
// HasCompatibleChanges returns true if the diff contains only compatible changes.
|
||||
// The only supported compatible change is adding values.
|
||||
func (e EnumTypeDiff) HasCompatibleChanges() bool {
|
||||
return len(e.RemovedValues) == 0
|
||||
}
|
||||
69
schema/diff/enum_diff_test.go
Normal file
69
schema/diff/enum_diff_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
package diff
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"cosmossdk.io/schema"
|
||||
)
|
||||
|
||||
func Test_compareEnumType(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
oldEnum schema.EnumType
|
||||
newEnum schema.EnumType
|
||||
diff EnumTypeDiff
|
||||
hasCompatibleChanges bool
|
||||
}{
|
||||
{
|
||||
name: "no change",
|
||||
oldEnum: schema.EnumType{
|
||||
Values: []string{"a", "b"},
|
||||
},
|
||||
newEnum: schema.EnumType{
|
||||
Values: []string{"a", "b"},
|
||||
},
|
||||
diff: EnumTypeDiff{},
|
||||
hasCompatibleChanges: true,
|
||||
},
|
||||
{
|
||||
name: "value added",
|
||||
oldEnum: schema.EnumType{
|
||||
Values: []string{"a"},
|
||||
},
|
||||
newEnum: schema.EnumType{
|
||||
Values: []string{"a", "b"},
|
||||
},
|
||||
diff: EnumTypeDiff{
|
||||
AddedValues: []string{"b"},
|
||||
},
|
||||
hasCompatibleChanges: true,
|
||||
},
|
||||
{
|
||||
name: "value removed",
|
||||
oldEnum: schema.EnumType{
|
||||
Values: []string{"a", "b"},
|
||||
},
|
||||
newEnum: schema.EnumType{
|
||||
Values: []string{"a"},
|
||||
},
|
||||
diff: EnumTypeDiff{
|
||||
RemovedValues: []string{"b"},
|
||||
},
|
||||
hasCompatibleChanges: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := compareEnumType(tc.oldEnum, tc.newEnum)
|
||||
if !reflect.DeepEqual(got, tc.diff) {
|
||||
t.Errorf("compareEnumType() = %v, want %v", got, tc.diff)
|
||||
}
|
||||
hasCompatibleChanges := got.HasCompatibleChanges()
|
||||
if hasCompatibleChanges != tc.hasCompatibleChanges {
|
||||
t.Errorf("HasCompatibleChanges() = %v, want %v", hasCompatibleChanges, tc.hasCompatibleChanges)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
73
schema/diff/field_diff.go
Normal file
73
schema/diff/field_diff.go
Normal file
@ -0,0 +1,73 @@
|
||||
package diff
|
||||
|
||||
import "cosmossdk.io/schema"
|
||||
|
||||
// FieldDiff represents the difference between two fields.
|
||||
// The KindChanged, NullableChanged, and EnumTypeChanged methods can be used to determine
|
||||
// what specific changes were made to the field.
|
||||
type FieldDiff struct {
|
||||
// Name is the name of the field.
|
||||
Name string
|
||||
|
||||
// OldKind is the old kind of the field. It will be InvalidKind if there was no change.
|
||||
OldKind schema.Kind
|
||||
|
||||
// NewKind is the new kind of the field. It will be InvalidKind if there was no change.
|
||||
NewKind schema.Kind
|
||||
|
||||
// OldNullable is the old nullable property of the field.
|
||||
OldNullable bool
|
||||
|
||||
// NewNullable is the new nullable property of the field.
|
||||
NewNullable bool
|
||||
|
||||
// OldEnumType is the name of the old enum type of the field.
|
||||
// It will be empty if the field is not an enum type or if there was no change.
|
||||
OldEnumType string
|
||||
|
||||
// NewEnumType is the name of the new enum type of the field.
|
||||
// It will be empty if the field is not an enum type or if there was no change.
|
||||
NewEnumType string
|
||||
}
|
||||
|
||||
func compareField(oldField, newField schema.Field) FieldDiff {
|
||||
diff := FieldDiff{
|
||||
Name: oldField.Name,
|
||||
}
|
||||
if oldField.Kind != newField.Kind {
|
||||
diff.OldKind = oldField.Kind
|
||||
diff.NewKind = newField.Kind
|
||||
}
|
||||
|
||||
diff.OldNullable = oldField.Nullable
|
||||
diff.NewNullable = newField.Nullable
|
||||
|
||||
if oldField.EnumType.Name != newField.EnumType.Name {
|
||||
diff.OldEnumType = oldField.EnumType.Name
|
||||
diff.NewEnumType = newField.EnumType.Name
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
// Empty returns true if the field diff has no changes.
|
||||
func (d FieldDiff) Empty() bool {
|
||||
return !d.KindChanged() && !d.NullableChanged() && !d.EnumTypeChanged()
|
||||
}
|
||||
|
||||
// KindChanged returns true if the field kind changed.
|
||||
func (d FieldDiff) KindChanged() bool {
|
||||
return d.OldKind != d.NewKind
|
||||
}
|
||||
|
||||
// NullableChanged returns true if the field nullable property changed.
|
||||
func (d FieldDiff) NullableChanged() bool {
|
||||
return d.OldNullable != d.NewNullable
|
||||
}
|
||||
|
||||
// EnumTypeChanged returns true if the field enum type changed.
|
||||
// Note that if the enum type name remained the same but the values of
|
||||
// the enum type changed, that won't be reported here but rather in the
|
||||
// ModuleSchemaDiff's ChangedEnumTypes field.
|
||||
func (d FieldDiff) EnumTypeChanged() bool {
|
||||
return d.OldEnumType != d.NewEnumType
|
||||
}
|
||||
63
schema/diff/field_diff_test.go
Normal file
63
schema/diff/field_diff_test.go
Normal file
@ -0,0 +1,63 @@
|
||||
package diff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"cosmossdk.io/schema"
|
||||
)
|
||||
|
||||
func Test_compareField(t *testing.T) {
|
||||
tests := []struct {
|
||||
oldField schema.Field
|
||||
newField schema.Field
|
||||
wantDiff FieldDiff
|
||||
trueF func(FieldDiff) bool
|
||||
}{
|
||||
{
|
||||
oldField: schema.Field{Kind: schema.Int32Kind},
|
||||
newField: schema.Field{Kind: schema.Int32Kind},
|
||||
wantDiff: FieldDiff{},
|
||||
trueF: FieldDiff.Empty,
|
||||
},
|
||||
{
|
||||
oldField: schema.Field{Kind: schema.StringKind},
|
||||
newField: schema.Field{Kind: schema.Int32Kind},
|
||||
wantDiff: FieldDiff{
|
||||
OldKind: schema.StringKind,
|
||||
NewKind: schema.Int32Kind,
|
||||
},
|
||||
trueF: FieldDiff.KindChanged,
|
||||
},
|
||||
{
|
||||
oldField: schema.Field{Kind: schema.StringKind},
|
||||
newField: schema.Field{Kind: schema.StringKind, Nullable: true},
|
||||
wantDiff: FieldDiff{
|
||||
NewNullable: true,
|
||||
},
|
||||
trueF: FieldDiff.NullableChanged,
|
||||
},
|
||||
{
|
||||
oldField: schema.Field{Kind: schema.EnumKind, EnumType: schema.EnumType{Name: "old"}},
|
||||
newField: schema.Field{Kind: schema.EnumKind, EnumType: schema.EnumType{Name: "new"}},
|
||||
wantDiff: FieldDiff{
|
||||
OldEnumType: "old",
|
||||
NewEnumType: "new",
|
||||
},
|
||||
trueF: FieldDiff.EnumTypeChanged,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||
gotDiff := compareField(tt.oldField, tt.newField)
|
||||
if !reflect.DeepEqual(gotDiff, tt.wantDiff) {
|
||||
t.Errorf("compareField() = %v, want %v", gotDiff, tt.wantDiff)
|
||||
}
|
||||
if tt.trueF != nil && !tt.trueF(gotDiff) {
|
||||
t.Errorf("trueF() = false, want true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
146
schema/diff/object_type_diff.go
Normal file
146
schema/diff/object_type_diff.go
Normal file
@ -0,0 +1,146 @@
|
||||
package diff
|
||||
|
||||
import "cosmossdk.io/schema"
|
||||
|
||||
// ObjectTypeDiff represents the difference between two object types.
|
||||
// The Empty method of KeyFieldsDiff and ValueFieldsDiff can be used to determine
|
||||
// if there were any changes to the key fields or value fields.
|
||||
type ObjectTypeDiff struct {
|
||||
// Name is the name of the object type.
|
||||
Name string
|
||||
|
||||
// KeyFieldsDiff is the difference between the key fields of the object type.
|
||||
KeyFieldsDiff FieldsDiff
|
||||
|
||||
// ValueFieldsDiff is the difference between the value fields of the object type.
|
||||
ValueFieldsDiff FieldsDiff
|
||||
}
|
||||
|
||||
// FieldsDiff represents the difference between two lists of fields.
|
||||
// Fields will be compared based on name first, and then if there is any
|
||||
// difference in ordering that will be reported in OldOrder and NewOrder.
|
||||
// If there is any order change, the OrderChanged method will return true.
|
||||
// If fields were only added or removed but the order otherwise didn't change,
|
||||
// then the OldOrder and NewOrder will still be empty.
|
||||
type FieldsDiff struct {
|
||||
// Added is a list of fields that were added.
|
||||
Added []schema.Field
|
||||
|
||||
// Changed is a list of fields that were changed.
|
||||
Changed []FieldDiff
|
||||
|
||||
// Removed is a list of fields that were removed.
|
||||
Removed []schema.Field
|
||||
|
||||
// OldOrder is the order of fields in the old list. It will be empty if the order has not changed.
|
||||
OldOrder []string
|
||||
|
||||
// NewOrder is the order of fields in the new list. It will be empty if the order has not changed.
|
||||
NewOrder []string
|
||||
}
|
||||
|
||||
func compareObjectType(oldObj, newObj schema.ObjectType) ObjectTypeDiff {
|
||||
diff := ObjectTypeDiff{
|
||||
Name: oldObj.TypeName(),
|
||||
}
|
||||
|
||||
diff.KeyFieldsDiff = compareFields(oldObj.KeyFields, newObj.KeyFields)
|
||||
diff.ValueFieldsDiff = compareFields(oldObj.ValueFields, newObj.ValueFields)
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
func compareFields(oldFields, newFields []schema.Field) FieldsDiff {
|
||||
diff := FieldsDiff{}
|
||||
|
||||
newFieldMap := make(map[string]schema.Field)
|
||||
for _, f := range newFields {
|
||||
newFieldMap[f.Name] = f
|
||||
}
|
||||
|
||||
oldFieldMap := make(map[string]schema.Field)
|
||||
for _, oldField := range oldFields {
|
||||
oldFieldMap[oldField.Name] = oldField
|
||||
newField, ok := newFieldMap[oldField.Name]
|
||||
if !ok {
|
||||
diff.Removed = append(diff.Removed, oldField)
|
||||
} else {
|
||||
fieldDiff := compareField(oldField, newField)
|
||||
if !fieldDiff.Empty() {
|
||||
diff.Changed = append(diff.Changed, fieldDiff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, newField := range newFields {
|
||||
if _, ok := oldFieldMap[newField.Name]; !ok {
|
||||
diff.Added = append(diff.Added, newField)
|
||||
}
|
||||
}
|
||||
|
||||
oldOrder := make([]string, 0, len(oldFields))
|
||||
for _, f := range oldFields {
|
||||
oldOrder = append(oldOrder, f.Name)
|
||||
}
|
||||
|
||||
orderChanged := false
|
||||
newOrder := make([]string, 0, len(newFields))
|
||||
for i, f := range newFields {
|
||||
newOrder = append(newOrder, f.Name)
|
||||
if i < len(oldOrder) && f.Name != oldOrder[i] {
|
||||
orderChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
if orderChanged {
|
||||
diff.OldOrder = oldOrder
|
||||
diff.NewOrder = newOrder
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
// Empty returns true if the object type diff has no changes.
|
||||
func (o ObjectTypeDiff) Empty() bool {
|
||||
return o.KeyFieldsDiff.Empty() && o.ValueFieldsDiff.Empty()
|
||||
}
|
||||
|
||||
// HasCompatibleChanges returns true if the diff contains only compatible changes.
|
||||
// The only supported compatible change is adding nullable value fields.
|
||||
func (o ObjectTypeDiff) HasCompatibleChanges() bool {
|
||||
if !o.KeyFieldsDiff.Empty() {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(o.ValueFieldsDiff.Changed) != 0 ||
|
||||
len(o.ValueFieldsDiff.Removed) != 0 ||
|
||||
o.ValueFieldsDiff.OrderChanged() {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, field := range o.ValueFieldsDiff.Added {
|
||||
if !field.Nullable {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Empty returns true if the field diff has no changes.
|
||||
func (d FieldsDiff) Empty() bool {
|
||||
if len(d.Added) != 0 || len(d.Changed) != 0 || len(d.Removed) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return !d.OrderChanged()
|
||||
}
|
||||
|
||||
// OrderChanged returns true if the field order changed.
|
||||
func (d FieldsDiff) OrderChanged() bool {
|
||||
if len(d.OldOrder) == 0 && len(d.NewOrder) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
269
schema/diff/object_type_diff_test.go
Normal file
269
schema/diff/object_type_diff_test.go
Normal file
@ -0,0 +1,269 @@
|
||||
package diff
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"cosmossdk.io/schema"
|
||||
)
|
||||
|
||||
func Test_objectTypeDiff(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
oldType schema.ObjectType
|
||||
newType schema.ObjectType
|
||||
diff ObjectTypeDiff
|
||||
trueF func(ObjectTypeDiff) bool
|
||||
hasCompatibleChanges bool
|
||||
}{
|
||||
{
|
||||
name: "no change",
|
||||
oldType: schema.ObjectType{
|
||||
KeyFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}},
|
||||
},
|
||||
newType: schema.ObjectType{
|
||||
KeyFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}},
|
||||
},
|
||||
diff: ObjectTypeDiff{},
|
||||
trueF: ObjectTypeDiff.Empty,
|
||||
hasCompatibleChanges: true,
|
||||
},
|
||||
{
|
||||
name: "key fields changed",
|
||||
oldType: schema.ObjectType{
|
||||
KeyFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}},
|
||||
},
|
||||
newType: schema.ObjectType{
|
||||
KeyFields: []schema.Field{{Name: "id", Kind: schema.StringKind}},
|
||||
},
|
||||
diff: ObjectTypeDiff{
|
||||
KeyFieldsDiff: FieldsDiff{
|
||||
Changed: []FieldDiff{
|
||||
{
|
||||
Name: "id",
|
||||
OldKind: schema.Int32Kind,
|
||||
NewKind: schema.StringKind,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
trueF: func(d ObjectTypeDiff) bool { return !d.KeyFieldsDiff.Empty() },
|
||||
hasCompatibleChanges: false,
|
||||
},
|
||||
{
|
||||
name: "value fields changed",
|
||||
oldType: schema.ObjectType{
|
||||
ValueFields: []schema.Field{{Name: "name", Kind: schema.StringKind}},
|
||||
},
|
||||
newType: schema.ObjectType{
|
||||
ValueFields: []schema.Field{{Name: "name", Kind: schema.Int32Kind}},
|
||||
},
|
||||
diff: ObjectTypeDiff{
|
||||
ValueFieldsDiff: FieldsDiff{
|
||||
Changed: []FieldDiff{
|
||||
{
|
||||
Name: "name",
|
||||
OldKind: schema.StringKind,
|
||||
NewKind: schema.Int32Kind,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
trueF: func(d ObjectTypeDiff) bool { return !d.ValueFieldsDiff.Empty() },
|
||||
hasCompatibleChanges: false,
|
||||
},
|
||||
{
|
||||
name: "nullable value field added",
|
||||
oldType: schema.ObjectType{
|
||||
ValueFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}},
|
||||
},
|
||||
newType: schema.ObjectType{
|
||||
ValueFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}, {Name: "name", Kind: schema.StringKind, Nullable: true}},
|
||||
},
|
||||
diff: ObjectTypeDiff{
|
||||
ValueFieldsDiff: FieldsDiff{
|
||||
Added: []schema.Field{{Name: "name", Kind: schema.StringKind, Nullable: true}},
|
||||
},
|
||||
},
|
||||
trueF: func(d ObjectTypeDiff) bool { return !d.ValueFieldsDiff.Empty() },
|
||||
hasCompatibleChanges: true,
|
||||
},
|
||||
{
|
||||
name: "non-nullable value field added",
|
||||
oldType: schema.ObjectType{
|
||||
ValueFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}},
|
||||
},
|
||||
newType: schema.ObjectType{
|
||||
ValueFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}, {Name: "name", Kind: schema.StringKind}},
|
||||
},
|
||||
diff: ObjectTypeDiff{
|
||||
ValueFieldsDiff: FieldsDiff{
|
||||
Added: []schema.Field{{Name: "name", Kind: schema.StringKind}},
|
||||
},
|
||||
},
|
||||
trueF: func(d ObjectTypeDiff) bool { return !d.ValueFieldsDiff.Empty() },
|
||||
hasCompatibleChanges: false,
|
||||
},
|
||||
{
|
||||
name: "fields reordered",
|
||||
oldType: schema.ObjectType{
|
||||
KeyFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}, {Name: "name", Kind: schema.StringKind}},
|
||||
ValueFields: []schema.Field{{Name: "x", Kind: schema.Int32Kind}, {Name: "y", Kind: schema.StringKind}},
|
||||
},
|
||||
newType: schema.ObjectType{
|
||||
KeyFields: []schema.Field{{Name: "name", Kind: schema.StringKind}, {Name: "id", Kind: schema.Int32Kind}},
|
||||
ValueFields: []schema.Field{{Name: "y", Kind: schema.StringKind}, {Name: "x", Kind: schema.Int32Kind}},
|
||||
},
|
||||
diff: ObjectTypeDiff{
|
||||
KeyFieldsDiff: FieldsDiff{
|
||||
OldOrder: []string{"id", "name"},
|
||||
NewOrder: []string{"name", "id"},
|
||||
},
|
||||
ValueFieldsDiff: FieldsDiff{
|
||||
OldOrder: []string{"x", "y"},
|
||||
NewOrder: []string{"y", "x"},
|
||||
},
|
||||
},
|
||||
trueF: func(d ObjectTypeDiff) bool { return !d.KeyFieldsDiff.Empty() && !d.ValueFieldsDiff.Empty() },
|
||||
hasCompatibleChanges: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := compareObjectType(tc.oldType, tc.newType)
|
||||
if !reflect.DeepEqual(got, tc.diff) {
|
||||
t.Errorf("compareObjectType() = %v, want %v", got, tc.diff)
|
||||
}
|
||||
hasCompatibleChanges := got.HasCompatibleChanges()
|
||||
if hasCompatibleChanges != tc.hasCompatibleChanges {
|
||||
t.Errorf("HasCompatibleChanges() = %v, want %v", hasCompatibleChanges, tc.hasCompatibleChanges)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_fieldsDiff(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
oldFields []schema.Field
|
||||
newFields []schema.Field
|
||||
diff FieldsDiff
|
||||
}{
|
||||
{
|
||||
name: "no change",
|
||||
oldFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}},
|
||||
newFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}},
|
||||
},
|
||||
{
|
||||
name: "field added",
|
||||
oldFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}},
|
||||
newFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}, {Name: "name", Kind: schema.StringKind}},
|
||||
diff: FieldsDiff{
|
||||
Added: []schema.Field{{Name: "name", Kind: schema.StringKind}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "field removed",
|
||||
oldFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}, {Name: "name", Kind: schema.StringKind}},
|
||||
newFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}},
|
||||
diff: FieldsDiff{
|
||||
Removed: []schema.Field{{Name: "name", Kind: schema.StringKind}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "field changed",
|
||||
oldFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}, {Name: "name", Kind: schema.StringKind}},
|
||||
newFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}, {Name: "name", Kind: schema.Int32Kind}},
|
||||
diff: FieldsDiff{
|
||||
Changed: []FieldDiff{
|
||||
{
|
||||
Name: "name",
|
||||
OldKind: schema.StringKind,
|
||||
NewKind: schema.Int32Kind,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "field order changed",
|
||||
oldFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}, {Name: "name", Kind: schema.StringKind}},
|
||||
newFields: []schema.Field{{Name: "name", Kind: schema.StringKind}, {Name: "id", Kind: schema.Int32Kind}},
|
||||
diff: FieldsDiff{
|
||||
OldOrder: []string{"id", "name"},
|
||||
NewOrder: []string{"name", "id"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "field order changed with added fields",
|
||||
oldFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}},
|
||||
newFields: []schema.Field{{Name: "name", Kind: schema.StringKind}, {Name: "id", Kind: schema.Int32Kind}},
|
||||
diff: FieldsDiff{
|
||||
Added: []schema.Field{{Name: "name", Kind: schema.StringKind}},
|
||||
OldOrder: []string{"id"},
|
||||
NewOrder: []string{"name", "id"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "field order changed with removed fields",
|
||||
oldFields: []schema.Field{{Name: "name", Kind: schema.StringKind}, {Name: "id", Kind: schema.Int32Kind}},
|
||||
newFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}},
|
||||
diff: FieldsDiff{
|
||||
Removed: []schema.Field{{Name: "name", Kind: schema.StringKind}},
|
||||
OldOrder: []string{"name", "id"},
|
||||
NewOrder: []string{"id"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "field order changed with changed fields",
|
||||
oldFields: []schema.Field{{Name: "name", Kind: schema.StringKind}, {Name: "id", Kind: schema.Int32Kind}},
|
||||
newFields: []schema.Field{{Name: "id", Kind: schema.Int32Kind}, {Name: "name", Kind: schema.Int32Kind}},
|
||||
diff: FieldsDiff{
|
||||
Changed: []FieldDiff{
|
||||
{
|
||||
Name: "name",
|
||||
OldKind: schema.StringKind,
|
||||
NewKind: schema.Int32Kind,
|
||||
},
|
||||
},
|
||||
OldOrder: []string{"name", "id"},
|
||||
NewOrder: []string{"id", "name"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "added, removed, changed and reordered fields",
|
||||
oldFields: []schema.Field{
|
||||
{Name: "id", Kind: schema.Int32Kind},
|
||||
{Name: "name", Kind: schema.StringKind},
|
||||
{Name: "age", Kind: schema.Int32Kind},
|
||||
},
|
||||
newFields: []schema.Field{
|
||||
{Name: "name", Kind: schema.Int32Kind},
|
||||
{Name: "age", Kind: schema.Int32Kind},
|
||||
{Name: "email", Kind: schema.StringKind},
|
||||
},
|
||||
diff: FieldsDiff{
|
||||
Added: []schema.Field{{Name: "email", Kind: schema.StringKind}},
|
||||
Removed: []schema.Field{{Name: "id", Kind: schema.Int32Kind}},
|
||||
Changed: []FieldDiff{
|
||||
{
|
||||
Name: "name",
|
||||
OldKind: schema.StringKind,
|
||||
NewKind: schema.Int32Kind,
|
||||
},
|
||||
},
|
||||
OldOrder: []string{"id", "name", "age"},
|
||||
NewOrder: []string{"name", "age", "email"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := compareFields(tc.oldFields, tc.newFields)
|
||||
if !reflect.DeepEqual(got, tc.diff) {
|
||||
t.Errorf("compareFields() = %v, want %v", got, tc.diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user