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 }