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  }