package atree import ( "errors" "fmt" octopus "gordenko.dev/dima/diploma" "gordenko.dev/dima/diploma/bin" "gordenko.dev/dima/diploma/enc" ) type BackwardCursor struct { metricType octopus.MetricType fracDigits byte atree *Atree pageNo uint32 pageData []byte timestampDecompressor octopus.TimestampDecompressor valueDecompressor octopus.ValueDecompressor } type BackwardCursorOptions struct { MetricType octopus.MetricType FracDigits byte PageNo uint32 PageData []byte Atree *Atree } func NewBackwardCursor(opt BackwardCursorOptions) (*BackwardCursor, error) { switch opt.MetricType { case octopus.Instant, octopus.Cumulative: // ok default: return nil, fmt.Errorf("MetricType option has wrong value: %d", opt.MetricType) } if opt.FracDigits > octopus.MaxFracDigits { 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 done bool err error ) 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.releaseDataPage(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) } return timestamp, value, false, nil } func (s *BackwardCursor) Close() { s.atree.releaseDataPage(s.pageNo) } // HELPER func (s *BackwardCursor) makeDecompressors() error { timestampsPayloadSize := bin.GetUint16(s.pageData[timestampsSizeIdx:]) valuesPayloadSize := bin.GetUint16(s.pageData[valuesSizeIdx:]) payloadSize := timestampsPayloadSize + valuesPayloadSize if payloadSize > dataFooterIdx { return fmt.Errorf("corrupted data page %d: timestamps + values size %d gt payload size", s.pageNo, payloadSize) } s.timestampDecompressor = enc.NewReverseTimeDeltaOfDeltaDecompressor( s.pageData[:timestampsPayloadSize], ) vbuf := s.pageData[timestampsPayloadSize : timestampsPayloadSize+valuesPayloadSize] switch s.metricType { case octopus.Instant: s.valueDecompressor = enc.NewReverseInstantDeltaDecompressor( vbuf, s.fracDigits) case octopus.Cumulative: s.valueDecompressor = enc.NewReverseCumulativeDeltaDecompressor( vbuf, s.fracDigits) default: return fmt.Errorf("bug: wrong metricType %d", s.metricType) } return nil } func makeDecompressors(pageData []byte, metricType octopus.MetricType, fracDigits byte) ( octopus.TimestampDecompressor, octopus.ValueDecompressor, error, ) { timestampsPayloadSize := bin.GetUint16(pageData[timestampsSizeIdx:]) valuesPayloadSize := bin.GetUint16(pageData[valuesSizeIdx:]) payloadSize := timestampsPayloadSize + valuesPayloadSize if payloadSize > dataFooterIdx { return nil, nil, fmt.Errorf("corrupted: timestamps + values size %d > payload size", payloadSize) } timestampDecompressor := enc.NewReverseTimeDeltaOfDeltaDecompressor( pageData[:timestampsPayloadSize], ) vbuf := pageData[timestampsPayloadSize : timestampsPayloadSize+valuesPayloadSize] var valueDecompressor octopus.ValueDecompressor switch metricType { case octopus.Instant: valueDecompressor = enc.NewReverseInstantDeltaDecompressor( vbuf, fracDigits) case octopus.Cumulative: valueDecompressor = enc.NewReverseCumulativeDeltaDecompressor( vbuf, fracDigits) default: return nil, nil, fmt.Errorf("bug: wrong metricType %d", metricType) } return timestampDecompressor, valueDecompressor, nil }