Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions sstable/rowblk/rowblk_iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ type Iter struct {
cachedBuf []byte
handle block.BufferHandle
// for block iteration for already loaded blocks.
//
// firstUserKey is the user key of the first KV in the block. It has the
// synthetic prefix applied (if any), but does NOT have the synthetic suffix
// applied — the on-disk suffix is preserved.
firstUserKey []byte
lazyValueHandling struct {
getValue block.GetInternalValueForPrefixAndValueHandler
Expand Down Expand Up @@ -541,6 +545,15 @@ func (i *Iter) cacheEntry() {
// IsLowerBound implements the block.DataBlockIterator interface.
func (i *Iter) IsLowerBound(k []byte) bool {
// Note: we ignore HideObsoletePoints, but false negatives are allowed.
if i.transforms.HasSyntheticSuffix() {
// firstUserKey has the synthetic prefix applied, but its suffix is the
// original on-disk suffix — not the synthetic suffix that the iterator will
// actually return. Instead of applying the synthetic suffix, we
// conservatively require firstUserKey's prefix portion to be strictly
// greater than k (false negatives are allowed).
firstPrefix := i.firstUserKey[:i.split(i.firstUserKey)]
return i.cmp(firstPrefix, k) > 0
}
return i.cmp(i.firstUserKey, k) >= 0
}

Expand Down
76 changes: 76 additions & 0 deletions sstable/rowblk/rowblk_iter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ package rowblk
import (
"bytes"
"fmt"
"math/rand/v2"
"slices"
"strings"
"testing"
"time"
"unsafe"

"github.com/cockroachdb/datadriven"
Expand Down Expand Up @@ -465,3 +468,76 @@ func TestBlockSyntheticSuffix(t *testing.T) {
func ikey(s string) base.InternalKey {
return base.InternalKey{UserKey: []byte(s)}
}

func TestIsLowerBoundRand(t *testing.T) {
cmp := testkeys.Comparer.Compare
suffixCmp := testkeys.Comparer.ComparePointSuffixes
split := testkeys.Comparer.Split

prefixes := []string{"a", "b", "c", "d", "e", "p/", "zzz/"}
suffixes := []string{"", "@1", "@5", "@10", "@20", "@100"}

seed := uint64(time.Now().UnixNano())
t.Logf("seed: %d", seed)
rng := rand.New(rand.NewPCG(seed, seed))
randKey := func() string {
return prefixes[rng.IntN(len(prefixes))] + suffixes[rng.IntN(len(suffixes))]
}

for iter := 0; iter < 1000; iter++ {
// Construct a block with a random combination of prefixes and suffixes.
n := 1 + rng.IntN(20)
keySet := make(map[string]struct{}, n)
for len(keySet) < n {
keySet[randKey()] = struct{}{}
}
keys := make([]string, 0, n)
for k := range keySet {
keys = append(keys, k)
}
slices.SortFunc(keys, func(a, b string) int { return cmp([]byte(a), []byte(b)) })

w := &Writer{RestartInterval: 1 + rng.IntN(4)}
for _, k := range keys {
require.NoError(t, w.Add(ikey(k), nil))
}
blk := w.Finish()

// Pick a random prefix and suffix for the transform.
var synthPrefix, synthSuffix []byte
if rand.IntN(2) == 0 {
synthPrefix = []byte(prefixes[rng.IntN(len(prefixes))])
}
if rand.IntN(2) == 0 {
synthSuffix = []byte(suffixes[rng.IntN(len(suffixes))])
}
transforms := block.IterTransforms{
SyntheticPrefixAndSuffix: block.MakeSyntheticPrefixAndSuffix(synthPrefix, synthSuffix),
}

it, err := NewIter(cmp, suffixCmp, split, blk, transforms)
require.NoError(t, err)

kv := it.First()
require.NotNil(t, kv)
// firstKey is the key after the transform.
firstKey := slices.Clone(kv.K.UserKey)

for probe := 0; probe < 50; probe++ {
probeKey := []byte(randKey())
if rand.IntN(2) == 0 {
// The transformed block keys are random keys with synthPrefix
// prepended. To generate more interesting probe keys, sometimes prepend
// this prefix to the random key.
probeKey = append(slices.Clip(synthPrefix), probeKey...)
}
if it.IsLowerBound(probeKey) {
if cmp(firstKey, probeKey) < 0 {
t.Fatalf("IsLowerBound(%q)=true but First()=%q < probe (transforms=%+v, keys=%v)",
probeKey, firstKey, transforms, keys)
}
}
}
require.NoError(t, it.Close())
}
}
Loading