github.com/weaviate/weaviate@v1.24.6/usecases/schema/schema_comparison.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package schema 13 14 import ( 15 "bytes" 16 "encoding/json" 17 "fmt" 18 19 "github.com/weaviate/weaviate/entities/models" 20 "github.com/weaviate/weaviate/usecases/sharding" 21 ) 22 23 // Diff creates human-readable information about the difference in two schemas, 24 // returns a len=0 slice if schemas are identical 25 func Diff( 26 leftLabel string, left *State, 27 rightLabel string, right *State, 28 ) []string { 29 var msgs []string 30 31 if len(left.ObjectSchema.Classes) != len(right.ObjectSchema.Classes) { 32 msg := fmt.Sprintf("%s has %d classes, but %s has %d classes", 33 leftLabel, len(left.ObjectSchema.Classes), 34 rightLabel, len(right.ObjectSchema.Classes)) 35 msgs = append(msgs, msg) 36 } 37 38 leftClasses := map[string]*models.Class{} 39 rightClasses := map[string]*models.Class{} 40 41 for _, class := range right.ObjectSchema.Classes { 42 rightClasses[class.Class] = class 43 } 44 45 for _, classLeft := range left.ObjectSchema.Classes { 46 className := classLeft.Class 47 leftClasses[className] = classLeft 48 if classRight, ok := rightClasses[className]; !ok { 49 msg := fmt.Sprintf("class %s exists in %s, but not in %s", 50 className, leftLabel, rightLabel) 51 msgs = append(msgs, msg) 52 } else { 53 cc := classComparison{ 54 left: classLeft, 55 right: classRight, 56 leftLabel: leftLabel, 57 rightLabel: rightLabel, 58 } 59 msgs = append(msgs, cc.diff()...) 60 61 ssc := shardingStateComparison{ 62 left: left.ShardingState[className], 63 right: right.ShardingState[className], 64 leftLabel: leftLabel, 65 rightLabel: rightLabel, 66 className: className, 67 } 68 msgs = append(msgs, ssc.diff()...) 69 } 70 } 71 72 for className := range rightClasses { 73 if _, ok := leftClasses[className]; !ok { 74 msg := fmt.Sprintf("class %s exists in %s, but not in %s", 75 className, rightLabel, leftLabel) 76 msgs = append(msgs, msg) 77 } 78 } 79 80 return msgs 81 } 82 83 type classComparison struct { 84 left, right *models.Class 85 leftLabel, rightLabel string 86 msgs []string 87 } 88 89 func (cc *classComparison) addMsg(msg ...string) { 90 cc.msgs = append(cc.msgs, msg...) 91 } 92 93 func (cc *classComparison) diff() []string { 94 lj, _ := json.Marshal(cc.left) 95 rj, _ := json.Marshal(cc.right) 96 97 if bytes.Equal(lj, rj) { 98 // classes are identical, we are done 99 return nil 100 } 101 102 // classes are not identical, log this fact, then dig deeper to find the diff 103 msg := fmt.Sprintf("class %s exists in both, but is not identical: "+ 104 "size %d vs %d", cc.left.Class, len(lj), len(rj)) 105 cc.addMsg(msg) 106 107 pc := propsComparison{ 108 left: cc.left.Properties, 109 right: cc.right.Properties, 110 leftLabel: cc.leftLabel, 111 rightLabel: cc.rightLabel, 112 className: cc.left.Class, 113 } 114 cc.addMsg(pc.diff()...) 115 116 ccc := classConfigComparison{ 117 left: cc.left, 118 right: cc.right, 119 leftLabel: cc.leftLabel, 120 rightLabel: cc.rightLabel, 121 className: cc.left.Class, 122 } 123 cc.addMsg(ccc.diff()...) 124 125 return cc.msgs 126 } 127 128 type propsComparison struct { 129 left, right []*models.Property 130 leftLabel, rightLabel string 131 className string 132 msgs []string 133 } 134 135 func (pc *propsComparison) addMsg(msg ...string) { 136 pc.msgs = append(pc.msgs, msg...) 137 } 138 139 func (pc *propsComparison) diff() []string { 140 containedLeft := map[string]*models.Property{} 141 containedRight := map[string]*models.Property{} 142 143 for _, prop := range pc.left { 144 containedLeft[prop.Name] = prop 145 } 146 for _, prop := range pc.right { 147 if leftProp, ok := containedLeft[prop.Name]; !ok { 148 msg := fmt.Sprintf("class %s: property %s exists in %s, but not in %s", 149 pc.className, prop.Name, pc.rightLabel, pc.leftLabel) 150 pc.addMsg(msg) 151 } else { 152 pc.compareProp(leftProp, prop) 153 } 154 containedRight[prop.Name] = prop 155 } 156 157 for _, prop := range pc.left { 158 if _, ok := containedRight[prop.Name]; !ok { 159 msg := fmt.Sprintf("class %s: property %s exists in %s, but not in %s", 160 pc.className, prop.Name, pc.leftLabel, pc.rightLabel) 161 pc.addMsg(msg) 162 } 163 } 164 165 return pc.msgs 166 } 167 168 func (pc *propsComparison) compareProp(left, right *models.Property) { 169 lj, _ := json.Marshal(left) 170 rj, _ := json.Marshal(right) 171 172 if bytes.Equal(lj, rj) { 173 return 174 } 175 176 msg := fmt.Sprintf("class %s: property %s: mismatch: %s has %s, but %s has %s", 177 pc.className, left.Name, pc.leftLabel, lj, pc.rightLabel, rj) 178 pc.addMsg(msg) 179 } 180 181 type classConfigComparison struct { 182 left, right *models.Class 183 leftLabel, rightLabel string 184 className string 185 msgs []string 186 } 187 188 func (ccc *classConfigComparison) addMsg(msg ...string) { 189 ccc.msgs = append(ccc.msgs, msg...) 190 } 191 192 func (ccc *classConfigComparison) diff() []string { 193 ccc.compare(ccc.left.Description, ccc.right.Description, "description") 194 ccc.compare(ccc.left.InvertedIndexConfig, 195 ccc.right.InvertedIndexConfig, "inverted index config") 196 ccc.compare(ccc.left.ModuleConfig, 197 ccc.right.ModuleConfig, "module config") 198 ccc.compare(ccc.left.ReplicationConfig, 199 ccc.right.ReplicationConfig, "replication config") 200 ccc.compare(ccc.left.ShardingConfig, 201 ccc.right.ShardingConfig, "sharding config") 202 ccc.compare(ccc.left.VectorIndexConfig, 203 ccc.right.VectorIndexConfig, "vector index config") 204 ccc.compare(ccc.left.VectorIndexType, 205 ccc.right.VectorIndexType, "vector index type") 206 ccc.compare(ccc.left.Vectorizer, 207 ccc.right.Vectorizer, "vectorizer") 208 ccc.compare(ccc.left.VectorConfig, 209 ccc.right.VectorConfig, "vector config") 210 return ccc.msgs 211 } 212 213 func (ccc *classConfigComparison) compare( 214 left, right any, label string, 215 ) { 216 lj, _ := json.Marshal(left) 217 rj, _ := json.Marshal(right) 218 219 if bytes.Equal(lj, rj) { 220 return 221 } 222 223 msg := fmt.Sprintf("class %s: %s mismatch: %s has %s, but %s has %s", 224 ccc.className, label, ccc.leftLabel, lj, ccc.rightLabel, rj) 225 ccc.addMsg(msg) 226 } 227 228 type shardingStateComparison struct { 229 left, right *sharding.State 230 leftLabel, rightLabel string 231 className string 232 msgs []string 233 } 234 235 func (ssc *shardingStateComparison) addMsg(msg ...string) { 236 ssc.msgs = append(ssc.msgs, msg...) 237 } 238 239 func (ssc *shardingStateComparison) diff() []string { 240 if ssc.left == nil && ssc.right != nil { 241 msg := fmt.Sprintf("class %s: missing sharding state in %s", 242 ssc.className, ssc.leftLabel) 243 ssc.addMsg(msg) 244 return ssc.msgs 245 } 246 247 if ssc.left != nil && ssc.right == nil { 248 msg := fmt.Sprintf("class %s: missing sharding state in %s", 249 ssc.className, ssc.rightLabel) 250 ssc.addMsg(msg) 251 return ssc.msgs 252 } 253 254 lj, _ := json.Marshal(ssc.left) 255 rj, _ := json.Marshal(ssc.right) 256 257 if bytes.Equal(lj, rj) { 258 return ssc.msgs 259 } 260 261 msg := fmt.Sprintf("class %s: sharding state mismatch: "+ 262 "%s has %s, but %s has %s", 263 ssc.className, ssc.leftLabel, lj, ssc.rightLabel, rj) 264 ssc.addMsg(msg) 265 266 return ssc.msgs 267 }