518 lines
16 KiB
Go
518 lines
16 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 tree
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/dolthub/dolt/go/store/val"
|
|
)
|
|
|
|
func TestPatchGeneratorFromRoots(t *testing.T) {
|
|
t.Run("OneChangedTuple", func(t *testing.T) {
|
|
// Changing one tuple, we expect a single Range for the key in the root.
|
|
// If we split the range diff, we expect a single ModifiedDiff for the key in the leaf.
|
|
ctx := context.Background()
|
|
ns := NewTestNodeStore()
|
|
|
|
fromTups, desc := AscendingUintTuples(1024)
|
|
fromRoot, err := MakeTreeForTest(fromTups)
|
|
assert.NoError(t, err)
|
|
|
|
toTups := make([][2]val.Tuple, len(fromTups))
|
|
// Copy elements from the original slice to the new slice
|
|
copy(toTups, fromTups)
|
|
bld := val.NewTupleBuilder(desc, ns)
|
|
bld.PutUint32(0, uint32(42))
|
|
toTups[23][1], err = bld.Build(context.Background(), sharedPool) // modify value at index 23.
|
|
assert.NoError(t, err)
|
|
toRoot, err := MakeTreeForTest(toTups)
|
|
assert.NoError(t, err)
|
|
|
|
dfr, err := PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
|
|
// Range and then EOF
|
|
var isMore bool
|
|
dif, diffType, isMore, err := dfr.Next(ctx)
|
|
assert.NoError(t, err)
|
|
assert.True(t, isMore)
|
|
assert.NotNil(t, dif)
|
|
assert.Equal(t, ModifiedDiff, diffType)
|
|
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
assert.False(t, isMore)
|
|
|
|
// Range and split visits the one different tuple.
|
|
dfr, err = PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
_, _, isMore, err = dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
assert.True(t, isMore)
|
|
|
|
dif, diffType, isMore, err = dfr.split(ctx)
|
|
assert.NoError(t, err)
|
|
assert.True(t, isMore)
|
|
assert.NotNil(t, dif)
|
|
assert.Equal(t, ModifiedDiff, diffType)
|
|
assert.Equal(t, fromTups[23][0], val.Tuple(dif.EndKey))
|
|
assert.Equal(t, fromTups[23][1], val.Tuple(dif.From))
|
|
assert.Equal(t, toTups[23][1], val.Tuple(dif.To))
|
|
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
assert.NoError(t, err)
|
|
assert.False(t, isMore)
|
|
})
|
|
t.Run("FromHasSuffix", func(t *testing.T) {
|
|
// One or more rows at the end were deleted.
|
|
// We expect that to manifest as a ModifiedDiff for the new final node,
|
|
// Followed by one or more RangeRemovedDiffs
|
|
|
|
ctx := context.Background()
|
|
ns := NewTestNodeStore()
|
|
|
|
fromTups, desc := AscendingUintTuples(1024)
|
|
fromRoot, err := MakeTreeForTest(fromTups)
|
|
chunkBoundaries := []uint32{414, 718, 830}
|
|
maxKey := uint32(1023)
|
|
assert.NoError(t, err)
|
|
|
|
toTups := make([][2]val.Tuple, 512)
|
|
copy(toTups, fromTups)
|
|
toRoot, err := MakeTreeForTest(toTups)
|
|
assert.NoError(t, err)
|
|
|
|
dfr, err := PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
|
|
dif, diffType, isMore, err := dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, ModifiedDiff, diffType)
|
|
require.NotNil(t, dif.To)
|
|
|
|
// One or more RangeRemovedDiff and then EOF
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, RemovedDiff, diffType)
|
|
require.Nil(t, dif.To)
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
|
|
for isMore {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, RemovedDiff, diffType)
|
|
require.Nil(t, dif.To)
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
}
|
|
|
|
assert.NoError(t, err)
|
|
assert.False(t, isMore)
|
|
|
|
// Range splits into level 0 removes until the next chunk boundary, then level 1 removes.
|
|
dfr, err = PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
_, _, isMore, err = dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
|
|
dif, diffType, isMore, err = dfr.split(ctx)
|
|
for i := uint32(512); i <= chunkBoundaries[1]; i++ {
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
require.NotNil(t, dif, "%d", i)
|
|
require.Equal(t, RemovedDiff, diffType)
|
|
assert.Equal(t, fromTups[i][0], val.Tuple(dif.EndKey), "%d", i)
|
|
assert.Equal(t, fromTups[i][1], val.Tuple(dif.From), "%d", i)
|
|
assert.Nil(t, dif.To)
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
}
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, RemovedDiff, diffType)
|
|
require.Equal(t, 1, dif.Level)
|
|
endKey, ok := desc.GetUint32(0, val.Tuple(dif.EndKey))
|
|
require.True(t, ok)
|
|
require.Equal(t, chunkBoundaries[2], endKey)
|
|
startKey, ok := desc.GetUint32(0, val.Tuple(dif.KeyBelowStart))
|
|
require.True(t, ok)
|
|
require.Equal(t, chunkBoundaries[1], startKey)
|
|
require.Nil(t, dif.To)
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, RemovedDiff, diffType)
|
|
require.Equal(t, 1, dif.Level)
|
|
endKey, ok = desc.GetUint32(0, val.Tuple(dif.EndKey))
|
|
require.True(t, ok)
|
|
require.Equal(t, maxKey, endKey)
|
|
startKey, ok = desc.GetUint32(0, val.Tuple(dif.KeyBelowStart))
|
|
require.True(t, ok)
|
|
require.Equal(t, chunkBoundaries[2], startKey)
|
|
require.Nil(t, dif.To)
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
|
|
assert.NoError(t, err)
|
|
require.False(t, isMore)
|
|
})
|
|
t.Run("FromHasPrefix", func(t *testing.T) {
|
|
// Some rows were deleted from the beginning.
|
|
// We expect this to present as a single ModifiedDiff at the start that covers all the removed rows.
|
|
ctx := context.Background()
|
|
ns := NewTestNodeStore()
|
|
|
|
fromTups, desc := AscendingUintTuples(1024)
|
|
fromRoot, err := MakeTreeForTest(fromTups)
|
|
assert.NoError(t, err)
|
|
|
|
toTups := make([][2]val.Tuple, 512)
|
|
copy(toTups, fromTups[512:])
|
|
toRoot, err := MakeTreeForTest(toTups)
|
|
assert.NoError(t, err)
|
|
|
|
dfr, err := PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
|
|
// Range and then EOF
|
|
dif, diffType, isMore, err := dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, ModifiedDiff, diffType)
|
|
assert.Equal(t, dif.KeyBelowStart, Item(nil))
|
|
assert.GreaterOrEqual(t, dif.EndKey, toTups[0][0])
|
|
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
require.False(t, isMore)
|
|
|
|
// Range splits into all 512 removes.
|
|
dfr, err = PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
_, _, isMore, err = dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
|
|
dif, diffType, isMore, err = dfr.split(ctx)
|
|
for i := 0; i < 512; i++ {
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, RemovedDiff, diffType)
|
|
assert.Equal(t, fromTups[i][0], val.Tuple(dif.EndKey))
|
|
assert.Equal(t, fromTups[i][1], val.Tuple(dif.From))
|
|
assert.Nil(t, dif.To)
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
}
|
|
|
|
assert.NoError(t, err)
|
|
assert.False(t, isMore)
|
|
})
|
|
t.Run("ToHasSuffix", func(t *testing.T) {
|
|
// Some rows were added to the end.
|
|
// We expect this to present as one or more ModifiedDiffs at the end that covers all the added rows.
|
|
ctx := context.Background()
|
|
ns := NewTestNodeStore()
|
|
|
|
toTups, desc := AscendingUintTuples(1024)
|
|
toRoot, err := MakeTreeForTest(toTups)
|
|
assert.NoError(t, err)
|
|
|
|
fromTups := make([][2]val.Tuple, 512)
|
|
copy(fromTups, toTups)
|
|
fromRoot, err := MakeTreeForTest(fromTups)
|
|
assert.NoError(t, err)
|
|
|
|
dfr, err := PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
|
|
// At least one ModifiedDiff. The previous key is less than the first added key, and the end key is greater.
|
|
dif, diffType, isMore, err := dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, ModifiedDiff, diffType)
|
|
assert.NotNil(t, dif.KeyBelowStart)
|
|
cmpBelow, err := dfr.order.Compare(ctx, val.Tuple(dif.KeyBelowStart), fromTups[511][0])
|
|
require.NoError(t, err)
|
|
assert.Equal(t, cmpBelow, -1)
|
|
cmpEnd, err := dfr.order.Compare(ctx, val.Tuple(dif.EndKey), fromTups[511][0])
|
|
require.NoError(t, err)
|
|
assert.Equal(t, cmpEnd, 1)
|
|
|
|
KeyBelowStart := dif.EndKey
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
for isMore {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, AddedDiff, diffType)
|
|
assert.Equal(t, KeyBelowStart, dif.KeyBelowStart)
|
|
|
|
KeyBelowStart = dif.EndKey
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
}
|
|
assert.NoError(t, err)
|
|
assert.False(t, isMore)
|
|
|
|
// Range splits into all 512 removes.
|
|
dfr, err = PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
for i := 0; i < 512; i++ {
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
for dif.Level > 0 {
|
|
dif, diffType, isMore, err = dfr.split(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
}
|
|
require.NotNil(t, dif)
|
|
if !assert.Equal(t, AddedDiff, diffType) {
|
|
panic(1)
|
|
}
|
|
assert.Equal(t, toTups[512+i][0], val.Tuple(dif.EndKey))
|
|
assert.Equal(t, toTups[512+i][1], val.Tuple(dif.To))
|
|
assert.Nil(t, dif.From)
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
}
|
|
|
|
assert.NoError(t, err)
|
|
assert.False(t, isMore)
|
|
})
|
|
t.Run("ToHasPrefix", func(t *testing.T) {
|
|
// Some rows were added to the beginning.
|
|
// We expect this to present as one or more ModifiedDiffs at the start that covers all the added rows.
|
|
ctx := context.Background()
|
|
ns := NewTestNodeStore()
|
|
|
|
toTups, desc := AscendingUintTuples(1024)
|
|
toRoot, err := MakeTreeForTest(toTups)
|
|
assert.NoError(t, err)
|
|
|
|
fromTups := make([][2]val.Tuple, 512)
|
|
copy(fromTups, toTups[512:])
|
|
fromRoot, err := MakeTreeForTest(fromTups)
|
|
assert.NoError(t, err)
|
|
|
|
dfr, err := PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
|
|
// At least one ModifiedDiff. The previous key is nil because it comes before any other observed keys, and the end key is greater.
|
|
dif, diffType, isMore, err := dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, ModifiedDiff, diffType)
|
|
assert.Nil(t, dif.KeyBelowStart, Item(nil))
|
|
|
|
KeyBelowStart := dif.EndKey
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
for isMore {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, ModifiedDiff, diffType)
|
|
assert.Equal(t, KeyBelowStart, dif.KeyBelowStart)
|
|
|
|
KeyBelowStart = dif.EndKey
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
}
|
|
assert.NoError(t, err)
|
|
assert.False(t, isMore)
|
|
|
|
// Range splits into all 512 removes.
|
|
dfr, err = PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
for i := 0; i < 512; i++ {
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
for dif.Level > 0 {
|
|
dif, diffType, isMore, err = dfr.split(ctx)
|
|
require.NoError(t, err)
|
|
}
|
|
require.NotNil(t, dif)
|
|
if !assert.Equal(t, AddedDiff, diffType) {
|
|
panic(1)
|
|
}
|
|
assert.Equal(t, toTups[i][0], val.Tuple(dif.EndKey))
|
|
assert.Equal(t, toTups[i][1], val.Tuple(dif.To))
|
|
assert.Nil(t, dif.From)
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
}
|
|
|
|
assert.NoError(t, err)
|
|
assert.False(t, isMore)
|
|
})
|
|
t.Run("ToIsEmpty", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
ns := NewTestNodeStore()
|
|
|
|
fromTups, desc := AscendingUintTuples(1024)
|
|
fromRoot, err := MakeTreeForTest(fromTups)
|
|
chunkBoundaries := []uint32{414, 718, 830}
|
|
maxKey := uint32(1023)
|
|
assert.NoError(t, err)
|
|
|
|
toTups := make([][2]val.Tuple, 0)
|
|
toRoot, err := MakeTreeForTest(toTups)
|
|
assert.NoError(t, err)
|
|
|
|
dfr, err := PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
|
|
var KeyBelowStart Item
|
|
dif, diffType, isMore, err := dfr.Next(ctx)
|
|
for isMore {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, RemovedDiff, diffType)
|
|
assert.Nil(t, dif.To)
|
|
assert.Equal(t, KeyBelowStart, dif.KeyBelowStart)
|
|
KeyBelowStart = dif.EndKey
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
}
|
|
|
|
assert.Equal(t, KeyBelowStart, Item(fromTups[1023][0]))
|
|
assert.NoError(t, err)
|
|
assert.False(t, isMore)
|
|
|
|
// Range splits into level 0 removes until the next chunk boundary, then level 1 removes.
|
|
dfr, err = PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
_, _, isMore, err = dfr.Next(ctx)
|
|
require.True(t, isMore)
|
|
|
|
dif, diffType, isMore, err = dfr.split(ctx)
|
|
for i := uint32(0); i <= chunkBoundaries[0]; i++ {
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
require.NotNil(t, dif, "%d", i)
|
|
require.Equal(t, RemovedDiff, diffType)
|
|
assert.Equal(t, fromTups[i][0], val.Tuple(dif.EndKey), "%d", i)
|
|
assert.Equal(t, fromTups[i][1], val.Tuple(dif.From), "%d", i)
|
|
assert.Nil(t, dif.To)
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
}
|
|
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, RemovedDiff, diffType)
|
|
require.Equal(t, 1, dif.Level)
|
|
endKey, ok := desc.GetUint32(0, val.Tuple(dif.EndKey))
|
|
require.True(t, ok)
|
|
require.Equal(t, chunkBoundaries[1], endKey)
|
|
startKey, ok := desc.GetUint32(0, val.Tuple(dif.KeyBelowStart))
|
|
require.True(t, ok)
|
|
require.Equal(t, chunkBoundaries[0], startKey)
|
|
require.Nil(t, dif.To)
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, RemovedDiff, diffType)
|
|
require.Equal(t, 1, dif.Level)
|
|
endKey, ok = desc.GetUint32(0, val.Tuple(dif.EndKey))
|
|
require.True(t, ok)
|
|
require.Equal(t, chunkBoundaries[2], endKey)
|
|
startKey, ok = desc.GetUint32(0, val.Tuple(dif.KeyBelowStart))
|
|
require.True(t, ok)
|
|
require.Equal(t, chunkBoundaries[1], startKey)
|
|
require.Nil(t, dif.To)
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, RemovedDiff, diffType)
|
|
require.Equal(t, 1, dif.Level)
|
|
endKey, ok = desc.GetUint32(0, val.Tuple(dif.EndKey))
|
|
require.True(t, ok)
|
|
require.Equal(t, maxKey, endKey)
|
|
startKey, ok = desc.GetUint32(0, val.Tuple(dif.KeyBelowStart))
|
|
require.True(t, ok)
|
|
require.Equal(t, chunkBoundaries[2], startKey)
|
|
require.Nil(t, dif.To)
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
|
|
assert.NoError(t, err)
|
|
assert.False(t, isMore)
|
|
})
|
|
|
|
t.Run("FromIsEmpty", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
ns := NewTestNodeStore()
|
|
|
|
toTups, desc := AscendingUintTuples(1024)
|
|
toRoot, err := MakeTreeForTest(toTups)
|
|
assert.NoError(t, err)
|
|
|
|
fromTups := make([][2]val.Tuple, 0)
|
|
fromRoot, err := MakeTreeForTest(fromTups)
|
|
assert.NoError(t, err)
|
|
|
|
dfr, err := PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
|
|
var KeyBelowStart Item
|
|
|
|
// Range and then EOF
|
|
dif, diffType, isMore, err := dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
require.Nil(t, dif.KeyBelowStart)
|
|
for isMore {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, dif)
|
|
require.Equal(t, AddedDiff, diffType)
|
|
assert.Equal(t, KeyBelowStart, dif.KeyBelowStart)
|
|
KeyBelowStart = dif.EndKey
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
}
|
|
assert.NoError(t, err)
|
|
assert.False(t, isMore)
|
|
|
|
dfr, err = PatchGeneratorFromRoots(ctx, ns, ns, fromRoot, toRoot, desc)
|
|
require.NoError(t, err)
|
|
for i := 0; i < 1024; i++ {
|
|
dif, diffType, isMore, err = dfr.Next(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
require.NotNil(t, dif)
|
|
for dif.Level > 0 {
|
|
dif, diffType, isMore, err = dfr.split(ctx)
|
|
require.NoError(t, err)
|
|
require.True(t, isMore)
|
|
require.NotNil(t, dif)
|
|
}
|
|
require.Equal(t, AddedDiff, diffType)
|
|
assert.Equal(t, toTups[i][0], val.Tuple(dif.EndKey))
|
|
assert.Equal(t, toTups[i][1], val.Tuple(dif.To))
|
|
assert.Nil(t, dif.From)
|
|
}
|
|
|
|
_, _, isMore, err = dfr.Next(ctx)
|
|
assert.NoError(t, err)
|
|
require.False(t, isMore)
|
|
})
|
|
}
|