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 }