200 lines
4.4 KiB
Go
200 lines
4.4 KiB
Go
package tsm1
|
|
|
|
import (
|
|
"github.com/influxdata/influxdb/tsdb"
|
|
"github.com/influxdata/influxdb/tsdb/cursors"
|
|
)
|
|
|
|
// TimeRangeIterator will iterate over the keys of a TSM file, starting at
|
|
// the provided key. It is used to determine if each key has data which exists
|
|
// within a specified time interval.
|
|
type TimeRangeIterator struct {
|
|
r *TSMReader
|
|
iter *TSMIndexIterator
|
|
tr TimeRange
|
|
err error
|
|
stats cursors.CursorStats
|
|
|
|
// temporary storage
|
|
trbuf []TimeRange
|
|
buf []byte
|
|
a tsdb.TimestampArray
|
|
}
|
|
|
|
func (b *TimeRangeIterator) Err() error {
|
|
if b.err != nil {
|
|
return b.err
|
|
}
|
|
return b.iter.Err()
|
|
}
|
|
|
|
// Next advances the iterator and reports if it is still valid.
|
|
func (b *TimeRangeIterator) Next() bool {
|
|
if b.Err() != nil {
|
|
return false
|
|
}
|
|
|
|
return b.iter.Next()
|
|
}
|
|
|
|
// Seek points the iterator at the smallest key greater than or equal to the
|
|
// given key, returning true if it was an exact match. It returns false for
|
|
// ok if the key does not exist.
|
|
func (b *TimeRangeIterator) Seek(key []byte) (exact, ok bool) {
|
|
if b.Err() != nil {
|
|
return false, false
|
|
}
|
|
|
|
return b.iter.Seek(key)
|
|
}
|
|
|
|
// Key reports the current key.
|
|
func (b *TimeRangeIterator) Key() []byte {
|
|
return b.iter.Key()
|
|
}
|
|
|
|
// HasData reports true if the current key has data for the time range.
|
|
func (b *TimeRangeIterator) HasData() bool {
|
|
if b.Err() != nil {
|
|
return false
|
|
}
|
|
|
|
e := excludeEntries(b.iter.Entries(), b.tr)
|
|
if len(e) == 0 {
|
|
return false
|
|
}
|
|
|
|
b.trbuf = b.r.TombstoneRange(b.iter.Key(), b.trbuf[:0])
|
|
var ts []TimeRange
|
|
if len(b.trbuf) > 0 {
|
|
ts = excludeTimeRanges(b.trbuf, b.tr)
|
|
}
|
|
|
|
if len(ts) == 0 {
|
|
// no tombstones, fast path will avoid decoding blocks
|
|
// if queried time interval intersects with one of the entries
|
|
if intersectsEntry(e, b.tr) {
|
|
return true
|
|
}
|
|
|
|
for i := range e {
|
|
if !b.readBlock(&e[i]) {
|
|
return false
|
|
}
|
|
|
|
if b.a.Contains(b.tr.Min, b.tr.Max) {
|
|
return true
|
|
}
|
|
}
|
|
} else {
|
|
for i := range e {
|
|
if !b.readBlock(&e[i]) {
|
|
return false
|
|
}
|
|
|
|
// remove tombstoned timestamps
|
|
for i := range ts {
|
|
b.a.Exclude(ts[i].Min, ts[i].Max)
|
|
}
|
|
|
|
if b.a.Contains(b.tr.Min, b.tr.Max) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// readBlock reads the block identified by IndexEntry e and accumulates
|
|
// statistics. readBlock returns true on success.
|
|
func (b *TimeRangeIterator) readBlock(e *IndexEntry) bool {
|
|
_, b.buf, b.err = b.r.ReadBytes(e, b.buf)
|
|
if b.err != nil {
|
|
return false
|
|
}
|
|
|
|
b.err = DecodeTimestampArrayBlock(b.buf, &b.a)
|
|
if b.err != nil {
|
|
return false
|
|
}
|
|
|
|
b.stats.ScannedBytes += b.a.Len() * 8 // sizeof Timestamp (int64)
|
|
b.stats.ScannedValues += b.a.Len()
|
|
return true
|
|
}
|
|
|
|
// Stats returns statistics accumulated by the iterator for any block reads.
|
|
func (b *TimeRangeIterator) Stats() cursors.CursorStats {
|
|
return b.stats
|
|
}
|
|
|
|
/*
|
|
intersectsEntry determines whether the range [min, max]
|
|
intersects one or both boundaries of IndexEntry.
|
|
|
|
+------------------+
|
|
| IndexEntry |
|
|
+---------+------------------+---------+
|
|
| RANGE | | RANGE |
|
|
+-+-------+-+ +----+----+----+
|
|
| RANGE | | RANGE |
|
|
+----+----+-----------+---------+
|
|
| RANGE |
|
|
+--------------------------+
|
|
*/
|
|
|
|
// intersectsEntry determines if tr overlaps one or both boundaries
|
|
// of at least one element of e. If that is the case,
|
|
// and the block has no tombstones, the block timestamps do not
|
|
// need to be decoded.
|
|
func intersectsEntry(e []IndexEntry, tr TimeRange) bool {
|
|
for i := range e {
|
|
min, max := e[i].MinTime, e[i].MaxTime
|
|
if tr.Overlaps(min, max) && !tr.Within(min, max) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// excludeEntries returns a slice which excludes leading and trailing
|
|
// elements of e that are outside the time range specified by tr.
|
|
func excludeEntries(e []IndexEntry, tr TimeRange) []IndexEntry {
|
|
for i := range e {
|
|
if e[i].OverlapsTimeRange(tr.Min, tr.Max) {
|
|
e = e[i:]
|
|
break
|
|
}
|
|
}
|
|
|
|
for i := range e {
|
|
if !e[i].OverlapsTimeRange(tr.Min, tr.Max) {
|
|
e = e[:i]
|
|
break
|
|
}
|
|
}
|
|
|
|
return e
|
|
}
|
|
|
|
// excludeTimeRanges returns a slice which excludes leading and trailing
|
|
// elements of e that are outside the time range specified by tr.
|
|
func excludeTimeRanges(e []TimeRange, tr TimeRange) []TimeRange {
|
|
for i := range e {
|
|
if e[i].Overlaps(tr.Min, tr.Max) {
|
|
e = e[i:]
|
|
break
|
|
}
|
|
}
|
|
|
|
for i := range e {
|
|
if !e[i].Overlaps(tr.Min, tr.Max) {
|
|
e = e[:i]
|
|
break
|
|
}
|
|
}
|
|
|
|
return e
|
|
}
|