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  }