feat(models): New APIs to create Tags from a list of key, value pairs
* New API to convert `models.Tags` to a slice of key, value pairs ``` BenchmarkNewTagsKeyValues/sorted/no_dupes/preallocate-8 20000000 63.0 ns/op 0 B/op 0 allocs/op BenchmarkNewTagsKeyValues/sorted/no_dupes/allocate-8 10000000 124 ns/op 144 B/op 1 allocs/op BenchmarkNewTagsKeyValues/sorted/dupes-8 10000000 181 ns/op 240 B/op 1 allocs/op BenchmarkNewTagsKeyValues/unsorted/no_dupes-8 10000000 204 ns/op 176 B/op 2 allocs/op BenchmarkNewTagsKeyValues/unsorted/dupes-8 5000000 308 ns/op 272 B/op 2 allocs/op ```pull/15033/head
parent
61c75ae434
commit
4da079410f
|
@ -55,6 +55,10 @@ var (
|
|||
|
||||
// ErrInvalidPoint is returned when a point cannot be parsed correctly.
|
||||
ErrInvalidPoint = errors.New("point is invalid")
|
||||
|
||||
// ErrInvalidKevValuePairs is returned when the number of key, value pairs
|
||||
// is odd, indicating a missing value.
|
||||
ErrInvalidKevValuePairs = errors.New("key, value pairs is an odd length")
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -2034,6 +2038,63 @@ func NewTags(m map[string]string) Tags {
|
|||
return a
|
||||
}
|
||||
|
||||
// NewTagsKeyValues returns a new Tags from a list of key, value pairs,
|
||||
// ensuring the returned result is correctly sorted. Duplicate keys are removed,
|
||||
// however, it which duplicate that remains is undefined.
|
||||
// NewTagsKeyValues will return ErrInvalidKevValuePairs if len(kvs) is not even.
|
||||
// If the input is guaranteed to be even, the error can be safely ignored.
|
||||
// If a has enough capacity, it will be reused.
|
||||
func NewTagsKeyValues(a Tags, kv ...[]byte) (Tags, error) {
|
||||
if len(kv)%2 == 1 {
|
||||
return nil, ErrInvalidKevValuePairs
|
||||
}
|
||||
if len(kv) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
l := len(kv) / 2
|
||||
if cap(a) < l {
|
||||
a = make(Tags, 0, l)
|
||||
} else {
|
||||
a = a[:0]
|
||||
}
|
||||
|
||||
for i := 0; i < len(kv)-1; i += 2 {
|
||||
a = append(a, NewTag(kv[i], kv[i+1]))
|
||||
}
|
||||
|
||||
if !a.sorted() {
|
||||
sort.Sort(a)
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
j := 0
|
||||
for i := 0; i < len(a)-1; i++ {
|
||||
if !bytes.Equal(a[i].Key, a[i+1].Key) {
|
||||
if j != i {
|
||||
// only copy if j has deviated from i, indicating duplicates
|
||||
a[j] = a[i]
|
||||
}
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
a[j] = a[len(a)-1]
|
||||
j++
|
||||
|
||||
return a[:j], nil
|
||||
}
|
||||
|
||||
// NewTagsKeyValuesStrings is equivalent to NewTagsKeyValues, except that
|
||||
// it will allocate new byte slices for each key, value pair.
|
||||
func NewTagsKeyValuesStrings(a Tags, kvs ...string) (Tags, error) {
|
||||
kv := make([][]byte, len(kvs))
|
||||
for i := range kvs {
|
||||
kv[i] = []byte(kvs[i])
|
||||
}
|
||||
return NewTagsKeyValues(a, kv...)
|
||||
}
|
||||
|
||||
// Keys returns the list of keys for a tag set.
|
||||
func (a Tags) Keys() []string {
|
||||
if len(a) == 0 {
|
||||
|
@ -2100,6 +2161,34 @@ func (a Tags) Clone() Tags {
|
|||
return others
|
||||
}
|
||||
|
||||
// KeyValues returns the Tags as a list of key, value pairs,
|
||||
// maintaining the original order of a. v will be used if it has
|
||||
// capacity.
|
||||
func (a Tags) KeyValues(v [][]byte) [][]byte {
|
||||
l := a.Len() * 2
|
||||
if cap(v) < l {
|
||||
v = make([][]byte, 0, l)
|
||||
} else {
|
||||
v = v[:l]
|
||||
}
|
||||
for i := range a {
|
||||
v = append(v, a[i].Key, a[i].Value)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// sorted returns true if a is sorted and is an optimization
|
||||
// to avoid an allocation when calling sort.IsSorted, improving
|
||||
// performance as much as 50%.
|
||||
func (a Tags) sorted() bool {
|
||||
for i := len(a) - 1; i > 0; i-- {
|
||||
if string(a[i].Key) < string(a[i-1].Key) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a Tags) Len() int { return len(a) }
|
||||
func (a Tags) Less(i, j int) bool { return bytes.Compare(a[i].Key, a[j].Key) == -1 }
|
||||
func (a Tags) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/influxdata/influxdb/models"
|
||||
)
|
||||
|
||||
|
@ -2619,6 +2620,90 @@ func TestValidTagTokens(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func equalError(a, b error) bool {
|
||||
return a == nil && b == nil || a != nil && b != nil && a.Error() == b.Error()
|
||||
}
|
||||
|
||||
func TestNewTagsKeyValues(t *testing.T) {
|
||||
t.Run("sorted", func(t *testing.T) {
|
||||
t.Run("no dupes", func(t *testing.T) {
|
||||
got, _ := models.NewTagsKeyValuesStrings(nil, "tag0", "v0", "tag1", "v1", "tag2", "v2")
|
||||
exp := models.NewTags(map[string]string{
|
||||
"tag0": "v0",
|
||||
"tag1": "v1",
|
||||
"tag2": "v2",
|
||||
})
|
||||
if !cmp.Equal(got, exp) {
|
||||
t.Errorf("unxpected; -got/+exp\n%s", cmp.Diff(got, exp))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("dupes", func(t *testing.T) {
|
||||
got, _ := models.NewTagsKeyValuesStrings(nil, "tag0", "v0", "tag1", "v1", "tag1", "v1", "tag2", "v2", "tag2", "v2")
|
||||
exp := models.NewTags(map[string]string{
|
||||
"tag0": "v0",
|
||||
"tag1": "v1",
|
||||
"tag2": "v2",
|
||||
})
|
||||
if !cmp.Equal(got, exp) {
|
||||
t.Errorf("unxpected; -got/+exp\n%s", cmp.Diff(got, exp))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("unsorted", func(t *testing.T) {
|
||||
t.Run("no dupes", func(t *testing.T) {
|
||||
got, _ := models.NewTagsKeyValuesStrings(nil, "tag2", "v2", "tag0", "v0", "tag1", "v1")
|
||||
exp := models.NewTags(map[string]string{
|
||||
"tag0": "v0",
|
||||
"tag1": "v1",
|
||||
"tag2": "v2",
|
||||
})
|
||||
if !cmp.Equal(got, exp) {
|
||||
t.Errorf("unxpected; -got/+exp\n%s", cmp.Diff(got, exp))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("dupes", func(t *testing.T) {
|
||||
got, _ := models.NewTagsKeyValuesStrings(nil, "tag2", "v2", "tag0", "v0", "tag1", "v1", "tag2", "v2", "tag0", "v0", "tag1", "v1")
|
||||
exp := models.NewTags(map[string]string{
|
||||
"tag0": "v0",
|
||||
"tag1": "v1",
|
||||
"tag2": "v2",
|
||||
})
|
||||
if !cmp.Equal(got, exp) {
|
||||
t.Errorf("unxpected; -got/+exp\n%s", cmp.Diff(got, exp))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("odd number of keys", func(t *testing.T) {
|
||||
got, err := models.NewTagsKeyValuesStrings(nil, "tag2", "v2", "tag0", "v0", "tag1")
|
||||
|
||||
if !cmp.Equal(got, models.Tags(nil)) {
|
||||
t.Errorf("expected nil")
|
||||
}
|
||||
|
||||
if !cmp.Equal(err, models.ErrInvalidKevValuePairs, cmp.Comparer(equalError)) {
|
||||
t.Errorf("expected ErrInvalidKevValuePairs, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTags_KeyValues(t *testing.T) {
|
||||
tags := models.NewTags(map[string]string{
|
||||
"tag0": "v0",
|
||||
"tag1": "v1",
|
||||
"tag2": "v2",
|
||||
})
|
||||
|
||||
got := tags.KeyValues(nil)
|
||||
exp := [][]byte{[]byte("tag0"), []byte("v0"), []byte("tag1"), []byte("v1"), []byte("tag2"), []byte("v2")}
|
||||
if !cmp.Equal(got, exp) {
|
||||
t.Errorf("unexpected, -got/+exp\n%s", cmp.Diff(got, exp))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEscapeStringField_Plain(b *testing.B) {
|
||||
s := "nothing special"
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
@ -2749,3 +2834,53 @@ func BenchmarkMakeKey(b *testing.B) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewTagsKeyValues(b *testing.B) {
|
||||
b.Run("sorted", func(b *testing.B) {
|
||||
b.Run("no dupes", func(b *testing.B) {
|
||||
kv := [][]byte{[]byte("tag0"), []byte("v0"), []byte("tag1"), []byte("v1"), []byte("tag2"), []byte("v2")}
|
||||
|
||||
b.Run("preallocate", func(b *testing.B) {
|
||||
t := make(models.Tags, 3)
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = models.NewTagsKeyValues(t, kv...)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("allocate", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = models.NewTagsKeyValues(nil, kv...)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("dupes", func(b *testing.B) {
|
||||
kv := [][]byte{[]byte("tag0"), []byte("v0"), []byte("tag1"), []byte("v1"), []byte("tag1"), []byte("v1"), []byte("tag2"), []byte("v2"), []byte("tag2"), []byte("v2")}
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = models.NewTagsKeyValues(nil, kv...)
|
||||
}
|
||||
})
|
||||
})
|
||||
b.Run("unsorted", func(b *testing.B) {
|
||||
b.Run("no dupes", func(b *testing.B) {
|
||||
kv := [][]byte{[]byte("tag1"), []byte("v1"), []byte("tag0"), []byte("v0"), []byte("tag2"), []byte("v2")}
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = models.NewTagsKeyValues(nil, kv...)
|
||||
}
|
||||
})
|
||||
b.Run("dupes", func(b *testing.B) {
|
||||
kv := [][]byte{[]byte("tag1"), []byte("v1"), []byte("tag2"), []byte("v2"), []byte("tag0"), []byte("v0"), []byte("tag1"), []byte("v1"), []byte("tag2"), []byte("v2")}
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = models.NewTagsKeyValues(nil, kv...)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue