github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/prolly/tree/three_way_differ_test.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 tree 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "io" 22 "testing" 23 24 "github.com/dolthub/go-mysql-server/sql" 25 "github.com/stretchr/testify/require" 26 27 "github.com/dolthub/dolt/go/store/prolly/message" 28 "github.com/dolthub/dolt/go/store/val" 29 ) 30 31 type testDiff struct { 32 op DiffOp 33 k int 34 l, r, m []int 35 } 36 37 func (d testDiff) String() string { 38 return fmt.Sprintf("%s(key=%d)", d.op, d.k) 39 } 40 41 func TestThreeWayDiffer(t *testing.T) { 42 tests := []struct { 43 name string 44 base [][]int 45 left [][]int 46 right [][]int 47 exp []testDiff 48 }{ 49 { 50 name: "left adds", 51 base: [][]int{{1, 1}, {2, 2}}, 52 left: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}}, 53 right: [][]int{{1, 1}, {2, 2}, {4, 4}}, 54 exp: []testDiff{ 55 {op: DiffOpLeftAdd, k: 3}, 56 {op: DiffOpConvergentAdd, k: 4}, 57 {op: DiffOpLeftAdd, k: 5}, 58 {op: DiffOpLeftAdd, k: 6}, 59 }, 60 }, 61 { 62 name: "right adds", 63 base: [][]int{{1, 1}, {2, 2}}, 64 left: [][]int{{1, 1}, {2, 2}, {4, 4}}, 65 right: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}}, 66 exp: []testDiff{ 67 {op: DiffOpRightAdd, k: 3}, 68 {op: DiffOpConvergentAdd, k: 4}, 69 {op: DiffOpRightAdd, k: 5}, 70 {op: DiffOpRightAdd, k: 6}, 71 }, 72 }, 73 { 74 name: "left deletes", 75 base: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}}, 76 left: [][]int{{1, 1}, {2, 2}}, 77 right: [][]int{{1, 1}, {2, 2}, {3, 3}, {5, 5}, {6, 6}}, 78 exp: []testDiff{ 79 {op: DiffOpLeftDelete, k: 3}, 80 {op: DiffOpConvergentDelete, k: 4}, 81 {op: DiffOpLeftDelete, k: 5}, 82 {op: DiffOpLeftDelete, k: 6}, 83 }, 84 }, 85 { 86 name: "right deletes", 87 base: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}}, 88 left: [][]int{{1, 1}, {2, 2}, {3, 3}, {5, 5}, {6, 6}}, 89 right: [][]int{{1, 1}, {2, 2}}, 90 exp: []testDiff{ 91 {op: DiffOpRightDelete, k: 3}, 92 {op: DiffOpConvergentDelete, k: 4}, 93 {op: DiffOpRightDelete, k: 5}, 94 {op: DiffOpRightDelete, k: 6}, 95 }, 96 }, 97 { 98 name: "left edits", 99 base: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}}, 100 left: [][]int{{1, 1}, {2, 3}, {3, 3}, {4, 5}, {5, 6}, {6, 7}}, 101 right: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 5}, {5, 5}, {6, 6}}, 102 exp: []testDiff{ 103 {op: DiffOpLeftModify, k: 2}, 104 {op: DiffOpConvergentModify, k: 4}, 105 {op: DiffOpLeftModify, k: 5}, 106 {op: DiffOpLeftModify, k: 6}, 107 }, 108 }, 109 { 110 name: "right edits", 111 base: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}}, 112 left: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 5}, {5, 5}, {6, 6}}, 113 right: [][]int{{1, 1}, {2, 3}, {3, 3}, {4, 5}, {5, 6}, {6, 7}}, 114 exp: []testDiff{ 115 {op: DiffOpRightModify, k: 2}, 116 {op: DiffOpConvergentModify, k: 4}, 117 {op: DiffOpRightModify, k: 5}, 118 {op: DiffOpRightModify, k: 6}, 119 }, 120 }, 121 { 122 name: "delete conflicts", 123 base: [][]int{{1, 1}, {2, 2}}, 124 left: [][]int{{1, 1}}, 125 right: [][]int{{1, 1}, {2, 3}}, 126 exp: []testDiff{ 127 {op: DiffOpDivergentDeleteConflict, k: 2}, 128 }, 129 }, 130 { 131 name: "convergent edits", 132 base: [][]int{{1, 1}, {4, 4}}, 133 left: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}}, 134 right: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}}, 135 exp: []testDiff{ 136 {op: DiffOpConvergentAdd, k: 2}, 137 {op: DiffOpConvergentAdd, k: 3}, 138 {op: DiffOpConvergentAdd, k: 5}, 139 }, 140 }, 141 { 142 name: "clash edits", 143 base: [][]int{{1, 1}, {4, 4}}, 144 left: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}}, 145 right: [][]int{{1, 1}, {2, 3}, {3, 4}, {4, 4}, {5, 6}}, 146 exp: []testDiff{ 147 {op: DiffOpDivergentModifyConflict, k: 2}, 148 {op: DiffOpDivergentModifyConflict, k: 3}, 149 {op: DiffOpDivergentModifyConflict, k: 5}, 150 }, 151 }, 152 { 153 name: "resolvable edits", 154 base: [][]int{{1, 1, 1}, {2, 2, 2}, {3, 3, 3}, {4, 4, 4}, {5, 5, 5}}, 155 left: [][]int{{1, 1, 1}, {2, 2, 3}, {3, 3, 4}, {4, 4, 4}, {5, 5, 6}}, 156 right: [][]int{{1, 1, 1}, {2, 3, 2}, {3, 4, 3}, {4, 4, 4}, {5, 6, 5}}, 157 exp: []testDiff{ 158 {op: DiffOpDivergentModifyResolved, k: 2, m: []int{3, 3}}, 159 {op: DiffOpDivergentModifyResolved, k: 3, m: []int{4, 4}}, 160 {op: DiffOpDivergentModifyResolved, k: 5, m: []int{6, 6}}, 161 }, 162 }, 163 { 164 name: "combine types", 165 base: [][]int{{1, 1, 1}, {2, 2, 2}, {3, 3, 3}, {4, 4, 4}, {5, 5, 5}, {8, 8, 8}}, 166 left: [][]int{{1, 1, 1}, {2, 2, 3}, {3, 3, 4}, {5, 5, 6}, {6, 6, 6}}, 167 right: [][]int{{1, 1, 1}, {2, 3, 4}, {3, 4, 3}, {4, 4, 4}, {5, 6, 5}, {7, 7, 7}}, 168 exp: []testDiff{ 169 {op: DiffOpDivergentModifyConflict, k: 2}, 170 {op: DiffOpDivergentModifyResolved, k: 3, m: []int{4, 4}}, 171 {op: DiffOpLeftDelete, k: 4}, 172 {op: DiffOpDivergentModifyResolved, k: 5, m: []int{6, 6}}, 173 {op: DiffOpLeftAdd, k: 6}, 174 {op: DiffOpRightAdd, k: 7}, 175 {op: DiffOpConvergentDelete, k: 8}, 176 }, 177 }, 178 } 179 180 for _, tt := range tests { 181 t.Run(tt.name, func(t *testing.T) { 182 ctx := sql.NewEmptyContext() 183 ns := NewTestNodeStore() 184 185 var valTypes []val.Type 186 for i := 0; i < len(tt.base[0])-1; i++ { 187 valTypes = append(valTypes, val.Type{Enc: val.Int64Enc, Nullable: true}) 188 } 189 190 valDesc := val.TupleDesc{Types: valTypes} 191 192 base := newTestMap(t, ctx, tt.base, ns, valDesc) 193 left := newTestMap(t, ctx, tt.left, ns, valDesc) 194 right := newTestMap(t, ctx, tt.right, ns, valDesc) 195 196 var diffInfo ThreeWayDiffInfo 197 iter, err := NewThreeWayDiffer(ctx, ns, left, right, base, testResolver(t, ns, valDesc, val.NewTupleBuilder(valDesc)), false, diffInfo, keyDesc) 198 require.NoError(t, err) 199 200 var cmp []testDiff 201 for { 202 diff, err := iter.Next(ctx) 203 if errors.Is(err, io.EOF) { 204 break 205 } 206 require.NoError(t, err) 207 cmp = append(cmp, formatTestDiff(t, diff, keyDesc, valDesc)) 208 } 209 210 require.Equal(t, len(cmp), len(tt.exp), "number of diffs not equal") 211 212 for i, exp := range tt.exp { 213 cmp := cmp[i] 214 compareDiffs(t, exp, cmp) 215 } 216 }) 217 } 218 } 219 220 func testResolver(t *testing.T, ns NodeStore, valDesc val.TupleDesc, valBuilder *val.TupleBuilder) func(*sql.Context, val.Tuple, val.Tuple, val.Tuple) (val.Tuple, bool, error) { 221 return func(_ *sql.Context, l, r, b val.Tuple) (val.Tuple, bool, error) { 222 for i := range valDesc.Types { 223 var base, left, right int64 224 var ok bool 225 if b != nil { 226 base, ok = valDesc.GetInt64(i, b) 227 require.True(t, ok) 228 } 229 230 if l != nil { 231 left, ok = valDesc.GetInt64(i, l) 232 require.True(t, ok) 233 } 234 235 if r != nil { 236 right, ok = valDesc.GetInt64(i, r) 237 require.True(t, ok) 238 } 239 240 if base != left && base != right && left != right { 241 return nil, false, nil 242 } else if base != left { 243 valBuilder.PutInt64(i, left) 244 } else if base != right { 245 valBuilder.PutInt64(i, right) 246 } else { 247 valBuilder.PutInt64(i, base) 248 } 249 } 250 return valBuilder.Build(ns.Pool()), true, nil 251 } 252 } 253 254 func compareDiffs(t *testing.T, exp, cmp testDiff) { 255 require.Equal(t, exp.op, cmp.op, fmt.Sprintf("unequal diffs:\nexp: %s\nfnd: %s", exp, cmp)) 256 require.Equal(t, exp.k, cmp.k, fmt.Sprintf("unequal diffs:\nexp: %s\nfnd: %s", exp, cmp)) 257 switch exp.op { 258 case DiffOpDivergentModifyResolved: 259 require.Equal(t, exp.m, cmp.m, fmt.Sprintf("unequal resolved:\nexp: %#v\nfnd: %#v", exp.m, cmp.m)) 260 } 261 } 262 263 func formatTestDiff(t *testing.T, d ThreeWayDiff, keyDesc, valDesc val.TupleDesc) testDiff { 264 key, ok := keyDesc.GetInt64(0, d.Key) 265 require.True(t, ok) 266 267 return testDiff{ 268 op: d.Op, 269 k: int(key), 270 l: extractTestVal(t, valDesc, d.Left), 271 r: extractTestVal(t, valDesc, d.Right), 272 m: extractTestVal(t, valDesc, d.Merged), 273 } 274 } 275 276 func extractTestVal(t *testing.T, valDesc val.TupleDesc, tuple val.Tuple) []int { 277 if tuple == nil { 278 return nil 279 } 280 ret := make([]int, len(valDesc.Types)) 281 for i, _ := range valDesc.Types { 282 val, ok := valDesc.GetInt64(i, tuple) 283 require.True(t, ok) 284 ret[i] = int(val) 285 } 286 return ret 287 } 288 289 // newTestMap makes a prolly tree from a matrix of integers. Each row corresponds 290 // to a row in the prolly map. The first value in a row will be the primary key. 291 // The rest of the values will be the value fields. 292 func newTestMap(t *testing.T, ctx context.Context, rows [][]int, ns NodeStore, valDesc val.TupleDesc) StaticMap[val.Tuple, val.Tuple, val.TupleDesc] { 293 serializer := message.NewProllyMapSerializer(valDesc, ns.Pool()) 294 chkr, err := newEmptyChunker(ctx, ns, serializer) 295 require.NoError(t, err) 296 297 keyBuilder := val.NewTupleBuilder(keyDesc) 298 valBuilder := val.NewTupleBuilder(valDesc) 299 300 for _, row := range rows { 301 keyBuilder.PutInt64(0, int64(row[0])) 302 key := keyBuilder.Build(ns.Pool()) 303 for j := 1; j < len(row); j++ { 304 valBuilder.PutInt64(j-1, int64(row[j])) 305 require.NoError(t, err) 306 } 307 val := valBuilder.Build(ns.Pool()) 308 err := chkr.AddPair(ctx, Item(key), Item(val)) 309 require.NoError(t, err) 310 } 311 312 root, err := chkr.Done(ctx) 313 require.NoError(t, err) 314 return StaticMap[val.Tuple, val.Tuple, val.TupleDesc]{ 315 Root: root, 316 NodeStore: ns, 317 Order: keyDesc, 318 } 319 }