feat(tsm1): Provide columnar value types

* separate slices for time and values
* structured to be Arrow ready
* batch decoders fill time and value slices independently that
  vastly improves performance (benchmarks linked in PR)
pull/10084/head
Stuart Carnie 2018-07-03 07:32:12 -07:00
parent b3e53ae2dc
commit 9c29cd69e5
6 changed files with 1911 additions and 0 deletions

1007
tsdb/arrayvalues.gen.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,207 @@
package tsdb
{{range .}}
{{ $typename := print .Name "Array" }}
type {{ $typename }} struct {
Timestamps []int64
Values []{{.Type}}
}
func New{{$typename}}Len(sz int) *{{$typename}} {
return &{{$typename}}{
Timestamps: make([]int64, sz),
Values: make([]{{.Type}}, sz),
}
}
func (a *{{ $typename }}) MinTime() int64 {
return a.Timestamps[0]
}
func (a *{{ $typename }}) MaxTime() int64 {
return a.Timestamps[len(a.Timestamps)-1]
}
func (a *{{ $typename }}) Size() int {
panic("not implemented")
}
func (a *{{ $typename}}) Len() int {
return len(a.Timestamps)
}
// Exclude removes the subset of values not in [min, max]. The values must
// be deduplicated and sorted before calling Exclude or the results are undefined.
func (a *{{ $typename }}) Exclude(min, max int64) {
rmin, rmax := a.FindRange(min, max)
if rmin == -1 && rmax == -1 {
return
}
// a.Timestamps[rmin] ≥ min
// a.Timestamps[rmax] ≥ max
if rmax < a.Len() {
if a.Timestamps[rmax] == max {
rmax++
}
rest := a.Len()-rmax
if rest > 0 {
ts := a.Timestamps[:rmin+rest]
copy(ts[rmin:], a.Timestamps[rmax:])
a.Timestamps = ts
vs := a.Values[:rmin+rest]
copy(vs[rmin:], a.Values[rmax:])
a.Values = vs
}
} else {
a.Timestamps = a.Timestamps[:rmin]
a.Values = a.Values[:rmin]
}
}
// Include returns the subset values between min and max inclusive. The values must
// be deduplicated and sorted before calling Exclude or the results are undefined.
func (a *{{ $typename }}) Include(min, max int64) {
rmin, rmax := a.FindRange(min, max)
if rmin == -1 && rmax == -1 {
a.Timestamps = a.Timestamps[:0]
a.Values = a.Values[:0]
return
}
// a.Timestamps[rmin] ≥ min
// a.Timestamps[rmax] ≥ max
if rmax < a.Len() && a.Timestamps[rmax] == max {
rmax++
}
if rmin > -1 {
ts := a.Timestamps[:rmax-rmin]
copy(ts, a.Timestamps[rmin:rmax])
a.Timestamps = ts
vs := a.Values[:rmax-rmin]
copy(vs, a.Values[rmin:rmax])
a.Values = vs
} else {
a.Timestamps = a.Timestamps[:rmax]
a.Values = a.Values[:rmax]
}
}
// search performs a binary search for UnixNano() v in a
// and returns the position, i, where v would be inserted.
// An additional check of a.Timestamps[i] == v is necessary
// to determine if the value v exists.
func (a *{{ $typename }}) search(v int64) int {
// Define: f(x) → a.Timestamps[x] < v
// Define: f(-1) == true, f(n) == false
// Invariant: f(lo-1) == true, f(hi) == false
lo := 0
hi := a.Len()
for lo < hi {
mid := int(uint(lo+hi) >> 1)
if a.Timestamps[mid] < v {
lo = mid + 1 // preserves f(lo-1) == true
} else {
hi = mid // preserves f(hi) == false
}
}
// lo == hi
return lo
}
// FindRange returns the positions where min and max would be
// inserted into the array. If a[0].UnixNano() > max or
// a[len-1].UnixNano() < min then FindRange returns (-1, -1)
// indicating the array is outside the [min, max]. The values must
// be deduplicated and sorted before calling Exclude or the results
// are undefined.
func (a *{{ $typename }}) FindRange(min, max int64) (int, int) {
if a.Len() == 0 || min > max {
return -1, -1
}
minVal := a.MinTime()
maxVal := a.MaxTime()
if maxVal < min || minVal > max {
return -1, -1
}
return a.search(min), a.search(max)
}
// Merge overlays b to top of a. If two values conflict with
// the same timestamp, b is used. Both a and b must be sorted
// in ascending order.
func (a *{{ $typename }}) Merge(b *{{ $typename }}) {
if a.Len() == 0 {
*a = *b
return
}
if b.Len() == 0 {
return
}
// Normally, both a and b should not contain duplicates. Due to a bug in older versions, it's
// possible stored blocks might contain duplicate values. Remove them if they exists before
// merging.
// a = a.Deduplicate()
// b = b.Deduplicate()
if a.MaxTime() < b.MinTime() {
a.Timestamps = append(a.Timestamps, b.Timestamps...)
a.Values = append(a.Values, b.Values...)
return
}
if b.MaxTime() < a.MinTime() {
var tmp {{$typename}}
tmp.Timestamps = append(b.Timestamps, a.Timestamps...)
tmp.Values = append(b.Values, a.Values...)
*a = tmp
return
}
out := New{{$typename}}Len(a.Len()+b.Len())
i, j, k := 0, 0, 0
for i < len(a.Timestamps) && j < len(b.Timestamps) {
if a.Timestamps[i] < b.Timestamps[j] {
out.Timestamps[k] = a.Timestamps[i]
out.Values[k] = a.Values[i]
i++
} else if a.Timestamps[i] == b.Timestamps[j] {
out.Timestamps[k] = b.Timestamps[j]
out.Values[k] = b.Values[j]
i++
j++
} else {
out.Timestamps[k] = b.Timestamps[j]
out.Values[k] = b.Values[j]
j++
}
k++
}
if i < len(a.Timestamps) {
n := copy(out.Timestamps[k:], a.Timestamps[i:])
copy(out.Values[k:], a.Values[i:])
k += n
} else if j < len(b.Timestamps) {
n := copy(out.Timestamps[k:], b.Timestamps[j:])
copy(out.Values[k:], b.Values[j:])
k += n
}
a.Timestamps = out.Timestamps[:k]
a.Values = out.Values[:k]
}
{{ end }}

View File

@ -0,0 +1,27 @@
[
{
"Name":"Float",
"name":"float",
"Type":"float64"
},
{
"Name":"Integer",
"name":"integer",
"Type":"int64"
},
{
"Name":"Unsigned",
"name":"unsigned",
"Type":"uint64"
},
{
"Name":"String",
"name":"string",
"Type":"string"
},
{
"Name":"Boolean",
"name":"boolean",
"Type":"bool"
}
]

View File

@ -0,0 +1,209 @@
package tsdb
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
)
func makeIntegerArray(count int, min, max int64) *IntegerArray {
vals := NewIntegerArrayLen(count)
ts := min
inc := (max - min) / int64(count)
for i := 0; i < count; i++ {
vals.Timestamps[i] = ts
ts += inc
}
return vals
}
func makeIntegerArrayFromSlice(t []int64) *IntegerArray {
iv := NewIntegerArrayLen(len(t))
copy(iv.Timestamps, t)
return iv
}
func TestIntegerArray_FindRangeNoValues(t *testing.T) {
var vals IntegerArray
l, r := vals.FindRange(0, 100)
if exp := -1; l != exp {
t.Errorf("invalid l; exp=%d, got=%d", exp, l)
}
if exp := -1; r != exp {
t.Errorf("invalid r; exp=%d, got=%d", exp, r)
}
}
func TestIntegerArray_FindRange(t *testing.T) {
vals := makeIntegerArrayFromSlice([]int64{10, 11, 13, 15, 17, 20, 21})
cases := []struct {
min, max int64
l, r int
}{
{12, 20, 2, 5},
{22, 40, -1, -1},
{1, 9, -1, -1},
{1, 10, 0, 0},
{1, 11, 0, 1},
{15, 15, 3, 3},
}
for _, tc := range cases {
t.Run(fmt.Sprintf("%d→%d", tc.min, tc.max), func(t *testing.T) {
l, r := vals.FindRange(tc.min, tc.max)
if l != tc.l {
t.Errorf("left: got %d, exp %d", l, tc.l)
}
if r != tc.r {
t.Errorf("right: got %d, exp %d", r, tc.r)
}
})
}
}
func TestIntegerArray_Exclude(t *testing.T) {
cases := []struct {
n string
min, max int64
exp []int64
}{
{"excl bad range", 18, 11, []int64{10, 12, 14, 16, 18}},
{"excl none-lo", 0, 9, []int64{10, 12, 14, 16, 18}},
{"excl none-hi", 19, 30, []int64{10, 12, 14, 16, 18}},
{"excl first", 0, 10, []int64{12, 14, 16, 18}},
{"excl last", 18, 20, []int64{10, 12, 14, 16}},
{"excl all but first and last", 12, 16, []int64{10, 18}},
{"excl none in middle", 13, 13, []int64{10, 12, 14, 16, 18}},
{"excl middle", 14, 14, []int64{10, 12, 16, 18}},
}
for _, tc := range cases {
t.Run(fmt.Sprintf("%s[%d,%d]", tc.n, tc.min, tc.max), func(t *testing.T) {
vals := makeIntegerArray(5, 10, 20)
vals.Exclude(tc.min, tc.max)
got := vals.Timestamps
if !cmp.Equal(got, tc.exp) {
t.Errorf("unexpected values -got/+exp\n%s", cmp.Diff(got, tc.exp))
}
})
}
}
func TestIntegerArray_Include(t *testing.T) {
cases := []struct {
n string
min, max int64
exp []int64
}{
{"incl none-lo", 0, 9, []int64{}},
{"incl none-hi", 19, 30, []int64{}},
{"incl first", 0, 10, []int64{10}},
{"incl last", 18, 20, []int64{18}},
{"incl all but first and last", 12, 16, []int64{12, 14, 16}},
{"incl none in middle", 13, 13, []int64{}},
{"incl middle", 14, 14, []int64{14}},
}
for _, tc := range cases {
t.Run(fmt.Sprintf("%s[%d,%d]", tc.n, tc.min, tc.max), func(t *testing.T) {
vals := makeIntegerArray(5, 10, 20)
vals.Include(tc.min, tc.max)
got := vals.Timestamps
if !cmp.Equal(got, tc.exp) {
t.Errorf("unexpected values -got/+exp\n%s", cmp.Diff(got, tc.exp))
}
})
}
}
func benchExclude(b *testing.B, vals *IntegerArray, min, max int64) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
vals.Exclude(min, max)
}
}
func BenchmarkIntegerArray_ExcludeNone_1000(b *testing.B) {
benchExclude(b, makeIntegerArray(1000, 1000, 2000), 0, 500)
}
func BenchmarkIntegerArray_ExcludeMiddleHalf_1000(b *testing.B) {
benchExclude(b, makeIntegerArray(1000, 1000, 2000), 1250, 1750)
}
func BenchmarkIntegerArray_ExcludeFirst_1000(b *testing.B) {
benchExclude(b, makeIntegerArray(1000, 1000, 2000), 0, 1000)
}
func BenchmarkIntegerArray_ExcludeLast_1000(b *testing.B) {
benchExclude(b, makeIntegerArray(1000, 1000, 2000), 1999, 2000)
}
func BenchmarkIntegerArray_ExcludeNone_10000(b *testing.B) {
benchExclude(b, makeIntegerArray(10000, 10000, 20000), 00, 5000)
}
func BenchmarkIntegerArray_ExcludeMiddleHalf_10000(b *testing.B) {
benchExclude(b, makeIntegerArray(10000, 10000, 20000), 12500, 17500)
}
func BenchmarkIntegerArray_ExcludeFirst_10000(b *testing.B) {
benchExclude(b, makeIntegerArray(10000, 10000, 20000), 0, 10000)
}
func BenchmarkIntegerArray_ExcludeLast_10000(b *testing.B) {
benchExclude(b, makeIntegerArray(10000, 10000, 20000), 19999, 20000)
}
func benchInclude(b *testing.B, vals *IntegerArray, min, max int64) {
src := *vals
tmp := NewIntegerArrayLen(vals.Len())
copy(tmp.Timestamps, vals.Timestamps)
copy(tmp.Values, vals.Values)
b.ResetTimer()
for i := 0; i < b.N; i++ {
vals.Include(min, max)
*vals = src
copy(vals.Timestamps, tmp.Timestamps)
copy(vals.Values, tmp.Values)
}
}
func BenchmarkIntegerArray_IncludeNone_1000(b *testing.B) {
benchInclude(b, makeIntegerArray(1000, 1000, 2000), 0, 500)
}
func BenchmarkIntegerArray_IncludeMiddleHalf_1000(b *testing.B) {
benchInclude(b, makeIntegerArray(1000, 1000, 2000), 1250, 1750)
}
func BenchmarkIntegerArray_IncludeFirst_1000(b *testing.B) {
benchInclude(b, makeIntegerArray(1000, 1000, 2000), 0, 1000)
}
func BenchmarkIntegerArray_IncludeLast_1000(b *testing.B) {
benchInclude(b, makeIntegerArray(1000, 1000, 2000), 1999, 2000)
}
func BenchmarkIntegerArray_IncludeNone_10000(b *testing.B) {
benchInclude(b, makeIntegerArray(10000, 10000, 20000), 00, 5000)
}
func BenchmarkIntegerArray_IncludeMiddleHalf_10000(b *testing.B) {
benchInclude(b, makeIntegerArray(10000, 10000, 20000), 12500, 17500)
}
func BenchmarkIntegerArray_IncludeFirst_10000(b *testing.B) {
benchInclude(b, makeIntegerArray(10000, 10000, 20000), 0, 10000)
}
func BenchmarkIntegerArray_IncludeLast_10000(b *testing.B) {
benchInclude(b, makeIntegerArray(10000, 10000, 20000), 19999, 20000)
}

459
tsdb/arrayvalues_test.go Normal file
View File

@ -0,0 +1,459 @@
package tsdb_test
import (
"strconv"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/influxdb/tsdb"
)
func makeBooleanArray(v ...interface{}) *tsdb.BooleanArray {
if len(v)&1 == 1 {
panic("invalid array length")
}
a := tsdb.NewBooleanArrayLen(len(v) / 2)
for i := 0; i < len(v); i += 2 {
a.Timestamps[i/2] = int64(v[i].(int))
a.Values[i/2] = v[i+1].(bool)
}
return a
}
func makeFloatArray(v ...interface{}) *tsdb.FloatArray {
if len(v)&1 == 1 {
panic("invalid array length")
}
a := tsdb.NewFloatArrayLen(len(v) / 2)
for i := 0; i < len(v); i += 2 {
a.Timestamps[i/2] = int64(v[i].(int))
a.Values[i/2] = v[i+1].(float64)
}
return a
}
func makeIntegerArray(v ...interface{}) *tsdb.IntegerArray {
if len(v)&1 == 1 {
panic("invalid array length")
}
a := tsdb.NewIntegerArrayLen(len(v) / 2)
for i := 0; i < len(v); i += 2 {
a.Timestamps[i/2] = int64(v[i].(int))
a.Values[i/2] = int64(v[i+1].(int))
}
return a
}
func makeUnsignedArray(v ...interface{}) *tsdb.UnsignedArray {
if len(v)&1 == 1 {
panic("invalid array length")
}
a := tsdb.NewUnsignedArrayLen(len(v) / 2)
for i := 0; i < len(v); i += 2 {
a.Timestamps[i/2] = int64(v[i].(int))
a.Values[i/2] = uint64(v[i+1].(int))
}
return a
}
func makeStringArray(v ...interface{}) *tsdb.StringArray {
if len(v)&1 == 1 {
panic("invalid array length")
}
a := tsdb.NewStringArrayLen(len(v) / 2)
for i := 0; i < len(v); i += 2 {
a.Timestamps[i/2] = int64(v[i].(int))
a.Values[i/2] = strconv.Itoa(v[i+1].(int))
}
return a
}
func TestBooleanArray_Merge(t *testing.T) {
tests := []struct {
name string
a, b, exp *tsdb.BooleanArray
}{
{
name: "empty a",
a: makeBooleanArray(),
b: makeBooleanArray(1, true, 2, true),
exp: makeBooleanArray(1, true, 2, true),
},
{
name: "empty b",
a: makeBooleanArray(1, true, 2, true),
b: makeBooleanArray(),
exp: makeBooleanArray(1, true, 2, true),
},
{
name: "b replaces a",
a: makeBooleanArray(1, true),
b: makeBooleanArray(
0, false,
1, false, // overwrites a
2, false,
3, false,
4, false,
),
exp: makeBooleanArray(0, false, 1, false, 2, false, 3, false, 4, false),
},
{
name: "b replaces partial a",
a: makeBooleanArray(1, true, 2, true, 3, true, 4, true),
b: makeBooleanArray(
1, false, // overwrites a
2, false, // overwrites a
),
exp: makeBooleanArray(
1, false, // overwrites a
2, false, // overwrites a
3, true,
4, true,
),
},
{
name: "b replaces all a",
a: makeBooleanArray(1, true, 2, true, 3, true, 4, true),
b: makeBooleanArray(1, false, 2, false, 3, false, 4, false),
exp: makeBooleanArray(1, false, 2, false, 3, false, 4, false),
},
{
name: "b replaces a interleaved",
a: makeBooleanArray(0, true, 1, true, 2, true, 3, true, 4, true),
b: makeBooleanArray(0, false, 2, false, 4, false),
exp: makeBooleanArray(0, false, 1, true, 2, false, 3, true, 4, false),
},
{
name: "b merges a interleaved",
a: makeBooleanArray(0, true, 2, true, 4, true),
b: makeBooleanArray(1, false, 3, false, 5, false),
exp: makeBooleanArray(0, true, 1, false, 2, true, 3, false, 4, true, 5, false),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
test.a.Merge(test.b)
if !cmp.Equal(test.a, test.exp) {
t.Fatalf("unexpected values -got/+exp\n%s", cmp.Diff(test.a, test.exp))
}
})
}
}
func TestFloatArray_Merge(t *testing.T) {
tests := []struct {
name string
a, b, exp *tsdb.FloatArray
}{
{
name: "empty a",
a: makeFloatArray(),
b: makeFloatArray(1, 1.1, 2, 2.1),
exp: makeFloatArray(1, 1.1, 2, 2.1),
},
{
name: "empty b",
a: makeFloatArray(1, 1.0, 2, 2.0),
b: makeFloatArray(),
exp: makeFloatArray(1, 1.0, 2, 2.0),
},
{
name: "b replaces a",
a: makeFloatArray(1, 1.0),
b: makeFloatArray(
0, 0.1,
1, 1.1, // overwrites a
2, 2.1,
3, 3.1,
4, 4.1,
),
exp: makeFloatArray(0, 0.1, 1, 1.1, 2, 2.1, 3, 3.1, 4, 4.1),
},
{
name: "b replaces partial a",
a: makeFloatArray(1, 1.0, 2, 2.0, 3, 3.0, 4, 4.0),
b: makeFloatArray(
1, 1.1, // overwrites a
2, 2.1, // overwrites a
),
exp: makeFloatArray(
1, 1.1, // overwrites a
2, 2.1, // overwrites a
3, 3.0,
4, 4.0,
),
},
{
name: "b replaces all a",
a: makeFloatArray(1, 1.0, 2, 2.0, 3, 3.0, 4, 4.0),
b: makeFloatArray(1, 1.1, 2, 2.1, 3, 3.1, 4, 4.1),
exp: makeFloatArray(1, 1.1, 2, 2.1, 3, 3.1, 4, 4.1),
},
{
name: "b replaces a interleaved",
a: makeFloatArray(0, 0.0, 1, 1.0, 2, 2.0, 3, 3.0, 4, 4.0),
b: makeFloatArray(0, 0.1, 2, 2.1, 4, 4.1),
exp: makeFloatArray(0, 0.1, 1, 1.0, 2, 2.1, 3, 3.0, 4, 4.1),
},
{
name: "b merges a interleaved",
a: makeFloatArray(0, 0.0, 2, 2.0, 4, 4.0),
b: makeFloatArray(1, 1.1, 3, 3.1, 5, 5.1),
exp: makeFloatArray(0, 0.0, 1, 1.1, 2, 2.0, 3, 3.1, 4, 4.0, 5, 5.1),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
test.a.Merge(test.b)
if !cmp.Equal(test.a, test.exp) {
t.Fatalf("unexpected values -got/+exp\n%s", cmp.Diff(test.a, test.exp))
}
})
}
}
func TestIntegerArray_Merge(t *testing.T) {
tests := []struct {
name string
a, b, exp *tsdb.IntegerArray
}{
{
name: "empty a",
a: makeIntegerArray(),
b: makeIntegerArray(1, 11, 2, 21),
exp: makeIntegerArray(1, 11, 2, 21),
},
{
name: "empty b",
a: makeIntegerArray(1, 10, 2, 20),
b: makeIntegerArray(),
exp: makeIntegerArray(1, 10, 2, 20),
},
{
name: "b replaces a",
a: makeIntegerArray(1, 10),
b: makeIntegerArray(
0, 1,
1, 11, // overwrites a
2, 21,
3, 31,
4, 41,
),
exp: makeIntegerArray(0, 1, 1, 11, 2, 21, 3, 31, 4, 41),
},
{
name: "b replaces partial a",
a: makeIntegerArray(1, 10, 2, 20, 3, 30, 4, 40),
b: makeIntegerArray(
1, 11, // overwrites a
2, 21, // overwrites a
),
exp: makeIntegerArray(
1, 11, // overwrites a
2, 21, // overwrites a
3, 30,
4, 40,
),
},
{
name: "b replaces all a",
a: makeIntegerArray(1, 10, 2, 20, 3, 30, 4, 40),
b: makeIntegerArray(1, 11, 2, 21, 3, 31, 4, 41),
exp: makeIntegerArray(1, 11, 2, 21, 3, 31, 4, 41),
},
{
name: "b replaces a interleaved",
a: makeIntegerArray(0, 0, 1, 10, 2, 20, 3, 30, 4, 40),
b: makeIntegerArray(0, 1, 2, 21, 4, 41),
exp: makeIntegerArray(0, 1, 1, 10, 2, 21, 3, 30, 4, 41),
},
{
name: "b merges a interleaved",
a: makeIntegerArray(0, 00, 2, 20, 4, 40),
b: makeIntegerArray(1, 11, 3, 31, 5, 51),
exp: makeIntegerArray(0, 00, 1, 11, 2, 20, 3, 31, 4, 40, 5, 51),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
test.a.Merge(test.b)
if !cmp.Equal(test.a, test.exp) {
t.Fatalf("unexpected values -got/+exp\n%s", cmp.Diff(test.a, test.exp))
}
})
}
}
func TestUnsignedArray_Merge(t *testing.T) {
tests := []struct {
name string
a, b, exp *tsdb.UnsignedArray
}{
{
name: "empty a",
a: makeUnsignedArray(),
b: makeUnsignedArray(1, 11, 2, 21),
exp: makeUnsignedArray(1, 11, 2, 21),
},
{
name: "empty b",
a: makeUnsignedArray(1, 10, 2, 20),
b: makeUnsignedArray(),
exp: makeUnsignedArray(1, 10, 2, 20),
},
{
name: "b replaces a",
a: makeUnsignedArray(1, 10),
b: makeUnsignedArray(
0, 1,
1, 11, // overwrites a
2, 21,
3, 31,
4, 41,
),
exp: makeUnsignedArray(0, 1, 1, 11, 2, 21, 3, 31, 4, 41),
},
{
name: "b replaces partial a",
a: makeUnsignedArray(1, 10, 2, 20, 3, 30, 4, 40),
b: makeUnsignedArray(
1, 11, // overwrites a
2, 21, // overwrites a
),
exp: makeUnsignedArray(
1, 11, // overwrites a
2, 21, // overwrites a
3, 30,
4, 40,
),
},
{
name: "b replaces all a",
a: makeUnsignedArray(1, 10, 2, 20, 3, 30, 4, 40),
b: makeUnsignedArray(1, 11, 2, 21, 3, 31, 4, 41),
exp: makeUnsignedArray(1, 11, 2, 21, 3, 31, 4, 41),
},
{
name: "b replaces a interleaved",
a: makeUnsignedArray(0, 0, 1, 10, 2, 20, 3, 30, 4, 40),
b: makeUnsignedArray(0, 1, 2, 21, 4, 41),
exp: makeUnsignedArray(0, 1, 1, 10, 2, 21, 3, 30, 4, 41),
},
{
name: "b merges a interleaved",
a: makeUnsignedArray(0, 00, 2, 20, 4, 40),
b: makeUnsignedArray(1, 11, 3, 31, 5, 51),
exp: makeUnsignedArray(0, 00, 1, 11, 2, 20, 3, 31, 4, 40, 5, 51),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
test.a.Merge(test.b)
if !cmp.Equal(test.a, test.exp) {
t.Fatalf("unexpected values -got/+exp\n%s", cmp.Diff(test.a, test.exp))
}
})
}
}
func TestStringArray_Merge(t *testing.T) {
tests := []struct {
name string
a, b, exp *tsdb.StringArray
}{
{
name: "empty a",
a: makeStringArray(),
b: makeStringArray(1, 11, 2, 21),
exp: makeStringArray(1, 11, 2, 21),
},
{
name: "empty b",
a: makeStringArray(1, 10, 2, 20),
b: makeStringArray(),
exp: makeStringArray(1, 10, 2, 20),
},
{
name: "b replaces a",
a: makeStringArray(1, 10),
b: makeStringArray(
0, 1,
1, 11, // overwrites a
2, 21,
3, 31,
4, 41,
),
exp: makeStringArray(0, 1, 1, 11, 2, 21, 3, 31, 4, 41),
},
{
name: "b replaces partial a",
a: makeStringArray(1, 10, 2, 20, 3, 30, 4, 40),
b: makeStringArray(
1, 11, // overwrites a
2, 21, // overwrites a
),
exp: makeStringArray(
1, 11, // overwrites a
2, 21, // overwrites a
3, 30,
4, 40,
),
},
{
name: "b replaces all a",
a: makeStringArray(1, 10, 2, 20, 3, 30, 4, 40),
b: makeStringArray(1, 11, 2, 21, 3, 31, 4, 41),
exp: makeStringArray(1, 11, 2, 21, 3, 31, 4, 41),
},
{
name: "b replaces a interleaved",
a: makeStringArray(0, 0, 1, 10, 2, 20, 3, 30, 4, 40),
b: makeStringArray(0, 1, 2, 21, 4, 41),
exp: makeStringArray(0, 1, 1, 10, 2, 21, 3, 30, 4, 41),
},
{
name: "b merges a interleaved",
a: makeStringArray(0, 00, 2, 20, 4, 40),
b: makeStringArray(1, 11, 3, 31, 5, 51),
exp: makeStringArray(0, 00, 1, 11, 2, 20, 3, 31, 4, 40, 5, 51),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
test.a.Merge(test.b)
if !cmp.Equal(test.a, test.exp) {
t.Fatalf("unexpected values -got/+exp\n%s", cmp.Diff(test.a, test.exp))
}
})
}
}

View File

@ -3,3 +3,5 @@ Package tsdb implements a durable time series database.
*/
package tsdb
//go:generate tmpl -data=@arrayvalues.gen.go.tmpldata arrayvalues.gen.go.tmpl