Repository: cockroachdb/pebble
Pull request #3905 (2024-08-29 19:33:47)

colblk: various tweaks, interface extensions
Various tweaks to the columnar block primitives that have fallen out of work on the sstable writer integration.

cockroach-teamcity: 

<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/cockroachdb/pebble/3905)
<!-- Reviewable:end -->

jbowens: TFTR!

Patch:
diff --git a/sstable/colblk/block.go b/sstable/colblk/block.go
index a70e4d6f..5b322c62 100644
--- a/sstable/colblk/block.go
+++ b/sstable/colblk/block.go
@@ -394,9 +394,8 @@ func (r *BlockReader) headerToBinFormatter(f *binfmt.Formatter) {
 	f.HexBytesln(2, "%d columns", r.header.Columns)
 	f.HexBytesln(4, "%d rows", r.header.Rows)
 	for i := 0; i < int(r.header.Columns); i++ {
-		f.CommentLine("column %d", i)
-		f.Byte("%s", r.DataType(i))
-		f.HexBytesln(4, "page start %d", r.pageStart(i))
+		f.Byte("col %d: %s", i, r.DataType(i))
+		f.HexBytesln(4, "col %d: page start %d", i, r.pageStart(i))
 	}
 }
 
diff --git a/sstable/colblk/data_block.go b/sstable/colblk/data_block.go
index 1d252dbd..40c9d610 100644
--- a/sstable/colblk/data_block.go
+++ b/sstable/colblk/data_block.go
@@ -181,7 +181,8 @@ func (w *defaultKeyWriter) ComparePrev(key []byte) KeyComparison {
 	// CommonPrefixLen, it would sort after [key].)
 	if cmpv.CommonPrefixLen == cmpv.PrefixLen {
 		// The keys share the same MVCC prefix. Compare the suffixes.
-		cmpv.UserKeyComparison = int32(w.comparer.CompareSuffixes(key[cmpv.PrefixLen:], w.suffixes.LastSlice()))
+		cmpv.UserKeyComparison = int32(w.comparer.CompareSuffixes(key[cmpv.PrefixLen:],
+			w.suffixes.UnsafeGet(w.suffixes.rows-1)))
 		if invariants.Enabled {
 			if !w.comparer.Equal(lp, key[:cmpv.PrefixLen]) {
 				panic(errors.AssertionFailedf("keys have different logical prefixes: %q != %q", lp, key[:cmpv.PrefixLen]))
@@ -206,7 +207,8 @@ func (w *defaultKeyWriter) ComparePrev(key []byte) KeyComparison {
 		// so the UserKeyComparison should be equal to the result of comparing
 		// the prefixes and nonzero.
 		if cmpv.UserKeyComparison == 0 {
-			panic(errors.AssertionFailedf("user keys should not be equal: %q+%q, %q", lp, w.suffixes.LastSlice(), key))
+			panic(errors.AssertionFailedf("user keys should not be equal: %q+%q, %q",
+				lp, w.suffixes.UnsafeGet(w.suffixes.rows-1), key))
 		}
 		if v := w.comparer.Compare(key, lp); v != int(cmpv.UserKeyComparison) {
 			panic(errors.AssertionFailedf("user key comparison mismatch: Compare(%q, %q) = %d ≠ %d",
diff --git a/sstable/colblk/index_block.go b/sstable/colblk/index_block.go
index 34a483a5..861d1f6f 100644
--- a/sstable/colblk/index_block.go
+++ b/sstable/colblk/index_block.go
@@ -38,7 +38,6 @@ type IndexBlockWriter struct {
 	offsets         UintBuilder
 	lengths         UintBuilder
 	blockProperties RawBytesBuilder
-	numColumns      int
 	rows            int
 	enc             blockEncoder
 }
@@ -56,7 +55,6 @@ func (w *IndexBlockWriter) Init(blockProperties ...ColumnWriter) {
 	w.separators.Init()
 	w.offsets.Init()
 	w.lengths.InitWithDefault()
-	w.numColumns = indexBlockColumnCount
 	w.blockProperties.Init()
 	w.rows = 0
 }
@@ -71,9 +69,13 @@ func (w *IndexBlockWriter) Reset() {
 	w.enc.reset()
 }
 
+// Rows returns the number of entries in the index block so far.
+func (w *IndexBlockWriter) Rows() int {
+	return w.rows
+}
+
 // AddBlockHandle adds a new separator and end offset of a data block to the
-// index block.  Add returns the index of the row. The caller should add rows to
-// the block properties columns too if necessary.
+// index block.  Add returns the index of the row.
 //
 // AddBlockHandle should only be used for first-level index blocks.
 func (w *IndexBlockWriter) AddBlockHandle(
@@ -88,9 +90,14 @@ func (w *IndexBlockWriter) AddBlockHandle(
 	return idx
 }
 
+// UnsafeSeparator returns the separator of the i'th entry.
+func (w *IndexBlockWriter) UnsafeSeparator(i int) []byte {
+	return w.separators.UnsafeGet(i)
+}
+
 // Size returns the size of the pending index block.
 func (w *IndexBlockWriter) Size() int {
-	off := blockHeaderSize(w.numColumns, indexBlockCustomHeaderSize)
+	off := blockHeaderSize(indexBlockColumnCount, indexBlockCustomHeaderSize)
 	off = w.separators.Size(w.rows, off)
 	off = w.offsets.Size(w.rows, off)
 	off = w.lengths.Size(w.rows, off)
@@ -103,7 +110,7 @@ func (w *IndexBlockWriter) Size() int {
 func (w *IndexBlockWriter) Finish() []byte {
 	w.enc.init(w.Size(), Header{
 		Version: Version1,
-		Columns: uint16(w.numColumns),
+		Columns: indexBlockColumnCount,
 		Rows:    uint32(w.rows),
 	}, indexBlockCustomHeaderSize)
 	w.enc.encode(w.rows, &w.separators)
@@ -118,6 +125,7 @@ type IndexReader struct {
 	separators RawBytes
 	offsets    UnsafeUints
 	lengths    UnsafeUints // only used for second-level index blocks
+	blockProps RawBytes
 	br         BlockReader
 }
 
@@ -127,6 +135,7 @@ func (r *IndexReader) Init(data []byte) {
 	r.separators = r.br.RawBytes(indexBlockColumnSeparator)
 	r.offsets = r.br.Uints(indexBlockColumnOffsets)
 	r.lengths = r.br.Uints(indexBlockColumnLengths)
+	r.blockProps = r.br.RawBytes(indexBlockColumnBlockProperties)
 }
 
 // DebugString prints a human-readable explanation of the keyspan block's binary
@@ -232,7 +241,7 @@ func (i *IndexIter) BlockHandleWithProperties() (block.HandleWithProperties, err
 			Offset: i.r.offsets.At(i.row),
 			Length: i.r.lengths.At(i.row),
 		},
-		Props: i.r.br.RawBytes(indexBlockColumnBlockProperties).At(i.row),
+		Props: i.r.blockProps.At(i.row),
 	}, nil
 }
 
diff --git a/sstable/colblk/keyspan.go b/sstable/colblk/keyspan.go
index b8ebfdd4..2462e863 100644
--- a/sstable/colblk/keyspan.go
+++ b/sstable/colblk/keyspan.go
@@ -82,7 +82,7 @@ func (w *KeyspanBlockWriter) Reset() {
 
 // AddSpan appends a new Span to the pending block. Spans must already be
 // fragmented (non-overlapping) and added in sorted order.
-func (w *KeyspanBlockWriter) AddSpan(s *keyspan.Span) {
+func (w *KeyspanBlockWriter) AddSpan(s keyspan.Span) {
 	// When keyspans are fragmented, abutting spans share a user key. One span's
 	// end key is the next span's start key.  Check if the previous user key
 	// equals this span's start key, and avoid encoding it again if so.
@@ -109,6 +109,26 @@ func (w *KeyspanBlockWriter) AddSpan(s *keyspan.Span) {
 	}
 }
 
+// KeyCount returns the count of keyspan.Keys written to the writer.
+func (w *KeyspanBlockWriter) KeyCount() int {
+	return w.keyCount
+}
+
+// UnsafeBoundaryKeys returns the smallest and largest keys written to the
+// keyspan block so far. The returned internal keys have user keys that point
+// directly into the block writer's memory and must not be mutated.
+func (w *KeyspanBlockWriter) UnsafeBoundaryKeys() (smallest, largest base.InternalKey) {
+	if w.keyCount == 0 {
+		return smallest, largest
+	}
+	smallest.UserKey = w.boundaryUserKeys.UnsafeGet(0)
+	smallest.Trailer = base.InternalKeyTrailer(w.trailers.Get(0))
+	largest.UserKey = w.boundaryUserKeys.UnsafeGet(w.boundaryUserKeys.rows - 1)
+	largest.Trailer = base.MakeTrailer(base.SeqNumMax,
+		base.InternalKeyTrailer(w.trailers.Get(w.keyCount-1)).Kind())
+	return smallest, largest
+}
+
 // Size returns the size of the pending block.
 func (w *KeyspanBlockWriter) Size() int {
 	off := blockHeaderSize(keyspanColumnCount, keyspanHeaderSize)
diff --git a/sstable/colblk/raw_bytes.go b/sstable/colblk/raw_bytes.go
index 6d1f3508..75b1c2d2 100644
--- a/sstable/colblk/raw_bytes.go
+++ b/sstable/colblk/raw_bytes.go
@@ -165,13 +165,18 @@ func (b *RawBytesBuilder) PutConcat(s1, s2 []byte) {
 	b.offsets.Set(b.rows, uint64(len(b.data)))
 }
 
-// LastSlice returns the last slice added to the builder. The returned slice is
+// Rows returns the count of slices that have been added to the builder.
+func (b *RawBytesBuilder) Rows() int {
+	return b.rows
+}
+
+// UnsafeGet returns the i'th slice added to the builder. The returned slice is
 // owned by the builder and must not be mutated.
-func (b *RawBytesBuilder) LastSlice() []byte {
+func (b *RawBytesBuilder) UnsafeGet(i int) []byte {
 	if b.rows == 0 {
 		return nil
 	}
-	return b.data[b.offsets.array.elems.At(b.rows-1):b.offsets.array.elems.At(b.rows)]
+	return b.data[b.offsets.array.elems.At(i):b.offsets.array.elems.At(i+1)]
 }
 
 // Finish writes the serialized byte slices to buf starting at offset. The buf
diff --git a/sstable/colblk/unsafe_slice.go b/sstable/colblk/unsafe_slice.go
index c8c63e8a..956020ad 100644
--- a/sstable/colblk/unsafe_slice.go
+++ b/sstable/colblk/unsafe_slice.go
@@ -59,7 +59,7 @@ var _ Array[uint64] = UnsafeUints{}
 func DecodeUnsafeUints(b []byte, off uint32, rows int) (_ UnsafeUints, endOffset uint32) {
 	encoding := UintEncoding(b[off])
 	if !encoding.IsValid() {
-		panic(errors.AssertionFailedf("invalid encoding 0x%x", b))
+		panic(errors.AssertionFailedf("invalid encoding 0x%x", b[off:off+1]))
 	}
 	off++
 	var base uint64


Test patch:
diff --git a/sstable/colblk/index_block_test.go b/sstable/colblk/index_block_test.go
index 7b06a143..fc47f5ee 100644
--- a/sstable/colblk/index_block_test.go
+++ b/sstable/colblk/index_block_test.go
@@ -39,9 +39,11 @@ func TestIndexBlock(t *testing.T) {
 				}
 				w.AddBlockHandle([]byte(fields[0]), h, bp)
 			}
+			fmt.Fprintf(&buf, "UnsafeSeparator(Rows()-1) = %q\n", w.UnsafeSeparator(w.Rows()-1))
 			data := w.Finish()
 			r.Init(data)
-			return r.DebugString()
+			fmt.Fprint(&buf, r.DebugString())
+			return buf.String()
 		case "iter":
 			var it IndexIter
 			it.Init(&r)
diff --git a/sstable/colblk/keyspan_test.go b/sstable/colblk/keyspan_test.go
index 72a28030..b62512df 100644
--- a/sstable/colblk/keyspan_test.go
+++ b/sstable/colblk/keyspan_test.go
@@ -15,6 +15,7 @@ import (
 	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
 	"github.com/cockroachdb/pebble/internal/keyspan"
+	"github.com/cockroachdb/pebble/internal/testkeys"
 	"golang.org/x/exp/rand"
 )
 
@@ -35,12 +36,13 @@ func TestKeyspanBlock(t *testing.T) {
 			return buf.String()
 		case "add":
 			for _, line := range strings.Split(td.Input, "\n") {
-				s := keyspan.ParseSpan(line)
-				w.AddSpan(&s)
+				w.AddSpan(keyspan.ParseSpan(line))
 			}
 			fmt.Fprint(&buf, &w)
 			return buf.String()
 		case "finish":
+			sm, la := w.UnsafeBoundaryKeys()
+			fmt.Fprintf(&buf, "Boundaries: %s — %s\n", sm.Pretty(testkeys.Comparer.FormatKey), la.Pretty(testkeys.Comparer.FormatKey))
 			block := w.Finish()
 			kr.Init(block)
 			fmt.Fprint(&buf, kr.DebugString())
@@ -90,7 +92,7 @@ func benchmarkKeyspanBlockRangeDeletions(b *testing.B, numSpans, keysPerSpan, ke
 			t := base.MakeTrailer(base.SeqNum(j), base.InternalKeyKindRangeDelete)
 			s.Keys = append(s.Keys, keyspan.Key{Trailer: t})
 		}
-		w.AddSpan(&s)
+		w.AddSpan(s)
 	}
 	block := w.Finish()
 	avgRowSize := float64(w.Size()) / float64(numSpans*keysPerSpan)
diff --git a/sstable/colblk/testdata/block_writer b/sstable/colblk/testdata/block_writer
index b7b3a7a9..2c27e0a4 100644
--- a/sstable/colblk/testdata/block_writer
+++ b/sstable/colblk/testdata/block_writer
@@ -20,9 +20,8 @@ finish
 00-01: x 01       # version 1
 01-03: x 0100     # 1 columns
 03-07: x 0a000000 # 10 rows
-# column 0
-07-08: b 00000010 # uint
-08-12: x 0c000000 # page start 12
+07-08: b 00000010 # col 0: uint
+08-12: x 0c000000 # col 0: page start 12
 # data for column 0
 12-13: x 00       # encoding: zero
 13-14: x 00       # block trailer padding
@@ -53,9 +52,8 @@ finish
 00-01: x 01               # version 1
 01-03: x 0100             # 1 columns
 03-07: x 0c000000         # 12 rows
-# column 0
-07-08: b 00000010         # uint
-08-12: x 0c000000         # page start 12
+07-08: b 00000010         # col 0: uint
+08-12: x 0c000000         # col 0: page start 12
 # data for column 0
 12-13: x 80               # encoding: const
 13-21: x ffffffffffffff7f # 64-bit constant: 9223372036854775807
@@ -81,9 +79,8 @@ finish
 00-01: x 01               # version 1
 01-03: x 0100             # 1 columns
 03-07: x 05000000         # 5 rows
-# column 0
-07-08: b 00000010         # uint
-08-12: x 0c000000         # page start 12
+07-08: b 00000010         # col 0: uint
+08-12: x 0c000000         # col 0: page start 12
 # data for column 0
 12-13: x 81               # encoding: 1b,delta
 13-21: x abbe105c738e9639 # 64-bit constant: 4149660732785475243
@@ -115,9 +112,8 @@ finish
 00-01: x 01               # version 1
 01-03: x 0100             # 1 columns
 03-07: x 06000000         # 6 rows
-# column 0
-07-08: b 00000010         # uint
-08-12: x 0c000000         # page start 12
+07-08: b 00000010         # col 0: uint
+08-12: x 0c000000         # col 0: page start 12
 # data for column 0
 12-13: x 82               # encoding: 2b,delta
 13-21: x abbe105c738e9639 # 64-bit constant: 4149660732785475243
@@ -151,9 +147,8 @@ finish
 00-01: x 01       # version 1
 01-03: x 0100     # 1 columns
 03-07: x 09000000 # 9 rows
-# column 0
-07-08: b 00000010 # uint
-08-12: x 0c000000 # page start 12
+07-08: b 00000010 # col 0: uint
+08-12: x 0c000000 # col 0: page start 12
 # data for column 0
 12-13: x 04       # encoding: 4b
 13-16: x 000000   # padding (aligning to 32-bit boundary)
@@ -192,9 +187,8 @@ finish
 00-01: x 01       # version 1
 01-03: x 0100     # 1 columns
 03-07: x 0a000000 # 10 rows
-# column 0
-07-08: b 00000010 # uint
-08-12: x 0c000000 # page start 12
+07-08: b 00000010 # col 0: uint
+08-12: x 0c000000 # col 0: page start 12
 # data for column 0
 12-13: x 01       # encoding: 1b
 13-14: x 00       # data[0] = 0
@@ -223,9 +217,8 @@ finish
 00-01: x 01       # version 1
 01-03: x 0100     # 1 columns
 03-07: x 02000000 # 2 rows
-# column 0
-07-08: b 00000010 # uint
-08-12: x 0c000000 # page start 12
+07-08: b 00000010 # col 0: uint
+08-12: x 0c000000 # col 0: page start 12
 # data for column 0
 12-13: x 02       # encoding: 2b
 13-14: x 00       # padding (aligning to 16-bit boundary)
@@ -269,9 +262,8 @@ finish
 00-01: x 01                                                               # version 1
 01-03: x 0100                                                             # 1 columns
 03-07: x 18000000                                                         # 24 rows
-# column 0
-07-08: b 00000001                                                         # bool
-08-12: x 0c000000                                                         # page start 12
+07-08: b 00000001                                                         # col 0: bool
+08-12: x 0c000000                                                         # col 0: page start 12
 # data for column 0
 12-13: x 00                                                               # bitmap encoding
 13-16: x 000000                                                           # padding to align to 64-bit boundary
@@ -300,12 +292,10 @@ finish
 000-001: x 01                     # version 1
 001-003: x 0200                   # 2 columns
 003-007: x 09000000               # 9 rows
-# column 0
-007-008: b 00000011               # bytes
-008-012: x 11000000               # page start 17
-# column 1
-012-013: b 00000010               # uint
-013-017: x 5a000000               # page start 90
+007-008: b 00000011               # col 0: bytes
+008-012: x 11000000               # col 0: page start 17
+012-013: b 00000010               # col 1: uint
+013-017: x 5a000000               # col 1: page start 90
 # data for column 0
 # rawbytes
 # offsets table
diff --git a/sstable/colblk/testdata/data_block/bundle_search b/sstable/colblk/testdata/data_block/bundle_search
index 6a10e82f..feb4fbd9 100644
--- a/sstable/colblk/testdata/data_block/bundle_search
+++ b/sstable/colblk/testdata/data_block/bundle_search
@@ -102,24 +102,18 @@ finish
 004-005: x 01                                                               # version 1
 005-007: x 0600                                                             # 6 columns
 007-011: x 42000000                                                         # 66 rows
-# column 0
-011-012: b 00000100                                                         # prefixbytes
-012-016: x 29000000                                                         # page start 41
-# column 1
-016-017: b 00000011                                                         # bytes
-017-021: x 25020000                                                         # page start 549
-# column 2
-021-022: b 00000010                                                         # uint
-022-026: x 26020000                                                         # page start 550
-# column 3
-026-027: b 00000001                                                         # bool
-027-031: x 2f020000                                                         # page start 559
-# column 4
-031-032: b 00000011                                                         # bytes
-032-036: x 48020000                                                         # page start 584
-# column 5
-036-037: b 00000001                                                         # bool
-037-041: x ce020000                                                         # page start 718
+011-012: b 00000100                                                         # col 0: prefixbytes
+012-016: x 29000000                                                         # col 0: page start 41
+016-017: b 00000011                                                         # col 1: bytes
+017-021: x 25020000                                                         # col 1: page start 549
+021-022: b 00000010                                                         # col 2: uint
+022-026: x 26020000                                                         # col 2: page start 550
+026-027: b 00000001                                                         # col 3: bool
+027-031: x 2f020000                                                         # col 3: page start 559
+031-032: b 00000011                                                         # col 4: bytes
+032-036: x 48020000                                                         # col 4: page start 584
+036-037: b 00000001                                                         # col 5: bool
+037-041: x ce020000                                                         # col 5: page start 718
 # data for column 0
 # PrefixBytes
 041-042: x 02                                                               # bundleSize: 4
diff --git a/sstable/colblk/testdata/data_block/external_value b/sstable/colblk/testdata/data_block/external_value
index 9b9a36f7..861dfbb1 100644
--- a/sstable/colblk/testdata/data_block/external_value
+++ b/sstable/colblk/testdata/data_block/external_value
@@ -46,24 +46,18 @@ finish
 004-005: x 01                                                               # version 1
 005-007: x 0600                                                             # 6 columns
 007-011: x 14000000                                                         # 20 rows
-# column 0
-011-012: b 00000100                                                         # prefixbytes
-012-016: x 29000000                                                         # page start 41
-# column 1
-016-017: b 00000011                                                         # bytes
-017-021: x 6e000000                                                         # page start 110
-# column 2
-021-022: b 00000010                                                         # uint
-022-026: x ba000000                                                         # page start 186
-# column 3
-026-027: b 00000001                                                         # bool
-027-031: x e4000000                                                         # page start 228
-# column 4
-031-032: b 00000011                                                         # bytes
-032-036: x f8000000                                                         # page start 248
-# column 5
-036-037: b 00000001                                                         # bool
-037-041: x 6f020000                                                         # page start 623
+011-012: b 00000100                                                         # col 0: prefixbytes
+012-016: x 29000000                                                         # col 0: page start 41
+016-017: b 00000011                                                         # col 1: bytes
+017-021: x 6e000000                                                         # col 1: page start 110
+021-022: b 00000010                                                         # col 2: uint
+022-026: x ba000000                                                         # col 2: page start 186
+026-027: b 00000001                                                         # col 3: bool
+027-031: x e4000000                                                         # col 3: page start 228
+031-032: b 00000011                                                         # col 4: bytes
+032-036: x f8000000                                                         # col 4: page start 248
+036-037: b 00000001                                                         # col 5: bool
+037-041: x 6f020000                                                         # col 5: page start 623
 # data for column 0
 # PrefixBytes
 041-042: x 04                                                               # bundleSize: 16
diff --git a/sstable/colblk/testdata/data_block/next_prefix b/sstable/colblk/testdata/data_block/next_prefix
index b1efbcb0..4e2a7ba7 100644
--- a/sstable/colblk/testdata/data_block/next_prefix
+++ b/sstable/colblk/testdata/data_block/next_prefix
@@ -46,24 +46,18 @@ finish
 004-005: x 01                                                               # version 1
 005-007: x 0600                                                             # 6 columns
 007-011: x 14000000                                                         # 20 rows
-# column 0
-011-012: b 00000100                                                         # prefixbytes
-012-016: x 29000000                                                         # page start 41
-# column 1
-016-017: b 00000011                                                         # bytes
-017-021: x 6e000000                                                         # page start 110
-# column 2
-021-022: b 00000010                                                         # uint
-022-026: x ba000000                                                         # page start 186
-# column 3
-026-027: b 00000001                                                         # bool
-027-031: x e4000000                                                         # page start 228
-# column 4
-031-032: b 00000011                                                         # bytes
-032-036: x f8000000                                                         # page start 248
-# column 5
-036-037: b 00000001                                                         # bool
-037-041: x 96010000                                                         # page start 406
+011-012: b 00000100                                                         # col 0: prefixbytes
+012-016: x 29000000                                                         # col 0: page start 41
+016-017: b 00000011                                                         # col 1: bytes
+017-021: x 6e000000                                                         # col 1: page start 110
+021-022: b 00000010                                                         # col 2: uint
+022-026: x ba000000                                                         # col 2: page start 186
+026-027: b 00000001                                                         # col 3: bool
+027-031: x e4000000                                                         # col 3: page start 228
+031-032: b 00000011                                                         # col 4: bytes
+032-036: x f8000000                                                         # col 4: page start 248
+036-037: b 00000001                                                         # col 5: bool
+037-041: x 96010000                                                         # col 5: page start 406
 # data for column 0
 # PrefixBytes
 041-042: x 04                                                               # bundleSize: 16
diff --git a/sstable/colblk/testdata/data_block/simple b/sstable/colblk/testdata/data_block/simple
index c3594142..7b35202c 100644
--- a/sstable/colblk/testdata/data_block/simple
+++ b/sstable/colblk/testdata/data_block/simple
@@ -43,24 +43,18 @@ finish
 004-005: x 01                                                               # version 1
 005-007: x 0600                                                             # 6 columns
 007-011: x 07000000                                                         # 7 rows
-# column 0
-011-012: b 00000100                                                         # prefixbytes
-012-016: x 29000000                                                         # page start 41
-# column 1
-016-017: b 00000011                                                         # bytes
-017-021: x 38000000                                                         # page start 56
-# column 2
-021-022: b 00000010                                                         # uint
-022-026: x 51000000                                                         # page start 81
-# column 3
-026-027: b 00000001                                                         # bool
-027-031: x 59000000                                                         # page start 89
-# column 4
-031-032: b 00000011                                                         # bytes
-032-036: x 70000000                                                         # page start 112
-# column 5
-036-037: b 00000001                                                         # bool
-037-041: x a7000000                                                         # page start 167
+011-012: b 00000100                                                         # col 0: prefixbytes
+012-016: x 29000000                                                         # col 0: page start 41
+016-017: b 00000011                                                         # col 1: bytes
+017-021: x 38000000                                                         # col 1: page start 56
+021-022: b 00000010                                                         # col 2: uint
+022-026: x 51000000                                                         # col 2: page start 81
+026-027: b 00000001                                                         # col 3: bool
+027-031: x 59000000                                                         # col 3: page start 89
+031-032: b 00000011                                                         # col 4: bytes
+032-036: x 70000000                                                         # col 4: page start 112
+036-037: b 00000001                                                         # col 5: bool
+037-041: x a7000000                                                         # col 5: page start 167
 # data for column 0
 # PrefixBytes
 041-042: x 04                                                               # bundleSize: 16
@@ -341,24 +335,18 @@ finish
 004-005: x 01                                                               # version 1
 005-007: x 0600                                                             # 6 columns
 007-011: x 11000000                                                         # 17 rows
-# column 0
-011-012: b 00000100                                                         # prefixbytes
-012-016: x 29000000                                                         # page start 41
-# column 1
-016-017: b 00000011                                                         # bytes
-017-021: x c3000000                                                         # page start 195
-# column 2
-021-022: b 00000010                                                         # uint
-022-026: x 09010000                                                         # page start 265
-# column 3
-026-027: b 00000001                                                         # bool
-027-031: x 12010000                                                         # page start 274
-# column 4
-031-032: b 00000011                                                         # bytes
-032-036: x 28010000                                                         # page start 296
-# column 5
-036-037: b 00000001                                                         # bool
-037-041: x 4c010000                                                         # page start 332
+011-012: b 00000100                                                         # col 0: prefixbytes
+012-016: x 29000000                                                         # col 0: page start 41
+016-017: b 00000011                                                         # col 1: bytes
+017-021: x c3000000                                                         # col 1: page start 195
+021-022: b 00000010                                                         # col 2: uint
+022-026: x 09010000                                                         # col 2: page start 265
+026-027: b 00000001                                                         # col 3: bool
+027-031: x 12010000                                                         # col 3: page start 274
+031-032: b 00000011                                                         # col 4: bytes
+032-036: x 28010000                                                         # col 4: page start 296
+036-037: b 00000001                                                         # col 5: bool
+037-041: x 4c010000                                                         # col 5: page start 332
 # data for column 0
 # PrefixBytes
 041-042: x 04                                                               # bundleSize: 16
diff --git a/sstable/colblk/testdata/index_block b/sstable/colblk/testdata/index_block
index c7bf9b0e..0f1a3ed6 100644
--- a/sstable/colblk/testdata/index_block
+++ b/sstable/colblk/testdata/index_block
@@ -6,23 +6,20 @@ bacitracin 412  212
 banana     632  215    bp5
 bonifide   963  326    bp6
 ----
+UnsafeSeparator(Rows()-1) = "bonifide"
 # index block header
 # columnar block header
 000-001: x 01                   # version 1
 001-003: x 0400                 # 4 columns
 003-007: x 06000000             # 6 rows
-# column 0
-007-008: b 00000011             # bytes
-008-012: x 1b000000             # page start 27
-# column 1
-012-013: b 00000010             # uint
-013-017: x 4c000000             # page start 76
-# column 2
-017-018: b 00000010             # uint
-018-022: x 5a000000             # page start 90
-# column 3
-022-023: b 00000011             # bytes
-023-027: x 68000000             # page start 104
+007-008: b 00000011             # col 0: bytes
+008-012: x 1b000000             # col 0: page start 27
+012-013: b 00000010             # col 1: uint
+013-017: x 4c000000             # col 1: page start 76
+017-018: b 00000010             # col 2: uint
+018-022: x 5a000000             # col 2: page start 90
+022-023: b 00000011             # col 3: bytes
+023-027: x 68000000             # col 3: page start 104
 # data for column 0
 # rawbytes
 # offsets table
@@ -134,23 +131,20 @@ catatonic   102422  20442
 cephalopod  122864   9104   bp4
 coat        293128  32104
 ----
+UnsafeSeparator(Rows()-1) = "coat"
 # index block header
 # columnar block header
 000-001: x 01                   # version 1
 001-003: x 0400                 # 4 columns
 003-007: x 05000000             # 5 rows
-# column 0
-007-008: b 00000011             # bytes
-008-012: x 1b000000             # page start 27
-# column 1
-012-013: b 00000010             # uint
-013-017: x 47000000             # page start 71
-# column 2
-017-018: b 00000010             # uint
-018-022: x 5c000000             # page start 92
-# column 3
-022-023: b 00000011             # bytes
-023-027: x 68000000             # page start 104
+007-008: b 00000011             # col 0: bytes
+008-012: x 1b000000             # col 0: page start 27
+012-013: b 00000010             # col 1: uint
+013-017: x 47000000             # col 1: page start 71
+017-018: b 00000010             # col 2: uint
+018-022: x 5c000000             # col 2: page start 92
+022-023: b 00000011             # col 3: bytes
+023-027: x 68000000             # col 3: page start 104
 # data for column 0
 # rawbytes
 # offsets table
diff --git a/sstable/colblk/testdata/keyspan_block b/sstable/colblk/testdata/keyspan_block
index 5311c7e5..83cf7281 100644
--- a/sstable/colblk/testdata/keyspan_block
+++ b/sstable/colblk/testdata/keyspan_block
@@ -49,27 +49,23 @@ size=73:
 
 finish
 ----
+Boundaries: a#0,RANGEDEL — e#inf,RANGEDEL
 # keyspan block header
 00-04: x 05000000 # user key count: 5
 # columnar block header
 04-05: x 01       # version 1
 05-07: x 0500     # 5 columns
 07-11: x 07000000 # 7 rows
-# column 0
-11-12: b 00000011 # bytes
-12-16: x 24000000 # page start 36
-# column 1
-16-17: b 00000010 # uint
-17-21: x 30000000 # page start 48
-# column 2
-21-22: b 00000010 # uint
-22-26: x 36000000 # page start 54
-# column 3
-26-27: b 00000011 # bytes
-27-31: x 46000000 # page start 70
-# column 4
-31-32: b 00000011 # bytes
-32-36: x 47000000 # page start 71
+11-12: b 00000011 # col 0: bytes
+12-16: x 24000000 # col 0: page start 36
+16-17: b 00000010 # col 1: uint
+17-21: x 30000000 # col 1: page start 48
+21-22: b 00000010 # col 2: uint
+22-26: x 36000000 # col 2: page start 54
+26-27: b 00000011 # col 3: bytes
+27-31: x 46000000 # col 3: page start 70
+31-32: b 00000011 # col 4: bytes
+32-36: x 47000000 # col 4: page start 71
 # data for column 0
 # rawbytes
 # offsets table
@@ -231,27 +227,23 @@ size=70:
 
 finish
 ----
+Boundaries: b#4,RANGEKEYSET — d#inf,RANGEKEYSET
 # keyspan block header
 00-04: x 02000000         # user key count: 2
 # columnar block header
 04-05: x 01               # version 1
 05-07: x 0500             # 5 columns
 07-11: x 01000000         # 1 rows
-# column 0
-11-12: b 00000011         # bytes
-12-16: x 24000000         # page start 36
-# column 1
-16-17: b 00000010         # uint
-17-21: x 2a000000         # page start 42
-# column 2
-21-22: b 00000010         # uint
-22-26: x 2d000000         # page start 45
-# column 3
-26-27: b 00000011         # bytes
-27-31: x 36000000         # page start 54
-# column 4
-31-32: b 00000011         # bytes
-32-36: x 3b000000         # page start 59
+11-12: b 00000011         # col 0: bytes
+12-16: x 24000000         # col 0: page start 36
+16-17: b 00000010         # col 1: uint
+17-21: x 2a000000         # col 1: page start 42
+21-22: b 00000010         # col 2: uint
+22-26: x 2d000000         # col 2: page start 45
+26-27: b 00000011         # col 3: bytes
+27-31: x 36000000         # col 3: page start 54
+31-32: b 00000011         # col 4: bytes
+32-36: x 3b000000         # col 4: page start 59
 # data for column 0
 # rawbytes
 # offsets table
@@ -321,27 +313,23 @@ size=80:
 
 finish
 ----
+Boundaries: b#4,RANGEKEYSET — g#inf,RANGEKEYSET
 # keyspan block header
 00-04: x 04000000       # user key count: 4
 # columnar block header
 04-05: x 01             # version 1
 05-07: x 0500           # 5 columns
 07-11: x 02000000       # 2 rows
-# column 0
-11-12: b 00000011       # bytes
-12-16: x 24000000       # page start 36
-# column 1
-16-17: b 00000010       # uint
-17-21: x 2e000000       # page start 46
-# column 2
-21-22: b 00000010       # uint
-22-26: x 33000000       # page start 51
-# column 3
-26-27: b 00000011       # bytes
-27-31: x 38000000       # page start 56
-# column 4
-31-32: b 00000011       # bytes
-32-36: x 40000000       # page start 64
+11-12: b 00000011       # col 0: bytes
+12-16: x 24000000       # col 0: page start 36
+16-17: b 00000010       # col 1: uint
+17-21: x 2e000000       # col 1: page start 46
+21-22: b 00000010       # col 2: uint
+22-26: x 33000000       # col 2: page start 51
+26-27: b 00000011       # col 3: bytes
+27-31: x 38000000       # col 3: page start 56
+31-32: b 00000011       # col 4: bytes
+32-36: x 40000000       # col 4: page start 64
 # data for column 0
 # rawbytes
 # offsets table