Skip to content

Commit 51b63f7

Browse files
authored
feat(schema): schema diffing (#21374)
1 parent 8ddea56 commit 51b63f7

File tree

9 files changed

+1137
-0
lines changed

9 files changed

+1137
-0
lines changed

schema/diff/diff.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package diff
2+
3+
import "cosmossdk.io/schema"
4+
5+
// ModuleSchemaDiff represents the difference between two module schemas.
6+
type ModuleSchemaDiff struct {
7+
// AddedObjectTypes is a list of object types that were added.
8+
AddedObjectTypes []schema.ObjectType
9+
10+
// ChangedObjectTypes is a list of object types that were changed.
11+
ChangedObjectTypes []ObjectTypeDiff
12+
13+
// RemovedObjectTypes is a list of object types that were removed.
14+
RemovedObjectTypes []schema.ObjectType
15+
16+
// AddedEnumTypes is a list of enum types that were added.
17+
AddedEnumTypes []schema.EnumType
18+
19+
// ChangedEnumTypes is a list of enum types that were changed.
20+
ChangedEnumTypes []EnumTypeDiff
21+
22+
// RemovedEnumTypes is a list of enum types that were removed.
23+
RemovedEnumTypes []schema.EnumType
24+
}
25+
26+
// CompareModuleSchemas compares an old and a new module schemas and returns the difference between them.
27+
// If the schemas are equivalent, the Empty method of the returned ModuleSchemaDiff will return true.
28+
//
29+
// Indexer implementations can use these diffs to perform automatic schema migration.
30+
// The specific supported changes that a specific indexer supports are defined by that indexer implementation.
31+
// However, as a general rule, it is suggested that indexers support the following changes to module schemas:
32+
// - Adding object types
33+
// - Adding enum types
34+
// - Adding nullable value fields to object types
35+
// - Adding enum values to enum types
36+
//
37+
// These changes are officially considered "compatible" changes, and the HasCompatibleChanges method of the returned
38+
// ModuleSchemaDiff will return true if only compatible changes are present.
39+
// Module authors can use the above guidelines as a reference point for what changes are generally
40+
// considered safe to make to a module schema without breaking existing indexers.
41+
func CompareModuleSchemas(oldSchema, newSchema schema.ModuleSchema) ModuleSchemaDiff {
42+
diff := ModuleSchemaDiff{}
43+
44+
oldSchema.ObjectTypes(func(oldObj schema.ObjectType) bool {
45+
newTyp, found := newSchema.LookupType(oldObj.Name)
46+
newObj, typeMatch := newTyp.(schema.ObjectType)
47+
if !found || !typeMatch {
48+
diff.RemovedObjectTypes = append(diff.RemovedObjectTypes, oldObj)
49+
return true
50+
}
51+
objDiff := compareObjectType(oldObj, newObj)
52+
if !objDiff.Empty() {
53+
diff.ChangedObjectTypes = append(diff.ChangedObjectTypes, objDiff)
54+
}
55+
return true
56+
})
57+
58+
newSchema.ObjectTypes(func(newObj schema.ObjectType) bool {
59+
oldTyp, found := oldSchema.LookupType(newObj.TypeName())
60+
_, typeMatch := oldTyp.(schema.ObjectType)
61+
if !found || !typeMatch {
62+
diff.AddedObjectTypes = append(diff.AddedObjectTypes, newObj)
63+
}
64+
return true
65+
})
66+
67+
oldSchema.EnumTypes(func(oldEnum schema.EnumType) bool {
68+
newTyp, found := newSchema.LookupType(oldEnum.Name)
69+
newEnum, typeMatch := newTyp.(schema.EnumType)
70+
if !found || !typeMatch {
71+
diff.RemovedEnumTypes = append(diff.RemovedEnumTypes, oldEnum)
72+
return true
73+
}
74+
enumDiff := compareEnumType(oldEnum, newEnum)
75+
if !enumDiff.Empty() {
76+
diff.ChangedEnumTypes = append(diff.ChangedEnumTypes, enumDiff)
77+
}
78+
return true
79+
})
80+
81+
newSchema.EnumTypes(func(newEnum schema.EnumType) bool {
82+
oldTyp, found := oldSchema.LookupType(newEnum.TypeName())
83+
_, typeMatch := oldTyp.(schema.EnumType)
84+
if !found || !typeMatch {
85+
diff.AddedEnumTypes = append(diff.AddedEnumTypes, newEnum)
86+
}
87+
return true
88+
})
89+
90+
return diff
91+
}
92+
93+
func (m ModuleSchemaDiff) Empty() bool {
94+
return len(m.AddedObjectTypes) == 0 &&
95+
len(m.ChangedObjectTypes) == 0 &&
96+
len(m.RemovedObjectTypes) == 0 &&
97+
len(m.AddedEnumTypes) == 0 &&
98+
len(m.ChangedEnumTypes) == 0 &&
99+
len(m.RemovedEnumTypes) == 0
100+
}
101+
102+
// HasCompatibleChanges returns true if the diff contains only compatible changes.
103+
// Compatible changes are changes that are generally safe to make to a module schema without breaking existing indexers
104+
// and indexers should aim to automatically migrate to such changes.
105+
// See the CompareModuleSchemas function for a list of changes that are considered compatible.
106+
func (m ModuleSchemaDiff) HasCompatibleChanges() bool {
107+
// object and enum types can be added but not removed
108+
// changed object and enum types must have compatible changes
109+
if len(m.RemovedObjectTypes) != 0 || len(m.RemovedEnumTypes) != 0 {
110+
return false
111+
}
112+
113+
for _, objectType := range m.ChangedObjectTypes {
114+
if !objectType.HasCompatibleChanges() {
115+
return false
116+
}
117+
}
118+
119+
for _, enumType := range m.ChangedEnumTypes {
120+
if !enumType.HasCompatibleChanges() {
121+
return false
122+
}
123+
}
124+
125+
return true
126+
}

0 commit comments

Comments
 (0)