package storage import ( "errors" "fmt" bin "gordenko.dev/dima/bin/little" "gordenko.dev/dima/qb" "gordenko.dev/dima/qb/enc" "gordenko.dev/dima/qb/util" ) type BackwardCursor struct { metricType qb.MetricType fracDigits byte fetchDataPage func(uint32) ([]byte, error) releasePage func(uint32) pageNo uint32 pageData []byte timestampDecompressor qb.TimestampDecompressor valueDecompressor qb.ValueDecompressor } type BackwardCursorOptions struct { MetricType qb.MetricType FracDigits byte PageNo uint32 PageData []byte FetchDataPage func(uint32) ([]byte, error) ReleasePage func(uint32) } func NewBackwardCursor(opt BackwardCursorOptions) (*BackwardCursor, error) { fmt.Printf("footer: % x\n", opt.PageData[DataPageSize-DataPageFooterSize:]) switch opt.MetricType { case qb.Instant, qb.Cumulative: // ok default: return nil, fmt.Errorf("MetricType option has wrong value: %d", opt.MetricType) } if opt.FracDigits > qb.MaxFracDigits { return nil, errors.New("FracDigits option is required") } if opt.FetchDataPage == nil { return nil, errors.New("FetchDataPage option is required") } if opt.ReleasePage == nil { return nil, errors.New("ReleasePage 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, fetchDataPage: opt.FetchDataPage, releasePage: opt.ReleasePage, pageNo: opt.PageNo, pageData: opt.PageData, } var err error s.timestampDecompressor, err = CreateTimestampDecompressor(s.pageData) if err != nil { return nil, fmt.Errorf("CreateTimestampDecompressor: %s", err) } s.valueDecompressor, err = CreateValueDecompressor(s.pageData, s.metricType, s.fracDigits) if err != nil { return nil, fmt.Errorf("CreateValueDecompressor: %s", 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:]) fmt.Printf("CURSOR: prev pageNo %d\n", prevPageNo) if prevPageNo == 0 { return 0, 0, true, nil } s.releasePage(s.pageNo) s.pageNo = prevPageNo s.pageData, err = s.fetchDataPage(s.pageNo) if err != nil { return 0, 0, false, fmt.Errorf("fetchDataPage(%d): %s", s.pageNo, err) } s.timestampDecompressor, err = CreateTimestampDecompressor(s.pageData) if err != nil { return 0, 0, false, fmt.Errorf("CreateTimestampDecompressor: %s", err) } s.valueDecompressor, err = CreateValueDecompressor(s.pageData, s.metricType, s.fracDigits) if err != nil { return 0, 0, false, fmt.Errorf("CreateValueDecompressor: %s", 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.releasePage(s.pageNo) } func CreateTimestampDecompressor(page []byte) (qb.TimestampDecompressor, error) { size, _ := bin.GetUint16(page[timestampsSizeIdx:]) if int(size) > DataPagePayloadSize { return nil, fmt.Errorf("bug: invalid timestamps size %d", size) } pos := DataPagePayloadSize - int(size) d := enc.NewTimeDeltaDecompressor() d.RestoreFromEnd(page[pos:DataPagePayloadSize]) payload := page[pos:DataPagePayloadSize] fmt.Printf("PAGE timestamps (CURSOR) %d:\n% x\n", len(payload), payload) return d, nil } func CreateValueDecompressor(page []byte, metricType qb.MetricType, fracDigits byte) (qb.ValueDecompressor, error) { size, _ := bin.GetUint16(page[valuesSizeIdx:]) if int(size) > DataPagePayloadSize { return nil, fmt.Errorf("bug: invalid values size %d", size) } d := enc.NewValueDeltaDecompressor(metricType, fracDigits) d.RestoreFromEnd(page[:size]) return d, nil } func VerifyDataPageCRC32(data []byte) error { var ( calculatedCRC = util.CalculateCRC32(data[:dataCRC32Idx]) writtenCRC, _ = bin.GetUint32(data[dataCRC32Idx:]) ) if calculatedCRC != writtenCRC { return fmt.Errorf("calculated CRC32 %d are not equal written CRC32 %d", calculatedCRC, writtenCRC) } return nil } func VerifyIndexPageCRC32(data []byte) error { var ( calculatedCRC = util.CalculateCRC32(data[:indexCRC32Idx]) writtenCRC, _ = bin.GetUint32(data[indexCRC32Idx:]) ) if calculatedCRC != writtenCRC { return fmt.Errorf("calculated CRC32 %d are not equal written CRC32 %d", calculatedCRC, writtenCRC) } return nil }