184 lines
5.0 KiB
Go
184 lines
5.0 KiB
Go
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
|
|
}
|