-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(schema): schema diffing (#21374)
- Loading branch information
Showing
9 changed files
with
1,137 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
Oops, something went wrong.