github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/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  	"io"
    20  	"testing"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  
    25  	cmd "github.com/dolthub/dolt/go/cmd/dolt/commands"
    26  	"github.com/dolthub/dolt/go/cmd/dolt/commands/cnfcmds"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
    29  	dtu "github.com/dolthub/dolt/go/libraries/doltcore/dtestutils"
    30  	"github.com/dolthub/dolt/go/libraries/doltcore/env"
    31  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    32  	"github.com/dolthub/dolt/go/store/hash"
    33  	"github.com/dolthub/dolt/go/store/pool"
    34  	"github.com/dolthub/dolt/go/store/prolly/tree"
    35  	"github.com/dolthub/dolt/go/store/types"
    36  	"github.com/dolthub/dolt/go/store/val"
    37  )
    38  
    39  func TestKeylessMerge(t *testing.T) {
    40  
    41  	tests := []struct {
    42  		name     string
    43  		setup    []testCommand
    44  		expected keylessEntries
    45  	}{
    46  		{
    47  			name: "fast-forward merge",
    48  			setup: []testCommand{
    49  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2);"}},
    50  				{cmd.AddCmd{}, []string{"."}},
    51  				{cmd.CommitCmd{}, []string{"-am", "added rows"}},
    52  				{cmd.CheckoutCmd{}, []string{"-b", "other"}},
    53  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4);"}},
    54  				{cmd.CommitCmd{}, []string{"-am", "added rows on other"}},
    55  				{cmd.CheckoutCmd{}, []string{env.DefaultInitBranch}},
    56  				{cmd.MergeCmd{}, []string{"other"}},
    57  			},
    58  			expected: []keylessEntry{
    59  				{2, 1, 2},
    60  				{1, 3, 4},
    61  			},
    62  		},
    63  		{
    64  			name: "3-way merge",
    65  			setup: []testCommand{
    66  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2);"}},
    67  				{cmd.AddCmd{}, []string{"."}},
    68  				{cmd.CommitCmd{}, []string{"-am", "added rows"}},
    69  				{cmd.CheckoutCmd{}, []string{"-b", "other"}},
    70  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4);"}},
    71  				{cmd.CommitCmd{}, []string{"-am", "added rows on other"}},
    72  				{cmd.CheckoutCmd{}, []string{env.DefaultInitBranch}},
    73  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (5,6);"}},
    74  				{cmd.CommitCmd{}, []string{"-am", "added rows on main"}},
    75  				{cmd.MergeCmd{}, []string{"other"}},
    76  			},
    77  			expected: []keylessEntry{
    78  				{2, 1, 2},
    79  				{1, 3, 4},
    80  				{1, 5, 6},
    81  			},
    82  		},
    83  		{
    84  			name: "3-way merge with duplicates",
    85  			setup: []testCommand{
    86  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2);"}},
    87  				{cmd.AddCmd{}, []string{"."}},
    88  				{cmd.CommitCmd{}, []string{"-am", "added rows"}},
    89  				{cmd.CheckoutCmd{}, []string{"-b", "other"}},
    90  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4), (3,4);"}},
    91  				{cmd.CommitCmd{}, []string{"-am", "added rows on other"}},
    92  				{cmd.CheckoutCmd{}, []string{env.DefaultInitBranch}},
    93  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (5,6), (5,6);"}},
    94  				{cmd.CommitCmd{}, []string{"-am", "added rows on main"}},
    95  				{cmd.MergeCmd{}, []string{"other"}},
    96  			},
    97  			expected: []keylessEntry{
    98  				{2, 1, 2},
    99  				{2, 3, 4},
   100  				{2, 5, 6},
   101  			},
   102  		},
   103  	}
   104  
   105  	for _, test := range tests {
   106  		t.Run(test.name, func(t *testing.T) {
   107  			ctx := context.Background()
   108  			dEnv := dtu.CreateTestEnv()
   109  			defer dEnv.DoltDB.Close()
   110  
   111  			root, err := dEnv.WorkingRoot(ctx)
   112  			require.NoError(t, err)
   113  			root, err = doltdb.CreateEmptyTable(ctx, root, doltdb.TableName{Name: tblName}, keylessSch)
   114  			require.NoError(t, err)
   115  			err = dEnv.UpdateWorkingRoot(ctx, root)
   116  			require.NoError(t, err)
   117  			cliCtx, err := cmd.NewArgFreeCliContext(ctx, dEnv)
   118  			require.NoError(t, err)
   119  
   120  			for _, c := range test.setup {
   121  				exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cliCtx)
   122  				require.Equal(t, 0, exitCode)
   123  			}
   124  
   125  			root, err = dEnv.WorkingRoot(ctx)
   126  			require.NoError(t, err)
   127  			tbl, _, err := root.GetTable(ctx, doltdb.TableName{Name: tblName})
   128  			require.NoError(t, err)
   129  
   130  			assertKeylessRows(t, ctx, tbl, test.expected)
   131  		})
   132  	}
   133  }
   134  
   135  func TestKeylessMergeConflicts(t *testing.T) {
   136  	tests := []struct {
   137  		name  string
   138  		setup []testCommand
   139  
   140  		// Tuple(
   141  		//    Tuple(baseVal)
   142  		//    Tuple(val)
   143  		//    Tuple(mergeVal)
   144  		// )
   145  		conflicts conflictEntries
   146  
   147  		oursExpected   keylessEntries
   148  		theirsExpected keylessEntries
   149  	}{
   150  		{
   151  			name: "identical parallel changes",
   152  			setup: []testCommand{
   153  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2);"}},
   154  				{cmd.AddCmd{}, []string{"."}},
   155  				{cmd.CommitCmd{}, []string{"-am", "added rows"}},
   156  				{cmd.CheckoutCmd{}, []string{"-b", "other"}},
   157  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4);"}},
   158  				{cmd.CommitCmd{}, []string{"-am", "added rows on other"}},
   159  				{cmd.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   160  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4);"}},
   161  				{cmd.CommitCmd{}, []string{"-am", "added rows on main"}},
   162  				{cmd.MergeCmd{}, []string{"other"}},
   163  			},
   164  			conflicts: []conflictEntry{
   165  				{
   166  					base:   nil,
   167  					ours:   &keylessEntry{1, 3, 4},
   168  					theirs: &keylessEntry{1, 3, 4},
   169  				},
   170  			},
   171  			oursExpected: []keylessEntry{
   172  				{2, 1, 2},
   173  				{1, 3, 4},
   174  			},
   175  			theirsExpected: []keylessEntry{
   176  				{2, 1, 2},
   177  				{1, 3, 4},
   178  			},
   179  		},
   180  		{
   181  			name: "asymmetric parallel deletes",
   182  			setup: []testCommand{
   183  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2),(1,2),(1,2);"}},
   184  				{cmd.AddCmd{}, []string{"."}},
   185  				{cmd.CommitCmd{}, []string{"-am", "added rows"}},
   186  				{cmd.CheckoutCmd{}, []string{"-b", "other"}},
   187  				{cmd.SqlCmd{}, []string{"-q", "delete from noKey where (c1,c2) = (1,2) limit 1;"}},
   188  				{cmd.CommitCmd{}, []string{"-am", "deleted 1 row on other"}},
   189  				{cmd.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   190  				{cmd.SqlCmd{}, []string{"-q", "delete from noKey where (c1,c2) = (1,2) limit 2;"}},
   191  				{cmd.CommitCmd{}, []string{"-am", "deleted 2 rows on main"}},
   192  				{cmd.MergeCmd{}, []string{"other"}},
   193  			},
   194  			conflicts: []conflictEntry{
   195  				{
   196  					base:   &keylessEntry{4, 1, 2},
   197  					ours:   &keylessEntry{2, 1, 2},
   198  					theirs: &keylessEntry{3, 1, 2},
   199  				},
   200  			},
   201  			oursExpected: []keylessEntry{
   202  				{2, 1, 2},
   203  			},
   204  			theirsExpected: []keylessEntry{
   205  				{3, 1, 2},
   206  			},
   207  		},
   208  		{
   209  			name: "asymmetric parallel updates",
   210  			setup: []testCommand{
   211  				{cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2),(1,2),(1,2);"}},
   212  				{cmd.AddCmd{}, []string{"."}},
   213  				{cmd.CommitCmd{}, []string{"-am", "added rows"}},
   214  				{cmd.CheckoutCmd{}, []string{"-b", "other"}},
   215  				{cmd.SqlCmd{}, []string{"-q", "update noKey set c2 = 9 limit 1;"}},
   216  				{cmd.CommitCmd{}, []string{"-am", "deleted 1 row on other"}},
   217  				{cmd.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   218  				{cmd.SqlCmd{}, []string{"-q", "update noKey set c2 = 9 limit 2;"}},
   219  				{cmd.CommitCmd{}, []string{"-am", "deleted 2 rows on main"}},
   220  				{cmd.MergeCmd{}, []string{"other"}},
   221  			},
   222  			conflicts: []conflictEntry{
   223  				{
   224  					base:   &keylessEntry{4, 1, 2},
   225  					ours:   &keylessEntry{2, 1, 2},
   226  					theirs: &keylessEntry{3, 1, 2},
   227  				},
   228  				{
   229  					base:   nil,
   230  					ours:   &keylessEntry{2, 1, 9},
   231  					theirs: &keylessEntry{1, 1, 9},
   232  				},
   233  			},
   234  			oursExpected: []keylessEntry{
   235  				{2, 1, 2},
   236  				{2, 1, 9},
   237  			},
   238  			theirsExpected: []keylessEntry{
   239  				{3, 1, 2},
   240  				{1, 1, 9},
   241  			},
   242  		},
   243  	}
   244  
   245  	setupTest := func(t *testing.T, ctx context.Context, dEnv *env.DoltEnv, cc []testCommand) {
   246  		root, err := dEnv.WorkingRoot(ctx)
   247  		require.NoError(t, err)
   248  		root, err = doltdb.CreateEmptyTable(ctx, root, doltdb.TableName{Name: tblName}, keylessSch)
   249  		require.NoError(t, err)
   250  		err = dEnv.UpdateWorkingRoot(ctx, root)
   251  		require.NoError(t, err)
   252  		cliCtx, err := cmd.NewArgFreeCliContext(ctx, dEnv)
   253  		require.NoError(t, err)
   254  
   255  		for _, c := range cc {
   256  			exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cliCtx)
   257  			// allow merge to fail with conflicts
   258  			if _, ok := c.cmd.(cmd.MergeCmd); !ok {
   259  				require.Equal(t, 0, exitCode)
   260  			}
   261  		}
   262  	}
   263  
   264  	ctx := context.Background()
   265  	for _, test := range tests {
   266  		t.Run(test.name, func(t *testing.T) {
   267  			dEnv := dtu.CreateTestEnv()
   268  			defer dEnv.DoltDB.Close()
   269  			setupTest(t, ctx, dEnv, test.setup)
   270  
   271  			root, err := dEnv.WorkingRoot(ctx)
   272  			require.NoError(t, err)
   273  			tbl, _, err := root.GetTable(ctx, doltdb.TableName{Name: tblName})
   274  			require.NoError(t, err)
   275  			assertConflicts(t, ctx, tbl, test.conflicts)
   276  		})
   277  
   278  		// conflict resolution
   279  
   280  		t.Run(test.name+"_resolved_ours", func(t *testing.T) {
   281  			dEnv := dtu.CreateTestEnv()
   282  			defer dEnv.DoltDB.Close()
   283  
   284  			setupTest(t, ctx, dEnv, test.setup)
   285  			cliCtx, verr := cmd.NewArgFreeCliContext(ctx, dEnv)
   286  			require.NoError(t, verr)
   287  
   288  			resolve := cnfcmds.ResolveCmd{}
   289  			args := []string{"--ours", tblName}
   290  			exitCode := resolve.Exec(ctx, resolve.Name(), args, dEnv, cliCtx)
   291  			require.Equal(t, 0, exitCode)
   292  
   293  			root, err := dEnv.WorkingRoot(ctx)
   294  			require.NoError(t, err)
   295  			tbl, _, err := root.GetTable(ctx, doltdb.TableName{Name: tblName})
   296  			require.NoError(t, err)
   297  
   298  			assertKeylessRows(t, ctx, tbl, test.oursExpected)
   299  		})
   300  		t.Run(test.name+"_resolved_theirs", func(t *testing.T) {
   301  			dEnv := dtu.CreateTestEnv()
   302  			defer dEnv.DoltDB.Close()
   303  
   304  			setupTest(t, ctx, dEnv, test.setup)
   305  			cliCtx, verr := cmd.NewArgFreeCliContext(ctx, dEnv)
   306  			require.NoError(t, verr)
   307  
   308  			resolve := cnfcmds.ResolveCmd{}
   309  			args := []string{"--theirs", tblName}
   310  			exitCode := resolve.Exec(ctx, resolve.Name(), args, dEnv, cliCtx)
   311  			require.Equal(t, 0, exitCode)
   312  
   313  			root, err := dEnv.WorkingRoot(ctx)
   314  			require.NoError(t, err)
   315  			tbl, _, err := root.GetTable(ctx, doltdb.TableName{Name: tblName})
   316  			require.NoError(t, err)
   317  
   318  			assertKeylessRows(t, ctx, tbl, test.theirsExpected)
   319  		})
   320  	}
   321  }
   322  
   323  func assertConflicts(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected conflictEntries) {
   324  	if types.IsFormat_DOLT(tbl.Format()) {
   325  		assertProllyConflicts(t, ctx, tbl, expected)
   326  		return
   327  	}
   328  	assertNomsConflicts(t, ctx, tbl, expected)
   329  }
   330  
   331  func assertProllyConflicts(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected conflictEntries) {
   332  	artIdx, err := tbl.GetArtifacts(ctx)
   333  	require.NoError(t, err)
   334  	artM := durable.ProllyMapFromArtifactIndex(artIdx)
   335  
   336  	itr, err := artM.IterAllConflicts(ctx)
   337  	require.NoError(t, err)
   338  
   339  	expectedSet := expected.toConflictSet()
   340  
   341  	var c int
   342  	var h [16]byte
   343  	for {
   344  		conf, err := itr.Next(ctx)
   345  		if err == io.EOF {
   346  			break
   347  		}
   348  		require.NoError(t, err)
   349  		c++
   350  
   351  		ours := mustGetRowValueFromTable(t, ctx, tbl, conf.Key)
   352  		theirs := mustGetRowValueFromRootIsh(t, ctx, tbl.ValueReadWriter(), tbl.NodeStore(), conf.TheirRootIsh, tblName, conf.Key)
   353  		base := mustGetRowValueFromRootIsh(t, ctx, tbl.ValueReadWriter(), tbl.NodeStore(), conf.Metadata.BaseRootIsh, tblName, conf.Key)
   354  
   355  		copy(h[:], conf.Key.GetField(0))
   356  		expectedConf, ok := expectedSet[h]
   357  		require.True(t, ok)
   358  
   359  		if expectedConf.base != nil {
   360  			_, value := expectedConf.base.HashAndValue()
   361  			require.Equal(t, valDesc.Format(value), valDesc.Format(base))
   362  		}
   363  		if expectedConf.ours != nil {
   364  			_, value := expectedConf.ours.HashAndValue()
   365  			require.Equal(t, valDesc.Format(value), valDesc.Format(ours))
   366  		}
   367  		if expectedConf.theirs != nil {
   368  			_, value := expectedConf.theirs.HashAndValue()
   369  			require.Equal(t, valDesc.Format(value), valDesc.Format(theirs))
   370  		}
   371  	}
   372  
   373  	require.Equal(t, len(expected), c)
   374  
   375  }
   376  
   377  func assertNomsConflicts(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected conflictEntries) {
   378  	_, confIdx, err := tbl.GetConflicts(ctx)
   379  	require.NoError(t, err)
   380  	conflicts := durable.NomsMapFromConflictIndex(confIdx)
   381  
   382  	assert.True(t, conflicts.Len() > 0)
   383  	assert.Equal(t, int(conflicts.Len()), len(expected))
   384  
   385  	expectedSet := expected.toTupleSet()
   386  
   387  	actual, err := conflicts.Iterator(ctx)
   388  	require.NoError(t, err)
   389  	for {
   390  		_, act, err := actual.Next(ctx)
   391  		if act == nil {
   392  			return
   393  		}
   394  		assert.NoError(t, err)
   395  		h, err := act.Hash(types.Format_Default)
   396  		assert.NoError(t, err)
   397  		exp, ok := expectedSet[h]
   398  		assert.True(t, ok)
   399  		assert.True(t, exp.Equals(act))
   400  	}
   401  }
   402  
   403  func mustGetRowValueFromTable(t *testing.T, ctx context.Context, tbl *doltdb.Table, key val.Tuple) val.Tuple {
   404  	idx, err := tbl.GetRowData(ctx)
   405  	require.NoError(t, err)
   406  	m := durable.ProllyMapFromIndex(idx)
   407  
   408  	var value val.Tuple
   409  	err = m.Get(ctx, key, func(_, v val.Tuple) error {
   410  		value = v
   411  		return nil
   412  	})
   413  	require.NoError(t, err)
   414  
   415  	return value
   416  }
   417  
   418  func mustGetRowValueFromRootIsh(t *testing.T, ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, rootIsh hash.Hash, tblName string, key val.Tuple) val.Tuple {
   419  	rv, err := doltdb.LoadRootValueFromRootIshAddr(ctx, vrw, ns, rootIsh)
   420  	require.NoError(t, err)
   421  	tbl, ok, err := rv.GetTable(ctx, doltdb.TableName{Name: tblName})
   422  	require.NoError(t, err)
   423  	require.True(t, ok)
   424  
   425  	return mustGetRowValueFromTable(t, ctx, tbl, key)
   426  }
   427  
   428  // |expected| is a tupleSet to compensate for random storage order
   429  func assertKeylessRows(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected keylessEntries) {
   430  	if types.IsFormat_DOLT(tbl.Format()) {
   431  		assertKeylessProllyRows(t, ctx, tbl, expected)
   432  		return
   433  	}
   434  
   435  	assertKeylessNomsRows(t, ctx, tbl, expected)
   436  }
   437  
   438  func assertKeylessProllyRows(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected []keylessEntry) {
   439  	idx, err := tbl.GetRowData(ctx)
   440  	require.NoError(t, err)
   441  	m := durable.ProllyMapFromIndex(idx)
   442  
   443  	expectedSet := mustHash128Set(expected...)
   444  
   445  	itr, err := m.IterAll(ctx)
   446  	require.NoError(t, err)
   447  
   448  	var c int
   449  	var h [16]byte
   450  	for {
   451  		hashId, value, err := itr.Next(ctx)
   452  		if err == io.EOF {
   453  			break
   454  		}
   455  		c++
   456  		require.NoError(t, err)
   457  		copy(h[:], hashId.GetField(0))
   458  		expectedVal, ok := expectedSet[h]
   459  		assert.True(t, ok)
   460  		assert.Equal(t, valDesc.Format(expectedVal), valDesc.Format(value))
   461  	}
   462  
   463  	require.Equal(t, len(expected), c)
   464  }
   465  
   466  func assertKeylessNomsRows(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected keylessEntries) {
   467  	rowData, err := tbl.GetNomsRowData(ctx)
   468  	require.NoError(t, err)
   469  
   470  	assert.Equal(t, int(rowData.Len()), len(expected))
   471  
   472  	expectedSet := expected.toTupleSet()
   473  
   474  	actual, err := rowData.Iterator(ctx)
   475  	require.NoError(t, err)
   476  	for {
   477  		_, act, err := actual.Next(ctx)
   478  		if act == nil {
   479  			break
   480  		}
   481  		assert.NoError(t, err)
   482  		h, err := act.Hash(types.Format_Default)
   483  		assert.NoError(t, err)
   484  		exp, ok := expectedSet[h]
   485  		assert.True(t, ok)
   486  		assert.True(t, exp.Equals(act))
   487  	}
   488  }
   489  
   490  const tblName = "noKey"
   491  
   492  var keylessSch = dtu.MustSchema(
   493  	schema.NewColumn("c1", 1, types.IntKind, false),
   494  	schema.NewColumn("c2", 2, types.IntKind, false),
   495  )
   496  var c1Tag = types.Uint(1)
   497  var c2Tag = types.Uint(2)
   498  var cardTag = types.Uint(schema.KeylessRowCardinalityTag)
   499  
   500  var valDesc = val.NewTupleDescriptor(val.Type{Enc: val.Uint64Enc}, val.Type{Enc: val.Int64Enc, Nullable: true}, val.Type{Enc: val.Int64Enc, Nullable: true})
   501  var valBld = val.NewTupleBuilder(valDesc)
   502  var sharePool = pool.NewBuffPool()
   503  
   504  type keylessEntries []keylessEntry
   505  type keylessEntry struct {
   506  	card int
   507  	c1   int
   508  	c2   int
   509  }
   510  
   511  func (e keylessEntries) toTupleSet() tupleSet {
   512  	tups := make([]types.Tuple, len(e))
   513  	for i, t := range e {
   514  		tups[i] = t.ToNomsTuple()
   515  	}
   516  	return mustTupleSet(tups...)
   517  }
   518  
   519  func (e keylessEntry) ToNomsTuple() types.Tuple {
   520  	return dtu.MustTuple(cardTag, types.Uint(e.card), c1Tag, types.Int(e.c1), c2Tag, types.Int(e.c2))
   521  }
   522  
   523  func (e keylessEntry) HashAndValue() ([]byte, val.Tuple) {
   524  	valBld.PutUint64(0, uint64(e.card))
   525  	valBld.PutInt64(1, int64(e.c1))
   526  	valBld.PutInt64(2, int64(e.c2))
   527  
   528  	value := valBld.Build(sharePool)
   529  	hashTup := val.HashTupleFromValue(sharePool, value)
   530  	return hashTup.GetField(0), value
   531  }
   532  
   533  type conflictSet map[[16]byte]conflictEntry
   534  type conflictEntries []conflictEntry
   535  type conflictEntry struct {
   536  	base, ours, theirs *keylessEntry
   537  }
   538  
   539  func (e conflictEntries) toConflictSet() conflictSet {
   540  	s := make(conflictSet, len(e))
   541  	for _, t := range e {
   542  		s[t.Key()] = t
   543  	}
   544  	return s
   545  }
   546  
   547  func (e conflictEntries) toTupleSet() tupleSet {
   548  	tups := make([]types.Tuple, len(e))
   549  	for i, t := range e {
   550  		tups[i] = t.ToNomsTuple()
   551  	}
   552  	return mustTupleSet(tups...)
   553  }
   554  
   555  func (e conflictEntry) Key() (h [16]byte) {
   556  	if e.base != nil {
   557  		h2, _ := e.base.HashAndValue()
   558  		copy(h[:], h2[:])
   559  		return
   560  	}
   561  	if e.ours != nil {
   562  		h2, _ := e.ours.HashAndValue()
   563  		copy(h[:], h2[:])
   564  		return
   565  	}
   566  	if e.theirs != nil {
   567  		h2, _ := e.theirs.HashAndValue()
   568  		copy(h[:], h2[:])
   569  		return
   570  	}
   571  
   572  	return
   573  }
   574  
   575  func (e conflictEntry) ToNomsTuple() types.Tuple {
   576  	var b, o, t types.Value = types.NullValue, types.NullValue, types.NullValue
   577  	if e.base != nil {
   578  		b = e.base.ToNomsTuple()
   579  	}
   580  	if e.ours != nil {
   581  		o = e.ours.ToNomsTuple()
   582  	}
   583  	if e.theirs != nil {
   584  		t = e.theirs.ToNomsTuple()
   585  	}
   586  	return dtu.MustTuple(b, o, t)
   587  }
   588  
   589  type tupleSet map[hash.Hash]types.Tuple
   590  
   591  func mustTupleSet(tt ...types.Tuple) (s tupleSet) {
   592  	s = make(tupleSet, len(tt))
   593  	for _, tup := range tt {
   594  		h, err := tup.Hash(types.Format_Default)
   595  		if err != nil {
   596  			panic(err)
   597  		}
   598  		s[h] = tup
   599  	}
   600  	return
   601  }
   602  
   603  type hash128Set map[[16]byte]val.Tuple
   604  
   605  func mustHash128Set(entries ...keylessEntry) (s hash128Set) {
   606  	var h [16]byte
   607  	s = make(hash128Set, len(entries))
   608  
   609  	for _, e := range entries {
   610  		h2, value := e.HashAndValue()
   611  		copy(h[:], h2)
   612  		s[h] = value
   613  	}
   614  
   615  	return s
   616  }