github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/sstable/block_property_test.go (about)

     1  // Copyright 2021 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package sstable
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"fmt"
    11  	"io"
    12  	"math"
    13  	"math/rand"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  	"testing"
    18  
    19  	"github.com/cockroachdb/datadriven"
    20  	"github.com/cockroachdb/errors"
    21  	"github.com/cockroachdb/pebble/internal/base"
    22  	"github.com/cockroachdb/pebble/internal/rangekey"
    23  	"github.com/cockroachdb/pebble/internal/testkeys"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  func TestIntervalEncodeDecode(t *testing.T) {
    28  	testCases := []struct {
    29  		name  string
    30  		lower uint64
    31  		upper uint64
    32  		len   int
    33  	}{
    34  		{
    35  			name:  "empty zero",
    36  			lower: 0,
    37  			upper: 0,
    38  			len:   0,
    39  		},
    40  		{
    41  			name:  "empty non-zero",
    42  			lower: 5,
    43  			upper: 5,
    44  			len:   0,
    45  		},
    46  		{
    47  			name:  "empty lower > upper",
    48  			lower: math.MaxUint64,
    49  			upper: math.MaxUint64 - 1,
    50  			len:   0,
    51  		},
    52  		{
    53  			name:  "small",
    54  			lower: 50,
    55  			upper: 61,
    56  			len:   2,
    57  		},
    58  		{
    59  			name:  "big",
    60  			lower: 0,
    61  			upper: math.MaxUint64,
    62  			len:   11,
    63  		},
    64  	}
    65  	for _, tc := range testCases {
    66  		buf := make([]byte, 100)
    67  		t.Run(tc.name, func(t *testing.T) {
    68  			i1 := interval{lower: tc.lower, upper: tc.upper}
    69  			b1 := i1.encode(nil)
    70  			b2 := i1.encode(buf[:0])
    71  			require.True(t, bytes.Equal(b1, b2), "%x != %x", b1, b2)
    72  			expectedInterval := i1
    73  			if expectedInterval.lower >= expectedInterval.upper {
    74  				expectedInterval = interval{}
    75  			}
    76  			// Arbitrary initial value.
    77  			arbitraryInterval := interval{lower: 1000, upper: 1000}
    78  			i2 := arbitraryInterval
    79  			i2.decode(b1)
    80  			require.Equal(t, expectedInterval, i2)
    81  			i2 = arbitraryInterval
    82  			i2.decode(b2)
    83  			require.Equal(t, expectedInterval, i2)
    84  			require.Equal(t, tc.len, len(b1))
    85  		})
    86  	}
    87  }
    88  
    89  func TestIntervalUnionIntersects(t *testing.T) {
    90  	testCases := []struct {
    91  		name       string
    92  		i1         interval
    93  		i2         interval
    94  		union      interval
    95  		intersects bool
    96  	}{
    97  		{
    98  			name:       "empty and empty",
    99  			i1:         interval{},
   100  			i2:         interval{},
   101  			union:      interval{},
   102  			intersects: false,
   103  		},
   104  		{
   105  			name:       "empty and empty non-zero",
   106  			i1:         interval{},
   107  			i2:         interval{100, 99},
   108  			union:      interval{},
   109  			intersects: false,
   110  		},
   111  		{
   112  			name:       "empty and non-empty",
   113  			i1:         interval{},
   114  			i2:         interval{80, 100},
   115  			union:      interval{80, 100},
   116  			intersects: false,
   117  		},
   118  		{
   119  			name:       "disjoint sets",
   120  			i1:         interval{50, 60},
   121  			i2:         interval{math.MaxUint64 - 5, math.MaxUint64},
   122  			union:      interval{50, math.MaxUint64},
   123  			intersects: false,
   124  		},
   125  		{
   126  			name:       "adjacent sets",
   127  			i1:         interval{50, 60},
   128  			i2:         interval{60, 100},
   129  			union:      interval{50, 100},
   130  			intersects: false,
   131  		},
   132  		{
   133  			name:       "overlapping sets",
   134  			i1:         interval{50, 60},
   135  			i2:         interval{59, 120},
   136  			union:      interval{50, 120},
   137  			intersects: true,
   138  		},
   139  	}
   140  	isEmpty := func(i interval) bool {
   141  		return i.lower >= i.upper
   142  	}
   143  	// adjustUnionExpectation exists because union does not try to
   144  	// canonicalize empty sets by turning them into [0, 0), since it is
   145  	// unnecessary -- the higher level context of the BlockIntervalCollector
   146  	// will do so when calling interval.encode.
   147  	adjustUnionExpectation := func(expected interval, i1 interval, i2 interval) interval {
   148  		if isEmpty(i2) {
   149  			return i1
   150  		}
   151  		if isEmpty(i1) {
   152  			return i2
   153  		}
   154  		return expected
   155  	}
   156  	for _, tc := range testCases {
   157  		t.Run(tc.name, func(t *testing.T) {
   158  			require.Equal(t, tc.intersects, tc.i1.intersects(tc.i2))
   159  			require.Equal(t, tc.intersects, tc.i2.intersects(tc.i1))
   160  			require.Equal(t, !isEmpty(tc.i1), tc.i1.intersects(tc.i1))
   161  			require.Equal(t, !isEmpty(tc.i2), tc.i2.intersects(tc.i2))
   162  			union := tc.i1
   163  			union.union(tc.i2)
   164  			require.Equal(t, adjustUnionExpectation(tc.union, tc.i1, tc.i2), union)
   165  			union = tc.i2
   166  			union.union(tc.i1)
   167  			require.Equal(t, adjustUnionExpectation(tc.union, tc.i2, tc.i1), union)
   168  		})
   169  	}
   170  }
   171  
   172  type testDataBlockIntervalCollector struct {
   173  	i interval
   174  }
   175  
   176  func (c *testDataBlockIntervalCollector) Add(key InternalKey, value []byte) error {
   177  	return nil
   178  }
   179  
   180  func (c *testDataBlockIntervalCollector) FinishDataBlock() (lower uint64, upper uint64, err error) {
   181  	return c.i.lower, c.i.upper, nil
   182  }
   183  
   184  func TestBlockIntervalCollector(t *testing.T) {
   185  	var points, ranges testDataBlockIntervalCollector
   186  	bic := NewBlockIntervalCollector("foo", &points, &ranges)
   187  	require.Equal(t, "foo", bic.Name())
   188  	// Set up the point key collector with an initial (empty) interval.
   189  	points.i = interval{1, 1}
   190  	// First data block has empty point key interval.
   191  	encoded, err := bic.FinishDataBlock(nil)
   192  	require.NoError(t, err)
   193  	require.True(t, bytes.Equal(nil, encoded))
   194  	bic.AddPrevDataBlockToIndexBlock()
   195  	// Second data block contains a point and range key interval. The latter
   196  	// should not contribute to the block interval.
   197  	points.i = interval{20, 25}
   198  	ranges.i = interval{5, 150}
   199  	encoded, err = bic.FinishDataBlock(nil)
   200  	require.NoError(t, err)
   201  	var decoded interval
   202  	require.NoError(t, decoded.decode(encoded))
   203  	require.Equal(t, interval{20, 25}, decoded)
   204  	var encodedIndexBlock []byte
   205  	// Finish index block before including second data block.
   206  	encodedIndexBlock, err = bic.FinishIndexBlock(nil)
   207  	require.NoError(t, err)
   208  	require.True(t, bytes.Equal(nil, encodedIndexBlock))
   209  	bic.AddPrevDataBlockToIndexBlock()
   210  	// Third data block.
   211  	points.i = interval{10, 15}
   212  	encoded, err = bic.FinishDataBlock(nil)
   213  	require.NoError(t, err)
   214  	require.NoError(t, decoded.decode(encoded))
   215  	require.Equal(t, interval{10, 15}, decoded)
   216  	bic.AddPrevDataBlockToIndexBlock()
   217  	// Fourth data block.
   218  	points.i = interval{100, 105}
   219  	encoded, err = bic.FinishDataBlock(nil)
   220  	require.NoError(t, err)
   221  	require.NoError(t, decoded.decode(encoded))
   222  	require.Equal(t, interval{100, 105}, decoded)
   223  	// Finish index block before including fourth data block.
   224  	encodedIndexBlock, err = bic.FinishIndexBlock(nil)
   225  	require.NoError(t, err)
   226  	require.NoError(t, decoded.decode(encodedIndexBlock))
   227  	require.Equal(t, interval{10, 25}, decoded)
   228  	bic.AddPrevDataBlockToIndexBlock()
   229  	// Finish index block that contains only fourth data block.
   230  	encodedIndexBlock, err = bic.FinishIndexBlock(nil)
   231  	require.NoError(t, err)
   232  	require.NoError(t, decoded.decode(encodedIndexBlock))
   233  	require.Equal(t, interval{100, 105}, decoded)
   234  	var encodedTable []byte
   235  	// Finish table. The table interval is the union of the current point key
   236  	// table interval [10, 105) and the range key interval [5, 150).
   237  	encodedTable, err = bic.FinishTable(nil)
   238  	require.NoError(t, err)
   239  	require.NoError(t, decoded.decode(encodedTable))
   240  	require.Equal(t, interval{5, 150}, decoded)
   241  }
   242  
   243  func TestBlockIntervalFilter(t *testing.T) {
   244  	testCases := []struct {
   245  		name       string
   246  		filter     interval
   247  		prop       interval
   248  		intersects bool
   249  	}{
   250  		{
   251  			name:       "non-empty and empty",
   252  			filter:     interval{10, 15},
   253  			prop:       interval{},
   254  			intersects: false,
   255  		},
   256  		{
   257  			name:       "does not intersect",
   258  			filter:     interval{10, 15},
   259  			prop:       interval{15, 20},
   260  			intersects: false,
   261  		},
   262  		{
   263  			name:       "intersects",
   264  			filter:     interval{10, 15},
   265  			prop:       interval{14, 20},
   266  			intersects: true,
   267  		},
   268  	}
   269  	for _, tc := range testCases {
   270  		t.Run(tc.name, func(t *testing.T) {
   271  			var points testDataBlockIntervalCollector
   272  			name := "foo"
   273  			bic := NewBlockIntervalCollector(name, &points, nil)
   274  			bif := NewBlockIntervalFilter(name, tc.filter.lower, tc.filter.upper)
   275  			points.i = tc.prop
   276  			prop, _ := bic.FinishDataBlock(nil)
   277  			intersects, err := bif.Intersects(prop)
   278  			require.NoError(t, err)
   279  			require.Equal(t, tc.intersects, intersects)
   280  		})
   281  	}
   282  }
   283  
   284  func TestBlockPropertiesEncoderDecoder(t *testing.T) {
   285  	var encoder blockPropertiesEncoder
   286  	scratch := encoder.getScratchForProp()
   287  	scratch = append(scratch, []byte("foo")...)
   288  	encoder.addProp(1, scratch)
   289  	scratch = encoder.getScratchForProp()
   290  	require.LessOrEqual(t, 3, cap(scratch))
   291  	scratch = append(scratch, []byte("cockroach")...)
   292  	encoder.addProp(10, scratch)
   293  	props1 := encoder.props()
   294  	unsafeProps := encoder.unsafeProps()
   295  	require.True(t, bytes.Equal(props1, unsafeProps), "%x != %x", props1, unsafeProps)
   296  	decodeProps1 := func() {
   297  		decoder := blockPropertiesDecoder{props: props1}
   298  		require.False(t, decoder.done())
   299  		id, prop, err := decoder.next()
   300  		require.NoError(t, err)
   301  		require.Equal(t, shortID(1), id)
   302  		require.Equal(t, string(prop), "foo")
   303  		require.False(t, decoder.done())
   304  		id, prop, err = decoder.next()
   305  		require.NoError(t, err)
   306  		require.Equal(t, shortID(10), id)
   307  		require.Equal(t, string(prop), "cockroach")
   308  		require.True(t, decoder.done())
   309  	}
   310  	decodeProps1()
   311  
   312  	encoder.resetProps()
   313  	scratch = encoder.getScratchForProp()
   314  	require.LessOrEqual(t, 9, cap(scratch))
   315  	scratch = append(scratch, []byte("bar")...)
   316  	encoder.addProp(10, scratch)
   317  	props2 := encoder.props()
   318  	unsafeProps = encoder.unsafeProps()
   319  	require.True(t, bytes.Equal(props2, unsafeProps), "%x != %x", props2, unsafeProps)
   320  	// Safe props should still decode.
   321  	decodeProps1()
   322  	// Decode props2
   323  	decoder := blockPropertiesDecoder{props: props2}
   324  	require.False(t, decoder.done())
   325  	id, prop, err := decoder.next()
   326  	require.NoError(t, err)
   327  	require.Equal(t, shortID(10), id)
   328  	require.Equal(t, string(prop), "bar")
   329  	require.True(t, decoder.done())
   330  }
   331  
   332  // filterWithTrueForEmptyProp is a wrapper for BlockPropertyFilter that
   333  // delegates to it except when the property is empty, in which case it returns
   334  // true.
   335  type filterWithTrueForEmptyProp struct {
   336  	BlockPropertyFilter
   337  }
   338  
   339  func (b filterWithTrueForEmptyProp) Intersects(prop []byte) (bool, error) {
   340  	if len(prop) == 0 {
   341  		return true, nil
   342  	}
   343  	return b.BlockPropertyFilter.Intersects(prop)
   344  }
   345  
   346  func TestBlockPropertiesFilterer_IntersectsUserPropsAndFinishInit(t *testing.T) {
   347  	// props with id=0, interval [10, 20); id=10, interval [110, 120).
   348  	var dbic testDataBlockIntervalCollector
   349  	bic0 := NewBlockIntervalCollector("p0", &dbic, nil)
   350  	bic0Id := byte(0)
   351  	bic10 := NewBlockIntervalCollector("p10", &dbic, nil)
   352  	bic10Id := byte(10)
   353  	dbic.i = interval{10, 20}
   354  	prop0 := append([]byte(nil), bic0Id)
   355  	_, err := bic0.FinishDataBlock(nil)
   356  	require.NoError(t, err)
   357  	prop0, err = bic0.FinishTable(prop0)
   358  	require.NoError(t, err)
   359  	dbic.i = interval{110, 120}
   360  	prop10 := append([]byte(nil), bic10Id)
   361  	_, err = bic10.FinishDataBlock(nil)
   362  	require.NoError(t, err)
   363  	prop10, err = bic10.FinishTable(prop10)
   364  	require.NoError(t, err)
   365  	prop0Str := string(prop0)
   366  	prop10Str := string(prop10)
   367  	type filter struct {
   368  		name string
   369  		i    interval
   370  	}
   371  	testCases := []struct {
   372  		name      string
   373  		userProps map[string]string
   374  		filters   []filter
   375  
   376  		// Expected results
   377  		intersects            bool
   378  		shortIDToFiltersIndex []int
   379  	}{
   380  		{
   381  			name:       "no filter, no props",
   382  			userProps:  map[string]string{},
   383  			filters:    nil,
   384  			intersects: true,
   385  		},
   386  		{
   387  			name:      "no props",
   388  			userProps: map[string]string{},
   389  			filters: []filter{
   390  				{name: "p0", i: interval{20, 30}},
   391  				{name: "p10", i: interval{20, 30}},
   392  			},
   393  			intersects: true,
   394  		},
   395  		{
   396  			name:      "prop0, does not intersect",
   397  			userProps: map[string]string{"p0": prop0Str},
   398  			filters: []filter{
   399  				{name: "p0", i: interval{20, 30}},
   400  				{name: "p10", i: interval{20, 30}},
   401  			},
   402  			intersects: false,
   403  		},
   404  		{
   405  			name:      "prop0, intersects",
   406  			userProps: map[string]string{"p0": prop0Str},
   407  			filters: []filter{
   408  				{name: "p0", i: interval{11, 21}},
   409  				{name: "p10", i: interval{20, 30}},
   410  			},
   411  			intersects:            true,
   412  			shortIDToFiltersIndex: []int{0},
   413  		},
   414  		{
   415  			name:      "prop10, does not intersect",
   416  			userProps: map[string]string{"p10": prop10Str},
   417  			filters: []filter{
   418  				{name: "p0", i: interval{11, 21}},
   419  				{name: "p10", i: interval{20, 30}},
   420  			},
   421  			intersects: false,
   422  		},
   423  		{
   424  			name:      "prop10, intersects",
   425  			userProps: map[string]string{"p10": prop10Str},
   426  			filters: []filter{
   427  				{name: "p0", i: interval{11, 21}},
   428  				{name: "p10", i: interval{115, 125}},
   429  			},
   430  			intersects:            true,
   431  			shortIDToFiltersIndex: []int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1},
   432  		},
   433  		{
   434  			name:      "prop10, intersects",
   435  			userProps: map[string]string{"p10": prop10Str},
   436  			filters: []filter{
   437  				{name: "p10", i: interval{115, 125}},
   438  				{name: "p0", i: interval{11, 21}},
   439  			},
   440  			intersects:            true,
   441  			shortIDToFiltersIndex: []int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0},
   442  		},
   443  		{
   444  			name:      "prop0 and prop10, does not intersect",
   445  			userProps: map[string]string{"p0": prop0Str, "p10": prop10Str},
   446  			filters: []filter{
   447  				{name: "p10", i: interval{115, 125}},
   448  				{name: "p0", i: interval{20, 30}},
   449  			},
   450  			intersects:            false,
   451  			shortIDToFiltersIndex: []int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0},
   452  		},
   453  		{
   454  			name:      "prop0 and prop10, does not intersect",
   455  			userProps: map[string]string{"p0": prop0Str, "p10": prop10Str},
   456  			filters: []filter{
   457  				{name: "p0", i: interval{10, 20}},
   458  				{name: "p10", i: interval{125, 135}},
   459  			},
   460  			intersects:            false,
   461  			shortIDToFiltersIndex: []int{0},
   462  		},
   463  		{
   464  			name:      "prop0 and prop10, intersects",
   465  			userProps: map[string]string{"p0": prop0Str, "p10": prop10Str},
   466  			filters: []filter{
   467  				{name: "p10", i: interval{115, 125}},
   468  				{name: "p0", i: interval{10, 20}},
   469  			},
   470  			intersects:            true,
   471  			shortIDToFiltersIndex: []int{1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0},
   472  		},
   473  	}
   474  	for _, tc := range testCases {
   475  		t.Run(tc.name, func(t *testing.T) {
   476  			var filters []BlockPropertyFilter
   477  			for _, f := range tc.filters {
   478  				filter := NewBlockIntervalFilter(f.name, f.i.lower, f.i.upper)
   479  				filters = append(filters, filter)
   480  			}
   481  			filterer := newBlockPropertiesFilterer(filters, nil)
   482  			intersects, err := filterer.intersectsUserPropsAndFinishInit(tc.userProps)
   483  			require.NoError(t, err)
   484  			require.Equal(t, tc.intersects, intersects)
   485  			require.Equal(t, tc.shortIDToFiltersIndex, filterer.shortIDToFiltersIndex)
   486  		})
   487  	}
   488  }
   489  
   490  func TestBlockPropertiesFilterer_Intersects(t *testing.T) {
   491  	// Setup two different properties values to filter against.
   492  	var emptyProps []byte
   493  	// props with id=0, interval [10, 20); id=10, interval [110, 120).
   494  	var encoder blockPropertiesEncoder
   495  	var dbic testDataBlockIntervalCollector
   496  	bic0 := NewBlockIntervalCollector("", &dbic, nil)
   497  	bic0Id := shortID(0)
   498  	bic10 := NewBlockIntervalCollector("", &dbic, nil)
   499  	bic10Id := shortID(10)
   500  	dbic.i = interval{10, 20}
   501  	prop, err := bic0.FinishDataBlock(encoder.getScratchForProp())
   502  	require.NoError(t, err)
   503  	encoder.addProp(bic0Id, prop)
   504  	dbic.i = interval{110, 120}
   505  	prop, err = bic10.FinishDataBlock(encoder.getScratchForProp())
   506  	require.NoError(t, err)
   507  	encoder.addProp(bic10Id, prop)
   508  	props0And10 := encoder.props()
   509  	type filter struct {
   510  		shortID                shortID
   511  		i                      interval
   512  		intersectsForEmptyProp bool
   513  	}
   514  	testCases := []struct {
   515  		name  string
   516  		props []byte
   517  		// filters must be in ascending order of shortID.
   518  		filters    []filter
   519  		intersects bool
   520  	}{
   521  		{
   522  			name:       "no filter, empty props",
   523  			props:      emptyProps,
   524  			intersects: true,
   525  		},
   526  		{
   527  			name:       "no filter",
   528  			props:      props0And10,
   529  			intersects: true,
   530  		},
   531  		{
   532  			name:  "filter 0, empty props, does not intersect",
   533  			props: emptyProps,
   534  			filters: []filter{
   535  				{
   536  					shortID: 0,
   537  					i:       interval{5, 15},
   538  				},
   539  			},
   540  			intersects: false,
   541  		},
   542  		{
   543  			name:  "filter 10, empty props, does not intersect",
   544  			props: emptyProps,
   545  			filters: []filter{
   546  				{
   547  					shortID: 0,
   548  					i:       interval{105, 111},
   549  				},
   550  			},
   551  			intersects: false,
   552  		},
   553  		{
   554  			name:  "filter 0, intersects",
   555  			props: props0And10,
   556  			filters: []filter{
   557  				{
   558  					shortID: 0,
   559  					i:       interval{5, 15},
   560  				},
   561  			},
   562  			intersects: true,
   563  		},
   564  		{
   565  			name:  "filter 0, does not intersect",
   566  			props: props0And10,
   567  			filters: []filter{
   568  				{
   569  					shortID: 0,
   570  					i:       interval{20, 25},
   571  				},
   572  			},
   573  			intersects: false,
   574  		},
   575  		{
   576  			name:  "filter 10, intersects",
   577  			props: props0And10,
   578  			filters: []filter{
   579  				{
   580  					shortID: 10,
   581  					i:       interval{105, 111},
   582  				},
   583  			},
   584  			intersects: true,
   585  		},
   586  		{
   587  			name:  "filter 10, does not intersect",
   588  			props: props0And10,
   589  			filters: []filter{
   590  				{
   591  					shortID: 10,
   592  					i:       interval{105, 110},
   593  				},
   594  			},
   595  			intersects: false,
   596  		},
   597  		{
   598  			name:  "filter 5, does not intersect since no property",
   599  			props: props0And10,
   600  			filters: []filter{
   601  				{
   602  					shortID: 5,
   603  					i:       interval{105, 110},
   604  				},
   605  			},
   606  			intersects: false,
   607  		},
   608  		{
   609  			name:  "filter 0 and 5, intersects and not intersects means overall not intersects",
   610  			props: props0And10,
   611  			filters: []filter{
   612  				{
   613  					shortID: 0,
   614  					i:       interval{5, 15},
   615  				},
   616  				{
   617  					shortID: 5,
   618  					i:       interval{105, 110},
   619  				},
   620  			},
   621  			intersects: false,
   622  		},
   623  		{
   624  			name:  "filter 0, 5, 7, 11, all intersect",
   625  			props: props0And10,
   626  			filters: []filter{
   627  				{
   628  					shortID: 0,
   629  					i:       interval{5, 15},
   630  				},
   631  				{
   632  					shortID:                5,
   633  					i:                      interval{105, 110},
   634  					intersectsForEmptyProp: true,
   635  				},
   636  				{
   637  					shortID:                7,
   638  					i:                      interval{105, 110},
   639  					intersectsForEmptyProp: true,
   640  				},
   641  				{
   642  					shortID:                11,
   643  					i:                      interval{105, 110},
   644  					intersectsForEmptyProp: true,
   645  				},
   646  			},
   647  			intersects: true,
   648  		},
   649  		{
   650  			name:  "filter 0, 5, 7, 10, 11, all intersect",
   651  			props: props0And10,
   652  			filters: []filter{
   653  				{
   654  					shortID: 0,
   655  					i:       interval{5, 15},
   656  				},
   657  				{
   658  					shortID:                5,
   659  					i:                      interval{105, 110},
   660  					intersectsForEmptyProp: true,
   661  				},
   662  				{
   663  					shortID:                7,
   664  					i:                      interval{105, 110},
   665  					intersectsForEmptyProp: true,
   666  				},
   667  				{
   668  					shortID: 10,
   669  					i:       interval{105, 111},
   670  				},
   671  				{
   672  					shortID:                11,
   673  					i:                      interval{105, 110},
   674  					intersectsForEmptyProp: true,
   675  				},
   676  			},
   677  			intersects: true,
   678  		},
   679  		{
   680  			name:  "filter 0, 5, 7, 10, 11, all intersect except for 10",
   681  			props: props0And10,
   682  			filters: []filter{
   683  				{
   684  					shortID: 0,
   685  					i:       interval{5, 15},
   686  				},
   687  				{
   688  					shortID:                5,
   689  					i:                      interval{105, 110},
   690  					intersectsForEmptyProp: true,
   691  				},
   692  				{
   693  					shortID:                7,
   694  					i:                      interval{105, 110},
   695  					intersectsForEmptyProp: true,
   696  				},
   697  				{
   698  					shortID: 10,
   699  					i:       interval{105, 110},
   700  				},
   701  				{
   702  					shortID:                11,
   703  					i:                      interval{105, 110},
   704  					intersectsForEmptyProp: true,
   705  				},
   706  			},
   707  			intersects: false,
   708  		},
   709  	}
   710  
   711  	for _, tc := range testCases {
   712  		t.Run(tc.name, func(t *testing.T) {
   713  			var filters []BlockPropertyFilter
   714  			var shortIDToFiltersIndex []int
   715  			if len(tc.filters) > 0 {
   716  				shortIDToFiltersIndex = make([]int, tc.filters[len(tc.filters)-1].shortID+1)
   717  				for i := range shortIDToFiltersIndex {
   718  					shortIDToFiltersIndex[i] = -1
   719  				}
   720  			}
   721  			for _, f := range tc.filters {
   722  				filter := NewBlockIntervalFilter("", f.i.lower, f.i.upper)
   723  				bpf := BlockPropertyFilter(filter)
   724  				if f.intersectsForEmptyProp {
   725  					bpf = filterWithTrueForEmptyProp{filter}
   726  				}
   727  				shortIDToFiltersIndex[f.shortID] = len(filters)
   728  				filters = append(filters, bpf)
   729  			}
   730  			doFiltering := func() {
   731  				bpFilterer := BlockPropertiesFilterer{
   732  					filters:               filters,
   733  					shortIDToFiltersIndex: shortIDToFiltersIndex,
   734  					boundLimitedShortID:   -1,
   735  				}
   736  				intersects, err := bpFilterer.intersects(tc.props)
   737  				require.NoError(t, err)
   738  				require.Equal(t, tc.intersects, intersects == blockIntersects)
   739  			}
   740  			doFiltering()
   741  			if len(filters) > 1 {
   742  				// Permute the filters so that the use of
   743  				// shortIDToFiltersIndex is better tested.
   744  				permutation := rand.Perm(len(filters))
   745  				filterPerm := make([]BlockPropertyFilter, len(filters))
   746  				for i := range permutation {
   747  					filterPerm[i] = filters[permutation[i]]
   748  					shortIDToFiltersIndex[tc.filters[permutation[i]].shortID] = i
   749  				}
   750  				filters = filterPerm
   751  				doFiltering()
   752  			}
   753  		})
   754  	}
   755  }
   756  
   757  // valueCharBlockIntervalCollector implements DataBlockIntervalCollector by
   758  // maintaining the (inclusive) lower and (exclusive) upper bound of a fixed
   759  // character position in the value, when represented as an integer.
   760  type valueCharBlockIntervalCollector struct {
   761  	charIdx      int
   762  	initialized  bool
   763  	lower, upper uint64
   764  }
   765  
   766  var _ DataBlockIntervalCollector = &valueCharBlockIntervalCollector{}
   767  
   768  // Add implements DataBlockIntervalCollector by maintaining the lower and upper
   769  // bound of a fixed character position in the value.
   770  func (c *valueCharBlockIntervalCollector) Add(_ InternalKey, value []byte) error {
   771  	charIdx := c.charIdx
   772  	if charIdx == -1 {
   773  		charIdx = len(value) - 1
   774  	}
   775  	val, err := strconv.Atoi(string(value[charIdx]))
   776  	if err != nil {
   777  		return err
   778  	}
   779  	uval := uint64(val)
   780  	if !c.initialized {
   781  		c.lower, c.upper = uval, uval+1
   782  		c.initialized = true
   783  		return nil
   784  	}
   785  	if uval < c.lower {
   786  		c.lower = uval
   787  	}
   788  	if uval >= c.upper {
   789  		c.upper = uval + 1
   790  	}
   791  
   792  	return nil
   793  }
   794  
   795  // Finish implements DataBlockIntervalCollector, returning the lower and upper
   796  // bound for the block. The range is reset to zero in anticipation of the next
   797  // block.
   798  func (c *valueCharBlockIntervalCollector) FinishDataBlock() (lower, upper uint64, err error) {
   799  	l, u := c.lower, c.upper
   800  	c.lower, c.upper = 0, 0
   801  	c.initialized = false
   802  	return l, u, nil
   803  }
   804  
   805  // testKeysSuffixIntervalCollector maintains an interval over the timestamps in
   806  // MVCC-like suffixes for keys (e.g. foo@123).
   807  type suffixIntervalCollector struct {
   808  	initialized  bool
   809  	lower, upper uint64
   810  }
   811  
   812  // Add implements DataBlockIntervalCollector by adding the timestamp(s) in the
   813  // suffix(es) of this record to the current interval.
   814  //
   815  // Note that range sets and unsets may have multiple suffixes. Range key deletes
   816  // do not have a suffix. All other point keys have a single suffix.
   817  func (c *suffixIntervalCollector) Add(key InternalKey, value []byte) error {
   818  	var bs [][]byte
   819  	// Range keys have their suffixes encoded into the value.
   820  	if rangekey.IsRangeKey(key.Kind()) {
   821  		if key.Kind() == base.InternalKeyKindRangeKeyDelete {
   822  			return nil
   823  		}
   824  		s, err := rangekey.Decode(key, value, nil)
   825  		if err != nil {
   826  			return err
   827  		}
   828  		for _, k := range s.Keys {
   829  			if len(k.Suffix) > 0 {
   830  				bs = append(bs, k.Suffix)
   831  			}
   832  		}
   833  	} else {
   834  		// All other keys have a single suffix encoded into the value.
   835  		bs = append(bs, key.UserKey)
   836  	}
   837  
   838  	for _, b := range bs {
   839  		i := testkeys.Comparer.Split(b)
   840  		ts, err := strconv.Atoi(string(b[i+1:]))
   841  		if err != nil {
   842  			return err
   843  		}
   844  		uts := uint64(ts)
   845  		if !c.initialized {
   846  			c.lower, c.upper = uts, uts+1
   847  			c.initialized = true
   848  			continue
   849  		}
   850  		if uts < c.lower {
   851  			c.lower = uts
   852  		}
   853  		if uts >= c.upper {
   854  			c.upper = uts + 1
   855  		}
   856  	}
   857  	return nil
   858  }
   859  
   860  // FinishDataBlock implements DataBlockIntervalCollector.
   861  func (c *suffixIntervalCollector) FinishDataBlock() (lower, upper uint64, err error) {
   862  	l, u := c.lower, c.upper
   863  	c.lower, c.upper = 0, 0
   864  	c.initialized = false
   865  	return l, u, nil
   866  }
   867  
   868  func TestBlockProperties(t *testing.T) {
   869  	var r *Reader
   870  	defer func() {
   871  		if r != nil {
   872  			require.NoError(t, r.Close())
   873  		}
   874  	}()
   875  
   876  	var stats base.InternalIteratorStats
   877  	datadriven.RunTest(t, "testdata/block_properties", func(t *testing.T, td *datadriven.TestData) string {
   878  		switch td.Cmd {
   879  		case "build":
   880  			if r != nil {
   881  				_ = r.Close()
   882  				r = nil
   883  			}
   884  			var output string
   885  			r, output = runBlockPropertiesBuildCmd(td)
   886  			return output
   887  
   888  		case "collectors":
   889  			return runCollectorsCmd(r, td)
   890  
   891  		case "table-props":
   892  			return runTablePropsCmd(r, td)
   893  
   894  		case "block-props":
   895  			return runBlockPropsCmd(r, td)
   896  
   897  		case "filter":
   898  			var points, ranges []BlockPropertyFilter
   899  			for _, cmd := range td.CmdArgs {
   900  				filter, err := parseIntervalFilter(cmd)
   901  				if err != nil {
   902  					return err.Error()
   903  				}
   904  				switch cmd.Key {
   905  				case "point-filter":
   906  					points = append(points, filter)
   907  				case "range-filter":
   908  					ranges = append(ranges, filter)
   909  				default:
   910  					return fmt.Sprintf("unknown command: %s", td.Cmd)
   911  				}
   912  			}
   913  
   914  			// Point keys filter matches.
   915  			var buf bytes.Buffer
   916  			var f *BlockPropertiesFilterer
   917  			buf.WriteString("points: ")
   918  			if len(points) > 0 {
   919  				f = newBlockPropertiesFilterer(points, nil)
   920  				ok, err := f.intersectsUserPropsAndFinishInit(r.Properties.UserProperties)
   921  				if err != nil {
   922  					return err.Error()
   923  				}
   924  				buf.WriteString(strconv.FormatBool(ok))
   925  				if !ok {
   926  					f = nil
   927  				}
   928  
   929  				// Enumerate point key data blocks encoded into the index.
   930  				if f != nil {
   931  					indexH, err := r.readIndex(context.Background(), nil, nil)
   932  					if err != nil {
   933  						return err.Error()
   934  					}
   935  					defer indexH.Release()
   936  
   937  					buf.WriteString(", blocks=[")
   938  
   939  					var blocks []int
   940  					var i int
   941  					iter, _ := newBlockIter(r.Compare, indexH.Get())
   942  					for key, value := iter.First(); key != nil; key, value = iter.Next() {
   943  						bh, err := decodeBlockHandleWithProperties(value.InPlaceValue())
   944  						if err != nil {
   945  							return err.Error()
   946  						}
   947  						intersects, err := f.intersects(bh.Props)
   948  						if err != nil {
   949  							return err.Error()
   950  						}
   951  						if intersects == blockIntersects {
   952  							blocks = append(blocks, i)
   953  						}
   954  						i++
   955  					}
   956  					for i, b := range blocks {
   957  						buf.WriteString(strconv.Itoa(b))
   958  						if i < len(blocks)-1 {
   959  							buf.WriteString(",")
   960  						}
   961  					}
   962  					buf.WriteString("]")
   963  				}
   964  			} else {
   965  				// Without filters, the table matches by default.
   966  				buf.WriteString("true (no filters provided)")
   967  			}
   968  			buf.WriteString("\n")
   969  
   970  			// Range key filter matches.
   971  			buf.WriteString("ranges: ")
   972  			if len(ranges) > 0 {
   973  				f := newBlockPropertiesFilterer(ranges, nil)
   974  				ok, err := f.intersectsUserPropsAndFinishInit(r.Properties.UserProperties)
   975  				if err != nil {
   976  					return err.Error()
   977  				}
   978  				buf.WriteString(strconv.FormatBool(ok))
   979  			} else {
   980  				// Without filters, the table matches by default.
   981  				buf.WriteString("true (no filters provided)")
   982  			}
   983  			buf.WriteString("\n")
   984  
   985  			return buf.String()
   986  
   987  		case "iter":
   988  			var lower, upper []byte
   989  			var filters []BlockPropertyFilter
   990  			for _, arg := range td.CmdArgs {
   991  				switch arg.Key {
   992  				case "lower":
   993  					lower = []byte(arg.Vals[0])
   994  				case "upper":
   995  					upper = []byte(arg.Vals[0])
   996  				case "point-key-filter":
   997  					f, err := parseIntervalFilter(arg)
   998  					if err != nil {
   999  						return err.Error()
  1000  					}
  1001  					filters = append(filters, f)
  1002  				}
  1003  			}
  1004  			filterer := newBlockPropertiesFilterer(filters, nil)
  1005  			ok, err := filterer.intersectsUserPropsAndFinishInit(r.Properties.UserProperties)
  1006  			if err != nil {
  1007  				return err.Error()
  1008  			} else if !ok {
  1009  				return "filter excludes entire table"
  1010  			}
  1011  			iter, err := r.NewIterWithBlockPropertyFilters(
  1012  				lower, upper, filterer, false /* use (bloom) filter */, &stats,
  1013  				CategoryAndQoS{}, nil, TrivialReaderProvider{Reader: r})
  1014  			if err != nil {
  1015  				return err.Error()
  1016  			}
  1017  			return runIterCmd(td, iter, false, runIterCmdEveryOpAfter(func(w io.Writer) {
  1018  				// After every op, point the value of MaybeFilteredKeys.
  1019  				fmt.Fprintf(w, " MaybeFilteredKeys()=%t", iter.MaybeFilteredKeys())
  1020  			}))
  1021  
  1022  		default:
  1023  			return fmt.Sprintf("unknown command: %s", td.Cmd)
  1024  		}
  1025  	})
  1026  }
  1027  
  1028  func TestBlockProperties_BoundLimited(t *testing.T) {
  1029  	var r *Reader
  1030  	defer func() {
  1031  		if r != nil {
  1032  			require.NoError(t, r.Close())
  1033  		}
  1034  	}()
  1035  
  1036  	var stats base.InternalIteratorStats
  1037  	datadriven.RunTest(t, "testdata/block_properties_boundlimited", func(t *testing.T, td *datadriven.TestData) string {
  1038  		switch td.Cmd {
  1039  		case "build":
  1040  			if r != nil {
  1041  				_ = r.Close()
  1042  				r = nil
  1043  			}
  1044  			var output string
  1045  			r, output = runBlockPropertiesBuildCmd(td)
  1046  			return output
  1047  		case "collectors":
  1048  			return runCollectorsCmd(r, td)
  1049  		case "table-props":
  1050  			return runTablePropsCmd(r, td)
  1051  		case "block-props":
  1052  			return runBlockPropsCmd(r, td)
  1053  		case "iter":
  1054  			var buf bytes.Buffer
  1055  			var lower, upper []byte
  1056  			filter := boundLimitedWrapper{
  1057  				w:   &buf,
  1058  				cmp: testkeys.Comparer.Compare,
  1059  			}
  1060  			for _, arg := range td.CmdArgs {
  1061  				switch arg.Key {
  1062  				case "lower":
  1063  					lower = []byte(arg.Vals[0])
  1064  				case "upper":
  1065  					upper = []byte(arg.Vals[0])
  1066  				case "filter":
  1067  					f, err := parseIntervalFilter(arg)
  1068  					if err != nil {
  1069  						return err.Error()
  1070  					}
  1071  					filter.inner = f
  1072  				case "filter-upper":
  1073  					ik := base.MakeInternalKey([]byte(arg.Vals[0]), 0, base.InternalKeyKindSet)
  1074  					filter.upper = &ik
  1075  				case "filter-lower":
  1076  					ik := base.MakeInternalKey([]byte(arg.Vals[0]), 0, base.InternalKeyKindSet)
  1077  					filter.lower = &ik
  1078  				}
  1079  			}
  1080  			if filter.inner == nil {
  1081  				return "missing block property filter"
  1082  			}
  1083  
  1084  			filterer := newBlockPropertiesFilterer(nil, &filter)
  1085  			ok, err := filterer.intersectsUserPropsAndFinishInit(r.Properties.UserProperties)
  1086  			if err != nil {
  1087  				return err.Error()
  1088  			} else if !ok {
  1089  				return "filter excludes entire table"
  1090  			}
  1091  			iter, err := r.NewIterWithBlockPropertyFilters(
  1092  				lower, upper, filterer, false /* use (bloom) filter */, &stats,
  1093  				CategoryAndQoS{}, nil, TrivialReaderProvider{Reader: r})
  1094  			if err != nil {
  1095  				return err.Error()
  1096  			}
  1097  			return runIterCmd(td, iter, false, runIterCmdEveryOp(func(w io.Writer) {
  1098  				// Copy the bound-limited-wrapper's accumulated output to the
  1099  				// iterator's writer. This interleaves its output with the
  1100  				// iterator output.
  1101  				io.Copy(w, &buf)
  1102  				buf.Reset()
  1103  			}), runIterCmdEveryOpAfter(func(w io.Writer) {
  1104  				// After every op, point the value of MaybeFilteredKeys.
  1105  				fmt.Fprintf(w, " MaybeFilteredKeys()=%t", iter.MaybeFilteredKeys())
  1106  			}))
  1107  		default:
  1108  			return fmt.Sprintf("unrecognized command %q", td.Cmd)
  1109  		}
  1110  	})
  1111  }
  1112  
  1113  type boundLimitedWrapper struct {
  1114  	w     io.Writer
  1115  	cmp   base.Compare
  1116  	inner BlockPropertyFilter
  1117  	lower *InternalKey
  1118  	upper *InternalKey
  1119  }
  1120  
  1121  func (bl *boundLimitedWrapper) Name() string { return bl.inner.Name() }
  1122  
  1123  func (bl *boundLimitedWrapper) Intersects(prop []byte) (bool, error) {
  1124  	propString := fmt.Sprintf("%x", prop)
  1125  	var i interval
  1126  	if err := i.decode(prop); err == nil {
  1127  		// If it decodes as an interval, pretty print it as an interval.
  1128  		propString = fmt.Sprintf("[%d, %d)", i.lower, i.upper)
  1129  	}
  1130  
  1131  	v, err := bl.inner.Intersects(prop)
  1132  	if bl.w != nil {
  1133  		fmt.Fprintf(bl.w, "    filter.Intersects(%s) = (%t, %v)\n", propString, v, err)
  1134  	}
  1135  	return v, err
  1136  }
  1137  
  1138  func (bl *boundLimitedWrapper) KeyIsWithinLowerBound(key []byte) (ret bool) {
  1139  	if bl.lower == nil {
  1140  		ret = true
  1141  	} else {
  1142  		ret = bl.cmp(key, bl.lower.UserKey) >= 0
  1143  	}
  1144  	if bl.w != nil {
  1145  		fmt.Fprintf(bl.w, "    filter.KeyIsWithinLowerBound(%s) = %t\n", key, ret)
  1146  	}
  1147  	return ret
  1148  }
  1149  
  1150  func (bl *boundLimitedWrapper) KeyIsWithinUpperBound(key []byte) (ret bool) {
  1151  	if bl.upper == nil {
  1152  		ret = true
  1153  	} else {
  1154  		ret = bl.cmp(key, bl.upper.UserKey) <= 0
  1155  	}
  1156  	if bl.w != nil {
  1157  		fmt.Fprintf(bl.w, "    filter.KeyIsWithinUpperBound(%s) = %t\n", key, ret)
  1158  	}
  1159  	return ret
  1160  }
  1161  
  1162  func parseIntervalFilter(cmd datadriven.CmdArg) (BlockPropertyFilter, error) {
  1163  	name := cmd.Vals[0]
  1164  	minS, maxS := cmd.Vals[1], cmd.Vals[2]
  1165  	min, err := strconv.ParseUint(minS, 10, 64)
  1166  	if err != nil {
  1167  		return nil, err
  1168  	}
  1169  	max, err := strconv.ParseUint(maxS, 10, 64)
  1170  	if err != nil {
  1171  		return nil, err
  1172  	}
  1173  	return NewBlockIntervalFilter(name, min, max), nil
  1174  }
  1175  
  1176  func runCollectorsCmd(r *Reader, td *datadriven.TestData) string {
  1177  	var lines []string
  1178  	for k, v := range r.Properties.UserProperties {
  1179  		lines = append(lines, fmt.Sprintf("%d: %s", v[0], k))
  1180  	}
  1181  	linesSorted := sort.StringSlice(lines)
  1182  	linesSorted.Sort()
  1183  	return strings.Join(lines, "\n")
  1184  }
  1185  
  1186  func runTablePropsCmd(r *Reader, td *datadriven.TestData) string {
  1187  	var lines []string
  1188  	for _, val := range r.Properties.UserProperties {
  1189  		id := shortID(val[0])
  1190  		var i interval
  1191  		if err := i.decode([]byte(val[1:])); err != nil {
  1192  			return err.Error()
  1193  		}
  1194  		lines = append(lines, fmt.Sprintf("%d: [%d, %d)", id, i.lower, i.upper))
  1195  	}
  1196  	linesSorted := sort.StringSlice(lines)
  1197  	linesSorted.Sort()
  1198  	return strings.Join(lines, "\n")
  1199  }
  1200  
  1201  func runBlockPropertiesBuildCmd(td *datadriven.TestData) (r *Reader, out string) {
  1202  	opts := WriterOptions{
  1203  		TableFormat:    TableFormatPebblev2,
  1204  		IndexBlockSize: math.MaxInt32, // Default to a single level index for simplicity.
  1205  	}
  1206  	for _, cmd := range td.CmdArgs {
  1207  		switch cmd.Key {
  1208  		case "block-size":
  1209  			if len(cmd.Vals) != 1 {
  1210  				return r, fmt.Sprintf("%s: arg %s expects 1 value", td.Cmd, cmd.Key)
  1211  			}
  1212  			var err error
  1213  			opts.BlockSize, err = strconv.Atoi(cmd.Vals[0])
  1214  			if err != nil {
  1215  				return r, err.Error()
  1216  			}
  1217  		case "collectors":
  1218  			for _, c := range cmd.Vals {
  1219  				var points, ranges DataBlockIntervalCollector
  1220  				switch c {
  1221  				case "value-first":
  1222  					points = &valueCharBlockIntervalCollector{charIdx: 0}
  1223  				case "value-last":
  1224  					points = &valueCharBlockIntervalCollector{charIdx: -1}
  1225  				case "suffix":
  1226  					points, ranges = &suffixIntervalCollector{}, &suffixIntervalCollector{}
  1227  				case "suffix-point-keys-only":
  1228  					points = &suffixIntervalCollector{}
  1229  				case "suffix-range-keys-only":
  1230  					ranges = &suffixIntervalCollector{}
  1231  				case "nil-points-and-ranges":
  1232  					points, ranges = nil, nil
  1233  				default:
  1234  					return r, fmt.Sprintf("unknown collector: %s", c)
  1235  				}
  1236  				name := c
  1237  				opts.BlockPropertyCollectors = append(
  1238  					opts.BlockPropertyCollectors,
  1239  					func() BlockPropertyCollector {
  1240  						return NewBlockIntervalCollector(name, points, ranges)
  1241  					})
  1242  			}
  1243  		case "index-block-size":
  1244  			var err error
  1245  			opts.IndexBlockSize, err = strconv.Atoi(cmd.Vals[0])
  1246  			if err != nil {
  1247  				return r, err.Error()
  1248  			}
  1249  		}
  1250  	}
  1251  	var meta *WriterMetadata
  1252  	var err error
  1253  	func() {
  1254  		defer func() {
  1255  			if r := recover(); r != nil {
  1256  				err = errors.Errorf("%v", r)
  1257  			}
  1258  		}()
  1259  		meta, r, err = runBuildCmd(td, &opts, 0)
  1260  	}()
  1261  	if err != nil {
  1262  		return r, err.Error()
  1263  	}
  1264  	return r, fmt.Sprintf("point:    [%s,%s]\nrangedel: [%s,%s]\nrangekey: [%s,%s]\nseqnums:  [%d,%d]\n",
  1265  		meta.SmallestPoint, meta.LargestPoint,
  1266  		meta.SmallestRangeDel, meta.LargestRangeDel,
  1267  		meta.SmallestRangeKey, meta.LargestRangeKey,
  1268  		meta.SmallestSeqNum, meta.LargestSeqNum)
  1269  }
  1270  
  1271  func runBlockPropsCmd(r *Reader, td *datadriven.TestData) string {
  1272  	bh, err := r.readIndex(context.Background(), nil, nil)
  1273  	if err != nil {
  1274  		return err.Error()
  1275  	}
  1276  	twoLevelIndex := r.Properties.IndexPartitions > 0
  1277  	i, err := newBlockIter(r.Compare, bh.Get())
  1278  	if err != nil {
  1279  		return err.Error()
  1280  	}
  1281  	defer bh.Release()
  1282  	var sb strings.Builder
  1283  	decodeProps := func(props []byte, indent string) error {
  1284  		d := blockPropertiesDecoder{props: props}
  1285  		var lines []string
  1286  		for !d.done() {
  1287  			id, prop, err := d.next()
  1288  			if err != nil {
  1289  				return err
  1290  			}
  1291  			var i interval
  1292  			if err := i.decode(prop); err != nil {
  1293  				return err
  1294  			}
  1295  			lines = append(lines, fmt.Sprintf("%s%d: [%d, %d)\n", indent, id, i.lower, i.upper))
  1296  		}
  1297  		linesSorted := sort.StringSlice(lines)
  1298  		linesSorted.Sort()
  1299  		for _, line := range lines {
  1300  			sb.WriteString(line)
  1301  		}
  1302  		return nil
  1303  	}
  1304  
  1305  	for key, val := i.First(); key != nil; key, val = i.Next() {
  1306  		sb.WriteString(fmt.Sprintf("%s:\n", key))
  1307  		bhp, err := decodeBlockHandleWithProperties(val.InPlaceValue())
  1308  		if err != nil {
  1309  			return err.Error()
  1310  		}
  1311  		if err := decodeProps(bhp.Props, "  "); err != nil {
  1312  			return err.Error()
  1313  		}
  1314  
  1315  		// If the table has a two-level index, also decode the index
  1316  		// block that bhp points to, along with its block properties.
  1317  		if twoLevelIndex {
  1318  			subiter := &blockIter{}
  1319  			subIndex, err := r.readBlock(
  1320  				context.Background(), bhp.BlockHandle, nil, nil, nil, nil, nil)
  1321  			if err != nil {
  1322  				return err.Error()
  1323  			}
  1324  			if err := subiter.init(
  1325  				r.Compare, subIndex.Get(), 0 /* globalSeqNum */, false); err != nil {
  1326  				return err.Error()
  1327  			}
  1328  			for key, value := subiter.First(); key != nil; key, value = subiter.Next() {
  1329  				sb.WriteString(fmt.Sprintf("  %s:\n", key))
  1330  				dataBH, err := decodeBlockHandleWithProperties(value.InPlaceValue())
  1331  				if err != nil {
  1332  					return err.Error()
  1333  				}
  1334  				if err := decodeProps(dataBH.Props, "    "); err != nil {
  1335  					return err.Error()
  1336  				}
  1337  			}
  1338  			subIndex.Release()
  1339  		}
  1340  	}
  1341  	return sb.String()
  1342  }
  1343  
  1344  type keyCountCollector struct {
  1345  	name                string
  1346  	block, index, table int
  1347  }
  1348  
  1349  var _ BlockPropertyCollector = &keyCountCollector{}
  1350  var _ SuffixReplaceableBlockCollector = &keyCountCollector{}
  1351  
  1352  func keyCountCollectorFn(name string) func() BlockPropertyCollector {
  1353  	return func() BlockPropertyCollector { return &keyCountCollector{name: name} }
  1354  }
  1355  
  1356  func (p *keyCountCollector) Name() string { return p.name }
  1357  
  1358  func (p *keyCountCollector) Add(k InternalKey, _ []byte) error {
  1359  	if rangekey.IsRangeKey(k.Kind()) {
  1360  		p.table++
  1361  	} else {
  1362  		p.block++
  1363  	}
  1364  	return nil
  1365  }
  1366  
  1367  func (p *keyCountCollector) FinishDataBlock(buf []byte) ([]byte, error) {
  1368  	buf = append(buf, []byte(strconv.Itoa(int(p.block)))...)
  1369  	p.table += p.block
  1370  	return buf, nil
  1371  }
  1372  
  1373  func (p *keyCountCollector) AddPrevDataBlockToIndexBlock() {
  1374  	p.index += p.block
  1375  	p.block = 0
  1376  }
  1377  
  1378  func (p *keyCountCollector) FinishIndexBlock(buf []byte) ([]byte, error) {
  1379  	buf = append(buf, []byte(strconv.Itoa(int(p.index)))...)
  1380  	p.index = 0
  1381  	return buf, nil
  1382  }
  1383  
  1384  func (p *keyCountCollector) FinishTable(buf []byte) ([]byte, error) {
  1385  	buf = append(buf, []byte(strconv.Itoa(int(p.table)))...)
  1386  	p.table = 0
  1387  	return buf, nil
  1388  }
  1389  
  1390  func (p *keyCountCollector) UpdateKeySuffixes(old []byte, _, _ []byte) error {
  1391  	n, err := strconv.Atoi(string(old))
  1392  	if err != nil {
  1393  		return err
  1394  	}
  1395  	p.block = n
  1396  	return nil
  1397  }
  1398  
  1399  // intSuffixCollector is testing prop collector that collects the min and
  1400  // max value of numeric suffix of keys (interpreting suffixLen bytes as ascii
  1401  // for conversion with atoi).
  1402  type intSuffixCollector struct {
  1403  	suffixLen int
  1404  	min, max  uint64 // inclusive
  1405  }
  1406  
  1407  func makeIntSuffixCollector(len int) intSuffixCollector {
  1408  	return intSuffixCollector{len, math.MaxUint64, 0}
  1409  }
  1410  
  1411  func (p *intSuffixCollector) setFromSuffix(to []byte) error {
  1412  	if len(to) >= p.suffixLen {
  1413  		parsed, err := strconv.Atoi(string(to[len(to)-p.suffixLen:]))
  1414  		if err != nil {
  1415  			return err
  1416  		}
  1417  		p.min = uint64(parsed)
  1418  		p.max = uint64(parsed)
  1419  	}
  1420  	return nil
  1421  }
  1422  
  1423  type intSuffixTablePropCollector struct {
  1424  	name string
  1425  	intSuffixCollector
  1426  }
  1427  
  1428  var _ TablePropertyCollector = &intSuffixTablePropCollector{}
  1429  var _ SuffixReplaceableTableCollector = &intSuffixTablePropCollector{}
  1430  
  1431  func intSuffixTablePropCollectorFn(name string, len int) func() TablePropertyCollector {
  1432  	return func() TablePropertyCollector { return &intSuffixTablePropCollector{name, makeIntSuffixCollector(len)} }
  1433  }
  1434  
  1435  func (p *intSuffixCollector) Add(key InternalKey, _ []byte) error {
  1436  	if len(key.UserKey) > p.suffixLen {
  1437  		parsed, err := strconv.Atoi(string(key.UserKey[len(key.UserKey)-p.suffixLen:]))
  1438  		if err != nil {
  1439  			return err
  1440  		}
  1441  		v := uint64(parsed)
  1442  		if v > p.max {
  1443  			p.max = v
  1444  		}
  1445  		if v < p.min {
  1446  			p.min = v
  1447  		}
  1448  	}
  1449  	return nil
  1450  }
  1451  
  1452  func (p *intSuffixTablePropCollector) Finish(userProps map[string]string) error {
  1453  	userProps[p.name+".min"] = fmt.Sprint(p.min)
  1454  	userProps[p.name+".max"] = fmt.Sprint(p.max)
  1455  	return nil
  1456  }
  1457  
  1458  func (p *intSuffixTablePropCollector) Name() string { return p.name }
  1459  
  1460  func (p *intSuffixTablePropCollector) UpdateKeySuffixes(
  1461  	oldProps map[string]string, from, to []byte,
  1462  ) error {
  1463  	return p.setFromSuffix(to)
  1464  }
  1465  
  1466  // testIntSuffixIntervalCollector is a wrapper for testIntSuffixCollector that
  1467  // uses it to implement a block interval collector.
  1468  type intSuffixIntervalCollector struct {
  1469  	intSuffixCollector
  1470  }
  1471  
  1472  func intSuffixIntervalCollectorFn(name string, length int) func() BlockPropertyCollector {
  1473  	return func() BlockPropertyCollector {
  1474  		return NewBlockIntervalCollector(name, &intSuffixIntervalCollector{makeIntSuffixCollector(length)}, nil)
  1475  	}
  1476  }
  1477  
  1478  var _ DataBlockIntervalCollector = &intSuffixIntervalCollector{}
  1479  var _ SuffixReplaceableBlockCollector = &intSuffixIntervalCollector{}
  1480  
  1481  func (p *intSuffixIntervalCollector) FinishDataBlock() (lower uint64, upper uint64, err error) {
  1482  	return p.min, p.max + 1, nil
  1483  }
  1484  
  1485  func (p *intSuffixIntervalCollector) UpdateKeySuffixes(oldProp []byte, from, to []byte) error {
  1486  	return p.setFromSuffix(to)
  1487  }