github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/merge/integration_test.go (about) 1 // Copyright 2021 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 "fmt" 20 "io" 21 "math/rand" 22 "strconv" 23 "strings" 24 "testing" 25 26 "github.com/dolthub/go-mysql-server/sql" 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 "golang.org/x/sync/errgroup" 30 31 cmd "github.com/dolthub/dolt/go/cmd/dolt/commands" 32 "github.com/dolthub/dolt/go/cmd/dolt/commands/cnfcmds" 33 "github.com/dolthub/dolt/go/cmd/dolt/commands/engine" 34 dtu "github.com/dolthub/dolt/go/libraries/doltcore/dtestutils" 35 "github.com/dolthub/dolt/go/libraries/doltcore/env" 36 "github.com/dolthub/dolt/go/libraries/doltcore/sqle" 37 ) 38 39 func TestMerge(t *testing.T) { 40 41 setupCommon := []testCommand{ 42 {cmd.SqlCmd{}, args{"-q", "CREATE TABLE test (pk int PRIMARY KEY, c0 int);"}}, 43 {cmd.AddCmd{}, args{"."}}, 44 {cmd.CommitCmd{}, args{"-am", "created table test"}}, 45 } 46 47 tests := []struct { 48 name string 49 setup []testCommand 50 51 query string 52 expected []sql.Row 53 }{ 54 { 55 name: "smoke test", 56 query: "SELECT * FROM test;", 57 }, 58 { 59 name: "fast-forward merge", 60 setup: []testCommand{ 61 {cmd.CheckoutCmd{}, args{"-b", "other"}}, 62 {cmd.SqlCmd{}, args{"-q", "INSERT INTO test VALUES (1,1),(2,2);"}}, 63 {cmd.CommitCmd{}, args{"-am", "added rows on other"}}, 64 {cmd.CheckoutCmd{}, args{env.DefaultInitBranch}}, 65 {cmd.MergeCmd{}, args{"other"}}, 66 }, 67 query: "SELECT * FROM test", 68 expected: []sql.Row{ 69 {int32(1), int32(1)}, 70 {int32(2), int32(2)}, 71 }, 72 }, 73 { 74 name: "three-way merge", 75 setup: []testCommand{ 76 {cmd.BranchCmd{}, args{"other"}}, 77 {cmd.SqlCmd{}, args{"-q", "INSERT INTO test VALUES (11,11),(22,22);"}}, 78 {cmd.CommitCmd{}, args{"-am", "added rows on main"}}, 79 {cmd.CheckoutCmd{}, args{"other"}}, 80 {cmd.SqlCmd{}, args{"-q", "INSERT INTO test VALUES (1,1),(2,2);"}}, 81 {cmd.CommitCmd{}, args{"-am", "added rows on other"}}, 82 {cmd.CheckoutCmd{}, args{env.DefaultInitBranch}}, 83 {cmd.MergeCmd{}, args{"other"}}, 84 }, 85 query: "SELECT * FROM test", 86 expected: []sql.Row{ 87 {int32(1), int32(1)}, 88 {int32(2), int32(2)}, 89 {int32(11), int32(11)}, 90 {int32(22), int32(22)}, 91 }, 92 }, 93 { 94 name: "create the same table schema, with different row data, on two branches", 95 setup: []testCommand{ 96 {cmd.BranchCmd{}, args{"other"}}, 97 {cmd.SqlCmd{}, args{"-q", "CREATE TABLE quiz (pk varchar(120) primary key);"}}, 98 {cmd.SqlCmd{}, args{"-q", "INSERT INTO quiz VALUES ('a'),('b'),('c');"}}, 99 {cmd.AddCmd{}, []string{"."}}, 100 {cmd.CommitCmd{}, args{"-am", "added rows on main"}}, 101 {cmd.CheckoutCmd{}, args{"other"}}, 102 {cmd.SqlCmd{}, args{"-q", "CREATE TABLE quiz (pk varchar(120) primary key);"}}, 103 {cmd.AddCmd{}, []string{"."}}, 104 {cmd.SqlCmd{}, args{"-q", "INSERT INTO quiz VALUES ('x'),('y'),('z');"}}, 105 {cmd.CommitCmd{}, args{"-am", "added rows on other"}}, 106 {cmd.CheckoutCmd{}, args{env.DefaultInitBranch}}, 107 {cmd.MergeCmd{}, args{"other"}}, 108 }, 109 query: "SELECT * FROM quiz ORDER BY pk", 110 expected: []sql.Row{ 111 {"a"}, 112 {"b"}, 113 {"c"}, 114 {"x"}, 115 {"y"}, 116 {"z"}, 117 }, 118 }, 119 } 120 121 for _, test := range tests { 122 t.Run(test.name, func(t *testing.T) { 123 ctx := context.Background() 124 dEnv := dtu.CreateTestEnv() 125 defer dEnv.DoltDB.Close() 126 127 for _, tc := range setupCommon { 128 exit := tc.exec(t, ctx, dEnv) 129 require.Equal(t, 0, exit) 130 } 131 for _, tc := range test.setup { 132 exit := tc.exec(t, ctx, dEnv) 133 require.Equal(t, 0, exit) 134 } 135 136 root, err := dEnv.WorkingRoot(ctx) 137 require.NoError(t, err) 138 actRows, err := sqle.ExecuteSelect(dEnv, root, test.query) 139 require.NoError(t, err) 140 141 require.Equal(t, len(test.expected), len(actRows)) 142 for i := range test.expected { 143 assert.Equal(t, test.expected[i], actRows[i]) 144 } 145 }) 146 } 147 } 148 149 func TestMergeConflicts(t *testing.T) { 150 151 setupCommon := []testCommand{ 152 {cmd.SqlCmd{}, args{"-q", "CREATE TABLE test (pk int PRIMARY KEY, c0 int);"}}, 153 {cmd.AddCmd{}, []string{"."}}, 154 {cmd.CommitCmd{}, args{"-am", "created table test"}}, 155 } 156 157 tests := []struct { 158 name string 159 setup []testCommand 160 161 query string 162 expected []sql.Row 163 }{ 164 { 165 name: "conflict on merge", 166 setup: []testCommand{ 167 {cmd.CheckoutCmd{}, args{"-b", "other"}}, 168 {cmd.SqlCmd{}, args{"-q", "INSERT INTO test VALUES (1,1),(2,2);"}}, 169 {cmd.CommitCmd{}, args{"-am", "added rows on other"}}, 170 {cmd.CheckoutCmd{}, args{env.DefaultInitBranch}}, 171 {cmd.SqlCmd{}, args{"-q", "INSERT INTO test VALUES (1,11),(2,22);"}}, 172 {cmd.CommitCmd{}, args{"-am", "added the same rows on main"}}, 173 {cmd.MergeCmd{}, args{"other"}}, 174 }, 175 query: "SELECT * FROM dolt_conflicts", 176 expected: []sql.Row{ 177 {"test", uint64(2)}, 178 }, 179 }, 180 { 181 name: "conflict on merge, resolve with ours", 182 setup: []testCommand{ 183 {cmd.CheckoutCmd{}, args{"-b", "other"}}, 184 {cmd.SqlCmd{}, args{"-q", "INSERT INTO test VALUES (1,1),(2,2);"}}, 185 {cmd.AddCmd{}, []string{"."}}, 186 {cmd.CommitCmd{}, args{"-am", "added rows on other"}}, 187 {cmd.CheckoutCmd{}, args{env.DefaultInitBranch}}, 188 {cmd.SqlCmd{}, args{"-q", "INSERT INTO test VALUES (1,11),(2,22);"}}, 189 {cmd.CommitCmd{}, args{"-am", "added the same rows on main"}}, 190 {cmd.MergeCmd{}, args{"other"}}, 191 {cnfcmds.ResolveCmd{}, args{"--ours", "test"}}, 192 }, 193 query: "SELECT * FROM test", 194 expected: []sql.Row{ 195 {int32(1), int32(11)}, 196 {int32(2), int32(22)}, 197 }, 198 }, 199 { 200 name: "conflict on merge, no table in ancestor", 201 setup: []testCommand{ 202 {cmd.CheckoutCmd{}, args{"-b", "other"}}, 203 {cmd.SqlCmd{}, args{"-q", "CREATE TABLE quiz (pk int PRIMARY KEY, c0 int);"}}, 204 {cmd.SqlCmd{}, args{"-q", "INSERT INTO quiz VALUES (1,1),(2,2);"}}, 205 {cmd.AddCmd{}, []string{"."}}, 206 {cmd.CommitCmd{}, args{"-am", "added rows on other"}}, 207 {cmd.CheckoutCmd{}, args{env.DefaultInitBranch}}, 208 {cmd.SqlCmd{}, args{"-q", "CREATE TABLE quiz (pk int PRIMARY KEY, c0 int);"}}, 209 {cmd.SqlCmd{}, args{"-q", "INSERT INTO quiz VALUES (1,11),(2,22);"}}, 210 {cmd.AddCmd{}, []string{"."}}, 211 {cmd.CommitCmd{}, args{"-am", "added the same rows on main"}}, 212 {cmd.MergeCmd{}, args{"other"}}, 213 }, 214 query: "SELECT * FROM dolt_conflicts", 215 expected: []sql.Row{ 216 {"quiz", uint64(2)}, 217 }, 218 }, 219 { 220 name: "conflict on merge, no table in ancestor, resolve with theirs", 221 setup: []testCommand{ 222 {cmd.CheckoutCmd{}, args{"-b", "other"}}, 223 {cmd.SqlCmd{}, args{"-q", "CREATE TABLE quiz (pk int PRIMARY KEY, c0 int);"}}, 224 {cmd.SqlCmd{}, args{"-q", "INSERT INTO quiz VALUES (1,1),(2,2);"}}, 225 {cmd.AddCmd{}, []string{"."}}, 226 {cmd.CommitCmd{}, args{"-am", "added rows on other"}}, 227 {cmd.CheckoutCmd{}, args{env.DefaultInitBranch}}, 228 {cmd.SqlCmd{}, args{"-q", "CREATE TABLE quiz (pk int PRIMARY KEY, c0 int);"}}, 229 {cmd.SqlCmd{}, args{"-q", "INSERT INTO quiz VALUES (1,11),(2,22);"}}, 230 {cmd.AddCmd{}, []string{"."}}, 231 {cmd.CommitCmd{}, args{"-am", "added the same rows on main"}}, 232 {cmd.MergeCmd{}, args{"other"}}, 233 {cnfcmds.ResolveCmd{}, args{"--theirs", "quiz"}}, 234 }, 235 query: "SELECT * FROM quiz", 236 expected: []sql.Row{ 237 {int32(1), int32(1)}, 238 {int32(2), int32(2)}, 239 }, 240 }, 241 } 242 243 for _, test := range tests { 244 t.Run(test.name, func(t *testing.T) { 245 ctx := context.Background() 246 dEnv := dtu.CreateTestEnv() 247 defer dEnv.DoltDB.Close() 248 249 for _, tc := range setupCommon { 250 exit := tc.exec(t, ctx, dEnv) 251 // allow merge to fail with conflicts 252 if _, ok := tc.cmd.(cmd.MergeCmd); !ok { 253 require.Equal(t, 0, exit) 254 } 255 } 256 for _, tc := range test.setup { 257 exit := tc.exec(t, ctx, dEnv) 258 // allow merge to fail with conflicts 259 if _, ok := tc.cmd.(cmd.MergeCmd); !ok { 260 require.Equal(t, 0, exit) 261 } 262 } 263 264 root, err := dEnv.WorkingRoot(ctx) 265 require.NoError(t, err) 266 actRows, err := sqle.ExecuteSelect(dEnv, root, test.query) 267 require.NoError(t, err) 268 269 require.Equal(t, len(test.expected), len(actRows)) 270 for i := range test.expected { 271 assert.Equal(t, test.expected[i], actRows[i]) 272 } 273 }) 274 } 275 } 276 277 const ( 278 concurrentScale = 10_000 279 concurrentIters = 100 280 concurrentThreads = 8 281 concurrentTable = "CREATE TABLE concurrent (" + 282 " id int NOT NULL," + 283 " c0 int NOT NULL," + 284 " c1 int NOT NULL," + 285 " PRIMARY KEY (id)," + 286 " KEY `idx0` (c0)," + 287 " KEY `idx1` (c1, c0)" + 288 ");" 289 ) 290 291 // TestMergeConcurrency runs current merges via 292 // concurrent SQL transactions. 293 func TestMergeConcurrency(t *testing.T) { 294 ctx := context.Background() 295 dEnv := setupConcurrencyTest(t, ctx) 296 defer dEnv.DoltDB.Close() 297 _, eng := engineFromEnvironment(ctx, dEnv) 298 299 eg, ctx := errgroup.WithContext(ctx) 300 for i := 0; i < concurrentThreads; i++ { 301 seed := i 302 eg.Go(func() error { 303 return runConcurrentTxs(ctx, eng, seed) 304 }) 305 } 306 assert.NoError(t, eg.Wait()) 307 } 308 309 func runConcurrentTxs(ctx context.Context, eng *engine.SqlEngine, seed int) error { 310 sess, err := eng.NewDoltSession(ctx, sql.NewBaseSession()) 311 if err != nil { 312 return err 313 } 314 sctx := sql.NewContext(ctx, sql.WithSession(sess)) 315 sctx.SetCurrentDatabase("dolt") 316 sctx.Session.SetClient(sql.Client{User: "root", Address: "%"}) 317 318 rnd := rand.New(rand.NewSource(int64(seed))) 319 zipf := rand.NewZipf(rnd, 1.1, 1.0, concurrentScale) 320 321 for i := 0; i < concurrentIters; i++ { 322 if err := executeQuery(sctx, eng, "BEGIN"); err != nil { 323 return err 324 } 325 326 id := zipf.Uint64() 327 sum := fmt.Sprintf("SELECT sum(c0), sum(c1) "+ 328 "FROM concurrent WHERE id BETWEEN %d AND %d", id, id+10) 329 update := fmt.Sprintf("UPDATE concurrent "+ 330 "SET c0 = c0 + %d, c1 = c1 + %d WHERE id = %d", 331 seed, seed, id) 332 333 if err := executeQuery(sctx, eng, sum); err != nil { 334 return err 335 } 336 if err := executeQuery(sctx, eng, update); err != nil { 337 return err 338 } 339 if err := executeQuery(sctx, eng, sum); err != nil { 340 return err 341 } 342 if err := executeQuery(sctx, eng, "COMMIT"); err != nil { 343 // allow serialization errors 344 if !sql.ErrLockDeadlock.Is(err) { 345 return err 346 } 347 } 348 } 349 return nil 350 } 351 352 func setupConcurrencyTest(t *testing.T, ctx context.Context) (dEnv *env.DoltEnv) { 353 dEnv = dtu.CreateTestEnv() 354 355 dbName, eng := engineFromEnvironment(ctx, dEnv) 356 sqlCtx, err := eng.NewLocalContext(ctx) 357 require.NoError(t, err) 358 sqlCtx.SetCurrentDatabase(dbName) 359 360 require.NoError(t, executeQuery(sqlCtx, eng, concurrentTable)) 361 require.NoError(t, executeQuery(sqlCtx, eng, generateTestData())) 362 return 363 } 364 365 func engineFromEnvironment(ctx context.Context, dEnv *env.DoltEnv) (dbName string, eng *engine.SqlEngine) { 366 mrEnv, err := env.MultiEnvForDirectory(ctx, dEnv.Config.WriteableConfig(), dEnv.FS, dEnv.Version, dEnv) 367 if err != nil { 368 panic(err) 369 } 370 371 eng, err = engine.NewSqlEngine(ctx, mrEnv, &engine.SqlEngineConfig{ 372 IsReadOnly: false, 373 ServerUser: "root", 374 ServerHost: "localhost", 375 Autocommit: true, 376 }) 377 if err != nil { 378 panic(err) 379 } 380 381 return mrEnv.GetFirstDatabase(), eng 382 } 383 384 func executeQuery(ctx *sql.Context, eng *engine.SqlEngine, query string) error { 385 _, iter, err := eng.Query(ctx, query) 386 if err != nil { 387 return err 388 } 389 for { 390 _, err = iter.Next(ctx) 391 if err == io.EOF { 392 break 393 } 394 if err != nil { 395 return err 396 } 397 } 398 return iter.Close(ctx) // tx commit 399 } 400 401 func generateTestData() string { 402 var sb strings.Builder 403 sb.WriteString("INSERT INTO concurrent VALUES ") 404 sb.WriteString("(0, 0, 0") 405 for i := 1; i < concurrentScale; i++ { 406 c0 := rand.Intn(concurrentScale) 407 c1 := rand.Intn(concurrentScale) 408 sb.WriteString("), (") 409 sb.WriteString(strconv.Itoa(i)) 410 sb.WriteString(", ") 411 sb.WriteString(strconv.Itoa(c0)) 412 sb.WriteString(", ") 413 sb.WriteString(strconv.Itoa(c1)) 414 } 415 sb.WriteString(");") 416 return sb.String() 417 }