120 lines
3.2 KiB
Go
120 lines
3.2 KiB
Go
package tsdb
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
)
|
|
|
|
const (
|
|
fieldFloat = 1
|
|
fieldInteger = 2
|
|
fieldBoolean = 3
|
|
fieldString = 4
|
|
)
|
|
|
|
var (
|
|
// ErrFieldNotFound is returned when a field cannot be found.
|
|
ErrFieldNotFound = errors.New("field not found")
|
|
|
|
// ErrFieldUnmappedID is returned when the system is presented, during decode, with a field ID
|
|
// there is no mapping for.
|
|
ErrFieldUnmappedID = errors.New("field ID not mapped")
|
|
)
|
|
|
|
// FieldCodec provides encoding and decoding functionality for the fields of a given
|
|
// Measurement.
|
|
type FieldCodec struct {
|
|
fieldsByID map[uint8]*Field
|
|
fieldsByName map[string]*Field
|
|
}
|
|
|
|
// NewFieldCodec returns a FieldCodec for the given Measurement. Must be called with
|
|
// a RLock that protects the Measurement.
|
|
func NewFieldCodec(fields map[string]*Field) *FieldCodec {
|
|
fieldsByID := make(map[uint8]*Field, len(fields))
|
|
fieldsByName := make(map[string]*Field, len(fields))
|
|
for _, f := range fields {
|
|
fieldsByID[f.ID] = f
|
|
fieldsByName[f.Name] = f
|
|
}
|
|
return &FieldCodec{fieldsByID: fieldsByID, fieldsByName: fieldsByName}
|
|
}
|
|
|
|
// FieldIDByName returns the ID for the given field.
|
|
func (f *FieldCodec) FieldIDByName(s string) (uint8, error) {
|
|
fi := f.fieldsByName[s]
|
|
if fi == nil {
|
|
return 0, ErrFieldNotFound
|
|
}
|
|
return fi.ID, nil
|
|
}
|
|
|
|
// DecodeByID scans a byte slice for a field with the given ID, converts it to its
|
|
// expected type, and return that value.
|
|
func (f *FieldCodec) DecodeByID(targetID uint8, b []byte) (interface{}, error) {
|
|
var value interface{}
|
|
for {
|
|
if len(b) == 0 {
|
|
// No more bytes.
|
|
return nil, ErrFieldNotFound
|
|
}
|
|
|
|
field := f.fieldsByID[b[0]]
|
|
if field == nil {
|
|
// This can happen, though is very unlikely. If this node receives encoded data, to be written
|
|
// to disk, and is queried for that data before its metastore is updated, there will be no field
|
|
// mapping for the data during decode. All this can happen because data is encoded by the node
|
|
// that first received the write request, not the node that actually writes the data to disk.
|
|
// So if this happens, the read must be aborted.
|
|
return nil, ErrFieldUnmappedID
|
|
}
|
|
|
|
switch field.Type {
|
|
case fieldFloat:
|
|
if field.ID == targetID {
|
|
value = math.Float64frombits(binary.BigEndian.Uint64(b[1:9]))
|
|
}
|
|
b = b[9:]
|
|
case fieldInteger:
|
|
if field.ID == targetID {
|
|
value = int64(binary.BigEndian.Uint64(b[1:9]))
|
|
}
|
|
b = b[9:]
|
|
case fieldBoolean:
|
|
if field.ID == targetID {
|
|
value = b[1] == 1
|
|
}
|
|
b = b[2:]
|
|
case fieldString:
|
|
length := binary.BigEndian.Uint16(b[1:3])
|
|
if field.ID == targetID {
|
|
value = string(b[3 : 3+length])
|
|
}
|
|
b = b[3+length:]
|
|
default:
|
|
panic(fmt.Sprintf("unsupported value type during decode by id: %T", field.Type))
|
|
}
|
|
|
|
if value != nil {
|
|
return value, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// DecodeByName scans a byte slice for a field with the given name, converts it to its
|
|
// expected type, and return that value.
|
|
func (f *FieldCodec) DecodeByName(name string, b []byte) (interface{}, error) {
|
|
fi := f.FieldByName(name)
|
|
if fi == nil {
|
|
return 0, ErrFieldNotFound
|
|
}
|
|
return f.DecodeByID(fi.ID, b)
|
|
}
|
|
|
|
// FieldByName returns the field by its name. It will return a nil if not found
|
|
func (f *FieldCodec) FieldByName(name string) *Field {
|
|
return f.fieldsByName[name]
|
|
}
|