github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/internal/keyspan/interleaving_iter_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 keyspan
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"sort"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/cockroachdb/datadriven"
    16  	"github.com/cockroachdb/pebble/internal/base"
    17  	"github.com/cockroachdb/pebble/internal/testkeys"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  func TestInterleavingIter(t *testing.T) {
    22  	runInterleavingIterTest(t, "testdata/interleaving_iter")
    23  }
    24  
    25  func TestInterleavingIter_Masking(t *testing.T) {
    26  	runInterleavingIterTest(t, "testdata/interleaving_iter_masking")
    27  }
    28  
    29  type maskingHooks struct {
    30  	log        io.Writer
    31  	cmp        base.Compare
    32  	split      base.Split
    33  	threshold  []byte
    34  	maskSuffix []byte
    35  }
    36  
    37  func (m *maskingHooks) SpanChanged(s *Span) {
    38  	if m.log != nil {
    39  		if s == nil {
    40  			fmt.Fprintln(m.log, "-- SpanChanged(nil)")
    41  		} else {
    42  			fmt.Fprintf(m.log, "-- SpanChanged(%s)\n", s)
    43  		}
    44  	}
    45  
    46  	// Find the smallest suffix of a key contained within the Span, excluding
    47  	// suffixes less than m.threshold.
    48  	m.maskSuffix = nil
    49  	if s == nil || m.threshold == nil || len(s.Keys) == 0 {
    50  		return
    51  	}
    52  	for i := range s.Keys {
    53  		if s.Keys[i].Suffix == nil {
    54  			continue
    55  		}
    56  		if m.cmp(s.Keys[i].Suffix, m.threshold) < 0 {
    57  			continue
    58  		}
    59  		if m.maskSuffix == nil || m.cmp(m.maskSuffix, s.Keys[i].Suffix) > 0 {
    60  			m.maskSuffix = s.Keys[i].Suffix
    61  		}
    62  	}
    63  }
    64  
    65  func (m *maskingHooks) SkipPoint(userKey []byte) bool {
    66  	pointSuffix := userKey[m.split(userKey):]
    67  	return m.maskSuffix != nil && len(pointSuffix) > 0 && m.cmp(m.maskSuffix, pointSuffix) < 0
    68  }
    69  
    70  func runInterleavingIterTest(t *testing.T, filename string) {
    71  	cmp := testkeys.Comparer.Compare
    72  	var keyspanIter MergingIter
    73  	var pointIter pointIterator
    74  	var iter InterleavingIter
    75  	var buf bytes.Buffer
    76  	hooks := maskingHooks{
    77  		log:   &buf,
    78  		cmp:   testkeys.Comparer.Compare,
    79  		split: testkeys.Comparer.Split,
    80  	}
    81  
    82  	var prevKey *base.InternalKey
    83  	formatKey := func(k *base.InternalKey, _ base.LazyValue) {
    84  		if k == nil {
    85  			fmt.Fprint(&buf, ".")
    86  			return
    87  		}
    88  		prevKey = k
    89  		s := iter.Span()
    90  		fmt.Fprintf(&buf, "PointKey: %s\n", k.String())
    91  		if s != nil {
    92  			fmt.Fprintf(&buf, "Span: %s\n-", s)
    93  		} else {
    94  			fmt.Fprintf(&buf, "Span: %s\n-", Span{})
    95  		}
    96  	}
    97  
    98  	datadriven.RunTest(t, filename, func(t *testing.T, td *datadriven.TestData) string {
    99  		buf.Reset()
   100  		switch td.Cmd {
   101  		case "set-masking-threshold":
   102  			hooks.threshold = []byte(strings.TrimSpace(td.Input))
   103  			return "OK"
   104  		case "define-rangekeys":
   105  			var spans []Span
   106  			lines := strings.Split(strings.TrimSpace(td.Input), "\n")
   107  			for _, line := range lines {
   108  				spans = append(spans, ParseSpan(line))
   109  			}
   110  			keyspanIter.Init(cmp, noopTransform, new(MergingBuffers), NewIter(cmp, spans))
   111  			hooks.maskSuffix = nil
   112  			iter.Init(testkeys.Comparer, &pointIter, &keyspanIter,
   113  				InterleavingIterOpts{Mask: &hooks})
   114  			return "OK"
   115  		case "define-pointkeys":
   116  			var points []base.InternalKey
   117  			lines := strings.Split(strings.TrimSpace(td.Input), "\n")
   118  			for _, line := range lines {
   119  				points = append(points, base.ParseInternalKey(line))
   120  			}
   121  			pointIter = pointIterator{cmp: cmp, keys: points}
   122  			hooks.maskSuffix = nil
   123  			iter.Init(testkeys.Comparer, &pointIter, &keyspanIter,
   124  				InterleavingIterOpts{Mask: &hooks})
   125  			return "OK"
   126  		case "iter":
   127  			buf.Reset()
   128  			// Clear any previous bounds.
   129  			iter.SetBounds(nil, nil)
   130  			prevKey = nil
   131  			lines := strings.Split(strings.TrimSpace(td.Input), "\n")
   132  			for _, line := range lines {
   133  				bufLen := buf.Len()
   134  				line = strings.TrimSpace(line)
   135  				i := strings.IndexByte(line, ' ')
   136  				iterCmd := line
   137  				if i > 0 {
   138  					iterCmd = string(line[:i])
   139  				}
   140  				switch iterCmd {
   141  				case "first":
   142  					formatKey(iter.First())
   143  				case "last":
   144  					formatKey(iter.Last())
   145  				case "next":
   146  					formatKey(iter.Next())
   147  				case "next-prefix":
   148  					succKey := testkeys.Comparer.ImmediateSuccessor(nil, prevKey.UserKey[:testkeys.Comparer.Split(prevKey.UserKey)])
   149  					formatKey(iter.NextPrefix(succKey))
   150  				case "prev":
   151  					formatKey(iter.Prev())
   152  				case "seek-ge":
   153  					formatKey(iter.SeekGE([]byte(strings.TrimSpace(line[i:])), base.SeekGEFlagsNone))
   154  				case "seek-prefix-ge":
   155  					key := []byte(strings.TrimSpace(line[i:]))
   156  					prefix := key[:testkeys.Comparer.Split(key)]
   157  					formatKey(iter.SeekPrefixGE(prefix, key, base.SeekGEFlagsNone))
   158  				case "seek-lt":
   159  					formatKey(iter.SeekLT([]byte(strings.TrimSpace(line[i:])), base.SeekLTFlagsNone))
   160  				case "set-bounds":
   161  					bounds := strings.Fields(line[i:])
   162  					if len(bounds) != 2 {
   163  						return fmt.Sprintf("set-bounds expects 2 bounds, got %d", len(bounds))
   164  					}
   165  					l, u := []byte(bounds[0]), []byte(bounds[1])
   166  					if bounds[0] == "." {
   167  						l = nil
   168  					}
   169  					if bounds[1] == "." {
   170  						u = nil
   171  					}
   172  					iter.SetBounds(l, u)
   173  				default:
   174  					return fmt.Sprintf("unrecognized iter command %q", iterCmd)
   175  				}
   176  				require.NoError(t, iter.Error())
   177  				if buf.Len() > bufLen {
   178  					fmt.Fprintln(&buf)
   179  				}
   180  			}
   181  			return strings.TrimSpace(buf.String())
   182  		default:
   183  			return fmt.Sprintf("unrecognized command %q", td.Cmd)
   184  		}
   185  	})
   186  	require.NoError(t, iter.Close())
   187  }
   188  
   189  type pointIterator struct {
   190  	cmp   base.Compare
   191  	keys  []base.InternalKey
   192  	lower []byte
   193  	upper []byte
   194  	index int
   195  }
   196  
   197  var _ base.InternalIterator = &pointIterator{}
   198  
   199  func (i *pointIterator) SeekGE(
   200  	key []byte, flags base.SeekGEFlags,
   201  ) (*base.InternalKey, base.LazyValue) {
   202  	i.index = sort.Search(len(i.keys), func(j int) bool {
   203  		return i.cmp(i.keys[j].UserKey, key) >= 0
   204  	})
   205  	if i.index < 0 || i.index >= len(i.keys) {
   206  		return nil, base.LazyValue{}
   207  	}
   208  	if i.upper != nil && i.cmp(i.keys[i.index].UserKey, i.upper) >= 0 {
   209  		return nil, base.LazyValue{}
   210  	}
   211  	return &i.keys[i.index], base.LazyValue{}
   212  }
   213  
   214  func (i *pointIterator) SeekPrefixGE(
   215  	prefix, key []byte, flags base.SeekGEFlags,
   216  ) (*base.InternalKey, base.LazyValue) {
   217  	return i.SeekGE(key, flags)
   218  }
   219  
   220  func (i *pointIterator) SeekLT(
   221  	key []byte, flags base.SeekLTFlags,
   222  ) (*base.InternalKey, base.LazyValue) {
   223  	i.index = sort.Search(len(i.keys), func(j int) bool {
   224  		return i.cmp(i.keys[j].UserKey, key) >= 0
   225  	})
   226  	i.index--
   227  	if i.index < 0 || i.index >= len(i.keys) {
   228  		return nil, base.LazyValue{}
   229  	}
   230  	if i.lower != nil && i.cmp(i.keys[i.index].UserKey, i.lower) < 0 {
   231  		return nil, base.LazyValue{}
   232  	}
   233  	return &i.keys[i.index], base.LazyValue{}
   234  }
   235  
   236  func (i *pointIterator) First() (*base.InternalKey, base.LazyValue) {
   237  	i.index = 0
   238  	if i.index < 0 || i.index >= len(i.keys) {
   239  		return nil, base.LazyValue{}
   240  	}
   241  	if i.upper != nil && i.cmp(i.keys[i.index].UserKey, i.upper) >= 0 {
   242  		return nil, base.LazyValue{}
   243  	}
   244  	return &i.keys[i.index], base.LazyValue{}
   245  }
   246  
   247  func (i *pointIterator) Last() (*base.InternalKey, base.LazyValue) {
   248  	i.index = len(i.keys) - 1
   249  	if i.index < 0 || i.index >= len(i.keys) {
   250  		return nil, base.LazyValue{}
   251  	}
   252  	if i.lower != nil && i.cmp(i.keys[i.index].UserKey, i.lower) < 0 {
   253  		return nil, base.LazyValue{}
   254  	}
   255  	return &i.keys[i.index], base.LazyValue{}
   256  }
   257  
   258  func (i *pointIterator) Next() (*base.InternalKey, base.LazyValue) {
   259  	i.index++
   260  	if i.index < 0 || i.index >= len(i.keys) {
   261  		return nil, base.LazyValue{}
   262  	}
   263  	if i.upper != nil && i.cmp(i.keys[i.index].UserKey, i.upper) >= 0 {
   264  		return nil, base.LazyValue{}
   265  	}
   266  	return &i.keys[i.index], base.LazyValue{}
   267  }
   268  
   269  func (i *pointIterator) NextPrefix(succKey []byte) (*base.InternalKey, base.LazyValue) {
   270  	return i.SeekGE(succKey, base.SeekGEFlagsNone)
   271  }
   272  
   273  func (i *pointIterator) Prev() (*base.InternalKey, base.LazyValue) {
   274  	i.index--
   275  	if i.index < 0 || i.index >= len(i.keys) {
   276  		return nil, base.LazyValue{}
   277  	}
   278  	if i.lower != nil && i.cmp(i.keys[i.index].UserKey, i.lower) < 0 {
   279  		return nil, base.LazyValue{}
   280  	}
   281  	return &i.keys[i.index], base.LazyValue{}
   282  }
   283  
   284  func (i *pointIterator) Close() error   { return nil }
   285  func (i *pointIterator) Error() error   { return nil }
   286  func (i *pointIterator) String() string { return "test-point-iterator" }
   287  func (i *pointIterator) SetBounds(lower, upper []byte) {
   288  	i.lower, i.upper = lower, upper
   289  }