2026-02-10 14:02:11 +00:00
|
|
|
package atree
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
"gordenko.dev/dima/bin"
|
2026-05-31 20:01:28 +00:00
|
|
|
"gordenko.dev/dima/qb"
|
2026-02-10 14:02:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type BackwardCursor struct {
|
2026-05-31 20:01:28 +00:00
|
|
|
metricType qb.MetricType
|
2026-02-10 14:02:11 +00:00
|
|
|
fracDigits byte
|
|
|
|
|
atree *Atree
|
|
|
|
|
pageNo uint32
|
|
|
|
|
pageData []byte
|
2026-05-31 20:01:28 +00:00
|
|
|
timestampDecompressor qb.TimestampDecompressor
|
|
|
|
|
valueDecompressor qb.ValueDecompressor
|
2026-02-10 14:02:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type BackwardCursorOptions struct {
|
2026-05-31 20:01:28 +00:00
|
|
|
MetricType qb.MetricType
|
2026-02-10 14:02:11 +00:00
|
|
|
FracDigits byte
|
|
|
|
|
PageNo uint32
|
|
|
|
|
PageData []byte
|
|
|
|
|
Atree *Atree
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewBackwardCursor(opt BackwardCursorOptions) (*BackwardCursor, error) {
|
|
|
|
|
switch opt.MetricType {
|
2026-05-31 20:01:28 +00:00
|
|
|
case qb.Instant, qb.Cumulative:
|
2026-02-10 14:02:11 +00:00
|
|
|
// ok
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("MetricType option has wrong value: %d", opt.MetricType)
|
|
|
|
|
}
|
2026-05-31 20:01:28 +00:00
|
|
|
if opt.FracDigits > qb.MaxFracDigits {
|
2026-02-10 14:02:11 +00:00
|
|
|
return nil, errors.New("FracDigits option is required")
|
|
|
|
|
}
|
|
|
|
|
if opt.Atree == nil {
|
|
|
|
|
return nil, errors.New("Atree option is required")
|
|
|
|
|
}
|
|
|
|
|
if opt.PageNo == 0 {
|
|
|
|
|
return nil, errors.New("PageNo option is required")
|
|
|
|
|
}
|
|
|
|
|
if len(opt.PageData) == 0 {
|
|
|
|
|
return nil, errors.New("PageData option is required")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := &BackwardCursor{
|
|
|
|
|
metricType: opt.MetricType,
|
|
|
|
|
fracDigits: opt.FracDigits,
|
|
|
|
|
atree: opt.Atree,
|
|
|
|
|
pageNo: opt.PageNo,
|
|
|
|
|
pageData: opt.PageData,
|
|
|
|
|
}
|
|
|
|
|
err := s.makeDecompressors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return s, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// timestamp, value, done, error
|
|
|
|
|
func (s *BackwardCursor) Prev() (uint32, float64, bool, error) {
|
|
|
|
|
var (
|
|
|
|
|
timestamp uint32
|
|
|
|
|
value float64
|
2026-06-10 06:18:45 +03:00
|
|
|
//done bool
|
|
|
|
|
//err error
|
2026-02-10 14:02:11 +00:00
|
|
|
)
|
|
|
|
|
|
2026-06-10 06:18:45 +03:00
|
|
|
// timestamp, done = s.timestampDecompressor.NextValue()
|
|
|
|
|
// if !done {
|
|
|
|
|
// value, done = s.valueDecompressor.NextValue()
|
|
|
|
|
// if done {
|
|
|
|
|
// return 0, 0, false,
|
|
|
|
|
// fmt.Errorf("corrupted data page %d: has timestamp, no value",
|
|
|
|
|
// s.pageNo)
|
|
|
|
|
// }
|
|
|
|
|
// return timestamp, value, false, nil
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// prevPageNo, _ := bin.GetUint32(s.pageData[prevPageIdx:])
|
|
|
|
|
// if prevPageNo == 0 {
|
|
|
|
|
// return 0, 0, true, nil
|
|
|
|
|
// }
|
|
|
|
|
// s.atree.releasePage(s.pageNo)
|
|
|
|
|
|
|
|
|
|
// s.pageNo = prevPageNo
|
|
|
|
|
// s.pageData, err = s.atree.fetchDataPage(s.pageNo)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// return 0, 0, false, fmt.Errorf("atree.fetchDataPage(%d): %s", s.pageNo, err)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// err = s.makeDecompressors()
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// return 0, 0, false, err
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// timestamp, done = s.timestampDecompressor.NextValue()
|
|
|
|
|
// if done {
|
|
|
|
|
// return 0, 0, false,
|
|
|
|
|
// fmt.Errorf("corrupted data page %d: no timestamps",
|
|
|
|
|
// s.pageNo)
|
|
|
|
|
// }
|
|
|
|
|
// value, done = s.valueDecompressor.NextValue()
|
|
|
|
|
// if done {
|
|
|
|
|
// return 0, 0, false,
|
|
|
|
|
// fmt.Errorf("corrupted data page %d: no values",
|
|
|
|
|
// s.pageNo)
|
|
|
|
|
// }
|
2026-02-10 14:02:11 +00:00
|
|
|
return timestamp, value, false, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BackwardCursor) Close() {
|
2026-02-23 19:37:05 +00:00
|
|
|
s.atree.releasePage(s.pageNo)
|
2026-02-10 14:02:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HELPER
|
|
|
|
|
|
|
|
|
|
func (s *BackwardCursor) makeDecompressors() error {
|
2026-06-15 05:32:15 +03:00
|
|
|
timestampsSize, _ := bin.GetUint16(s.pageData[timestampsSizeIdx:])
|
|
|
|
|
valuesSize, _ := bin.GetUint16(s.pageData[valuesSizeIdx:])
|
2026-02-10 14:02:11 +00:00
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
payloadSize := timestampsSize + valuesSize
|
2026-02-10 14:02:11 +00:00
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
if payloadSize > dataFooterIdx {
|
|
|
|
|
return fmt.Errorf("corrupted data page %d: timestamps + values size %d gt payload size",
|
|
|
|
|
s.pageNo, payloadSize)
|
|
|
|
|
}
|
2026-02-10 14:02:11 +00:00
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
s.timestampDecompressor = enc.NewTimeDeltaDecompressor(
|
|
|
|
|
s.pageData[:timestampsSize],
|
|
|
|
|
)
|
2026-02-10 14:02:11 +00:00
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
vbuf := s.pageData[timestampsSize : timestampsSize+valuesSize]
|
2026-02-10 14:02:11 +00:00
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
switch s.metricType {
|
|
|
|
|
case qb.Instant:
|
|
|
|
|
s.valueDecompressor = enc.NewInstantDeltaDecompressor(
|
|
|
|
|
vbuf, s.fracDigits)
|
2026-02-10 14:02:11 +00:00
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
case qb.Cumulative:
|
|
|
|
|
s.valueDecompressor = enc.NewCumulativeDeltaDecompressor(
|
|
|
|
|
vbuf, s.fracDigits)
|
2026-02-10 14:02:11 +00:00
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
default:
|
|
|
|
|
return fmt.Errorf("bug: wrong metricType %d", s.metricType)
|
|
|
|
|
}
|
2026-02-10 14:02:11 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
// func makeDecompressors(pageData []byte, metricType qb.MetricType, fracDigits byte) (
|
|
|
|
|
// qb.TimestampDecompressor, qb.ValueDecompressor, error,
|
|
|
|
|
// ) {
|
|
|
|
|
// timestampsSize, _ := bin.GetUint16(pageData[timestampsSizeIdx:])
|
|
|
|
|
// valuesSize, _ := bin.GetUint16(pageData[valuesSizeIdx:])
|
|
|
|
|
|
|
|
|
|
// payloadSize := timestampsSize + valuesSize
|
|
|
|
|
|
|
|
|
|
// if payloadSize > dataFooterIdx {
|
|
|
|
|
// return nil, nil, fmt.Errorf("corrupted: timestamps + values size %d > payload size",
|
|
|
|
|
// payloadSize)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// timestampDecompressor := enc.NewTimeDeltaDecompressor(
|
|
|
|
|
// pageData[:timestampsSize],
|
|
|
|
|
// )
|
|
|
|
|
|
|
|
|
|
// vbuf := pageData[timestampsSize : timestampsSize+valuesSize]
|
|
|
|
|
|
|
|
|
|
// var valueDecompressor qb.ValueDecompressor
|
|
|
|
|
// switch metricType {
|
|
|
|
|
// case qb.Instant:
|
|
|
|
|
// valueDecompressor = enc.NewInstantDeltaDecompressor(
|
|
|
|
|
// vbuf, fracDigits)
|
|
|
|
|
|
|
|
|
|
// case qb.Cumulative:
|
|
|
|
|
// valueDecompressor = enc.NewCumulativeDeltaDecompressor(
|
|
|
|
|
// vbuf, fracDigits)
|
|
|
|
|
|
|
|
|
|
// default:
|
|
|
|
|
// return nil, nil, fmt.Errorf("bug: wrong metricType %d", metricType)
|
|
|
|
|
// }
|
|
|
|
|
//return timestampDecompressor, valueDecompressor, nil
|
|
|
|
|
// return nil, nil, nil
|
|
|
|
|
// }
|