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