github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/internal/manifest/version_test.go (about) 1 // Copyright 2012 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 manifest 6 7 import ( 8 "bytes" 9 "fmt" 10 "strings" 11 "sync" 12 "testing" 13 14 "github.com/cockroachdb/datadriven" 15 "github.com/cockroachdb/pebble/internal/base" 16 "github.com/cockroachdb/pebble/internal/testkeys" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func levelMetadata(level int, files ...*FileMetadata) LevelMetadata { 21 return makeLevelMetadata(base.DefaultComparer.Compare, level, files) 22 } 23 24 func ikey(s string) InternalKey { 25 return base.MakeInternalKey([]byte(s), 0, base.InternalKeyKindSet) 26 } 27 28 func TestIkeyRange(t *testing.T) { 29 cmp := base.DefaultComparer.Compare 30 testCases := []struct { 31 input, want string 32 }{ 33 { 34 "", 35 "-", 36 }, 37 { 38 "a-e", 39 "a-e", 40 }, 41 { 42 "a-e a-e", 43 "a-e", 44 }, 45 { 46 "c-g a-e", 47 "a-g", 48 }, 49 { 50 "a-e c-g a-e", 51 "a-g", 52 }, 53 { 54 "b-d f-g", 55 "b-g", 56 }, 57 { 58 "d-e b-d", 59 "b-e", 60 }, 61 { 62 "e-e", 63 "e-e", 64 }, 65 { 66 "f-g e-e d-e c-g b-d a-e", 67 "a-g", 68 }, 69 } 70 for _, tc := range testCases { 71 var f []*FileMetadata 72 if tc.input != "" { 73 for i, s := range strings.Split(tc.input, " ") { 74 m := (&FileMetadata{ 75 FileNum: base.FileNum(i), 76 }).ExtendPointKeyBounds(cmp, ikey(s[0:1]), ikey(s[2:3])) 77 m.InitPhysicalBacking() 78 f = append(f, m) 79 } 80 } 81 levelMetadata := makeLevelMetadata(base.DefaultComparer.Compare, 0, f) 82 83 sm, la := KeyRange(base.DefaultComparer.Compare, levelMetadata.Iter()) 84 got := string(sm.UserKey) + "-" + string(la.UserKey) 85 if got != tc.want { 86 t.Errorf("KeyRange(%q) = %q, %q", tc.input, got, tc.want) 87 } 88 } 89 } 90 91 func TestOverlaps(t *testing.T) { 92 var v *Version 93 cmp := testkeys.Comparer.Compare 94 fmtKey := testkeys.Comparer.FormatKey 95 datadriven.RunTest(t, "testdata/overlaps", func(t *testing.T, d *datadriven.TestData) string { 96 switch d.Cmd { 97 case "define": 98 var err error 99 v, err = ParseVersionDebug(cmp, fmtKey, 64>>10 /* flush split bytes */, d.Input) 100 if err != nil { 101 return err.Error() 102 } 103 return v.String() 104 case "overlaps": 105 var level int 106 var start, end string 107 var exclusiveEnd bool 108 d.ScanArgs(t, "level", &level) 109 d.ScanArgs(t, "start", &start) 110 d.ScanArgs(t, "end", &end) 111 d.ScanArgs(t, "exclusive-end", &exclusiveEnd) 112 overlaps := v.Overlaps(level, testkeys.Comparer.Compare, []byte(start), []byte(end), exclusiveEnd) 113 var buf bytes.Buffer 114 fmt.Fprintf(&buf, "%d files:\n", overlaps.Len()) 115 overlaps.Each(func(f *FileMetadata) { 116 fmt.Fprintf(&buf, "%s\n", f.DebugString(base.DefaultFormatter, false)) 117 }) 118 return buf.String() 119 default: 120 return fmt.Sprintf("unknown command: %s", d.Cmd) 121 } 122 }) 123 } 124 125 func TestContains(t *testing.T) { 126 cmp := base.DefaultComparer.Compare 127 newFileMeta := func(fileNum base.FileNum, size uint64, smallest, largest base.InternalKey) *FileMetadata { 128 m := (&FileMetadata{ 129 FileNum: fileNum, 130 Size: size, 131 }).ExtendPointKeyBounds(cmp, smallest, largest) 132 m.InitPhysicalBacking() 133 return m 134 } 135 m00 := newFileMeta( 136 700, 137 1, 138 base.ParseInternalKey("b.SET.7008"), 139 base.ParseInternalKey("e.SET.7009"), 140 ) 141 m01 := newFileMeta( 142 701, 143 1, 144 base.ParseInternalKey("c.SET.7018"), 145 base.ParseInternalKey("f.SET.7019"), 146 ) 147 m02 := newFileMeta( 148 702, 149 1, 150 base.ParseInternalKey("f.SET.7028"), 151 base.ParseInternalKey("g.SET.7029"), 152 ) 153 m03 := newFileMeta( 154 703, 155 1, 156 base.ParseInternalKey("x.SET.7038"), 157 base.ParseInternalKey("y.SET.7039"), 158 ) 159 m04 := newFileMeta( 160 704, 161 1, 162 base.ParseInternalKey("n.SET.7048"), 163 base.ParseInternalKey("p.SET.7049"), 164 ) 165 m05 := newFileMeta( 166 705, 167 1, 168 base.ParseInternalKey("p.SET.7058"), 169 base.ParseInternalKey("p.SET.7059"), 170 ) 171 m06 := newFileMeta( 172 706, 173 1, 174 base.ParseInternalKey("p.SET.7068"), 175 base.ParseInternalKey("u.SET.7069"), 176 ) 177 m07 := newFileMeta( 178 707, 179 1, 180 base.ParseInternalKey("r.SET.7078"), 181 base.ParseInternalKey("s.SET.7079"), 182 ) 183 184 m10 := newFileMeta( 185 710, 186 1, 187 base.ParseInternalKey("d.SET.7108"), 188 base.ParseInternalKey("g.SET.7109"), 189 ) 190 m11 := newFileMeta( 191 711, 192 1, 193 base.ParseInternalKey("g.SET.7118"), 194 base.ParseInternalKey("j.SET.7119"), 195 ) 196 m12 := newFileMeta( 197 712, 198 1, 199 base.ParseInternalKey("n.SET.7128"), 200 base.ParseInternalKey("p.SET.7129"), 201 ) 202 m13 := newFileMeta( 203 713, 204 1, 205 base.ParseInternalKey("p.SET.7148"), 206 base.ParseInternalKey("p.SET.7149"), 207 ) 208 m14 := newFileMeta( 209 714, 210 1, 211 base.ParseInternalKey("p.SET.7138"), 212 base.ParseInternalKey("u.SET.7139"), 213 ) 214 215 v := Version{ 216 Levels: [NumLevels]LevelMetadata{ 217 0: levelMetadata(0, m00, m01, m02, m03, m04, m05, m06, m07), 218 1: levelMetadata(1, m10, m11, m12, m13, m14), 219 }, 220 } 221 222 testCases := []struct { 223 level int 224 file *FileMetadata 225 want bool 226 }{ 227 // Level 0: m00=b-e, m01=c-f, m02=f-g, m03=x-y, m04=n-p, m05=p-p, m06=p-u, m07=r-s. 228 // Note that: 229 // - the slice isn't sorted (e.g. m02=f-g, m03=x-y, m04=n-p), 230 // - m00 and m01 overlap (not just touch), 231 // - m06 contains m07, 232 // - m00, m01 and m02 transitively overlap/touch each other, and 233 // - m04, m05, m06 and m07 transitively overlap/touch each other. 234 {0, m00, true}, 235 {0, m01, true}, 236 {0, m02, true}, 237 {0, m03, true}, 238 {0, m04, true}, 239 {0, m05, true}, 240 {0, m06, true}, 241 {0, m07, true}, 242 {0, m10, false}, 243 {0, m11, false}, 244 {0, m12, false}, 245 {0, m13, false}, 246 {0, m14, false}, 247 {1, m00, false}, 248 {1, m01, false}, 249 {1, m02, false}, 250 {1, m03, false}, 251 {1, m04, false}, 252 {1, m05, false}, 253 {1, m06, false}, 254 {1, m07, false}, 255 {1, m10, true}, 256 {1, m11, true}, 257 {1, m12, true}, 258 {1, m13, true}, 259 {1, m14, true}, 260 261 // Level 2: empty. 262 {2, m00, false}, 263 {2, m14, false}, 264 } 265 266 for _, tc := range testCases { 267 got := v.Contains(tc.level, cmp, tc.file) 268 if got != tc.want { 269 t.Errorf("level=%d, file=%s\ngot %t\nwant %t", tc.level, tc.file, got, tc.want) 270 } 271 } 272 } 273 274 func TestVersionUnref(t *testing.T) { 275 list := &VersionList{} 276 list.Init(&sync.Mutex{}) 277 v := &Version{Deleted: func([]*FileBacking) {}} 278 v.Ref() 279 list.PushBack(v) 280 v.Unref() 281 if !list.Empty() { 282 t.Fatalf("expected version list to be empty") 283 } 284 } 285 286 func TestCheckOrdering(t *testing.T) { 287 cmp := base.DefaultComparer.Compare 288 fmtKey := base.DefaultComparer.FormatKey 289 datadriven.RunTest(t, "testdata/version_check_ordering", 290 func(t *testing.T, d *datadriven.TestData) string { 291 switch d.Cmd { 292 case "check-ordering": 293 orderingInvariants := ProhibitSplitUserKeys 294 if d.HasArg("allow-split-user-keys") { 295 orderingInvariants = AllowSplitUserKeys 296 } 297 v, err := ParseVersionDebug(cmp, fmtKey, 10<<20, d.Input) 298 if err != nil { 299 return err.Error() 300 } 301 // L0 files compare on sequence numbers. Use the seqnums from the 302 // smallest / largest bounds for the table. 303 v.Levels[0].Slice().Each(func(m *FileMetadata) { 304 m.SmallestSeqNum = m.Smallest.SeqNum() 305 m.LargestSeqNum = m.Largest.SeqNum() 306 }) 307 if err = v.CheckOrdering(cmp, base.DefaultFormatter, orderingInvariants); err != nil { 308 return err.Error() 309 } 310 return "OK" 311 312 default: 313 return fmt.Sprintf("unknown command: %s", d.Cmd) 314 } 315 }) 316 } 317 318 func TestExtendBounds(t *testing.T) { 319 cmp := base.DefaultComparer.Compare 320 parseBounds := func(line string) (lower, upper InternalKey) { 321 parts := strings.Split(line, "-") 322 if len(parts) == 1 { 323 parts = strings.Split(parts[0], ":") 324 start, end := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) 325 lower = base.ParseInternalKey(start) 326 switch k := lower.Kind(); k { 327 case base.InternalKeyKindRangeDelete: 328 upper = base.MakeRangeDeleteSentinelKey([]byte(end)) 329 case base.InternalKeyKindRangeKeySet, base.InternalKeyKindRangeKeyUnset, base.InternalKeyKindRangeKeyDelete: 330 upper = base.MakeExclusiveSentinelKey(k, []byte(end)) 331 default: 332 panic(fmt.Sprintf("unknown kind %s with end key", k)) 333 } 334 } else { 335 l, u := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) 336 lower, upper = base.ParseInternalKey(l), base.ParseInternalKey(u) 337 } 338 return 339 } 340 format := func(m *FileMetadata) string { 341 var b bytes.Buffer 342 var smallest, largest string 343 switch m.boundTypeSmallest { 344 case boundTypePointKey: 345 smallest = "point" 346 case boundTypeRangeKey: 347 smallest = "range" 348 default: 349 return fmt.Sprintf("unknown bound type %d", m.boundTypeSmallest) 350 } 351 switch m.boundTypeLargest { 352 case boundTypePointKey: 353 largest = "point" 354 case boundTypeRangeKey: 355 largest = "range" 356 default: 357 return fmt.Sprintf("unknown bound type %d", m.boundTypeLargest) 358 } 359 bounds, err := m.boundsMarker() 360 if err != nil { 361 panic(err) 362 } 363 fmt.Fprintf(&b, "%s\n", m.DebugString(base.DefaultFormatter, true)) 364 fmt.Fprintf(&b, " bounds: (smallest=%s,largest=%s) (0x%08b)\n", smallest, largest, bounds) 365 return b.String() 366 } 367 m := &FileMetadata{} 368 datadriven.RunTest(t, "testdata/file_metadata_bounds", func(t *testing.T, d *datadriven.TestData) string { 369 switch d.Cmd { 370 case "reset": 371 m = &FileMetadata{} 372 return "" 373 case "extend-point-key-bounds": 374 u, l := parseBounds(d.Input) 375 m.ExtendPointKeyBounds(cmp, u, l) 376 return format(m) 377 case "extend-range-key-bounds": 378 u, l := parseBounds(d.Input) 379 m.ExtendRangeKeyBounds(cmp, u, l) 380 return format(m) 381 default: 382 return fmt.Sprintf("unknown command %s\n", d.Cmd) 383 } 384 }) 385 } 386 387 func TestFileMetadata_ParseRoundTrip(t *testing.T) { 388 testCases := []struct { 389 name string 390 input string 391 output string 392 }{ 393 { 394 name: "point keys only", 395 input: "000001:[a#0,SET-z#0,DEL] seqnums:[0-0] points:[a#0,SET-z#0,DEL]", 396 }, 397 { 398 name: "range keys only", 399 input: "000001:[a#0,RANGEKEYSET-z#0,RANGEKEYDEL] seqnums:[0-0] ranges:[a#0,RANGEKEYSET-z#0,RANGEKEYDEL]", 400 }, 401 { 402 name: "point and range keys", 403 input: "000001:[a#0,RANGEKEYSET-d#0,DEL] seqnums:[0-0] points:[b#0,SET-d#0,DEL] ranges:[a#0,RANGEKEYSET-c#0,RANGEKEYDEL]", 404 }, 405 { 406 name: "point and range keys with nonzero senums", 407 input: "000001:[a#3,RANGEKEYSET-d#4,DEL] seqnums:[3-7] points:[b#3,SET-d#4,DEL] ranges:[a#3,RANGEKEYSET-c#5,RANGEKEYDEL]", 408 }, 409 { 410 name: "whitespace", 411 input: " 000001 : [ a#0,SET - z#0,DEL] points : [ a#0,SET - z#0,DEL] ", 412 output: "000001:[a#0,SET-z#0,DEL] seqnums:[0-0] points:[a#0,SET-z#0,DEL]", 413 }, 414 } 415 for _, tc := range testCases { 416 t.Run(tc.name, func(t *testing.T) { 417 m, err := ParseFileMetadataDebug(tc.input) 418 require.NoError(t, err) 419 err = m.Validate(base.DefaultComparer.Compare, base.DefaultFormatter) 420 require.NoError(t, err) 421 got := m.DebugString(base.DefaultFormatter, true) 422 want := tc.input 423 if tc.output != "" { 424 want = tc.output 425 } 426 require.Equal(t, want, got) 427 }) 428 } 429 }