github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/merge/three_way_json_differ.go (about)

     1  // Copyright 2023 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package merge
    16  
    17  import (
    18  	"bytes"
    19  	"io"
    20  	"strings"
    21  
    22  	"github.com/dolthub/go-mysql-server/sql/types"
    23  
    24  	"github.com/dolthub/dolt/go/store/prolly/tree"
    25  )
    26  
    27  type ThreeWayJsonDiffer struct {
    28  	leftDiffer, rightDiffer           tree.JsonDiffer
    29  	leftCurrentDiff, rightCurrentDiff *tree.JsonDiff
    30  	leftIsDone, rightIsDone           bool
    31  }
    32  
    33  func NewThreeWayJsonDiffer(base, left, right types.JsonObject) ThreeWayJsonDiffer {
    34  	return ThreeWayJsonDiffer{
    35  		leftDiffer:  tree.NewJsonDiffer("$", base, left),
    36  		rightDiffer: tree.NewJsonDiffer("$", base, right),
    37  	}
    38  }
    39  
    40  type ThreeWayJsonDiff struct {
    41  	// Op indicates the type of diff
    42  	Op tree.DiffOp
    43  	// a partial set of document values are set
    44  	// depending on the diffOp
    45  	Key                       string
    46  	Base, Left, Right, Merged *types.JSONDocument
    47  }
    48  
    49  func (differ *ThreeWayJsonDiffer) Next() (ThreeWayJsonDiff, error) {
    50  	for {
    51  		err := differ.loadNextDiff()
    52  		if err != nil {
    53  			return ThreeWayJsonDiff{}, err
    54  		}
    55  
    56  		if differ.rightIsDone {
    57  			// We don't care if there are remaining left diffs because they don't affect the merge.
    58  			return ThreeWayJsonDiff{}, io.EOF
    59  		}
    60  
    61  		if differ.leftIsDone {
    62  			return differ.processRightSideOnlyDiff(), nil
    63  		}
    64  
    65  		// !differ.rightIsDone && !differ.leftIsDone
    66  		leftDiff := differ.leftCurrentDiff
    67  		rightDiff := differ.rightCurrentDiff
    68  		cmp := bytes.Compare([]byte(leftDiff.Key), []byte(rightDiff.Key))
    69  		if cmp > 0 {
    70  			if strings.HasPrefix(leftDiff.Key, rightDiff.Key) {
    71  				// The left diff must be replacing or deleting an object,
    72  				// and the right diff makes changes to that object.
    73  				// Note the fact that all keys in these paths are quoted means we don't have to worry about
    74  				// one key being a prefix of the other and triggering a false positive here.
    75  				result := ThreeWayJsonDiff{
    76  					Op: tree.DiffOpDivergentModifyConflict,
    77  				}
    78  				differ.leftCurrentDiff = nil
    79  				return result, nil
    80  			}
    81  			// key only changed on right
    82  			return differ.processRightSideOnlyDiff(), nil
    83  		} else if cmp < 0 {
    84  			if strings.HasPrefix(rightDiff.Key, leftDiff.Key) {
    85  				// The right diff must be replacing or deleting an object,
    86  				// and the left diff makes changes to that object.
    87  				// Note the fact that all keys in these paths are quoted means we don't have to worry about
    88  				// one key being a prefix of the other and triggering a false positive here.
    89  				result := ThreeWayJsonDiff{
    90  					Op: tree.DiffOpDivergentModifyConflict,
    91  				}
    92  				differ.rightCurrentDiff = nil
    93  				return result, nil
    94  			}
    95  			// left side was modified. We don't need to do anything with this diff.
    96  			differ.leftCurrentDiff = nil
    97  			continue
    98  		} else {
    99  			// cmp == 0; Both diffs are on the same key
   100  			if differ.leftCurrentDiff.From == nil {
   101  				// Key did not exist at base, so both sides are inserts.
   102  				// Check that they're inserting the same value.
   103  				valueCmp, err := differ.leftCurrentDiff.To.Compare(differ.rightCurrentDiff.To)
   104  				if err != nil {
   105  					return ThreeWayJsonDiff{}, err
   106  				}
   107  				if valueCmp == 0 {
   108  					return differ.processMergedDiff(tree.DiffOpConvergentModify, differ.leftCurrentDiff.To), nil
   109  				} else {
   110  					return differ.processMergedDiff(tree.DiffOpDivergentModifyConflict, nil), nil
   111  				}
   112  			}
   113  			if differ.leftCurrentDiff.To == nil && differ.rightCurrentDiff.To == nil {
   114  				return differ.processMergedDiff(tree.DiffOpConvergentDelete, nil), nil
   115  			}
   116  			if differ.leftCurrentDiff.To == nil || differ.rightCurrentDiff.To == nil {
   117  				return differ.processMergedDiff(tree.DiffOpDivergentDeleteConflict, nil), nil
   118  			}
   119  			// If the key existed at base, we can do a recursive three-way merge to resolve
   120  			// changes to the values.
   121  			// This shouldn't be necessary: if its an object on all three branches, the original diff is recursive.
   122  			mergedValue, conflict, err := mergeJSON(*differ.leftCurrentDiff.From,
   123  				*differ.leftCurrentDiff.To,
   124  				*differ.rightCurrentDiff.To)
   125  			if err != nil {
   126  				return ThreeWayJsonDiff{}, err
   127  			}
   128  			if conflict {
   129  				return differ.processMergedDiff(tree.DiffOpDivergentModifyConflict, nil), nil
   130  			} else {
   131  				return differ.processMergedDiff(tree.DiffOpDivergentModifyResolved, &mergedValue), nil
   132  			}
   133  		}
   134  	}
   135  }
   136  
   137  func (differ *ThreeWayJsonDiffer) loadNextDiff() error {
   138  	if differ.leftCurrentDiff == nil && !differ.leftIsDone {
   139  		newLeftDiff, err := differ.leftDiffer.Next()
   140  		if err == io.EOF {
   141  			differ.leftIsDone = true
   142  		} else if err != nil {
   143  			return err
   144  		} else {
   145  			differ.leftCurrentDiff = &newLeftDiff
   146  		}
   147  	}
   148  	if differ.rightCurrentDiff == nil && !differ.rightIsDone {
   149  		newRightDiff, err := differ.rightDiffer.Next()
   150  		if err == io.EOF {
   151  			differ.rightIsDone = true
   152  		} else if err != nil {
   153  			return err
   154  		} else {
   155  			differ.rightCurrentDiff = &newRightDiff
   156  		}
   157  	}
   158  	return nil
   159  }
   160  
   161  func (differ *ThreeWayJsonDiffer) processRightSideOnlyDiff() ThreeWayJsonDiff {
   162  	switch differ.rightCurrentDiff.Type {
   163  	case tree.AddedDiff:
   164  		result := ThreeWayJsonDiff{
   165  			Op:    tree.DiffOpRightAdd,
   166  			Key:   differ.rightCurrentDiff.Key,
   167  			Right: differ.rightCurrentDiff.To,
   168  		}
   169  		differ.rightCurrentDiff = nil
   170  		return result
   171  
   172  	case tree.RemovedDiff:
   173  		result := ThreeWayJsonDiff{
   174  			Op:   tree.DiffOpRightDelete,
   175  			Key:  differ.rightCurrentDiff.Key,
   176  			Base: differ.rightCurrentDiff.From,
   177  		}
   178  		differ.rightCurrentDiff = nil
   179  		return result
   180  
   181  	case tree.ModifiedDiff:
   182  		result := ThreeWayJsonDiff{
   183  			Op:    tree.DiffOpRightModify,
   184  			Key:   differ.rightCurrentDiff.Key,
   185  			Base:  differ.rightCurrentDiff.From,
   186  			Right: differ.rightCurrentDiff.To,
   187  		}
   188  		differ.rightCurrentDiff = nil
   189  		return result
   190  	default:
   191  		panic("unreachable")
   192  	}
   193  }
   194  
   195  func (differ *ThreeWayJsonDiffer) processMergedDiff(op tree.DiffOp, merged *types.JSONDocument) ThreeWayJsonDiff {
   196  	result := ThreeWayJsonDiff{
   197  		Op:     op,
   198  		Key:    differ.leftCurrentDiff.Key,
   199  		Base:   differ.leftCurrentDiff.From,
   200  		Left:   differ.leftCurrentDiff.To,
   201  		Right:  differ.rightCurrentDiff.To,
   202  		Merged: merged,
   203  	}
   204  	differ.leftCurrentDiff = nil
   205  	differ.rightCurrentDiff = nil
   206  	return result
   207  }