@@ -6,18 +6,21 @@ package compact
66
77import (
88 "bytes"
9+ "context"
910 "encoding/binary"
1011 "fmt"
1112 "io"
1213 "slices"
1314 "strconv"
1415 "strings"
1516 "testing"
17+ "unicode"
1618
1719 "github.com/cockroachdb/datadriven"
1820 "github.com/cockroachdb/pebble/internal/base"
1921 "github.com/cockroachdb/pebble/internal/keyspan"
2022 "github.com/cockroachdb/pebble/internal/rangekey"
23+ "github.com/cockroachdb/pebble/sstable/valblk"
2124 "github.com/stretchr/testify/require"
2225)
2326
@@ -53,7 +56,6 @@ func TestCompactionIter(t *testing.T) {
5356 var snapshots Snapshots
5457 var elideTombstones bool
5558 var allowZeroSeqnum bool
56-
5759 var ineffectualSingleDeleteKeys []string
5860 var invariantViolationSingleDeleteKeys []string
5961 resetSingleDelStats := func () {
@@ -133,16 +135,18 @@ func TestCompactionIter(t *testing.T) {
133135 continue
134136 }
135137
136- var value [] byte
138+ var iv base. InternalValue
137139 if strings .HasPrefix (key [j + 1 :], "varint(" ) {
138140 valueStr := strings .TrimSuffix (strings .TrimPrefix (key [j + 1 :], "varint(" ), ")" )
139141 v , err := strconv .ParseUint (valueStr , 10 , 64 )
140142 require .NoError (t , err )
141- value = binary .AppendUvarint ([]byte (nil ), v )
143+ iv = base .MakeInPlaceValue (binary .AppendUvarint ([]byte (nil ), v ))
144+ } else if strings .HasPrefix (key [j + 1 :], "blobref(" ) {
145+ iv = decodeBlobReference (t , key [j + 1 :])
142146 } else {
143- value = []byte (key [j + 1 :])
147+ iv = base . MakeInPlaceValue ( []byte (key [j + 1 :]) )
144148 }
145- kvs = append (kvs , base .MakeInternalKV ( ik , value ) )
149+ kvs = append (kvs , base.InternalKV { K : ik , V : iv } )
146150 }
147151 rangeDelFragmenter .Finish ()
148152 return ""
@@ -202,9 +206,15 @@ func TestCompactionIter(t *testing.T) {
202206 }
203207 var value []byte
204208 if kv != nil {
205- var err error
206- value , _ , err = kv .Value (nil )
207- require .NoError (t , err )
209+ if kv .V .IsBlobValueHandle () {
210+ lv := kv .V .LazyValue ()
211+ value = []byte (fmt .Sprintf ("<blobref(%s, encodedHandle=%x, valLen=%d)>" ,
212+ lv .Fetcher .BlobFileNum , lv .ValueOrHandle , lv .Fetcher .Attribute .ValueLen ))
213+ } else {
214+ var err error
215+ value , _ , err = kv .Value (nil )
216+ require .NoError (t , err )
217+ }
208218 }
209219
210220 if kv != nil {
@@ -267,6 +277,60 @@ func TestCompactionIter(t *testing.T) {
267277 runTest (t , "testdata/iter_delete_sized" )
268278}
269279
280+ // mockBlobValueFetcher is a dummy ValueFetcher implementation which produces
281+ // string values referencing the blobref.
282+ type mockBlobValueFetcher struct {}
283+
284+ var _ base.ValueFetcher = mockBlobValueFetcher {}
285+
286+ func (mockBlobValueFetcher ) Fetch (
287+ ctx context.Context , handle []byte , fileNum base.DiskFileNum , valLen uint32 , buf []byte ,
288+ ) (val []byte , callerOwned bool , err error ) {
289+ s := fmt .Sprintf ("<fetched value from blobref(%s, encodedHandle=%x, valLen=%d)>" ,
290+ fileNum , handle , valLen )
291+ return []byte (s ), false , nil
292+ }
293+
294+ // decodeBlobReference decodes a blob reference from a debug string. It expects
295+ // a value of the form: blobref(<filenum>, blk<blocknum>, <offset>, <valLen>).
296+ // For example: blobref(000124, blk255, 10, 9235)
297+ func decodeBlobReference (t testing.TB , ref string ) base.InternalValue {
298+ fields := strings .FieldsFunc (strings .TrimSuffix (strings .TrimPrefix (ref , "blobref(" ), ")" ),
299+ func (r rune ) bool { return r == ',' || unicode .IsSpace (r ) })
300+ require .Equal (t , 4 , len (fields ))
301+ fileNum , err := strconv .ParseUint (fields [0 ], 10 , 64 )
302+ require .NoError (t , err )
303+ blockNum , err := strconv .ParseUint (strings .TrimPrefix (fields [1 ], "blk" ), 10 , 32 )
304+ require .NoError (t , err )
305+ off , err := strconv .ParseUint (fields [2 ], 10 , 32 )
306+ require .NoError (t , err )
307+ valLen , err := strconv .ParseUint (fields [3 ], 10 , 32 )
308+ require .NoError (t , err )
309+
310+ // TODO(jackson): Support short (and long, when introduced) attributes.
311+ return base .MakeLazyValue (base.LazyValue {
312+ ValueOrHandle : encodeRemainingHandle (uint32 (blockNum ), uint32 (off )),
313+ Fetcher : & base.LazyFetcher {
314+ Fetcher : mockBlobValueFetcher {},
315+ BlobFileNum : base .DiskFileNum (fileNum ),
316+ Attribute : base.AttributeAndLen {
317+ ValueLen : uint32 (valLen ),
318+ },
319+ },
320+ })
321+ }
322+
323+ func encodeRemainingHandle (blockNum uint32 , offsetInBlock uint32 ) []byte {
324+ // TODO(jackson): Pull this into a common helper.
325+ dst := make ([]byte , valblk .HandleMaxLen )
326+ n := valblk .EncodeHandle (dst , valblk.Handle {
327+ ValueLen : 0 ,
328+ BlockNum : blockNum ,
329+ OffsetInBlock : offsetInBlock ,
330+ })
331+ return dst [1 :n ]
332+ }
333+
270334// makeInputIters creates the iterators necessthat can be used to create a compaction
271335// Iter.
272336func makeInputIters (
0 commit comments