github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/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) 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 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 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) 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(context.Background(), bhp.BlockHandle, nil, nil, nil, nil) 1320 if err != nil { 1321 return err.Error() 1322 } 1323 if err := subiter.init( 1324 r.Compare, subIndex.Get(), 0 /* globalSeqNum */, false); err != nil { 1325 return err.Error() 1326 } 1327 for key, value := subiter.First(); key != nil; key, value = subiter.Next() { 1328 sb.WriteString(fmt.Sprintf(" %s:\n", key)) 1329 dataBH, err := decodeBlockHandleWithProperties(value.InPlaceValue()) 1330 if err != nil { 1331 return err.Error() 1332 } 1333 if err := decodeProps(dataBH.Props, " "); err != nil { 1334 return err.Error() 1335 } 1336 } 1337 subIndex.Release() 1338 } 1339 } 1340 return sb.String() 1341 } 1342 1343 type keyCountCollector struct { 1344 name string 1345 block, index, table int 1346 } 1347 1348 var _ BlockPropertyCollector = &keyCountCollector{} 1349 var _ SuffixReplaceableBlockCollector = &keyCountCollector{} 1350 1351 func keyCountCollectorFn(name string) func() BlockPropertyCollector { 1352 return func() BlockPropertyCollector { return &keyCountCollector{name: name} } 1353 } 1354 1355 func (p *keyCountCollector) Name() string { return p.name } 1356 1357 func (p *keyCountCollector) Add(k InternalKey, _ []byte) error { 1358 if rangekey.IsRangeKey(k.Kind()) { 1359 p.table++ 1360 } else { 1361 p.block++ 1362 } 1363 return nil 1364 } 1365 1366 func (p *keyCountCollector) FinishDataBlock(buf []byte) ([]byte, error) { 1367 buf = append(buf, []byte(strconv.Itoa(int(p.block)))...) 1368 p.table += p.block 1369 return buf, nil 1370 } 1371 1372 func (p *keyCountCollector) AddPrevDataBlockToIndexBlock() { 1373 p.index += p.block 1374 p.block = 0 1375 } 1376 1377 func (p *keyCountCollector) FinishIndexBlock(buf []byte) ([]byte, error) { 1378 buf = append(buf, []byte(strconv.Itoa(int(p.index)))...) 1379 p.index = 0 1380 return buf, nil 1381 } 1382 1383 func (p *keyCountCollector) FinishTable(buf []byte) ([]byte, error) { 1384 buf = append(buf, []byte(strconv.Itoa(int(p.table)))...) 1385 p.table = 0 1386 return buf, nil 1387 } 1388 1389 func (p *keyCountCollector) UpdateKeySuffixes(old []byte, _, _ []byte) error { 1390 n, err := strconv.Atoi(string(old)) 1391 if err != nil { 1392 return err 1393 } 1394 p.block = n 1395 return nil 1396 } 1397 1398 // intSuffixCollector is testing prop collector that collects the min and 1399 // max value of numeric suffix of keys (interpreting suffixLen bytes as ascii 1400 // for conversion with atoi). 1401 type intSuffixCollector struct { 1402 suffixLen int 1403 min, max uint64 // inclusive 1404 } 1405 1406 func makeIntSuffixCollector(len int) intSuffixCollector { 1407 return intSuffixCollector{len, math.MaxUint64, 0} 1408 } 1409 1410 func (p *intSuffixCollector) setFromSuffix(to []byte) error { 1411 if len(to) >= p.suffixLen { 1412 parsed, err := strconv.Atoi(string(to[len(to)-p.suffixLen:])) 1413 if err != nil { 1414 return err 1415 } 1416 p.min = uint64(parsed) 1417 p.max = uint64(parsed) 1418 } 1419 return nil 1420 } 1421 1422 type intSuffixTablePropCollector struct { 1423 name string 1424 intSuffixCollector 1425 } 1426 1427 var _ TablePropertyCollector = &intSuffixTablePropCollector{} 1428 var _ SuffixReplaceableTableCollector = &intSuffixTablePropCollector{} 1429 1430 func intSuffixTablePropCollectorFn(name string, len int) func() TablePropertyCollector { 1431 return func() TablePropertyCollector { return &intSuffixTablePropCollector{name, makeIntSuffixCollector(len)} } 1432 } 1433 1434 func (p *intSuffixCollector) Add(key InternalKey, _ []byte) error { 1435 if len(key.UserKey) > p.suffixLen { 1436 parsed, err := strconv.Atoi(string(key.UserKey[len(key.UserKey)-p.suffixLen:])) 1437 if err != nil { 1438 return err 1439 } 1440 v := uint64(parsed) 1441 if v > p.max { 1442 p.max = v 1443 } 1444 if v < p.min { 1445 p.min = v 1446 } 1447 } 1448 return nil 1449 } 1450 1451 func (p *intSuffixTablePropCollector) Finish(userProps map[string]string) error { 1452 userProps[p.name+".min"] = fmt.Sprint(p.min) 1453 userProps[p.name+".max"] = fmt.Sprint(p.max) 1454 return nil 1455 } 1456 1457 func (p *intSuffixTablePropCollector) Name() string { return p.name } 1458 1459 func (p *intSuffixTablePropCollector) UpdateKeySuffixes( 1460 oldProps map[string]string, from, to []byte, 1461 ) error { 1462 return p.setFromSuffix(to) 1463 } 1464 1465 // testIntSuffixIntervalCollector is a wrapper for testIntSuffixCollector that 1466 // uses it to implement a block interval collector. 1467 type intSuffixIntervalCollector struct { 1468 intSuffixCollector 1469 } 1470 1471 func intSuffixIntervalCollectorFn(name string, length int) func() BlockPropertyCollector { 1472 return func() BlockPropertyCollector { 1473 return NewBlockIntervalCollector(name, &intSuffixIntervalCollector{makeIntSuffixCollector(length)}, nil) 1474 } 1475 } 1476 1477 var _ DataBlockIntervalCollector = &intSuffixIntervalCollector{} 1478 var _ SuffixReplaceableBlockCollector = &intSuffixIntervalCollector{} 1479 1480 func (p *intSuffixIntervalCollector) FinishDataBlock() (lower uint64, upper uint64, err error) { 1481 return p.min, p.max + 1, nil 1482 } 1483 1484 func (p *intSuffixIntervalCollector) UpdateKeySuffixes(oldProp []byte, from, to []byte) error { 1485 return p.setFromSuffix(to) 1486 }