package atree import ( "gordenko.dev/dima/qb" "gordenko.dev/dima/qb/bin" "gordenko.dev/dima/qb/util" ) const ( payloadSize = 24 recordSize = 4 + 4 // timestamp + pageNo ) type DataPage struct { PrevPageNo uint32 LowerTimestamp uint32 Timestamps [][]byte // chunks TimestampsSize int Values [][]byte // chunks ValuesSize int PageNo uint32 IsReused bool //Checksum uint32 } type IndexPage struct { LowerTimestamp uint32 Data []byte PageNo uint32 IsReused bool } type IndexLevel struct { // вже записані дані (лише для першої в списку індексної сторінки, беремо із _metric) // дані потрібні, якщо сторінка буде заповнена і піде на запис в .index файл Offset int // (заповнені одразу) LowerTimestamp uint32 // (заповнені одразу) Data []byte // поточні дані index сторінки (заповнені одразу) Filled []*IndexPage // пусто } func appendIndexRecord(data []byte, timestamp uint32, pageNo uint32) []byte { var tmp = []byte{ 0, 0, 0, 0, // timestamp 0, 0, 0, 0, // pageNo } bin.PutUint32(tmp, timestamp) bin.PutUint32(tmp[4:], pageNo) return append(data, tmp...) } type ChunksToDataPageReq struct { PrevPageNo uint32 Timestamps [][]byte // chunks TimestampsSize int Values [][]byte // chunks ValuesSize int } // return checksum func ChunksToDataPage(buf []byte, req ChunksToDataPageReq) { var ( remainingSize = int(req.TimestampsSize) pos = 0 ) for _, chunk := range req.Timestamps { if remainingSize >= len(chunk) { copy(buf[pos:], chunk) remainingSize -= len(chunk) pos += len(chunk) } else { copy(buf[pos:], chunk[:remainingSize]) break } } remainingSize = int(req.ValuesSize) pos = int(req.TimestampsSize) for _, chunk := range req.Values { if remainingSize >= len(chunk) { copy(buf[pos:], chunk) remainingSize -= len(chunk) pos += len(chunk) } else { copy(buf[pos:], chunk[:remainingSize]) break } } bin.PutUint16(buf[timestampsSizeIdx:], uint16(req.TimestampsSize)) bin.PutUint16(buf[valuesSizeIdx:], uint16(req.ValuesSize)) bin.PutUint32(buf[prevPageIdx:], req.PrevPageNo) checksum := util.CalcChecksum(buf[:dataCRC32Idx]) bin.PutUint32(buf[dataCRC32Idx:], checksum) } func DataToIndexPage(buf []byte, data []byte, lastLevel bool) { copy(buf, data) bin.PutUint16(buf[indexRecordsQtyIdx:], uint16(len(data)/indexRecordSize)) if lastLevel { buf[isLastLevelIdx] = 1 } checksum := util.CalcChecksum(buf[:indexCRC32Idx]) bin.PutUint32(buf[indexCRC32Idx:], checksum) } // fix - reduce leves func AppendDataPagesToTree(getDataPageNumber func() (uint32, bool, error), getIndexPageNumber func() (uint32, bool, error), levels []*IndexLevel, dataPages []*DataPage) { for i, d := range dataPages[1:] { pageNo, isReused, err := getDataPageNumber() if err != nil { qb.Abort(qb.FailedGetPageNumber, err) } d.PageNo = pageNo d.IsReused = isReused d.PrevPageNo = dataPages[i-1].PageNo } for _, d := range dataPages { var ( upTimestamp = d.LowerTimestamp upPageNo = d.PageNo levelIdx = 0 ) for { if levelIdx == len(levels) { // encode prev page record + up* levels = append(levels, &IndexLevel{ LowerTimestamp: upTimestamp, Data: appendIndexRecord(nil, upTimestamp, upPageNo), }) break } level := levels[levelIdx] if len(level.Data)+recordSize <= payloadSize { level.Data = appendIndexRecord(level.Data, upTimestamp, upPageNo) break } // в буфері немає місця для додавання нової пари, отже це // заповнена сторінка filled := &IndexPage{ LowerTimestamp: level.LowerTimestamp, Data: level.Data, } pageNo, isReused, err := getIndexPageNumber() if err != nil { qb.Abort(qb.FailedGetPageNumber, err) } filled.PageNo = pageNo filled.IsReused = isReused level.Filled = append(level.Filled, filled) // level.LowerTimestamp = upTimestamp level.Data = appendIndexRecord(nil, upTimestamp, upPageNo) // upPageNo = filled.PageNo upTimestamp = filled.LowerTimestamp levelIdx++ } } }