github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/merge/merge_test.go (about) 1 // Copyright 2019 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 "context" 19 "strconv" 20 "testing" 21 22 "github.com/google/uuid" 23 "github.com/stretchr/testify/assert" 24 "github.com/stretchr/testify/require" 25 26 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 27 "github.com/dolthub/dolt/go/libraries/doltcore/ref" 28 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 29 "github.com/dolthub/dolt/go/libraries/doltcore/schema/encoding" 30 "github.com/dolthub/dolt/go/libraries/doltcore/table/editor" 31 "github.com/dolthub/dolt/go/store/types" 32 ) 33 34 func mustTuple(tpl types.Tuple, err error) types.Tuple { 35 if err != nil { 36 panic(err) 37 } 38 39 return tpl 40 } 41 42 func mustString(str string, err error) string { 43 if err != nil { 44 panic(err) 45 } 46 47 return str 48 } 49 50 func mustGetValue(val types.Value, _ bool, err error) types.Value { 51 if err != nil { 52 panic(err) 53 } 54 55 return val 56 } 57 58 type RowMergeTest struct { 59 name string 60 row, mergeRow, ancRow types.Value 61 sch schema.Schema 62 expectedResult types.Value 63 expectConflict bool 64 } 65 66 func valsToTestTupleWithoutPks(vals []types.Value) types.Value { 67 return valsToTestTuple(vals, false) 68 } 69 70 func valsToTestTupleWithPks(vals []types.Value) types.Value { 71 return valsToTestTuple(vals, true) 72 } 73 74 func valsToTestTuple(vals []types.Value, includePrimaryKeys bool) types.Value { 75 if vals == nil { 76 return nil 77 } 78 79 tplVals := make([]types.Value, 0, 2*len(vals)) 80 for i, val := range vals { 81 if !types.IsNull(val) { 82 tag := i 83 // Assume one primary key tag, add 1 to all other tags 84 if includePrimaryKeys { 85 tag++ 86 } 87 tplVals = append(tplVals, types.Uint(tag)) 88 tplVals = append(tplVals, val) 89 } 90 } 91 92 return mustTuple(types.NewTuple(types.Format_Default, tplVals...)) 93 } 94 95 func createRowMergeStruct(name string, vals, mergeVals, ancVals, expected []types.Value, expectCnf bool) RowMergeTest { 96 longest := vals 97 98 if len(mergeVals) > len(longest) { 99 longest = mergeVals 100 } 101 102 if len(ancVals) > len(longest) { 103 longest = ancVals 104 } 105 106 cols := make([]schema.Column, len(longest)+1) 107 // Schema needs a primary key to be valid, but all the logic being tested works only on the non-key columns. 108 cols[0] = schema.NewColumn("primaryKey", 0, types.IntKind, true) 109 for i, val := range longest { 110 tag := i + 1 111 cols[tag] = schema.NewColumn(strconv.FormatInt(int64(tag), 10), uint64(tag), val.Kind(), false) 112 } 113 114 colColl := schema.NewColCollection(cols...) 115 sch := schema.MustSchemaFromCols(colColl) 116 117 tpl := valsToTestTupleWithPks(vals) 118 mergeTpl := valsToTestTupleWithPks(mergeVals) 119 ancTpl := valsToTestTupleWithPks(ancVals) 120 expectedTpl := valsToTestTupleWithPks(expected) 121 return RowMergeTest{name, tpl, mergeTpl, ancTpl, sch, expectedTpl, expectCnf} 122 } 123 124 func TestRowMerge(t *testing.T) { 125 tests := []RowMergeTest{ 126 createRowMergeStruct( 127 "add same row", 128 []types.Value{types.String("one"), types.Int(2)}, 129 []types.Value{types.String("one"), types.Int(2)}, 130 nil, 131 []types.Value{types.String("one"), types.Int(2)}, 132 false, 133 ), 134 createRowMergeStruct( 135 "add diff row", 136 []types.Value{types.String("one"), types.String("two")}, 137 []types.Value{types.String("one"), types.String("three")}, 138 nil, 139 nil, 140 true, 141 ), 142 createRowMergeStruct( 143 "both delete row", 144 nil, 145 nil, 146 []types.Value{types.String("one"), types.Uint(2)}, 147 nil, 148 false, 149 ), 150 createRowMergeStruct( 151 "one delete one modify", 152 nil, 153 []types.Value{types.String("two"), types.Uint(2)}, 154 []types.Value{types.String("one"), types.Uint(2)}, 155 nil, 156 true, 157 ), 158 createRowMergeStruct( 159 "modify rows without overlap", 160 []types.Value{types.String("two"), types.Uint(2)}, 161 []types.Value{types.String("one"), types.Uint(3)}, 162 []types.Value{types.String("one"), types.Uint(2)}, 163 []types.Value{types.String("two"), types.Uint(3)}, 164 false, 165 ), 166 createRowMergeStruct( 167 "modify rows with equal overlapping changes", 168 []types.Value{types.String("two"), types.Uint(2), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))}, 169 []types.Value{types.String("one"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))}, 170 []types.Value{types.String("one"), types.Uint(2), types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000000"))}, 171 []types.Value{types.String("two"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))}, 172 false, 173 ), 174 createRowMergeStruct( 175 "modify rows with differing overlapping changes", 176 []types.Value{types.String("two"), types.Uint(2), types.UUID(uuid.MustParse("99999999-9999-9999-9999-999999999999"))}, 177 []types.Value{types.String("one"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))}, 178 []types.Value{types.String("one"), types.Uint(2), types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000000"))}, 179 nil, 180 true, 181 ), 182 createRowMergeStruct( 183 "modify rows where one adds a column", 184 []types.Value{types.String("two"), types.Uint(2)}, 185 []types.Value{types.String("one"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))}, 186 []types.Value{types.String("one"), types.Uint(2)}, 187 []types.Value{types.String("two"), types.Uint(3), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))}, 188 false, 189 ), 190 createRowMergeStruct( 191 "modify row where values added in different columns", 192 []types.Value{types.String("one"), types.Uint(2), types.String(""), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff"))}, 193 []types.Value{types.String("one"), types.Uint(2), types.UUID(uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff")), types.String("")}, 194 []types.Value{types.String("one"), types.Uint(2), types.NullValue, types.NullValue}, 195 nil, 196 true, 197 ), 198 createRowMergeStruct( 199 "modify row where initial value wasn't given", 200 []types.Value{mustTuple(types.NewTuple(types.Format_Default, types.String("one"), types.Uint(2), types.String("a")))}, 201 []types.Value{mustTuple(types.NewTuple(types.Format_Default, types.String("one"), types.Uint(2), types.String("b")))}, 202 []types.Value{mustTuple(types.NewTuple(types.Format_Default, types.String("one"), types.Uint(2), types.NullValue))}, 203 nil, 204 true, 205 ), 206 } 207 208 for _, test := range tests { 209 t.Run(test.name, func(t *testing.T) { 210 actualResult, isConflict, err := pkRowMerge(context.Background(), types.Format_Default, test.sch, test.row, test.mergeRow, test.ancRow) 211 assert.NoError(t, err) 212 assert.Equal(t, test.expectedResult, actualResult, "expected "+mustString(types.EncodedValue(context.Background(), test.expectedResult))+"got "+mustString(types.EncodedValue(context.Background(), actualResult))) 213 assert.Equal(t, test.expectConflict, isConflict) 214 }) 215 } 216 } 217 218 const ( 219 tableName = "test-table" 220 name = "billy bob" 221 email = "bigbillieb@fake.horse" 222 223 idTag = 100 224 nameTag = 0 225 titleTag = 1 226 ) 227 228 var colColl = schema.NewColCollection( 229 schema.NewColumn("id", idTag, types.UUIDKind, true, schema.NotNullConstraint{}), 230 schema.NewColumn("name", nameTag, types.StringKind, false, schema.NotNullConstraint{}), 231 schema.NewColumn("title", titleTag, types.StringKind, false), 232 ) 233 var sch = schema.MustSchemaFromCols(colColl) 234 235 var uuids = []types.UUID{ 236 types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000000")), 237 types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000001")), 238 types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000002")), 239 types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000003")), 240 types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000004")), 241 types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000005")), 242 types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000006")), 243 types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000007")), 244 types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000008")), 245 types.UUID(uuid.MustParse("00000000-0000-0000-0000-000000000009")), 246 types.UUID(uuid.MustParse("00000000-0000-0000-0000-00000000000a")), 247 types.UUID(uuid.MustParse("00000000-0000-0000-0000-00000000000b")), 248 types.UUID(uuid.MustParse("00000000-0000-0000-0000-00000000000c")), 249 } 250 251 var keyTuples = make([]types.Tuple, len(uuids)) 252 253 var index schema.Index 254 255 func init() { 256 keyTag := types.Uint(idTag) 257 258 for i, id := range uuids { 259 keyTuples[i] = mustTuple(types.NewTuple(types.Format_Default, keyTag, id)) 260 } 261 262 index, _ = sch.Indexes().AddIndexByColTags("idx_name", []uint64{nameTag}, schema.IndexProperties{IsUnique: false, Comment: ""}) 263 } 264 265 func setupMergeTest(t *testing.T) (types.ValueReadWriter, *doltdb.Commit, *doltdb.Commit, types.Map, types.Map) { 266 ddb, _ := doltdb.LoadDoltDB(context.Background(), types.Format_Default, doltdb.InMemDoltDB) 267 vrw := ddb.ValueReadWriter() 268 269 err := ddb.WriteEmptyRepo(context.Background(), name, email) 270 require.NoError(t, err) 271 272 masterHeadSpec, _ := doltdb.NewCommitSpec("master") 273 masterHead, err := ddb.Resolve(context.Background(), masterHeadSpec, nil) 274 require.NoError(t, err) 275 276 initialRows, err := types.NewMap(context.Background(), vrw, 277 keyTuples[0], valsToTestTupleWithoutPks([]types.Value{types.String("person 1"), types.String("dufus")}), 278 keyTuples[1], valsToTestTupleWithoutPks([]types.Value{types.String("person 2"), types.NullValue}), 279 keyTuples[2], valsToTestTupleWithoutPks([]types.Value{types.String("person 3"), types.NullValue}), 280 keyTuples[3], valsToTestTupleWithoutPks([]types.Value{types.String("person 4"), types.String("senior dufus")}), 281 keyTuples[4], valsToTestTupleWithoutPks([]types.Value{types.String("person 5"), types.NullValue}), 282 keyTuples[5], valsToTestTupleWithoutPks([]types.Value{types.String("person 6"), types.NullValue}), 283 keyTuples[6], valsToTestTupleWithoutPks([]types.Value{types.String("person 7"), types.String("madam")}), 284 keyTuples[7], valsToTestTupleWithoutPks([]types.Value{types.String("person 8"), types.String("miss")}), 285 keyTuples[8], valsToTestTupleWithoutPks([]types.Value{types.String("person 9"), types.NullValue}), 286 ) 287 require.NoError(t, err) 288 289 updateRowEditor := initialRows.Edit() // leave 0 as is 290 updateRowEditor.Remove(keyTuples[1]) // remove 1 from both 291 updateRowEditor.Remove(keyTuples[2]) // remove 2 from update 292 updateRowEditor.Set(keyTuples[4], valsToTestTupleWithoutPks([]types.Value{types.String("person five"), types.NullValue})) // modify 4 only in update 293 updateRowEditor.Set(keyTuples[6], valsToTestTupleWithoutPks([]types.Value{types.String("person 7"), types.String("dr")})) // modify 6 in both without overlap 294 updateRowEditor.Set(keyTuples[7], valsToTestTupleWithoutPks([]types.Value{types.String("person eight"), types.NullValue})) // modify 7 in both with equal overlap 295 updateRowEditor.Set(keyTuples[8], valsToTestTupleWithoutPks([]types.Value{types.String("person nine"), types.NullValue})) // modify 8 in both with conflicting overlap 296 updateRowEditor.Set(keyTuples[9], valsToTestTupleWithoutPks([]types.Value{types.String("person ten"), types.NullValue})) // add 9 in update 297 updateRowEditor.Set(keyTuples[11], valsToTestTupleWithoutPks([]types.Value{types.String("person twelve"), types.NullValue})) // add 11 in both without difference 298 updateRowEditor.Set(keyTuples[12], valsToTestTupleWithoutPks([]types.Value{types.String("person thirteen"), types.NullValue})) // add 12 in both with differences 299 300 updatedRows, err := updateRowEditor.Map(context.Background()) 301 require.NoError(t, err) 302 303 mergeRowEditor := initialRows.Edit() // leave 0 as is 304 mergeRowEditor.Remove(keyTuples[1]) // remove 1 from both 305 mergeRowEditor.Remove(keyTuples[3]) // remove 3 from merge 306 mergeRowEditor.Set(keyTuples[5], valsToTestTupleWithoutPks([]types.Value{types.String("person six"), types.NullValue})) // modify 5 only in merge 307 mergeRowEditor.Set(keyTuples[6], valsToTestTupleWithoutPks([]types.Value{types.String("person seven"), types.String("madam")})) // modify 6 in both without overlap 308 mergeRowEditor.Set(keyTuples[7], valsToTestTupleWithoutPks([]types.Value{types.String("person eight"), types.NullValue})) // modify 7 in both with equal overlap 309 mergeRowEditor.Set(keyTuples[8], valsToTestTupleWithoutPks([]types.Value{types.String("person number nine"), types.NullValue})) // modify 8 in both with conflicting overlap 310 mergeRowEditor.Set(keyTuples[10], valsToTestTupleWithoutPks([]types.Value{types.String("person eleven"), types.NullValue})) // add 10 in merge 311 mergeRowEditor.Set(keyTuples[11], valsToTestTupleWithoutPks([]types.Value{types.String("person twelve"), types.NullValue})) // add 11 in both without difference 312 mergeRowEditor.Set(keyTuples[12], valsToTestTupleWithoutPks([]types.Value{types.String("person number thirteen"), types.NullValue})) // add 12 in both with differences 313 314 mergeRows, err := mergeRowEditor.Map(context.Background()) 315 require.NoError(t, err) 316 317 expectedRows, err := types.NewMap(context.Background(), vrw, 318 keyTuples[0], mustGetValue(initialRows.MaybeGet(context.Background(), keyTuples[0])), // unaltered 319 keyTuples[4], mustGetValue(updatedRows.MaybeGet(context.Background(), keyTuples[4])), // modified in updated 320 keyTuples[5], mustGetValue(mergeRows.MaybeGet(context.Background(), keyTuples[5])), // modified in merged 321 keyTuples[6], valsToTestTupleWithoutPks([]types.Value{types.String("person seven"), types.String("dr")}), // modified in both with no overlap 322 keyTuples[7], mustGetValue(updatedRows.MaybeGet(context.Background(), keyTuples[7])), // modify both with the same value 323 keyTuples[8], mustGetValue(updatedRows.MaybeGet(context.Background(), keyTuples[8])), // conflict 324 keyTuples[9], mustGetValue(updatedRows.MaybeGet(context.Background(), keyTuples[9])), // added in update 325 keyTuples[10], mustGetValue(mergeRows.MaybeGet(context.Background(), keyTuples[10])), // added in merge 326 keyTuples[11], mustGetValue(updatedRows.MaybeGet(context.Background(), keyTuples[11])), // added same in both 327 keyTuples[12], mustGetValue(updatedRows.MaybeGet(context.Background(), keyTuples[12])), // conflict 328 ) 329 require.NoError(t, err) 330 331 updateConflict := doltdb.NewConflict( 332 mustGetValue(initialRows.MaybeGet(context.Background(), keyTuples[8])), 333 mustGetValue(updatedRows.MaybeGet(context.Background(), keyTuples[8])), 334 mustGetValue(mergeRows.MaybeGet(context.Background(), keyTuples[8]))) 335 336 addConflict := doltdb.NewConflict( 337 nil, 338 valsToTestTupleWithoutPks([]types.Value{types.String("person thirteen"), types.NullValue}), 339 valsToTestTupleWithoutPks([]types.Value{types.String("person number thirteen"), types.NullValue}), 340 ) 341 342 expectedConflicts, err := types.NewMap(context.Background(), vrw, 343 keyTuples[8], mustTuple(updateConflict.ToNomsList(vrw)), 344 keyTuples[12], mustTuple(addConflict.ToNomsList(vrw)), 345 ) 346 require.NoError(t, err) 347 348 schVal, err := encoding.MarshalSchemaAsNomsValue(context.Background(), vrw, sch) 349 require.NoError(t, err) 350 351 emptyMap, err := types.NewMap(context.Background(), vrw) 352 require.NoError(t, err) 353 354 tbl, err := doltdb.NewTable(context.Background(), vrw, schVal, initialRows, emptyMap, nil) 355 require.NoError(t, err) 356 tbl, err = editor.RebuildAllIndexes(context.Background(), tbl) 357 require.NoError(t, err) 358 359 updatedTbl, err := doltdb.NewTable(context.Background(), vrw, schVal, updatedRows, emptyMap, nil) 360 require.NoError(t, err) 361 updatedTbl, err = editor.RebuildAllIndexes(context.Background(), updatedTbl) 362 require.NoError(t, err) 363 364 mergeTbl, err := doltdb.NewTable(context.Background(), vrw, schVal, mergeRows, emptyMap, nil) 365 require.NoError(t, err) 366 mergeTbl, err = editor.RebuildAllIndexes(context.Background(), mergeTbl) 367 require.NoError(t, err) 368 369 mRoot, err := masterHead.GetRootValue() 370 require.NoError(t, err) 371 372 mRoot, err = mRoot.PutTable(context.Background(), tableName, tbl) 373 require.NoError(t, err) 374 375 updatedRoot, err := mRoot.PutTable(context.Background(), tableName, updatedTbl) 376 require.NoError(t, err) 377 378 mergeRoot, err := mRoot.PutTable(context.Background(), tableName, mergeTbl) 379 require.NoError(t, err) 380 381 masterHash, err := ddb.WriteRootValue(context.Background(), mRoot) 382 require.NoError(t, err) 383 hash, err := ddb.WriteRootValue(context.Background(), updatedRoot) 384 require.NoError(t, err) 385 mergeHash, err := ddb.WriteRootValue(context.Background(), mergeRoot) 386 require.NoError(t, err) 387 388 meta, err := doltdb.NewCommitMeta(name, email, "fake") 389 require.NoError(t, err) 390 initialCommit, err := ddb.Commit(context.Background(), masterHash, ref.NewBranchRef("master"), meta) 391 require.NoError(t, err) 392 commit, err := ddb.Commit(context.Background(), hash, ref.NewBranchRef("master"), meta) 393 require.NoError(t, err) 394 395 err = ddb.NewBranchAtCommit(context.Background(), ref.NewBranchRef("to-merge"), initialCommit) 396 require.NoError(t, err) 397 mergeCommit, err := ddb.Commit(context.Background(), mergeHash, ref.NewBranchRef("to-merge"), meta) 398 require.NoError(t, err) 399 400 return vrw, commit, mergeCommit, expectedRows, expectedConflicts 401 } 402 403 func TestMergeCommits(t *testing.T) { 404 vrw, commit, mergeCommit, expectedRows, expectedConflicts := setupMergeTest(t) 405 406 root, err := commit.GetRootValue() 407 require.NoError(t, err) 408 409 mergeRoot, err := mergeCommit.GetRootValue() 410 require.NoError(t, err) 411 412 ancCm, err := doltdb.GetCommitAncestor(context.Background(), commit, mergeCommit) 413 require.NoError(t, err) 414 415 ancRoot, err := ancCm.GetRootValue() 416 require.NoError(t, err) 417 418 ff, err := commit.CanFastForwardTo(context.Background(), mergeCommit) 419 require.NoError(t, err) 420 require.False(t, ff) 421 422 merger := NewMerger(context.Background(), root, mergeRoot, ancRoot, vrw) 423 tableEditSession := editor.CreateTableEditSession(root, editor.TableEditSessionProps{}) 424 merged, stats, err := merger.MergeTable(context.Background(), tableName, tableEditSession) 425 426 if err != nil { 427 t.Fatal(err) 428 } 429 430 if stats.Adds != 2 || stats.Deletes != 2 || stats.Modifications != 3 || stats.Conflicts != 2 { 431 t.Error("Actual stats differ from expected") 432 } 433 434 tbl, _, err := root.GetTable(context.Background(), tableName) 435 assert.NoError(t, err) 436 schRef, err := tbl.GetSchemaRef() 437 assert.NoError(t, err) 438 targVal, err := schRef.TargetValue(context.Background(), vrw) 439 assert.NoError(t, err) 440 emptyMap, err := types.NewMap(context.Background(), vrw) 441 assert.NoError(t, err) 442 expected, err := doltdb.NewTable(context.Background(), vrw, targVal, expectedRows, emptyMap, nil) 443 assert.NoError(t, err) 444 expected, err = editor.RebuildAllIndexes(context.Background(), expected) 445 assert.NoError(t, err) 446 expected, err = expected.SetConflicts(context.Background(), doltdb.NewConflict(schRef, schRef, schRef), expectedConflicts) 447 assert.NoError(t, err) 448 449 h, err := merged.HashOf() 450 assert.NoError(t, err) 451 eh, err := expected.HashOf() 452 assert.NoError(t, err) 453 if h == eh { 454 mergedRows, err := merged.GetRowData(context.Background()) 455 assert.NoError(t, err) 456 if !mergedRows.Equals(expectedRows) { 457 t.Error(mustString(types.EncodedValue(context.Background(), mergedRows)), "\n!=\n", mustString(types.EncodedValue(context.Background(), expectedRows))) 458 } 459 mergedIndexRows, err := merged.GetIndexRowData(context.Background(), index.Name()) 460 assert.NoError(t, err) 461 expectedIndexRows, err := expected.GetIndexRowData(context.Background(), index.Name()) 462 assert.NoError(t, err) 463 if expectedRows.Len() != mergedIndexRows.Len() || !mergedIndexRows.Equals(expectedIndexRows) { 464 t.Error("index contents are incorrect") 465 } 466 } else { 467 assert.Fail(t, "%v and %v do not equal", h, eh) 468 } 469 }