github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/merge/keyless_integration_test.go (about) 1 // Copyright 2020 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_test 16 17 import ( 18 "context" 19 "testing" 20 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23 24 cmd "github.com/dolthub/dolt/go/cmd/dolt/commands" 25 "github.com/dolthub/dolt/go/cmd/dolt/commands/cnfcmds" 26 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 27 dtu "github.com/dolthub/dolt/go/libraries/doltcore/dtestutils" 28 "github.com/dolthub/dolt/go/libraries/doltcore/env" 29 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 30 "github.com/dolthub/dolt/go/store/hash" 31 "github.com/dolthub/dolt/go/store/types" 32 ) 33 34 const tblName = "noKey" 35 36 var sch = dtu.MustSchema( 37 schema.NewColumn("c1", 1, types.IntKind, false), 38 schema.NewColumn("c2", 2, types.IntKind, false), 39 ) 40 var c1Tag = types.Uint(1) 41 var c2Tag = types.Uint(2) 42 var cardTag = types.Uint(schema.KeylessRowCardinalityTag) 43 44 func TestKeylessMerge(t *testing.T) { 45 46 tests := []struct { 47 name string 48 setup []testCommand 49 expected tupleSet 50 }{ 51 { 52 name: "fast-forward merge", 53 setup: []testCommand{ 54 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2);"}}, 55 {cmd.CommitCmd{}, []string{"-am", "added rows"}}, 56 {cmd.CheckoutCmd{}, []string{"-b", "other"}}, 57 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4);"}}, 58 {cmd.CommitCmd{}, []string{"-am", "added rows on other"}}, 59 {cmd.CheckoutCmd{}, []string{"master"}}, 60 {cmd.MergeCmd{}, []string{"other"}}, 61 }, 62 expected: mustTupleSet( 63 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(1), c2Tag, types.Int(2)), 64 dtu.MustTuple(cardTag, types.Uint(1), c1Tag, types.Int(3), c2Tag, types.Int(4)), 65 ), 66 }, 67 { 68 name: "3-way merge", 69 setup: []testCommand{ 70 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2);"}}, 71 {cmd.CommitCmd{}, []string{"-am", "added rows"}}, 72 {cmd.CheckoutCmd{}, []string{"-b", "other"}}, 73 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4);"}}, 74 {cmd.CommitCmd{}, []string{"-am", "added rows on other"}}, 75 {cmd.CheckoutCmd{}, []string{"master"}}, 76 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (5,6);"}}, 77 {cmd.CommitCmd{}, []string{"-am", "added rows on master"}}, 78 {cmd.MergeCmd{}, []string{"other"}}, 79 }, 80 expected: mustTupleSet( 81 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(1), c2Tag, types.Int(2)), 82 dtu.MustTuple(cardTag, types.Uint(1), c1Tag, types.Int(3), c2Tag, types.Int(4)), 83 dtu.MustTuple(cardTag, types.Uint(1), c1Tag, types.Int(5), c2Tag, types.Int(6)), 84 ), 85 }, 86 { 87 name: "3-way merge with duplicates", 88 setup: []testCommand{ 89 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2);"}}, 90 {cmd.CommitCmd{}, []string{"-am", "added rows"}}, 91 {cmd.CheckoutCmd{}, []string{"-b", "other"}}, 92 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4), (3,4);"}}, 93 {cmd.CommitCmd{}, []string{"-am", "added rows on other"}}, 94 {cmd.CheckoutCmd{}, []string{"master"}}, 95 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (5,6), (5,6);"}}, 96 {cmd.CommitCmd{}, []string{"-am", "added rows on master"}}, 97 {cmd.MergeCmd{}, []string{"other"}}, 98 }, 99 expected: mustTupleSet( 100 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(1), c2Tag, types.Int(2)), 101 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(3), c2Tag, types.Int(4)), 102 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(5), c2Tag, types.Int(6)), 103 ), 104 }, 105 } 106 107 for _, test := range tests { 108 t.Run(test.name, func(t *testing.T) { 109 ctx := context.Background() 110 dEnv := dtu.CreateTestEnv() 111 112 root, err := dEnv.WorkingRoot(ctx) 113 require.NoError(t, err) 114 root, err = root.CreateEmptyTable(ctx, tblName, sch) 115 require.NoError(t, err) 116 err = dEnv.UpdateWorkingRoot(ctx, root) 117 require.NoError(t, err) 118 119 for _, c := range test.setup { 120 exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv) 121 require.Equal(t, 0, exitCode) 122 } 123 124 root, err = dEnv.WorkingRoot(ctx) 125 require.NoError(t, err) 126 tbl, _, err := root.GetTable(ctx, tblName) 127 require.NoError(t, err) 128 129 assertKeylessRows(t, ctx, tbl, test.expected) 130 }) 131 } 132 } 133 134 func TestKeylessMergeConflicts(t *testing.T) { 135 tests := []struct { 136 name string 137 setup []testCommand 138 139 // Tuple( 140 // Tuple(baseVal) 141 // Tuple(val) 142 // Tuple(mergeVal) 143 // ) 144 conflicts tupleSet 145 146 oursExpected tupleSet 147 theirsExpected tupleSet 148 }{ 149 { 150 name: "identical parallel changes", 151 setup: []testCommand{ 152 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2);"}}, 153 {cmd.CommitCmd{}, []string{"-am", "added rows"}}, 154 {cmd.CheckoutCmd{}, []string{"-b", "other"}}, 155 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4);"}}, 156 {cmd.CommitCmd{}, []string{"-am", "added rows on other"}}, 157 {cmd.CheckoutCmd{}, []string{"master"}}, 158 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4);"}}, 159 {cmd.CommitCmd{}, []string{"-am", "added rows on master"}}, 160 {cmd.MergeCmd{}, []string{"other"}}, 161 }, 162 conflicts: mustTupleSet( 163 dtu.MustTuple( 164 types.NullValue, 165 dtu.MustTuple(cardTag, types.Uint(1), c1Tag, types.Int(3), c2Tag, types.Int(4)), 166 dtu.MustTuple(cardTag, types.Uint(1), c1Tag, types.Int(3), c2Tag, types.Int(4)), 167 ), 168 ), 169 oursExpected: mustTupleSet( 170 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(1), c2Tag, types.Int(2)), 171 dtu.MustTuple(cardTag, types.Uint(1), c1Tag, types.Int(3), c2Tag, types.Int(4)), 172 ), 173 theirsExpected: mustTupleSet( 174 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(1), c2Tag, types.Int(2)), 175 dtu.MustTuple(cardTag, types.Uint(1), c1Tag, types.Int(3), c2Tag, types.Int(4)), 176 ), 177 }, 178 { 179 name: "asymmetric parallel deletes", 180 setup: []testCommand{ 181 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2),(1,2),(1,2);"}}, 182 {cmd.CommitCmd{}, []string{"-am", "added rows"}}, 183 {cmd.CheckoutCmd{}, []string{"-b", "other"}}, 184 {cmd.SqlCmd{}, []string{"-q", "delete from noKey where (c1,c2) = (1,2) limit 1;"}}, 185 {cmd.CommitCmd{}, []string{"-am", "deleted 1 row on other"}}, 186 {cmd.CheckoutCmd{}, []string{"master"}}, 187 {cmd.SqlCmd{}, []string{"-q", "delete from noKey where (c1,c2) = (1,2) limit 2;"}}, 188 {cmd.CommitCmd{}, []string{"-am", "deleted 2 rows on master"}}, 189 {cmd.MergeCmd{}, []string{"other"}}, 190 }, 191 conflicts: mustTupleSet( 192 dtu.MustTuple( 193 dtu.MustTuple(cardTag, types.Uint(4), c1Tag, types.Int(1), c2Tag, types.Int(2)), 194 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(1), c2Tag, types.Int(2)), 195 dtu.MustTuple(cardTag, types.Uint(3), c1Tag, types.Int(1), c2Tag, types.Int(2)), 196 ), 197 ), 198 oursExpected: mustTupleSet( 199 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(1), c2Tag, types.Int(2)), 200 ), 201 theirsExpected: mustTupleSet( 202 dtu.MustTuple(cardTag, types.Uint(3), c1Tag, types.Int(1), c2Tag, types.Int(2)), 203 ), 204 }, 205 { 206 name: "asymmetric parallel updates", 207 setup: []testCommand{ 208 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2),(1,2),(1,2);"}}, 209 {cmd.CommitCmd{}, []string{"-am", "added rows"}}, 210 {cmd.CheckoutCmd{}, []string{"-b", "other"}}, 211 {cmd.SqlCmd{}, []string{"-q", "update noKey set c2 = 9 limit 1;"}}, 212 {cmd.CommitCmd{}, []string{"-am", "deleted 1 row on other"}}, 213 {cmd.CheckoutCmd{}, []string{"master"}}, 214 {cmd.SqlCmd{}, []string{"-q", "update noKey set c2 = 9 limit 2;"}}, 215 {cmd.CommitCmd{}, []string{"-am", "deleted 2 rows on master"}}, 216 {cmd.MergeCmd{}, []string{"other"}}, 217 }, 218 conflicts: mustTupleSet( 219 dtu.MustTuple( 220 dtu.MustTuple(cardTag, types.Uint(4), c1Tag, types.Int(1), c2Tag, types.Int(2)), 221 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(1), c2Tag, types.Int(2)), 222 dtu.MustTuple(cardTag, types.Uint(3), c1Tag, types.Int(1), c2Tag, types.Int(2)), 223 ), 224 dtu.MustTuple( 225 types.NullValue, 226 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(1), c2Tag, types.Int(9)), 227 dtu.MustTuple(cardTag, types.Uint(1), c1Tag, types.Int(1), c2Tag, types.Int(9)), 228 ), 229 ), 230 oursExpected: mustTupleSet( 231 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(1), c2Tag, types.Int(2)), 232 dtu.MustTuple(cardTag, types.Uint(2), c1Tag, types.Int(1), c2Tag, types.Int(9)), 233 ), 234 theirsExpected: mustTupleSet( 235 dtu.MustTuple(cardTag, types.Uint(3), c1Tag, types.Int(1), c2Tag, types.Int(2)), 236 dtu.MustTuple(cardTag, types.Uint(1), c1Tag, types.Int(1), c2Tag, types.Int(9)), 237 ), 238 }, 239 } 240 241 setupTest := func(t *testing.T, ctx context.Context, dEnv *env.DoltEnv, cc []testCommand) { 242 root, err := dEnv.WorkingRoot(ctx) 243 require.NoError(t, err) 244 root, err = root.CreateEmptyTable(ctx, tblName, sch) 245 require.NoError(t, err) 246 err = dEnv.UpdateWorkingRoot(ctx, root) 247 require.NoError(t, err) 248 249 for _, c := range cc { 250 exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv) 251 require.Equal(t, 0, exitCode) 252 } 253 } 254 255 ctx := context.Background() 256 for _, test := range tests { 257 t.Run(test.name, func(t *testing.T) { 258 dEnv := dtu.CreateTestEnv() 259 setupTest(t, ctx, dEnv, test.setup) 260 261 root, err := dEnv.WorkingRoot(ctx) 262 require.NoError(t, err) 263 tbl, _, err := root.GetTable(ctx, tblName) 264 require.NoError(t, err) 265 _, conflicts, err := tbl.GetConflicts(ctx) 266 require.NoError(t, err) 267 268 assert.True(t, conflicts.Len() > 0) 269 assert.Equal(t, int(conflicts.Len()), len(test.conflicts)) 270 271 actual, err := conflicts.Iterator(ctx) 272 require.NoError(t, err) 273 for { 274 _, act, err := actual.Next(ctx) 275 if act == nil { 276 return 277 } 278 assert.NoError(t, err) 279 h, err := act.Hash(types.Format_Default) 280 assert.NoError(t, err) 281 exp, ok := test.conflicts[h] 282 assert.True(t, ok) 283 assert.True(t, exp.Equals(act)) 284 } 285 }) 286 287 // conflict resolution 288 289 t.Run(test.name+"_resolved_ours", func(t *testing.T) { 290 dEnv := dtu.CreateTestEnv() 291 setupTest(t, ctx, dEnv, test.setup) 292 293 resolve := cnfcmds.ResolveCmd{} 294 args := []string{"--ours", tblName} 295 exitCode := resolve.Exec(ctx, resolve.Name(), args, dEnv) 296 require.Equal(t, 0, exitCode) 297 298 root, err := dEnv.WorkingRoot(ctx) 299 require.NoError(t, err) 300 tbl, _, err := root.GetTable(ctx, tblName) 301 require.NoError(t, err) 302 303 assertKeylessRows(t, ctx, tbl, test.oursExpected) 304 }) 305 t.Run(test.name+"_resolved_theirs", func(t *testing.T) { 306 dEnv := dtu.CreateTestEnv() 307 setupTest(t, ctx, dEnv, test.setup) 308 309 resolve := cnfcmds.ResolveCmd{} 310 args := []string{"--theirs", tblName} 311 exitCode := resolve.Exec(ctx, resolve.Name(), args, dEnv) 312 require.Equal(t, 0, exitCode) 313 314 root, err := dEnv.WorkingRoot(ctx) 315 require.NoError(t, err) 316 tbl, _, err := root.GetTable(ctx, tblName) 317 require.NoError(t, err) 318 319 assertKeylessRows(t, ctx, tbl, test.theirsExpected) 320 }) 321 } 322 } 323 324 // |expected| is a tupleSet to compensate for random storage order 325 func assertKeylessRows(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected tupleSet) { 326 rowData, err := tbl.GetRowData(ctx) 327 require.NoError(t, err) 328 329 assert.Equal(t, int(rowData.Len()), len(expected)) 330 331 actual, err := rowData.Iterator(ctx) 332 require.NoError(t, err) 333 for { 334 _, act, err := actual.Next(ctx) 335 if act == nil { 336 return 337 } 338 assert.NoError(t, err) 339 h, err := act.Hash(types.Format_Default) 340 assert.NoError(t, err) 341 exp, ok := expected[h] 342 assert.True(t, ok) 343 assert.True(t, exp.Equals(act)) 344 } 345 } 346 347 type tupleSet map[hash.Hash]types.Tuple 348 349 func mustTupleSet(tt ...types.Tuple) (s tupleSet) { 350 s = make(tupleSet, len(tt)) 351 for _, tup := range tt { 352 h, err := tup.Hash(types.Format_Default) 353 if err != nil { 354 panic(err) 355 } 356 s[h] = tup 357 } 358 return 359 }