pkg/pb/v1/value.go (247 lines of code) (raw):
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package v1
import (
"bytes"
"fmt"
"strconv"
"github.com/pkg/errors"
databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1"
"github.com/apache/skywalking-banyandb/pkg/convert"
"github.com/apache/skywalking-banyandb/pkg/encoding"
"github.com/apache/skywalking-banyandb/pkg/logger"
)
// ValueType is the type of the tag and field value.
type ValueType byte
// ValueType constants.
const (
ValueTypeUnknown ValueType = iota
ValueTypeStr
ValueTypeInt64
ValueTypeFloat64
ValueTypeBinaryData
ValueTypeStrArr
ValueTypeInt64Arr
)
// MustTagValueToValueType converts modelv1.TagValue to ValueType.
func MustTagValueToValueType(tag *modelv1.TagValue) ValueType {
switch tag.Value.(type) {
case *modelv1.TagValue_Null:
return ValueTypeUnknown
case *modelv1.TagValue_Str:
return ValueTypeStr
case *modelv1.TagValue_Int:
return ValueTypeInt64
case *modelv1.TagValue_BinaryData:
return ValueTypeBinaryData
case *modelv1.TagValue_StrArray:
return ValueTypeStrArr
case *modelv1.TagValue_IntArray:
return ValueTypeInt64Arr
default:
panic("unknown tag value type")
}
}
// MustTagValueSpecToValueType converts databasev1.TagType to ValueType.
func MustTagValueSpecToValueType(tag databasev1.TagType) ValueType {
switch tag {
case databasev1.TagType_TAG_TYPE_STRING:
return ValueTypeStr
case databasev1.TagType_TAG_TYPE_INT:
return ValueTypeInt64
case databasev1.TagType_TAG_TYPE_DATA_BINARY:
return ValueTypeBinaryData
case databasev1.TagType_TAG_TYPE_STRING_ARRAY:
return ValueTypeStrArr
case databasev1.TagType_TAG_TYPE_INT_ARRAY:
return ValueTypeInt64Arr
default:
panic("unknown tag value type")
}
}
// MustTagValueToStr converts modelv1.TagValue to string.
func MustTagValueToStr(tag *modelv1.TagValue) string {
switch tag.Value.(type) {
case *modelv1.TagValue_Str:
return `"` + tag.GetStr().Value + `"`
case *modelv1.TagValue_Int:
return strconv.FormatInt(tag.GetInt().Value, 10)
case *modelv1.TagValue_BinaryData:
return fmt.Sprintf("%x", tag.GetBinaryData())
default:
panic("unknown tag value type")
}
}
// MarshalTagValues marshals tag values.
func MarshalTagValues(dest []byte, tags []*modelv1.TagValue) ([]byte, error) {
var err error
for _, tag := range tags {
dest, err = marshalTagValue(dest, tag)
if err != nil {
return nil, err
}
}
return dest, nil
}
// UnmarshalTagValues unmarshals tag values.
func UnmarshalTagValues(dest []byte, destTags []*modelv1.TagValue, src []byte) ([]byte, []*modelv1.TagValue, error) {
var err error
var tag *modelv1.TagValue
for len(src) > 0 {
dest = dest[:0]
dest, src, tag, err = unmarshalTagValue(dest, src)
if err != nil {
return nil, nil, err
}
destTags = append(destTags, tag)
}
return dest, destTags, nil
}
func marshalTagValue(dest []byte, tv *modelv1.TagValue) ([]byte, error) {
dest = append(dest, byte(MustTagValueToValueType(tv)))
switch tv.Value.(type) {
case *modelv1.TagValue_Null:
dest = marshalEntityValue(dest, nil)
case *modelv1.TagValue_Str:
dest = marshalEntityValue(dest, []byte(tv.GetStr().Value))
case *modelv1.TagValue_Int:
dest = marshalEntityValue(dest, encoding.Int64ToBytes(nil, tv.GetInt().Value))
case *modelv1.TagValue_BinaryData:
dest = marshalEntityValue(dest, tv.GetBinaryData())
default:
return nil, errors.New("unsupported tag value type: " + tv.String())
}
return dest, nil
}
func marshalTagValueWithWildcard(dest []byte, tv *modelv1.TagValue) ([]byte, error) {
if tv == AnyTagValue {
dest = marshalEntityValue(dest, anyWildcard)
return dest, nil
}
return marshalTagValue(dest, tv)
}
func unmarshalTagValue(dest []byte, src []byte) ([]byte, []byte, *modelv1.TagValue, error) {
if len(src) == 0 {
return nil, nil, nil, errors.New("empty tag value")
}
var err error
vt := ValueType(src[0])
switch vt {
case ValueTypeUnknown:
// skip ValueType and entityDelimiter
return dest, src[2:], NullTagValue, nil
case ValueTypeStr:
if dest, src, err = unmarshalEntityValue(dest, src[1:]); err != nil {
return nil, nil, nil, errors.WithMessage(err, "unmarshal string tag value")
}
if len(dest) == 0 {
return dest, src, NullTagValue, nil
}
return dest, src, &modelv1.TagValue{
Value: &modelv1.TagValue_Str{
Str: &modelv1.Str{
Value: string(dest),
},
},
}, nil
case ValueTypeInt64:
if dest, src, err = unmarshalEntityValue(dest, src[1:]); err != nil {
return nil, nil, nil, errors.WithMessage(err, "unmarshal int tag value")
}
return dest, src, &modelv1.TagValue{
Value: &modelv1.TagValue_Int{
Int: &modelv1.Int{
Value: encoding.BytesToInt64(dest),
},
},
}, nil
case ValueTypeBinaryData:
if dest, src, err = unmarshalEntityValue(dest, src[1:]); err != nil {
return nil, nil, nil, errors.WithMessage(err, "unmarshal binary tag value")
}
if len(dest) == 0 {
return dest, src, NullTagValue, nil
}
data := make([]byte, len(dest))
copy(data, dest)
return dest, src, &modelv1.TagValue{
Value: &modelv1.TagValue_BinaryData{
BinaryData: data,
},
}, nil
default:
return nil, src, nil, fmt.Errorf("unsupported tag value type %d, tag value: %s", vt, src)
}
}
const (
entityDelimiter = '|'
escape = '\\'
)
var anyWildcard = []byte{'*'}
func marshalEntityValue(dest, src []byte) []byte {
if src == nil {
dest = append(dest, entityDelimiter)
return dest
}
if bytes.IndexByte(src, entityDelimiter) < 0 && bytes.IndexByte(src, escape) < 0 {
dest = append(dest, src...)
dest = append(dest, entityDelimiter)
return dest
}
for _, b := range src {
if b == entityDelimiter || b == escape {
dest = append(dest, escape)
}
dest = append(dest, b)
}
dest = append(dest, entityDelimiter)
return dest
}
func unmarshalEntityValue(dest, src []byte) ([]byte, []byte, error) {
if len(src) == 0 {
return nil, nil, errors.New("empty entity value")
}
if src[0] == entityDelimiter {
return dest, src[1:], nil
}
for len(src) > 0 {
switch {
case src[0] == escape:
if len(src) < 2 {
return nil, nil, errors.New("invalid escape character")
}
src = src[1:]
dest = append(dest, src[0])
case src[0] == entityDelimiter:
return dest, src[1:], nil
default:
dest = append(dest, src[0])
}
src = src[1:]
}
return nil, nil, errors.New("invalid entity value")
}
// MustCompareTagValue compares two tag values.
// It returns 0 if tv1 == tv2, -1 if tv1 < tv2, 1 if tv1 > tv2.
// It panics if the tag value type is inconsistent.
func MustCompareTagValue(tv1, tv2 *modelv1.TagValue) int {
if tv1 == nil && tv2 == nil {
return 0
}
if tv1 == nil {
return -1
}
if tv2 == nil {
return 1
}
if tv1 == AnyTagValue {
return 1
}
if tv2 == AnyTagValue {
return -1
}
vt1 := MustTagValueToValueType(tv1)
vt2 := MustTagValueToValueType(tv2)
if vt1 != vt2 {
logger.Panicf("inconsistent tag value type: %v vs %v", vt1, vt2)
}
switch vt1 {
case ValueTypeStr:
return bytes.Compare(convert.StringToBytes(tv1.GetStr().Value), convert.StringToBytes(tv2.GetStr().Value))
case ValueTypeInt64:
return int(tv1.GetInt().Value - tv2.GetInt().Value)
case ValueTypeBinaryData:
return bytes.Compare(tv1.GetBinaryData(), tv2.GetBinaryData())
default:
logger.Panicf("unsupported tag value type: %v", vt1)
return 0
}
}