diff --git a/roaring/roaring.go b/roaring/roaring.go index 587cc5f7b..503046690 100644 --- a/roaring/roaring.go +++ b/roaring/roaring.go @@ -1756,6 +1756,14 @@ type RoaringIterator interface { // Clone copies the iterator, preserving it at this point in the iteration. // It may well share much underlying data. Clone() RoaringIterator + + // ContainerKeySpan provides the smallest and largest + // container keys that the iterator will return. + // The current implementation requires that the underlying header + // lists the keys in ascending order. + // Iff there no keys, then empty will be returned true. + // If there is only a single key, then ckeyLast will equal ckeyFirst. + ContainerKeySpan() (ckeyFirst, ckeyLast uint64, empty bool) } // baseRoaringIterator holds values used by both Pilosa and official Roaring @@ -1924,6 +1932,22 @@ func (r *baseRoaringIterator) Done(err error) { r.currentDataOffset = 0 } +func (r *baseRoaringIterator) ContainerKeySpan() (ckeyFirst, ckeyLast uint64, empty bool) { + n := r.keys + if n == 0 { + empty = true + return + } + ckeyFirst = binary.LittleEndian.Uint64(r.headers[0:8]) + if n == 1 { + ckeyLast = ckeyFirst + return + } + beg := (n - 1) * 12 + ckeyLast = binary.LittleEndian.Uint64(r.headers[beg : beg+8]) + return +} + // Len() indicates the total number of containers the iterator expects to have. func (r *baseRoaringIterator) Len() int64 { return r.keys diff --git a/roaring/roaring_internal_test.go b/roaring/roaring_internal_test.go index e924b63a6..a7575121d 100644 --- a/roaring/roaring_internal_test.go +++ b/roaring/roaring_internal_test.go @@ -4430,6 +4430,17 @@ func TestCloneRoaringIterator(t *testing.T) { itr2 := itr.Clone() + firstCkey, lastCkey, empty := itr2.ContainerKeySpan() + if empty { + t.Fatalf("should not be empty") + } + if firstCkey != 0 { + t.Fatalf("firstCkey should be 0") + } + if lastCkey != 10001 { + t.Fatalf("lastCkey should be 10001") + } + var keys []uint64 for itrKey, synthC := itr.NextContainer(); synthC != nil; itrKey, synthC = itr.NextContainer() { keys = append(keys, itrKey) @@ -4446,6 +4457,54 @@ func TestCloneRoaringIterator(t *testing.T) { } } +func TestRoaringIteratorContainerKeySpan(t *testing.T) { + + ca := NewContainerArray([]uint16{1, 10, 100, 1000}) + ba := NewFileBitmap() + ba.Containers.Put(101, ca) + ba.Containers.Put(10, ca) + ba.Containers.Put(10001, ca) + var buf bytes.Buffer + _, err := ba.WriteTo(&buf) + if err != nil { + t.Fatalf("error writing: %v", err) + } + + itr, err := NewRoaringIterator(buf.Bytes()) + if err != nil { + t.Fatalf("error NewRoaringIterator(buf.Bytes()): %v", err) + } + + firstCkey, lastCkey, empty := itr.ContainerKeySpan() + if empty { + t.Fatalf("should not be empty") + } + if firstCkey != 10 { + t.Fatalf("firstCkey should be 10") + } + if lastCkey != 10001 { + t.Fatalf("lastCkey should be 10001") + } + + // make and check empty bitmap + + baEmpty := NewFileBitmap() + var bufEmpty bytes.Buffer + _, err = baEmpty.WriteTo(&bufEmpty) + if err != nil { + t.Fatalf("error writing: %v", err) + } + + itrEmpty, err := NewRoaringIterator(bufEmpty.Bytes()) + if err != nil { + t.Fatalf("error NewRoaringIterator(bufEmpty.Bytes()): %v", err) + } + _, _, empty = itrEmpty.ContainerKeySpan() + if !empty { + t.Fatalf("should be empty") + } +} + // we were seeing unionInterval16InPlace() returning too // large an run container, which was causing problems when // we write to the transactional backends. Verify that