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 }