github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/prolly/tree/three_way_differ_test.go (about)

     1  // Copyright 2023 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 tree
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"testing"
    23  
    24  	"github.com/dolthub/go-mysql-server/sql"
    25  	"github.com/stretchr/testify/require"
    26  
    27  	"github.com/dolthub/dolt/go/store/prolly/message"
    28  	"github.com/dolthub/dolt/go/store/val"
    29  )
    30  
    31  type testDiff struct {
    32  	op      DiffOp
    33  	k       int
    34  	l, r, m []int
    35  }
    36  
    37  func (d testDiff) String() string {
    38  	return fmt.Sprintf("%s(key=%d)", d.op, d.k)
    39  }
    40  
    41  func TestThreeWayDiffer(t *testing.T) {
    42  	tests := []struct {
    43  		name  string
    44  		base  [][]int
    45  		left  [][]int
    46  		right [][]int
    47  		exp   []testDiff
    48  	}{
    49  		{
    50  			name:  "left adds",
    51  			base:  [][]int{{1, 1}, {2, 2}},
    52  			left:  [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}},
    53  			right: [][]int{{1, 1}, {2, 2}, {4, 4}},
    54  			exp: []testDiff{
    55  				{op: DiffOpLeftAdd, k: 3},
    56  				{op: DiffOpConvergentAdd, k: 4},
    57  				{op: DiffOpLeftAdd, k: 5},
    58  				{op: DiffOpLeftAdd, k: 6},
    59  			},
    60  		},
    61  		{
    62  			name:  "right adds",
    63  			base:  [][]int{{1, 1}, {2, 2}},
    64  			left:  [][]int{{1, 1}, {2, 2}, {4, 4}},
    65  			right: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}},
    66  			exp: []testDiff{
    67  				{op: DiffOpRightAdd, k: 3},
    68  				{op: DiffOpConvergentAdd, k: 4},
    69  				{op: DiffOpRightAdd, k: 5},
    70  				{op: DiffOpRightAdd, k: 6},
    71  			},
    72  		},
    73  		{
    74  			name:  "left deletes",
    75  			base:  [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}},
    76  			left:  [][]int{{1, 1}, {2, 2}},
    77  			right: [][]int{{1, 1}, {2, 2}, {3, 3}, {5, 5}, {6, 6}},
    78  			exp: []testDiff{
    79  				{op: DiffOpLeftDelete, k: 3},
    80  				{op: DiffOpConvergentDelete, k: 4},
    81  				{op: DiffOpLeftDelete, k: 5},
    82  				{op: DiffOpLeftDelete, k: 6},
    83  			},
    84  		},
    85  		{
    86  			name:  "right deletes",
    87  			base:  [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}},
    88  			left:  [][]int{{1, 1}, {2, 2}, {3, 3}, {5, 5}, {6, 6}},
    89  			right: [][]int{{1, 1}, {2, 2}},
    90  			exp: []testDiff{
    91  				{op: DiffOpRightDelete, k: 3},
    92  				{op: DiffOpConvergentDelete, k: 4},
    93  				{op: DiffOpRightDelete, k: 5},
    94  				{op: DiffOpRightDelete, k: 6},
    95  			},
    96  		},
    97  		{
    98  			name:  "left edits",
    99  			base:  [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}},
   100  			left:  [][]int{{1, 1}, {2, 3}, {3, 3}, {4, 5}, {5, 6}, {6, 7}},
   101  			right: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 5}, {5, 5}, {6, 6}},
   102  			exp: []testDiff{
   103  				{op: DiffOpLeftModify, k: 2},
   104  				{op: DiffOpConvergentModify, k: 4},
   105  				{op: DiffOpLeftModify, k: 5},
   106  				{op: DiffOpLeftModify, k: 6},
   107  			},
   108  		},
   109  		{
   110  			name:  "right edits",
   111  			base:  [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}},
   112  			left:  [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 5}, {5, 5}, {6, 6}},
   113  			right: [][]int{{1, 1}, {2, 3}, {3, 3}, {4, 5}, {5, 6}, {6, 7}},
   114  			exp: []testDiff{
   115  				{op: DiffOpRightModify, k: 2},
   116  				{op: DiffOpConvergentModify, k: 4},
   117  				{op: DiffOpRightModify, k: 5},
   118  				{op: DiffOpRightModify, k: 6},
   119  			},
   120  		},
   121  		{
   122  			name:  "delete conflicts",
   123  			base:  [][]int{{1, 1}, {2, 2}},
   124  			left:  [][]int{{1, 1}},
   125  			right: [][]int{{1, 1}, {2, 3}},
   126  			exp: []testDiff{
   127  				{op: DiffOpDivergentDeleteConflict, k: 2},
   128  			},
   129  		},
   130  		{
   131  			name:  "convergent edits",
   132  			base:  [][]int{{1, 1}, {4, 4}},
   133  			left:  [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}},
   134  			right: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}},
   135  			exp: []testDiff{
   136  				{op: DiffOpConvergentAdd, k: 2},
   137  				{op: DiffOpConvergentAdd, k: 3},
   138  				{op: DiffOpConvergentAdd, k: 5},
   139  			},
   140  		},
   141  		{
   142  			name:  "clash edits",
   143  			base:  [][]int{{1, 1}, {4, 4}},
   144  			left:  [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}},
   145  			right: [][]int{{1, 1}, {2, 3}, {3, 4}, {4, 4}, {5, 6}},
   146  			exp: []testDiff{
   147  				{op: DiffOpDivergentModifyConflict, k: 2},
   148  				{op: DiffOpDivergentModifyConflict, k: 3},
   149  				{op: DiffOpDivergentModifyConflict, k: 5},
   150  			},
   151  		},
   152  		{
   153  			name:  "resolvable edits",
   154  			base:  [][]int{{1, 1, 1}, {2, 2, 2}, {3, 3, 3}, {4, 4, 4}, {5, 5, 5}},
   155  			left:  [][]int{{1, 1, 1}, {2, 2, 3}, {3, 3, 4}, {4, 4, 4}, {5, 5, 6}},
   156  			right: [][]int{{1, 1, 1}, {2, 3, 2}, {3, 4, 3}, {4, 4, 4}, {5, 6, 5}},
   157  			exp: []testDiff{
   158  				{op: DiffOpDivergentModifyResolved, k: 2, m: []int{3, 3}},
   159  				{op: DiffOpDivergentModifyResolved, k: 3, m: []int{4, 4}},
   160  				{op: DiffOpDivergentModifyResolved, k: 5, m: []int{6, 6}},
   161  			},
   162  		},
   163  		{
   164  			name:  "combine types",
   165  			base:  [][]int{{1, 1, 1}, {2, 2, 2}, {3, 3, 3}, {4, 4, 4}, {5, 5, 5}, {8, 8, 8}},
   166  			left:  [][]int{{1, 1, 1}, {2, 2, 3}, {3, 3, 4}, {5, 5, 6}, {6, 6, 6}},
   167  			right: [][]int{{1, 1, 1}, {2, 3, 4}, {3, 4, 3}, {4, 4, 4}, {5, 6, 5}, {7, 7, 7}},
   168  			exp: []testDiff{
   169  				{op: DiffOpDivergentModifyConflict, k: 2},
   170  				{op: DiffOpDivergentModifyResolved, k: 3, m: []int{4, 4}},
   171  				{op: DiffOpLeftDelete, k: 4},
   172  				{op: DiffOpDivergentModifyResolved, k: 5, m: []int{6, 6}},
   173  				{op: DiffOpLeftAdd, k: 6},
   174  				{op: DiffOpRightAdd, k: 7},
   175  				{op: DiffOpConvergentDelete, k: 8},
   176  			},
   177  		},
   178  	}
   179  
   180  	for _, tt := range tests {
   181  		t.Run(tt.name, func(t *testing.T) {
   182  			ctx := sql.NewEmptyContext()
   183  			ns := NewTestNodeStore()
   184  
   185  			var valTypes []val.Type
   186  			for i := 0; i < len(tt.base[0])-1; i++ {
   187  				valTypes = append(valTypes, val.Type{Enc: val.Int64Enc, Nullable: true})
   188  			}
   189  
   190  			valDesc := val.TupleDesc{Types: valTypes}
   191  
   192  			base := newTestMap(t, ctx, tt.base, ns, valDesc)
   193  			left := newTestMap(t, ctx, tt.left, ns, valDesc)
   194  			right := newTestMap(t, ctx, tt.right, ns, valDesc)
   195  
   196  			var diffInfo ThreeWayDiffInfo
   197  			iter, err := NewThreeWayDiffer(ctx, ns, left, right, base, testResolver(t, ns, valDesc, val.NewTupleBuilder(valDesc)), false, diffInfo, keyDesc)
   198  			require.NoError(t, err)
   199  
   200  			var cmp []testDiff
   201  			for {
   202  				diff, err := iter.Next(ctx)
   203  				if errors.Is(err, io.EOF) {
   204  					break
   205  				}
   206  				require.NoError(t, err)
   207  				cmp = append(cmp, formatTestDiff(t, diff, keyDesc, valDesc))
   208  			}
   209  
   210  			require.Equal(t, len(cmp), len(tt.exp), "number of diffs not equal")
   211  
   212  			for i, exp := range tt.exp {
   213  				cmp := cmp[i]
   214  				compareDiffs(t, exp, cmp)
   215  			}
   216  		})
   217  	}
   218  }
   219  
   220  func testResolver(t *testing.T, ns NodeStore, valDesc val.TupleDesc, valBuilder *val.TupleBuilder) func(*sql.Context, val.Tuple, val.Tuple, val.Tuple) (val.Tuple, bool, error) {
   221  	return func(_ *sql.Context, l, r, b val.Tuple) (val.Tuple, bool, error) {
   222  		for i := range valDesc.Types {
   223  			var base, left, right int64
   224  			var ok bool
   225  			if b != nil {
   226  				base, ok = valDesc.GetInt64(i, b)
   227  				require.True(t, ok)
   228  			}
   229  
   230  			if l != nil {
   231  				left, ok = valDesc.GetInt64(i, l)
   232  				require.True(t, ok)
   233  			}
   234  
   235  			if r != nil {
   236  				right, ok = valDesc.GetInt64(i, r)
   237  				require.True(t, ok)
   238  			}
   239  
   240  			if base != left && base != right && left != right {
   241  				return nil, false, nil
   242  			} else if base != left {
   243  				valBuilder.PutInt64(i, left)
   244  			} else if base != right {
   245  				valBuilder.PutInt64(i, right)
   246  			} else {
   247  				valBuilder.PutInt64(i, base)
   248  			}
   249  		}
   250  		return valBuilder.Build(ns.Pool()), true, nil
   251  	}
   252  }
   253  
   254  func compareDiffs(t *testing.T, exp, cmp testDiff) {
   255  	require.Equal(t, exp.op, cmp.op, fmt.Sprintf("unequal diffs:\nexp: %s\nfnd: %s", exp, cmp))
   256  	require.Equal(t, exp.k, cmp.k, fmt.Sprintf("unequal diffs:\nexp: %s\nfnd: %s", exp, cmp))
   257  	switch exp.op {
   258  	case DiffOpDivergentModifyResolved:
   259  		require.Equal(t, exp.m, cmp.m, fmt.Sprintf("unequal resolved:\nexp: %#v\nfnd: %#v", exp.m, cmp.m))
   260  	}
   261  }
   262  
   263  func formatTestDiff(t *testing.T, d ThreeWayDiff, keyDesc, valDesc val.TupleDesc) testDiff {
   264  	key, ok := keyDesc.GetInt64(0, d.Key)
   265  	require.True(t, ok)
   266  
   267  	return testDiff{
   268  		op: d.Op,
   269  		k:  int(key),
   270  		l:  extractTestVal(t, valDesc, d.Left),
   271  		r:  extractTestVal(t, valDesc, d.Right),
   272  		m:  extractTestVal(t, valDesc, d.Merged),
   273  	}
   274  }
   275  
   276  func extractTestVal(t *testing.T, valDesc val.TupleDesc, tuple val.Tuple) []int {
   277  	if tuple == nil {
   278  		return nil
   279  	}
   280  	ret := make([]int, len(valDesc.Types))
   281  	for i, _ := range valDesc.Types {
   282  		val, ok := valDesc.GetInt64(i, tuple)
   283  		require.True(t, ok)
   284  		ret[i] = int(val)
   285  	}
   286  	return ret
   287  }
   288  
   289  // newTestMap makes a prolly tree from a matrix of integers. Each row corresponds
   290  // to a row in the prolly map. The first value in a row will be the primary key.
   291  // The rest of the values will be the value fields.
   292  func newTestMap(t *testing.T, ctx context.Context, rows [][]int, ns NodeStore, valDesc val.TupleDesc) StaticMap[val.Tuple, val.Tuple, val.TupleDesc] {
   293  	serializer := message.NewProllyMapSerializer(valDesc, ns.Pool())
   294  	chkr, err := newEmptyChunker(ctx, ns, serializer)
   295  	require.NoError(t, err)
   296  
   297  	keyBuilder := val.NewTupleBuilder(keyDesc)
   298  	valBuilder := val.NewTupleBuilder(valDesc)
   299  
   300  	for _, row := range rows {
   301  		keyBuilder.PutInt64(0, int64(row[0]))
   302  		key := keyBuilder.Build(ns.Pool())
   303  		for j := 1; j < len(row); j++ {
   304  			valBuilder.PutInt64(j-1, int64(row[j]))
   305  			require.NoError(t, err)
   306  		}
   307  		val := valBuilder.Build(ns.Pool())
   308  		err := chkr.AddPair(ctx, Item(key), Item(val))
   309  		require.NoError(t, err)
   310  	}
   311  
   312  	root, err := chkr.Done(ctx)
   313  	require.NoError(t, err)
   314  	return StaticMap[val.Tuple, val.Tuple, val.TupleDesc]{
   315  		Root:      root,
   316  		NodeStore: ns,
   317  		Order:     keyDesc,
   318  	}
   319  }