Files
dolthub--dolt/go/store/prolly/patch_based_merging_test.go
wehub-resource-sync 5357c39144
Fuzzer / Run Fuzzer (push) Has been cancelled
Race tests / Go race tests (ubuntu-22.04) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:01:40 +08:00

2101 lines
84 KiB
Go

// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prolly
import (
"context"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"
"github.com/dolthub/dolt/go/store/prolly/message"
"github.com/dolthub/dolt/go/store/prolly/tree"
"github.com/dolthub/dolt/go/store/val"
)
// getChunkBoundaries returns all the keys in a node, which indicate the chunk boundaries in the previous tree level.
func getChunkBoundaries(t *testing.T, desc *val.TupleDesc, nd *tree.Node) (boundaries []uint32) {
for i := 0; i < nd.Count(); i++ {
key := nd.GetKey(i)
keyInt, ok := desc.GetUint32(0, val.Tuple(key))
require.True(t, ok, "failed to get int key from node")
boundaries = append(boundaries, keyInt)
}
return boundaries
}
// slicePatchIter implements tree.PatchIter and is used in tests to inspect a list of patches from a RangeDiffer
// before applying them.
type slicePatchIter struct {
slice []tree.Patch
index int
}
var _ tree.PatchIter = &slicePatchIter{}
func (s *slicePatchIter) NextPatch(ctx context.Context) (tree.Patch, error) {
if s.index >= len(s.slice) {
return tree.Patch{}, nil
}
result := s.slice[s.index]
s.index++
return result, nil
}
func (s *slicePatchIter) Close() error {
return nil
}
// producePatches produces a set of patches that can be applied to the left tree to produce the result of a three way merge.
// It passes the produced PatchIter to the callback function.
func producePatches[K ~[]byte, O tree.Ordering[K]](
ctx context.Context,
ns tree.NodeStore,
left, right, base *tree.Node,
collide tree.CollisionFn,
order O,
cb func(buffer tree.PatchIter) error,
) (err error) {
ld, err := tree.PatchGeneratorFromRoots[K](ctx, ns, ns, base, left, order)
if err != nil {
return err
}
rd, err := tree.PatchGeneratorFromRoots[K](ctx, ns, ns, base, right, order)
if err != nil {
return err
}
eg, ctx := errgroup.WithContext(ctx)
patches := tree.NewPatchBuffer(tree.PatchBufferSize)
// One goroutine produces the patches, the other consumes them.
eg.Go(func() (err error) {
defer func() {
if cerr := patches.Close(); err == nil {
err = cerr
}
}()
err = tree.SendPatches(ctx, ld, rd, patches, collide)
return
})
eg.Go(func() error {
return cb(patches)
})
if err = eg.Wait(); err != nil {
return err
}
return nil
}
// mutation is a description of a single key-value change, used for tests to describe changes made to each branch.
type mutation[T interface{}] struct {
key T
value *T
}
func update[T interface{}](key, value T) mutation[T] {
return mutation[T]{key, &value}
}
func remove[T interface{}](key T) mutation[T] {
return mutation[T]{key, nil}
}
// expectedPatch is a description of a patch returned by the patch generator. It's used in tests to describe expected
// output.
type expectedPatch[T interface{}] struct {
startKey T
endKey T
toValue T
level int
subtreeCount uint64
noStartKey bool
isRemoval bool
}
func pointUpdate[T interface{}](key, value T) expectedPatch[T] {
return expectedPatch[T]{
endKey: key,
noStartKey: false,
level: 0,
subtreeCount: 1,
toValue: value,
isRemoval: false,
}
}
func pointRemove[T interface{}](key T) expectedPatch[T] {
return expectedPatch[T]{
endKey: key,
noStartKey: false,
isRemoval: true,
}
}
func manyPointRemoves(keyStart, keyEnd uint32) (patches []expectedPatch[uint32]) {
for i := keyStart; i <= keyEnd; i++ {
patches = append(patches, pointRemove(i))
}
return patches
}
func manyPointInserts(keyStart, keyEnd uint32) (patches []expectedPatch[uint32]) {
for i := keyStart; i <= keyEnd; i++ {
patches = append(patches, pointUpdate(i, i))
}
return patches
}
// testPatchBasedMergingResultsOnly checks that the result of a merge is the same with both the original merge
// algorithm and the tree-based merge, but it skips asserting that the set of produced patches matches what's expected.
// We use this when there's known issues where we produce sub-optimal patches but we still want to check correctness.
func testPatchBasedMergingResultsOnly[T interface{}](
t *testing.T,
ctx context.Context,
ns tree.NodeStore,
keyDesc *val.TupleDesc,
collide tree.CollisionFn,
baseRoot, leftRoot, rightRoot *tree.Node,
expectedPatches []expectedPatch[T],
) {
serializer := message.NewProllyMapSerializer(keyDesc, ns.Pool())
traditionalMergeRoot, err := traditionalThreeWayMerge(ctx, ns, leftRoot, rightRoot, baseRoot, collide, false, false, keyDesc, serializer)
patchBasedMergeRoot, _, err := tree.ThreeWayMerge(ctx, ns, leftRoot, rightRoot, baseRoot, collide, keyDesc, serializer)
require.NoError(t, err)
assert.Equal(t, patchBasedMergeRoot, traditionalMergeRoot)
}
func testPatchBasedMerging[T interface{}](t *testing.T, ctx context.Context, ns tree.NodeStore, keyDesc *val.TupleDesc, collide tree.CollisionFn, baseRoot, leftRoot, rightRoot *tree.Node, expectedPatches []expectedPatch[T]) {
err := producePatches(ctx, ns, leftRoot, rightRoot, baseRoot, collide, keyDesc, func(iter tree.PatchIter) error {
var actualPatches []tree.Patch
actualPatch, err := iter.NextPatch(ctx)
require.NoError(t, err)
for actualPatch.EndKey != nil {
actualPatches = append(actualPatches, actualPatch)
actualPatch, err = iter.NextPatch(ctx)
require.NoError(t, err)
}
assert.Equal(t, len(expectedPatches), len(actualPatches), "expected %d patches but found %d", len(expectedPatches), len(actualPatches))
for i, actualPatch := range actualPatches {
expectedPatch := expectedPatches[i]
require.Equal(t, expectedPatch.level, actualPatch.Level, "patch %d has unexpected level. Expected %d, found %d", i, expectedPatch.level, actualPatch.Level)
if expectedPatch.noStartKey || expectedPatch.level == 0 {
assert.Nil(t, actualPatch.KeyBelowStart)
} else {
require.NotNil(t, actualPatch.KeyBelowStart, "patch %d has unexpected start key. Expected %d, found nil", i, expectedPatch.startKey)
if keyDesc.Types[0].Enc == val.Uint32Enc {
actualStartKey, _ := keyDesc.GetUint32(0, val.Tuple(actualPatch.KeyBelowStart))
assert.Equal(t, expectedPatch.startKey, actualStartKey, "patch %d has unexpected start key. Expected %d, found %d", i, expectedPatch.startKey, actualStartKey)
} else {
actualStartKey, _ := keyDesc.GetString(0, val.Tuple(actualPatch.KeyBelowStart))
assert.Equal(t, expectedPatch.startKey, actualStartKey, "patch %d has unexpected start key. Expected %s, found %s", i, expectedPatch.startKey, actualStartKey)
}
}
if keyDesc.Types[0].Enc == val.Uint32Enc {
actualEndKey, _ := keyDesc.GetUint32(0, val.Tuple(actualPatch.EndKey))
assert.Equal(t, expectedPatch.endKey, actualEndKey, "patch %d has unexpected end key. Expected %d, found %d", i, expectedPatch.endKey, actualEndKey)
} else {
actualEndKey, _ := keyDesc.GetString(0, val.Tuple(actualPatch.EndKey))
assert.Equal(t, expectedPatch.endKey, actualEndKey, "patch %d has unexpected end key. Expected %s, found %s", i, expectedPatch.endKey, actualEndKey)
}
if actualPatch.To == nil {
assert.True(t, expectedPatch.isRemoval, "patch %d should be a removal patch, but it isn't.", i)
} else {
if actualPatch.Level > 0 {
assert.Equal(t, expectedPatch.subtreeCount, actualPatch.SubtreeCount, "patch %d has unexpected subtree count. Expected %d, found %d", i, expectedPatch.subtreeCount, actualPatch.SubtreeCount)
expectedAddress, ok, err := tree.GetAddressFromLevelAndKeyForTest(ctx, ns, rightRoot, actualPatch.Level, val.Tuple(actualPatch.EndKey), keyDesc)
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, tree.Item(expectedAddress[:]), actualPatch.To)
} else {
if keyDesc.Types[0].Enc == val.Uint32Enc {
actualToValue, _ := keyDesc.GetUint32(0, val.Tuple(actualPatch.To))
assert.Equal(t, expectedPatch.toValue, actualToValue, "patch %d has unexpected to value. Expected %d, found %d", i, expectedPatch.toValue, actualToValue)
} else {
actualToValue, _ := keyDesc.GetString(0, val.Tuple(actualPatch.To))
assert.Equal(t, expectedPatch.toValue, actualToValue, "patch %d has unexpected to value. Expected %s, found %s", i, expectedPatch.toValue, actualToValue)
}
}
}
}
// In order to apply the patches, we need to wrap them in a new tree.PatchIter
patchIter := slicePatchIter{slice: actualPatches}
serializer := message.NewProllyMapSerializer(keyDesc, ns.Pool())
// verify that this is equivalent to a traditional merge.
traditionalMergeRoot, err := traditionalThreeWayMerge(ctx, ns, leftRoot, rightRoot, baseRoot, collide, false, false, keyDesc, serializer)
mergedRoot, err := tree.ApplyPatches(ctx, ns, leftRoot, keyDesc, serializer, &patchIter)
require.NoError(t, err)
assert.Equal(t, mergedRoot, traditionalMergeRoot)
return nil
})
require.NoError(t, err)
}
// mutate creates a new tree map by applying a sequence of mutations to an existing tree map.
func mutate(t *testing.T, ctx context.Context, baseRoot *tree.Node, keyDesc, valDesc *val.TupleDesc, mutations []mutation[uint32]) *tree.Node {
baseMap := NewMap(baseRoot, ns, keyDesc, valDesc)
keyBld := val.NewTupleBuilder(keyDesc, ns)
valBld := val.NewTupleBuilder(valDesc, ns)
var err error
mutMap := baseMap.Mutate()
for _, m := range mutations {
keyBld.PutUint32(0, m.key)
var newValue val.Tuple
if m.value != nil {
valBld.PutUint32(0, *m.value)
newValue, err = valBld.Build(context.Background(), sharedPool)
require.NoError(t, err)
}
newKey, err := keyBld.Build(context.Background(), sharedPool)
require.NoError(t, err)
err = mutMap.Put(ctx, newKey, newValue)
require.NoError(t, err)
}
newMap, err := mutMap.Map(ctx)
require.NoError(t, err)
return newMap.Node()
}
func mutateStrings(t *testing.T, ctx context.Context, baseRoot *tree.Node, keyDesc, valDesc *val.TupleDesc, mutations []mutation[string]) *tree.Node {
baseMap := NewMap(baseRoot, ns, keyDesc, valDesc)
keyBld := val.NewTupleBuilder(keyDesc, ns)
valBld := val.NewTupleBuilder(valDesc, ns)
var err error
mutMap := baseMap.Mutate()
for _, m := range mutations {
err := keyBld.PutString(0, m.key)
require.NoError(t, err)
var newValue val.Tuple
if m.value != nil {
err = valBld.PutString(0, *m.value)
require.NoError(t, err)
newValue, err = valBld.Build(context.Background(), sharedPool)
require.NoError(t, err)
}
newKey, err := keyBld.Build(context.Background(), sharedPool)
require.NoError(t, err)
err = mutMap.Put(ctx, newKey, newValue)
require.NoError(t, err)
}
newMap, err := mutMap.Map(ctx)
require.NoError(t, err)
return newMap.Node()
}
// makeDeletePatches produces mutations describing deleting all keys in a range.
func makeDeletePatches(start, stop uint32) (mutations []mutation[uint32]) {
for i := start; i <= stop; i++ {
mutations = append(mutations, remove(i))
}
return mutations
}
func makeSimpleIntMap(t *testing.T, start, stop int) (*tree.Node, *val.TupleDesc) {
tups, desc := tree.AscendingUintTuplesWithStep(stop-start+1, start, start, 1)
root, err := tree.MakeTreeForTest(tups)
require.NoError(t, err)
return root, desc
}
// TestPatchBasedMerging tests that the patching process produces the minimal set of patches which must be applied
// to the left tree in order to produce the merged tree.
func TestPatchBasedMerging(t *testing.T) {
ctx := context.Background()
ns := tree.NewTestNodeStore()
emptyMap, err := tree.MakeTreeForTest(nil)
require.NoError(t, err)
fourAndAHalfChunks, desc := makeSimpleIntMap(t, 1, 1250)
threeAndAHalfChunks, _ := makeSimpleIntMap(t, 1, 1024)
threeChunks, _ := makeSimpleIntMap(t, 1, 830)
twoAndAHalfChunks, _ := makeSimpleIntMap(t, 1, 800)
// The base map will happen to have these chunk boundaries.
chunkBoundaries := []uint32{414, 718, 830, 1207}
maxKey := uint32(1024)
stringDesc := val.NewTupleDescriptor(val.Type{Enc: val.StringEnc})
mapWithUpdates := func(root *tree.Node, updates ...mutation[uint32]) *tree.Node {
return mutate(t, ctx, root, desc, desc, updates)
}
stringMapWithUpdates := func(root *tree.Node, updates ...mutation[string]) *tree.Node {
return mutateStrings(t, ctx, root, stringDesc, stringDesc, updates)
}
t.Run("concurrent updates in same leaf node produce level 0 patch", func(t *testing.T) {
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
mapWithUpdates(threeAndAHalfChunks, update[uint32](1, 0)),
mapWithUpdates(threeAndAHalfChunks, update[uint32](chunkBoundaries[0]-4, 1)),
[]expectedPatch[uint32]{pointUpdate(chunkBoundaries[0]-4, 1)})
})
t.Run("update in first child node produces top-level patch with no start key", func(t *testing.T) {
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
mapWithUpdates(threeAndAHalfChunks, update[uint32](1000, 1)),
mapWithUpdates(threeAndAHalfChunks, update[uint32](chunkBoundaries[0]-10, 1)),
[]expectedPatch[uint32]{
{
noStartKey: true,
endKey: chunkBoundaries[0],
level: 1,
subtreeCount: uint64(chunkBoundaries[0]),
},
})
})
t.Run("update in middle child node produces top-level patch with start key", func(t *testing.T) {
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
mapWithUpdates(threeAndAHalfChunks, update[uint32](1, 1)),
mapWithUpdates(threeAndAHalfChunks, update[uint32](chunkBoundaries[2]-50, 1)),
[]expectedPatch[uint32]{
{
startKey: chunkBoundaries[1],
endKey: chunkBoundaries[2],
level: 1,
subtreeCount: uint64(chunkBoundaries[2] - chunkBoundaries[1]),
},
})
})
t.Run("update in final child node produces leaf patch (a top-level patch would not end on a proper chunk boundary)", func(t *testing.T) {
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
mapWithUpdates(threeAndAHalfChunks, update[uint32](1, 1)),
mapWithUpdates(threeAndAHalfChunks, update[uint32](maxKey-100, 1)),
[]expectedPatch[uint32]{
{
level: 0,
endKey: maxKey - 100,
toValue: 1,
},
})
})
t.Run("an insert beyond the final key doesn't create an extra chunk boundary", func(t *testing.T) {
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
mapWithUpdates(threeAndAHalfChunks, update[uint32](0, 1)),
mapWithUpdates(threeAndAHalfChunks, update[uint32](maxKey+1, 1)),
[]expectedPatch[uint32]{
{
level: 0,
endKey: maxKey + 1,
toValue: 1,
},
})
})
t.Run("an insert before the first key doesn't create an extra chunk boundary", func(t *testing.T) {
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
mapWithUpdates(threeAndAHalfChunks, update[uint32](maxKey+1, 1)),
mapWithUpdates(threeAndAHalfChunks, update[uint32](0, 1)),
[]expectedPatch[uint32]{
{
noStartKey: true,
endKey: chunkBoundaries[0],
level: 1,
subtreeCount: uint64(chunkBoundaries[0] + 1),
},
})
})
t.Run("removing a value from a leaf node produces a top-level patch", func(t *testing.T) {
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
mapWithUpdates(threeAndAHalfChunks, remove[uint32](maxKey-20)),
mapWithUpdates(threeAndAHalfChunks, remove[uint32](1)),
[]expectedPatch[uint32]{
{
noStartKey: true,
endKey: chunkBoundaries[0],
level: 1,
subtreeCount: uint64(chunkBoundaries[0] - 1),
},
})
})
t.Run("concurrent removals in a leaf node produces a leaf-level patch", func(t *testing.T) {
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
mapWithUpdates(threeAndAHalfChunks, remove[uint32](1)),
mapWithUpdates(threeAndAHalfChunks, remove[uint32](2)),
[]expectedPatch[uint32]{
{
endKey: 2,
level: 0,
isRemoval: true,
},
})
})
t.Run("deleting an entire chunk at the end produces a single range", func(t *testing.T) {
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
mapWithUpdates(threeAndAHalfChunks, remove[uint32](1)),
mapWithUpdates(threeAndAHalfChunks, makeDeletePatches(chunkBoundaries[2]+1, maxKey)...),
[]expectedPatch[uint32]{
{
startKey: chunkBoundaries[2],
endKey: maxKey,
level: 1,
isRemoval: true,
},
})
})
// We expect removing an entire chunk produces a single range covering the removed chunk and the subsequent one.
t.Run("deleting an entire chunk in the middle", func(t *testing.T) {
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
mapWithUpdates(threeAndAHalfChunks, remove[uint32](1)),
mapWithUpdates(threeAndAHalfChunks, makeDeletePatches(chunkBoundaries[0]+1, chunkBoundaries[1])...),
[]expectedPatch[uint32]{
{
startKey: chunkBoundaries[0],
endKey: chunkBoundaries[2],
level: 1,
subtreeCount: uint64(chunkBoundaries[2] - chunkBoundaries[1]),
},
})
})
t.Run("deleting an entire chunk concurrent with a point delete", func(t *testing.T) {
// This needs to recurse into the leaf node in order to verify that the concurrently modified key isn't a conflict.
// Once we confirm there's no conflict, we could potentially emit a single high-level patch, but the logic would be
// more complicated. So currently we emit multiple patches instead.
pointDelete := mapWithUpdates(threeAndAHalfChunks, remove[uint32](1000))
chunkDelete := mapWithUpdates(threeAndAHalfChunks, makeDeletePatches(831, 1024)...)
expectedPatches := manyPointRemoves(831, 999)
expectedPatches = append(expectedPatches, manyPointRemoves(1001, 1024)...)
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
pointDelete,
chunkDelete,
expectedPatches)
// When the left side contains the larger change, the merged tree is the same as the left tree and there
// are no patches to emit.
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
chunkDelete,
pointDelete,
[]expectedPatch[uint32]{})
})
t.Run("deleting many rows at the end", func(t *testing.T) {
// We expect the chunk before the deleted rows to be modified, and emit then a removal patch for each chunk until the end.
// Again, we could potentially emit a single removal patch, or even a single modified patch, but the logic would be
// more complicated.
newMaxKey := chunkBoundaries[2] - 1
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
mapWithUpdates(threeAndAHalfChunks, remove[uint32](1)),
mapWithUpdates(threeAndAHalfChunks, makeDeletePatches(newMaxKey+1, maxKey)...),
[]expectedPatch[uint32]{
{
level: 0,
endKey: chunkBoundaries[2],
isRemoval: true,
},
{
startKey: chunkBoundaries[2],
endKey: 1024,
level: 1,
isRemoval: true,
},
})
})
t.Run("deleting many rows at the end doesn't emit unnecessary leaf removals", func(t *testing.T) {
newMaxKey := chunkBoundaries[2] - 1
testPatchBasedMerging(t, ctx, ns, desc, nil,
threeAndAHalfChunks,
mapWithUpdates(threeAndAHalfChunks, remove[uint32](818)),
mapWithUpdates(threeAndAHalfChunks, makeDeletePatches(newMaxKey+1, maxKey)...),
[]expectedPatch[uint32]{
{
endKey: chunkBoundaries[2],
level: 0,
subtreeCount: uint64(newMaxKey - chunkBoundaries[1]),
isRemoval: true,
},
{
startKey: chunkBoundaries[2],
endKey: 1024,
level: 1,
isRemoval: true,
},
})
})
t.Run("deleting many rows in the middle doesn't emit unnecessary leaf removals", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: chunkBoundaries[0],
endKey: 1237,
subtreeCount: uint64(1237 - chunkBoundaries[0] - (1210 - 715) - 1),
})
testPatchBasedMerging(t, ctx, ns, desc, nil,
fourAndAHalfChunks,
mapWithUpdates(fourAndAHalfChunks, remove[uint32](100)),
mapWithUpdates(fourAndAHalfChunks, makeDeletePatches(715, 1210)...),
expectedPatches)
})
t.Run("deleting an chunk boundary produces new boundaries until they realign", func(t *testing.T) {
newChunkBoundary := uint32(436)
testPatchBasedMerging(t, ctx, ns, desc, nil, threeAndAHalfChunks, mapWithUpdates(threeAndAHalfChunks, remove[uint32](1000)), mapWithUpdates(threeAndAHalfChunks, remove[uint32](chunkBoundaries[0])), []expectedPatch[uint32]{
{
noStartKey: true,
endKey: newChunkBoundary, // new chunk boundary
level: 1,
subtreeCount: uint64(newChunkBoundary - 1),
},
{
startKey: newChunkBoundary,
endKey: chunkBoundaries[1],
level: 1,
subtreeCount: uint64(chunkBoundaries[1] - newChunkBoundary),
},
})
})
t.Run("range insert at end", func(t *testing.T) {
// The last chunk from the base is replaced, a new chunk is added, and the remaining rows,
// which don't end in a natural chunk boundary, get emitted as individual rows.
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
startKey: chunkBoundaries[1],
endKey: chunkBoundaries[2],
level: 1,
subtreeCount: uint64(chunkBoundaries[2] - chunkBoundaries[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
startKey: chunkBoundaries[2],
endKey: chunkBoundaries[3],
level: 1,
subtreeCount: uint64(chunkBoundaries[3] - chunkBoundaries[2]),
})
for i := chunkBoundaries[3] + 1; i <= 1250; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
endKey: i,
toValue: i,
level: 0,
})
}
testPatchBasedMerging(t, ctx, ns, desc, nil, twoAndAHalfChunks, mapWithUpdates(twoAndAHalfChunks, update[uint32](1, 0)), fourAndAHalfChunks, expectedPatches)
})
t.Run("non-overlapping inserts to empty map", func(t *testing.T) {
lowerMap, _ := makeSimpleIntMap(t, 1, 831)
upperMap, _ := makeSimpleIntMap(t, 1001, 1847)
upperMapChunkBoundary := []uint32{1237, 1450, 1846}
{
// The right map is the upper map.
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
// Once we hit the first chunk boundary, we emit higher level patches.
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointInserts(1001, upperMapChunkBoundary[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[0],
endKey: upperMapChunkBoundary[1],
subtreeCount: uint64(upperMapChunkBoundary[1] - upperMapChunkBoundary[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[1],
endKey: upperMapChunkBoundary[2],
subtreeCount: uint64(upperMapChunkBoundary[2] - upperMapChunkBoundary[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 1847,
toValue: 1847,
})
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, lowerMap, upperMap, expectedPatches)
}
{
// The right map is the lower map.
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
// Once we hit the first chunk boundary, we emit higher level patches.
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointInserts(1, chunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: chunkBoundaries[0],
endKey: chunkBoundaries[1],
subtreeCount: uint64(chunkBoundaries[1] - chunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: chunkBoundaries[1],
endKey: chunkBoundaries[2],
subtreeCount: uint64(chunkBoundaries[2] - chunkBoundaries[1]),
})
expectedPatches = append(expectedPatches, pointUpdate[uint32](831, 831))
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, upperMap, lowerMap, expectedPatches)
}
})
t.Run("non-overlapping inserts to nearly empty map", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 1000, 1000)
lowerMap, _ := makeSimpleIntMap(t, 1, 831)
upperMap, _ := makeSimpleIntMap(t, 1001, 1847)
upperMapChunkBoundary := []uint32{1237, 1450, 1846}
t.Run("right map is the upper map", func(t *testing.T) {
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
// Once we hit the first chunk boundary, we emit higher level patches.
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointInserts(1001, upperMapChunkBoundary[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[0],
endKey: upperMapChunkBoundary[1],
subtreeCount: uint64(upperMapChunkBoundary[1] - upperMapChunkBoundary[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[1],
endKey: upperMapChunkBoundary[2],
subtreeCount: uint64(upperMapChunkBoundary[2] - upperMapChunkBoundary[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 1847,
toValue: 1847,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right map is the lower map", func(t *testing.T) {
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
// Once we hit the first chunk boundary, we emit higher level patches.
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointInserts(1, chunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: chunkBoundaries[0],
endKey: chunkBoundaries[1],
subtreeCount: uint64(chunkBoundaries[1] - chunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: chunkBoundaries[1],
endKey: chunkBoundaries[2],
subtreeCount: uint64(chunkBoundaries[2] - chunkBoundaries[1]),
})
expectedPatches = append(expectedPatches, pointUpdate[uint32](831, 831))
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("overlapping inserts to empty map", func(t *testing.T) {
lowerMap, _ := makeSimpleIntMap(t, 1, 1000)
upperMap, _ := makeSimpleIntMap(t, 751, 1847)
upperMapChunkBoundary := []uint32{1012, 1237, 1450, 1846}
t.Run("right map is the upper map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointInserts(1001, upperMapChunkBoundary[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[0],
endKey: upperMapChunkBoundary[1],
subtreeCount: uint64(upperMapChunkBoundary[1] - upperMapChunkBoundary[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[1],
endKey: upperMapChunkBoundary[2],
subtreeCount: uint64(upperMapChunkBoundary[2] - upperMapChunkBoundary[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[2],
endKey: upperMapChunkBoundary[3],
subtreeCount: uint64(upperMapChunkBoundary[3] - upperMapChunkBoundary[2]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 1847,
toValue: 1847,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointInserts(1, chunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: chunkBoundaries[0],
endKey: chunkBoundaries[1],
subtreeCount: uint64(chunkBoundaries[1] - chunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, manyPointInserts(chunkBoundaries[1]+1, 750)...)
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("unequal sized inserts to empty map: small before large", func(t *testing.T) {
lowerMap, _ := makeSimpleIntMap(t, 1, 1)
upperMap, _ := makeSimpleIntMap(t, 1001, 1847)
upperMapChunkBoundaries := []uint32{1237, 1450, 1846}
t.Run("right map is the upper map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
for i := uint32(1001); i <= upperMapChunkBoundaries[0]; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: i,
toValue: i,
})
}
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[0],
endKey: upperMapChunkBoundaries[1],
subtreeCount: uint64(upperMapChunkBoundaries[1] - upperMapChunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[1],
endKey: upperMapChunkBoundaries[2],
subtreeCount: uint64(upperMapChunkBoundaries[2] - upperMapChunkBoundaries[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 1847,
toValue: 1847,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches = []expectedPatch[uint32]{
pointUpdate[uint32](1, 1),
}
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("unequal sized inserts to nearly empty map: small insert, then base, then large insert", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 500, 500)
lowerMap, _ := makeSimpleIntMap(t, 1, 1)
upperMap, _ := makeSimpleIntMap(t, 1001, 1847)
rightMapChunkBoundaries := []uint32{1237, 1450, 1846}
t.Run("right map is the upper map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
for i := uint32(1001); i <= rightMapChunkBoundaries[0]; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: i,
toValue: i,
})
}
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: rightMapChunkBoundaries[0],
endKey: rightMapChunkBoundaries[1],
subtreeCount: uint64(rightMapChunkBoundaries[1] - rightMapChunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: rightMapChunkBoundaries[1],
endKey: rightMapChunkBoundaries[2],
subtreeCount: uint64(rightMapChunkBoundaries[2] - rightMapChunkBoundaries[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 1847,
toValue: 1847,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches = []expectedPatch[uint32]{
pointUpdate[uint32](1, 1),
}
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("unequal sized inserts to nearly empty map: base, then small insert, then large insert", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 0, 0)
lowerMap, _ := makeSimpleIntMap(t, 500, 500)
upperMap, _ := makeSimpleIntMap(t, 1001, 1847)
upperMapChunkBoundaries := []uint32{1237, 1450, 1846}
t.Run("right map is the upper map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointInserts(1001, upperMapChunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[0],
endKey: upperMapChunkBoundaries[1],
subtreeCount: uint64(upperMapChunkBoundaries[1] - upperMapChunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[1],
endKey: upperMapChunkBoundaries[2],
subtreeCount: uint64(upperMapChunkBoundaries[2] - upperMapChunkBoundaries[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 1847,
toValue: 1847,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches = []expectedPatch[uint32]{
pointUpdate[uint32](500, 500),
}
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("unequal sized inserts to empty map: small after large", func(t *testing.T) {
upperMap, _ := makeSimpleIntMap(t, 2000, 2000)
lowerMap, _ := makeSimpleIntMap(t, 1, 1000)
lowerMapChunkBoundaries := []uint32{414, 718, 830}
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
noStartKey: true,
endKey: lowerMapChunkBoundaries[0],
subtreeCount: uint64(lowerMapChunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: lowerMapChunkBoundaries[0],
endKey: lowerMapChunkBoundaries[1],
subtreeCount: uint64(lowerMapChunkBoundaries[1] - lowerMapChunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: lowerMapChunkBoundaries[1],
endKey: lowerMapChunkBoundaries[2],
subtreeCount: uint64(lowerMapChunkBoundaries[2] - lowerMapChunkBoundaries[1]),
})
for i := lowerMapChunkBoundaries[2] + 1; i <= 1000; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: i,
toValue: i,
})
}
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, upperMap, lowerMap, expectedPatches)
})
t.Run("right map is the upper map", func(t *testing.T) {
var expectedPatches = []expectedPatch[uint32]{
pointUpdate[uint32](2000, 2000),
}
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, lowerMap, upperMap, expectedPatches)
})
})
t.Run("unequal sized inserts to nearly empty map: large insert, then base, then small insert", func(t *testing.T) {
// The right map will need to recurse down to the leaf level to verify that the key at 500 is not modified.
// This is a place where we could then emit a range for the entire node once we verify that all changes are
// concurrent, but we currently don't.
baseMap, _ := makeSimpleIntMap(t, 900, 900)
upperMap, _ := makeSimpleIntMap(t, 2000, 2000)
lowerMap, _ := makeSimpleIntMap(t, 1, 831)
lowerMapChunkBoundaries := []uint32{414, 718, 830}
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
noStartKey: true,
endKey: lowerMapChunkBoundaries[0],
subtreeCount: uint64(lowerMapChunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: lowerMapChunkBoundaries[0],
endKey: lowerMapChunkBoundaries[1],
subtreeCount: uint64(lowerMapChunkBoundaries[1] - lowerMapChunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: lowerMapChunkBoundaries[1],
endKey: lowerMapChunkBoundaries[2],
subtreeCount: uint64(lowerMapChunkBoundaries[2] - lowerMapChunkBoundaries[1]),
})
expectedPatches = append(expectedPatches, pointUpdate[uint32](831, 831))
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
t.Run("right map is the upper map", func(t *testing.T) {
var expectedPatches = []expectedPatch[uint32]{
pointUpdate[uint32](2000, 2000),
}
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
})
t.Run("unequal sized inserts to nearly empty map: base, then large insert, then small insert", func(t *testing.T) {
// The right map will need to recurse down to the leaf level to verify that the key at 500 is not modified.
// This is a place where we could then emit a range for the entire node once we verify that all changes are
// concurrent, but we currently don't.
baseMap, _ := makeSimpleIntMap(t, 0, 0)
upperMap, _ := makeSimpleIntMap(t, 2000, 2000)
lowerMap, _ := makeSimpleIntMap(t, 1, 831)
lowerMapChunkBoundaries := []uint32{414, 718, 830}
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointInserts(1, lowerMapChunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: lowerMapChunkBoundaries[0],
endKey: lowerMapChunkBoundaries[1],
subtreeCount: uint64(lowerMapChunkBoundaries[1] - lowerMapChunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: lowerMapChunkBoundaries[1],
endKey: lowerMapChunkBoundaries[2],
subtreeCount: uint64(lowerMapChunkBoundaries[2] - lowerMapChunkBoundaries[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 831,
toValue: 831,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
t.Run("right map is the upper map", func(t *testing.T) {
expectedPatches := []expectedPatch[uint32]{
pointUpdate[uint32](2000, 2000),
}
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
})
t.Run("unequal sized inserts to nearly empty map: large insert, then small insert, then base", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 3000, 3000)
upperMap, _ := makeSimpleIntMap(t, 2000, 2000)
lowerMap, _ := makeSimpleIntMap(t, 1, 831)
rightMapChunkBoundaries := []uint32{414, 718, 830}
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
noStartKey: true,
endKey: rightMapChunkBoundaries[0],
subtreeCount: uint64(rightMapChunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: rightMapChunkBoundaries[0],
endKey: rightMapChunkBoundaries[1],
subtreeCount: uint64(rightMapChunkBoundaries[1] - rightMapChunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: rightMapChunkBoundaries[1],
endKey: rightMapChunkBoundaries[2],
subtreeCount: uint64(rightMapChunkBoundaries[2] - rightMapChunkBoundaries[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 831,
toValue: 831,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
t.Run("right map is the upper map", func(t *testing.T) {
expectedPatches := []expectedPatch[uint32]{
pointUpdate[uint32](2000, 2000),
}
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
})
t.Run("unequal sized inserts to nearly empty map: large insert concurrent with base, then small insert", func(t *testing.T) {
// The right map will need to recurse down to the leaf level to verify that the key at 500 is not modified.
// This is a place where we could then emit a range for the entire node once we verify that all changes are
// concurrent, but we currently don't.
baseMap, _ := makeSimpleIntMap(t, 500, 500)
upperMap := mapWithUpdates(baseMap, update[uint32](2000, 2000))
lowerMap, _ := makeSimpleIntMap(t, 1, 831)
lowerMapChunkBoundaries := []uint32{414, 718, 830}
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
noStartKey: true,
endKey: lowerMapChunkBoundaries[0],
subtreeCount: uint64(lowerMapChunkBoundaries[0]),
})
// We're able to emit a level 1 patch that covers the left key 500, because there's no diff on the left
// for that key; this is safe.
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: lowerMapChunkBoundaries[0],
endKey: lowerMapChunkBoundaries[1],
subtreeCount: uint64(lowerMapChunkBoundaries[1] - lowerMapChunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: lowerMapChunkBoundaries[1],
endKey: lowerMapChunkBoundaries[2],
subtreeCount: uint64(lowerMapChunkBoundaries[2] - lowerMapChunkBoundaries[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 831,
toValue: 831,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
t.Run("right map is the upper map", func(t *testing.T) {
expectedPatches := []expectedPatch[uint32]{
pointUpdate[uint32](2000, 2000),
}
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
})
t.Run("unequal sized inserts to empty map: concurrent", func(t *testing.T) {
subsetMap, _ := makeSimpleIntMap(t, 500, 500)
supersetMap, _ := makeSimpleIntMap(t, 1, 831)
supersetMapChunkBoundaries := []uint32{414, 718, 830}
t.Run("right map is superset", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
noStartKey: true,
endKey: supersetMapChunkBoundaries[0],
subtreeCount: uint64(supersetMapChunkBoundaries[0]),
})
for i := uint32(415); i < 500; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: i,
toValue: i,
})
}
for i := uint32(501); i <= supersetMapChunkBoundaries[1]; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: i,
toValue: i,
})
}
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: supersetMapChunkBoundaries[1],
endKey: supersetMapChunkBoundaries[2],
subtreeCount: uint64(supersetMapChunkBoundaries[2] - supersetMapChunkBoundaries[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 831,
toValue: 831,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, subsetMap, supersetMap, expectedPatches)
})
t.Run("right map is subset", func(t *testing.T) {
expectedPatches := []expectedPatch[uint32]{}
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, supersetMap, subsetMap, expectedPatches)
})
})
t.Run("unequal sized removals produce empty map: small removal less than large removal", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 1, 2000)
lowerMap, _ := makeSimpleIntMap(t, 1, 1)
upperMap, _ := makeSimpleIntMap(t, 2, 2000)
upperMapChunkBoundaries := getChunkBoundaries(t, desc, upperMap)
t.Run("right side removes single row", func(t *testing.T) {
expectedPatches := []expectedPatch[uint32]{pointRemove[uint32](1)}
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right side removes many rows", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(2, upperMapChunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[0],
endKey: upperMapChunkBoundaries[1],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[1],
endKey: upperMapChunkBoundaries[2],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[2],
endKey: upperMapChunkBoundaries[3],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[3],
endKey: upperMapChunkBoundaries[4],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[4],
endKey: upperMapChunkBoundaries[5],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[5],
endKey: 2000,
isRemoval: true,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("unequal sized removals produce nearly empty map: small removal less than large removal", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 1, 2000)
lowerMap, _ := makeSimpleIntMap(t, 1, 1)
upperMap, _ := makeSimpleIntMap(t, 3, 2000)
upperMapChunkBoundaries := getChunkBoundaries(t, desc, upperMap)
t.Run("right side removes single row", func(t *testing.T) {
expectedPatches := []expectedPatch[uint32]{pointRemove[uint32](1)}
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right side removes many rows", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(3, upperMapChunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[0],
endKey: upperMapChunkBoundaries[1],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[1],
endKey: upperMapChunkBoundaries[2],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[2],
endKey: upperMapChunkBoundaries[3],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[3],
endKey: upperMapChunkBoundaries[4],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[4],
endKey: upperMapChunkBoundaries[5],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaries[5],
endKey: 2000,
isRemoval: true,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("unequal sized removals produce empty map: concurrent", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 1, 1000)
mapWithPointRemoval := mapWithUpdates(baseMap, remove[uint32](750))
rightMapChunkBoundaries := getChunkBoundaries(t, desc, mapWithPointRemoval)
t.Run("right is empty", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
noStartKey: true,
endKey: rightMapChunkBoundaries[0],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: rightMapChunkBoundaries[0],
endKey: rightMapChunkBoundaries[1],
isRemoval: true,
})
// TODO: This can be improved by emitting a single modfied patch instead of many removed patches.
expectedPatches = append(expectedPatches, manyPointRemoves(rightMapChunkBoundaries[1]+1, 749)...)
expectedPatches = append(expectedPatches, manyPointRemoves(751, rightMapChunkBoundaries[2])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: rightMapChunkBoundaries[2],
endKey: 1000,
isRemoval: true,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, mapWithPointRemoval, emptyMap, expectedPatches)
})
t.Run("left is empty", func(t *testing.T) {
// In this case, the left contains a superset of the changes from the right.
// The merge is equal to leftMap, with no patches necessary.
expectedPatches := []expectedPatch[uint32]{}
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, emptyMap, mapWithPointRemoval, expectedPatches)
})
})
t.Run("unequal sized removals produce nearly empty map: concurrent", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 1, 1000)
mapWithPointRemoval := mapWithUpdates(baseMap, remove[uint32](750))
nearlyEmptyMap, _ := makeSimpleIntMap(t, 1, 1)
rightMapChunkBoundaries := getChunkBoundaries(t, desc, mapWithPointRemoval)
t.Run("right is nearly empty", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(2, rightMapChunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: rightMapChunkBoundaries[0],
endKey: rightMapChunkBoundaries[1],
isRemoval: true,
})
// TODO: This can be improved by emitting a single modfied patch instead of many removed patches.
expectedPatches = append(expectedPatches, manyPointRemoves(rightMapChunkBoundaries[1]+1, 749)...)
expectedPatches = append(expectedPatches, manyPointRemoves(751, rightMapChunkBoundaries[2])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: rightMapChunkBoundaries[2],
endKey: 1000,
isRemoval: true,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, mapWithPointRemoval, nearlyEmptyMap, expectedPatches)
})
t.Run("left is nearly empty", func(t *testing.T) {
// In this case, the left contains a superset of the changes from the right.
// The merge is equal to leftMap, with no patches necessary.
expectedPatches := []expectedPatch[uint32]{}
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, nearlyEmptyMap, mapWithPointRemoval, expectedPatches)
})
})
t.Run("merge with resolvable conflicts", func(t *testing.T) {
valueOnCollision := uint32(3000)
collide := func(left, right tree.Diff) (tree.Diff, bool) {
tupleBuilder := val.NewTupleBuilder(desc, ns)
tupleBuilder.PutUint32(0, valueOnCollision)
newVal, err := tupleBuilder.Build(context.Background(), sharedPool)
require.NoError(t, err)
// Resolve conflicts by returning a special value that we check for
return tree.Diff{
Key: left.Key,
From: left.From,
To: tree.Item(newVal),
Type: tree.ModifiedDiff,
}, true
}
testPatchBasedMerging(t, ctx, ns, desc, collide, threeChunks, mapWithUpdates(threeChunks, update[uint32](1, 10), update[uint32](990, 0)), mapWithUpdates(threeChunks, update[uint32](1, 20), update[uint32](1000, 0)), []expectedPatch[uint32]{
{
endKey: 1,
level: 0,
toValue: valueOnCollision,
},
{
endKey: 1000,
level: 0,
toValue: 0,
},
})
})
t.Run("merge with unresolvable conflicts", func(t *testing.T) {
var conflicts []struct{ left, right tree.Diff }
// A custom callback records all conflicts and treats them as unresolvable.
collide := func(left, right tree.Diff) (tree.Diff, bool) {
conflicts = append(conflicts, struct{ left, right tree.Diff }{left, right})
return tree.Diff{}, false
}
testPatchBasedMerging(t, ctx, ns, desc, collide, threeAndAHalfChunks, mapWithUpdates(threeAndAHalfChunks, update[uint32](1, 10), update[uint32](500, 0)), mapWithUpdates(threeAndAHalfChunks, update[uint32](1, 20), update[uint32](800, 0)), []expectedPatch[uint32]{
{
startKey: chunkBoundaries[1],
endKey: chunkBoundaries[2],
level: 1,
subtreeCount: uint64(chunkBoundaries[2] - chunkBoundaries[1]),
},
})
// The callback will be called twice: once during the tree merge, and once during the traditional merge
// we're comparing the results to.
require.Len(t, conflicts, 2)
for _, conflict := range conflicts {
collisionKey, ok := desc.GetUint32(0, val.Tuple(conflict.left.Key))
require.True(t, ok)
assert.Equal(t, collisionKey, uint32(1))
}
})
t.Run("both branches produce same address with different key ranges", func(t *testing.T) {
// This is a corner case that can happen when one branch adds or removes an entire chunk, immediately before
// a chunk that is concurrently modified.
testPatchBasedMerging(t, ctx, ns, desc, nil, threeAndAHalfChunks, mapWithUpdates(threeAndAHalfChunks, update[uint32](chunkBoundaries[2]-30, 0)), mapWithUpdates(threeAndAHalfChunks, append(makeDeletePatches(chunkBoundaries[0]+1, chunkBoundaries[1]), update[uint32](chunkBoundaries[2]-30, 0))...), []expectedPatch[uint32]{
{
startKey: chunkBoundaries[0],
endKey: chunkBoundaries[2],
level: 1,
subtreeCount: uint64(chunkBoundaries[2] - chunkBoundaries[1]),
},
})
})
t.Run("multi-level tree: deleting many rows off the end produces minimal diffs", func(t *testing.T) {
bld := val.NewTupleBuilder(stringDesc, nil)
tuples := make([][2]val.Tuple, 10000)
var err error
for i := range tuples {
err = bld.PutString(0, "long_string_key_goes_here_"+strconv.Itoa(i))
require.NoError(t, err)
tuples[i][0], err = bld.Build(context.Background(), sharedPool)
require.NoError(t, err)
err = bld.PutString(0, "long_string_value_goes_here_"+strconv.Itoa(i))
require.NoError(t, err)
tuples[i][1], err = bld.Build(context.Background(), sharedPool)
require.NoError(t, err)
}
baseRoot, err := tree.MakeTreeForTest(tuples)
require.NoError(t, err)
leftRoot := stringMapWithUpdates(baseRoot, update[string]("long_string_key_goes_here_8659", ""))
require.NoError(t, err)
// This key is specifically chosen to be the last key of the last key of the last key:
// Removing it should result in exactly three diffs, one at each level.
rightRoot, err := tree.MakeTreeForTest(tuples[:8660])
require.NoError(t, err)
// The base map will happen to have these chunk boundaries.
testPatchBasedMerging[string](t, ctx, ns, stringDesc, nil, baseRoot, leftRoot, rightRoot, []expectedPatch[string]{
{
endKey: "long_string_key_goes_here_8660",
level: 0,
isRemoval: true,
},
{
startKey: "long_string_key_goes_here_8660",
endKey: "long_string_key_goes_here_8702",
level: 1,
isRemoval: true,
},
{
startKey: "long_string_key_goes_here_8702",
endKey: "long_string_key_goes_here_9999",
level: 2,
isRemoval: true,
},
})
})
}
// TestPatchBasedMergingSkipCheckingPatches tests that patching process produces the same merged tree as a traditional merge,
// but does not require that the set of produced patches exactly matches the expected set of patches.
// These tests document cases where we may not produce the optimal set of patches, but still produce the correct final result.
// All of these cases are caused by the same issue: a patch generator emits unncessary leaf patches for a contiguous block of modified rows.
// TODO: Fix this issue and move these into TestPatchBasedMerging
func TestPatchBasedMergingSkipCheckingPatches(t *testing.T) {
ctx := context.Background()
ns := tree.NewTestNodeStore()
fourAndAHalfChunks, desc := makeSimpleIntMap(t, 1, 1250)
chunkBoundaries := []uint32{414, 718, 830, 1207}
emptyMap, err := tree.MakeTreeForTest(nil)
require.NoError(t, err)
mapWithUpdates := func(root *tree.Node, updates ...mutation[uint32]) *tree.Node {
return mutate(t, ctx, root, desc, desc, updates)
}
t.Run("overlapping inserts to nearly empty map: inserts overlap with base", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 800, 800)
lowerMap, _ := makeSimpleIntMap(t, 1, 1000)
upperMap, _ := makeSimpleIntMap(t, 751, 1847)
upperMapChunkBoundary := []uint32{1012, 1237, 1450, 1846}
t.Run("right map is the upper map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
for i := uint32(1001); i <= upperMapChunkBoundary[0]; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: i,
toValue: i,
})
}
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[0],
endKey: upperMapChunkBoundary[1],
subtreeCount: uint64(upperMapChunkBoundary[1] - upperMapChunkBoundary[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[1],
endKey: upperMapChunkBoundary[2],
subtreeCount: uint64(upperMapChunkBoundary[2] - upperMapChunkBoundary[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[2],
endKey: upperMapChunkBoundary[3],
subtreeCount: uint64(upperMapChunkBoundary[3] - upperMapChunkBoundary[2]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 1847,
toValue: 1847,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointInserts(1, chunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: chunkBoundaries[0],
endKey: chunkBoundaries[1],
subtreeCount: uint64(chunkBoundaries[1] - chunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, manyPointInserts(chunkBoundaries[1]+1, 750)...)
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("overlapping inserts to nearly empty map: inserts are before base", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 2000, 2000)
lowerMap, _ := makeSimpleIntMap(t, 1, 1000)
upperMap, _ := makeSimpleIntMap(t, 751, 1847)
upperMapChunkBoundary := []uint32{1012, 1237, 1450, 1846}
t.Run("right map is the upper map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
for i := uint32(1001); i <= upperMapChunkBoundary[0]; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: i,
toValue: i,
})
}
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[0],
endKey: upperMapChunkBoundary[1],
subtreeCount: uint64(upperMapChunkBoundary[1] - upperMapChunkBoundary[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[1],
endKey: upperMapChunkBoundary[2],
subtreeCount: uint64(upperMapChunkBoundary[2] - upperMapChunkBoundary[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[2],
endKey: upperMapChunkBoundary[3],
subtreeCount: uint64(upperMapChunkBoundary[3] - upperMapChunkBoundary[2]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 1847,
toValue: 1847,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointInserts(1, chunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: chunkBoundaries[0],
endKey: chunkBoundaries[1],
subtreeCount: uint64(chunkBoundaries[1] - chunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, manyPointInserts(chunkBoundaries[1]+1, 750)...)
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("overlapping inserts to nearly empty map: inserts are after base", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 0, 0)
lowerMap, _ := makeSimpleIntMap(t, 1, 1000)
upperMap, _ := makeSimpleIntMap(t, 751, 1847)
upperMapChunkBoundary := []uint32{1012, 1237, 1450, 1846}
t.Run("right map is the upper map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
for i := uint32(1001); i <= upperMapChunkBoundary[0]; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: i,
toValue: i,
})
}
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[0],
endKey: upperMapChunkBoundary[1],
subtreeCount: uint64(upperMapChunkBoundary[1] - upperMapChunkBoundary[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[1],
endKey: upperMapChunkBoundary[2],
subtreeCount: uint64(upperMapChunkBoundary[2] - upperMapChunkBoundary[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundary[2],
endKey: upperMapChunkBoundary[3],
subtreeCount: uint64(upperMapChunkBoundary[3] - upperMapChunkBoundary[2]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 1847,
toValue: 1847,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointInserts(1, chunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: chunkBoundaries[0],
endKey: chunkBoundaries[1],
subtreeCount: uint64(chunkBoundaries[1] - chunkBoundaries[0]),
})
expectedPatches = append(expectedPatches, manyPointInserts(chunkBoundaries[1]+1, 750)...)
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("non-overlapping removals produce empty map", func(t *testing.T) {
// The patch generator for the right branch needs to recurse into the leaves in order to know the minimum
// key value of the first child node. A result, we get a separate patch for each removed key. This can likely
// be improved in the future.
baseMap, _ := makeSimpleIntMap(t, 1, 2000)
baseMapChunkBoundaries := getChunkBoundaries(t, desc, baseMap)
lowerMap, _ := makeSimpleIntMap(t, 1, 1000)
upperMap, _ := makeSimpleIntMap(t, 1001, 2000)
t.Run("right removes lower bound", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(1, baseMapChunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[0],
endKey: baseMapChunkBoundaries[1],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[1],
endKey: baseMapChunkBoundaries[2],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[2],
endKey: 1000,
isRemoval: true,
})
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right removes upper bound", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(1001, baseMapChunkBoundaries[3])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[3],
endKey: baseMapChunkBoundaries[4],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[4],
endKey: baseMapChunkBoundaries[5],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[5],
endKey: 2000,
isRemoval: true,
})
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("non-overlapping removals produce nearly-empty map", func(t *testing.T) {
// The patch generator for the right branch needs to recurse into the leaves in order to know the minimum
// key value of the first child node. A result, we get a separate patch for each removed key. This can likely
// be improved in the future.
baseMap, _ := makeSimpleIntMap(t, 1, 2000)
baseMapChunkBoundaries := getChunkBoundaries(t, desc, baseMap)
lowerMap, _ := makeSimpleIntMap(t, 1, 999)
upperMap, _ := makeSimpleIntMap(t, 1001, 2000)
t.Run("right removes lower bound", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(1, baseMapChunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[0],
endKey: baseMapChunkBoundaries[1],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[1],
endKey: baseMapChunkBoundaries[2],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[2],
endKey: 1000,
isRemoval: true,
})
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right removes upper bound", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(1001, baseMapChunkBoundaries[3])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[3],
endKey: baseMapChunkBoundaries[4],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[4],
endKey: baseMapChunkBoundaries[5],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[5],
endKey: 2000,
isRemoval: true,
})
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("overlapping removals produce empty map", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 1, 2000)
lowerMap, _ := makeSimpleIntMap(t, 1, 500)
upperMap, _ := makeSimpleIntMap(t, 1500, 2000)
baseMapChunkBoundaries := getChunkBoundaries(t, desc, baseMap)
t.Run("right removes lower bound", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(1, baseMapChunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[0],
endKey: 500,
isRemoval: true,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right removes upper bound", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(1500, baseMapChunkBoundaries[len(baseMapChunkBoundaries)-2])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[len(baseMapChunkBoundaries)-2],
endKey: 2000,
isRemoval: true,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("overlapping removals produce nearly empty map", func(t *testing.T) {
// Both branches delete most of the rows in the map. After merging, only the first and last rows remain.
baseMap, _ := makeSimpleIntMap(t, 1, 2000)
baseMapChunkBoundaries := getChunkBoundaries(t, desc, baseMap)
lowerMap, _ := makeSimpleIntMap(t, 1, 500)
lowerMap = mapWithUpdates(lowerMap, update[uint32](2000, 2000))
upperMap, _ := makeSimpleIntMap(t, 1500, 2000)
upperMap = mapWithUpdates(upperMap, update[uint32](1, 1))
// upperMapChunkBoundary := getChunkBoundaries(t, desc, upperMap) //[]uint32{1207, 1450, 1846}
t.Run("right removes lower bound", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(1, baseMapChunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[0],
endKey: 500,
isRemoval: true,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right removes upper bound", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(1500, baseMapChunkBoundaries[len(baseMapChunkBoundaries)-2])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[len(baseMapChunkBoundaries)-2],
endKey: 2000,
isRemoval: true,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
})
t.Run("unequal sized inserts to nearly empty map: concurrent, right diff is superset", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 2000, 2000)
leftMap, _ := makeSimpleIntMap(t, 500, 500)
rightMap, _ := makeSimpleIntMap(t, 1, 831)
rightMapChunkBoundaries := []uint32{414, 718, 830}
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
noStartKey: true,
endKey: rightMapChunkBoundaries[0],
subtreeCount: uint64(rightMapChunkBoundaries[0]),
})
for i := rightMapChunkBoundaries[0] + 1; i < 500; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: i,
toValue: i,
})
}
for i := uint32(501); i < rightMapChunkBoundaries[1]; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: i,
toValue: i,
})
}
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
noStartKey: true,
endKey: rightMapChunkBoundaries[2],
subtreeCount: uint64(rightMapChunkBoundaries[2] - rightMapChunkBoundaries[1]),
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, leftMap, rightMap, expectedPatches)
})
t.Run("unequal sized removals produce empty map: small removal greater than than large removal", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 1, 2000)
lowerMap, _ := makeSimpleIntMap(t, 1, 1999)
upperMap, _ := makeSimpleIntMap(t, 2000, 2000)
baseMapChunkBoundaries := getChunkBoundaries(t, desc, baseMap)
t.Run("right side removes single row", func(t *testing.T) {
expectedPatches := []expectedPatch[uint32]{pointRemove[uint32](2000)}
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
t.Run("right side removes many rows", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(1, baseMapChunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[0],
endKey: baseMapChunkBoundaries[1],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[1],
endKey: baseMapChunkBoundaries[2],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[2],
endKey: baseMapChunkBoundaries[3],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[3],
endKey: baseMapChunkBoundaries[4],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[4],
endKey: baseMapChunkBoundaries[5],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[5],
endKey: 1999,
isRemoval: true,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
})
t.Run("unequal sized removals produce nearly empty map: small removal greater than than large removal", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 1, 2000)
lowerMap, _ := makeSimpleIntMap(t, 1, 1998)
upperMap, _ := makeSimpleIntMap(t, 2000, 2000)
baseMapChunkBoundaries := getChunkBoundaries(t, desc, baseMap)
t.Run("right side removes single row", func(t *testing.T) {
expectedPatches := []expectedPatch[uint32]{pointRemove[uint32](2000)}
testPatchBasedMerging(t, ctx, ns, desc, nil, baseMap, upperMap, lowerMap, expectedPatches)
})
t.Run("right side removes many rows", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(1, baseMapChunkBoundaries[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[0],
endKey: baseMapChunkBoundaries[1],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[1],
endKey: baseMapChunkBoundaries[2],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[2],
endKey: baseMapChunkBoundaries[3],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[3],
endKey: baseMapChunkBoundaries[4],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[4],
endKey: baseMapChunkBoundaries[5],
isRemoval: true,
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: baseMapChunkBoundaries[5],
endKey: 1999,
isRemoval: true,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
})
t.Run("unequal sized removals produce almost empty map: concurrent, right is nearly empty", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 1, 2000)
leftMap := mapWithUpdates(baseMap, remove[uint32](500))
rightMap, _ := makeSimpleIntMap(t, 1000, 1000)
rightMapChunkBoundaries := []uint32{414, 718, 830}
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
noStartKey: true,
endKey: rightMapChunkBoundaries[0],
subtreeCount: uint64(rightMapChunkBoundaries[0]),
})
for i := uint32(415); i < 500; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: i,
toValue: i,
})
}
for i := uint32(501); i <= rightMapChunkBoundaries[1]; i++ {
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: i,
toValue: i,
})
}
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: rightMapChunkBoundaries[1],
endKey: rightMapChunkBoundaries[2],
subtreeCount: uint64(rightMapChunkBoundaries[2] - rightMapChunkBoundaries[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: rightMapChunkBoundaries[2],
endKey: 1000,
subtreeCount: uint64(1000 - rightMapChunkBoundaries[2]),
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, leftMap, rightMap, expectedPatches)
})
t.Run("unequal sized removals produce almost empty map: concurrent, left is nearly empty", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 1, 2000)
leftMap, _ := makeSimpleIntMap(t, 1000, 1000)
rightMap := mapWithUpdates(baseMap, remove[uint32](500))
// In this case, the left contains a superset of the changes from the right.
// The merge is equal to leftMap, with no patches necessary.
expectedPatches := []expectedPatch[uint32]{}
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, leftMap, rightMap, expectedPatches)
})
t.Run("many leaf row removals in the middle doesn't emit unnecessary leaf removals", func(t *testing.T) {
// If we need to emit leaf removals for a continuous range of removals, we should be able to eventually emit
// a node modification.
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointRemoves(715, 718)...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: chunkBoundaries[1],
endKey: chunkBoundaries[3],
subtreeCount: uint64(chunkBoundaries[3] - chunkBoundaries[2]),
})
expectedPatches = append(expectedPatches, manyPointRemoves(1207, 1210)...)
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil,
fourAndAHalfChunks,
mapWithUpdates(fourAndAHalfChunks, remove[uint32](710)),
mapWithUpdates(fourAndAHalfChunks, makeDeletePatches(715, 1210)...),
expectedPatches)
})
t.Run("unequal sized inserts to nearly empty map: small insert, then large insert, then base", func(t *testing.T) {
baseMap, _ := makeSimpleIntMap(t, 2000, 2000)
lowerMap, _ := makeSimpleIntMap(t, 1, 1)
upperMap, _ := makeSimpleIntMap(t, 1001, 1847)
upperMapChunkBoundaires := []uint32{1237, 1450, 1846}
t.Run("right map is the upper map", func(t *testing.T) {
var expectedPatches []expectedPatch[uint32]
expectedPatches = append(expectedPatches, manyPointInserts(1001, upperMapChunkBoundaires[0])...)
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaires[0],
endKey: upperMapChunkBoundaires[1],
subtreeCount: uint64(upperMapChunkBoundaires[1] - upperMapChunkBoundaires[0]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 1,
startKey: upperMapChunkBoundaires[1],
endKey: upperMapChunkBoundaires[2],
subtreeCount: uint64(upperMapChunkBoundaires[2] - upperMapChunkBoundaires[1]),
})
expectedPatches = append(expectedPatches, expectedPatch[uint32]{
level: 0,
endKey: 1847,
toValue: 1847,
})
// Because nodes don't store their lower bound, we have to recurse to the leaf level for the first patches.
// This could be improved in the future.
testPatchBasedMergingResultsOnly(t, ctx, ns, desc, nil, baseMap, lowerMap, upperMap, expectedPatches)
})
t.Run("right map is the lower map", func(t *testing.T) {
var expectedPatches = []expectedPatch[uint32]{
pointUpdate[uint32](1, 1),
}
testPatchBasedMerging(t, ctx, ns, desc, nil, emptyMap, upperMap, lowerMap, expectedPatches)
})
})
}