refactor map functions to use list of values

This commit changes `tsdb.mapFunc` to use `tsdb.MapInput` instead
of an iterator. This will make it easier and faster to pass blocks
of values from the new storage engine into the engine.
pull/4264/head
Ben Johnson 2015-09-28 16:34:10 -06:00
parent f79377b488
commit 343dd23ee7
5 changed files with 333 additions and 444 deletions

View File

@ -27,6 +27,7 @@
- [#4222](https://github.com/influxdb/influxdb/pull/4222): Graphite TCP connections should not block shutdown - [#4222](https://github.com/influxdb/influxdb/pull/4222): Graphite TCP connections should not block shutdown
- [#4180](https://github.com/influxdb/influxdb/pull/4180): Cursor & SelectMapper Refactor - [#4180](https://github.com/influxdb/influxdb/pull/4180): Cursor & SelectMapper Refactor
- [#1577](https://github.com/influxdb/influxdb/issues/1577): selectors (e.g. min, max, first, last) should have equivalents to return the actual point - [#1577](https://github.com/influxdb/influxdb/issues/1577): selectors (e.g. min, max, first, last) should have equivalents to return the actual point
- [#4264](https://github.com/influxdb/influxdb/issues/4264): Refactor map functions to use list of values
## v0.9.4 [2015-09-14] ## v0.9.4 [2015-09-14]

View File

@ -2573,6 +2573,7 @@ func TestServer_Query_AggregateSelectors(t *testing.T) {
t.Logf("SKIP:: %s", query.name) t.Logf("SKIP:: %s", query.name)
continue continue
} }
if err := query.Execute(s); err != nil { if err := query.Execute(s); err != nil {
t.Error(query.Error(err)) t.Error(query.Error(err))
} else if !query.success() { } else if !query.success() {

View File

@ -19,7 +19,7 @@ import (
"github.com/influxdb/influxdb/influxql" "github.com/influxdb/influxdb/influxql"
) )
// iterator represents a forward-only iterator over a set of points. // Iterator represents a forward-only iterator over a set of points.
// These are used by the mapFunctions in this file // These are used by the mapFunctions in this file
type Iterator interface { type Iterator interface {
Next() (time int64, value interface{}) Next() (time int64, value interface{})
@ -28,9 +28,26 @@ type Iterator interface {
TMin() int64 TMin() int64
} }
type MapInput struct {
TMin int64
Items []MapItem
}
type MapItem struct {
Timestamp int64
Value interface{}
// TODO(benbjohnson):
// Move fields and tags up to MapInput. Currently the engine combines
// multiple series together during processing. This needs to be fixed so
// that each map function only operates on a single series at a time instead.
Fields map[string]interface{}
Tags map[string]string
}
// mapFunc represents a function used for mapping over a sequential series of data. // mapFunc represents a function used for mapping over a sequential series of data.
// The iterator represents a single group by interval // The iterator represents a single group by interval
type mapFunc func(Iterator) interface{} type mapFunc func(*MapInput) interface{}
// reduceFunc represents a function used for reducing mapper output. // reduceFunc represents a function used for reducing mapper output.
type reduceFunc func([]interface{}) interface{} type reduceFunc func([]interface{}) interface{}
@ -67,24 +84,24 @@ func initializeMapFunc(c *influxql.Call) (mapFunc, error) {
case "median": case "median":
return MapStddev, nil return MapStddev, nil
case "min": case "min":
return func(itr Iterator) interface{} { return func(input *MapInput) interface{} {
return MapMin(itr, c.Fields()[0]) return MapMin(input, c.Fields()[0])
}, nil }, nil
case "max": case "max":
return func(itr Iterator) interface{} { return func(input *MapInput) interface{} {
return MapMax(itr, c.Fields()[0]) return MapMax(input, c.Fields()[0])
}, nil }, nil
case "spread": case "spread":
return MapSpread, nil return MapSpread, nil
case "stddev": case "stddev":
return MapStddev, nil return MapStddev, nil
case "first": case "first":
return func(itr Iterator) interface{} { return func(input *MapInput) interface{} {
return MapFirst(itr, c.Fields()[0]) return MapFirst(input, c.Fields()[0])
}, nil }, nil
case "last": case "last":
return func(itr Iterator) interface{} { return func(input *MapInput) interface{} {
return MapLast(itr, c.Fields()[0]) return MapLast(input, c.Fields()[0])
}, nil }, nil
case "top", "bottom": case "top", "bottom":
@ -93,8 +110,8 @@ func initializeMapFunc(c *influxql.Call) (mapFunc, error) {
limit := int(lit.Val) limit := int(lit.Val)
fields := topCallArgs(c) fields := topCallArgs(c)
return func(itr Iterator) interface{} { return func(input *MapInput) interface{} {
return MapTopBottom(itr, limit, fields, len(c.Args), c.Name) return MapTopBottom(input, limit, fields, len(c.Args), c.Name)
}, nil }, nil
case "percentile": case "percentile":
return MapEcho, nil return MapEcho, nil
@ -228,9 +245,9 @@ func InitializeUnmarshaller(c *influxql.Call) (UnmarshalFunc, error) {
} }
// MapCount computes the number of values in an iterator. // MapCount computes the number of values in an iterator.
func MapCount(itr Iterator) interface{} { func MapCount(input *MapInput) interface{} {
n := float64(0) n := float64(0)
for k, _ := itr.Next(); k != -1; k, _ = itr.Next() { for range input.Items {
n++ n++
} }
if n > 0 { if n > 0 {
@ -253,20 +270,19 @@ func (d interfaceValues) Less(i, j int) bool {
} }
// MapDistinct computes the unique values in an iterator. // MapDistinct computes the unique values in an iterator.
func MapDistinct(itr Iterator) interface{} { func MapDistinct(input *MapInput) interface{} {
var index = make(map[interface{}]struct{}) m := make(map[interface{}]struct{})
for _, item := range input.Items {
for time, value := itr.Next(); time != -1; time, value = itr.Next() { m[item.Value] = struct{}{}
index[value] = struct{}{}
} }
if len(index) == 0 { if len(m) == 0 {
return nil return nil
} }
results := make(interfaceValues, len(index)) results := make(interfaceValues, len(m))
var i int var i int
for value, _ := range index { for value, _ := range m {
results[i] = value results[i] = value
i++ i++
} }
@ -307,11 +323,11 @@ func ReduceDistinct(values []interface{}) interface{} {
} }
// MapCountDistinct computes the unique count of values in an iterator. // MapCountDistinct computes the unique count of values in an iterator.
func MapCountDistinct(itr Iterator) interface{} { func MapCountDistinct(input *MapInput) interface{} {
var index = make(map[interface{}]struct{}) var index = make(map[interface{}]struct{})
for time, value := itr.Next(); time != -1; time, value = itr.Next() { for _, item := range input.Items {
index[value] = struct{}{} index[item.Value] = struct{}{}
} }
if len(index) == 0 { if len(index) == 0 {
@ -351,29 +367,31 @@ const (
) )
// MapSum computes the summation of values in an iterator. // MapSum computes the summation of values in an iterator.
func MapSum(itr Iterator) interface{} { func MapSum(input *MapInput) interface{} {
if len(input.Items) == 0 {
return nil
}
n := float64(0) n := float64(0)
count := 0
var resultType NumberType var resultType NumberType
for k, v := itr.Next(); k != -1; k, v = itr.Next() { for _, item := range input.Items {
count++ switch v := item.Value.(type) {
switch n1 := v.(type) {
case float64: case float64:
n += n1 n += v
case int64: case int64:
n += float64(n1) n += float64(v)
resultType = Int64Type resultType = Int64Type
} }
} }
if count > 0 {
switch resultType { switch resultType {
case Float64Type: case Float64Type:
return n return n
case Int64Type: case Int64Type:
return int64(n) return int64(n)
} default:
}
return nil return nil
}
} }
// ReduceSum computes the sum of values for each key. // ReduceSum computes the sum of values for each key.
@ -406,25 +424,23 @@ func ReduceSum(values []interface{}) interface{} {
} }
// MapMean computes the count and sum of values in an iterator to be combined by the reducer. // MapMean computes the count and sum of values in an iterator to be combined by the reducer.
func MapMean(itr Iterator) interface{} { func MapMean(input *MapInput) interface{} {
out := &meanMapOutput{} if len(input.Items) == 0 {
return nil
}
for k, v := itr.Next(); k != -1; k, v = itr.Next() { out := &meanMapOutput{}
for _, item := range input.Items {
out.Count++ out.Count++
switch n1 := v.(type) { switch v := item.Value.(type) {
case float64: case float64:
out.Mean += (n1 - out.Mean) / float64(out.Count) out.Mean += (v - out.Mean) / float64(out.Count)
case int64: case int64:
out.Mean += (float64(n1) - out.Mean) / float64(out.Count) out.Mean += (float64(v) - out.Mean) / float64(out.Count)
out.ResultType = Int64Type out.ResultType = Int64Type
} }
} }
if out.Count > 0 {
return out return out
}
return nil
} }
type meanMapOutput struct { type meanMapOutput struct {
@ -615,21 +631,21 @@ type minMaxMapOut struct {
} }
// MapMin collects the values to pass to the reducer // MapMin collects the values to pass to the reducer
func MapMin(itr Iterator, fieldName string) interface{} { func MapMin(input *MapInput, fieldName string) interface{} {
min := &minMaxMapOut{} min := &minMaxMapOut{}
pointsYielded := false pointsYielded := false
var val float64 var val float64
for k, v := itr.Next(); k != -1; k, v = itr.Next() { for _, item := range input.Items {
switch n := v.(type) { switch v := item.Value.(type) {
case float64: case float64:
val = n val = v
case int64: case int64:
val = float64(n) val = float64(v)
min.Type = Int64Type min.Type = Int64Type
case map[string]interface{}: case map[string]interface{}:
if d, t, ok := decodeValueAndNumberType(n[fieldName]); ok { if d, t, ok := decodeValueAndNumberType(v[fieldName]); ok {
val, min.Type = d, t val, min.Type = d, t
} else { } else {
continue continue
@ -638,19 +654,20 @@ func MapMin(itr Iterator, fieldName string) interface{} {
// Initialize min // Initialize min
if !pointsYielded { if !pointsYielded {
min.Time = k min.Time = item.Timestamp
min.Val = val min.Val = val
min.Fields = itr.Fields() min.Fields = item.Fields
min.Tags = itr.Tags() min.Tags = item.Tags
pointsYielded = true pointsYielded = true
} }
current := min.Val current := min.Val
min.Val = math.Min(min.Val, val) min.Val = math.Min(min.Val, val)
// Check to see if the value changed, if so, update the fields/tags // Check to see if the value changed, if so, update the fields/tags
if current != min.Val { if current != min.Val {
min.Time = k min.Time = item.Timestamp
min.Fields = itr.Fields() min.Fields = item.Fields
min.Tags = itr.Tags() min.Tags = item.Tags
} }
} }
if pointsYielded { if pointsYielded {
@ -724,21 +741,21 @@ func decodeValueAndNumberType(v interface{}) (float64, NumberType, bool) {
} }
// MapMax collects the values to pass to the reducer // MapMax collects the values to pass to the reducer
func MapMax(itr Iterator, fieldName string) interface{} { func MapMax(input *MapInput, fieldName string) interface{} {
max := &minMaxMapOut{} max := &minMaxMapOut{}
pointsYielded := false pointsYielded := false
var val float64 var val float64
for k, v := itr.Next(); k != -1; k, v = itr.Next() { for _, item := range input.Items {
switch n := v.(type) { switch v := item.Value.(type) {
case float64: case float64:
val = n val = v
case int64: case int64:
val = float64(n) val = float64(v)
max.Type = Int64Type max.Type = Int64Type
case map[string]interface{}: case map[string]interface{}:
if d, t, ok := decodeValueAndNumberType(n[fieldName]); ok { if d, t, ok := decodeValueAndNumberType(v[fieldName]); ok {
val, max.Type = d, t val, max.Type = d, t
} else { } else {
continue continue
@ -747,19 +764,20 @@ func MapMax(itr Iterator, fieldName string) interface{} {
// Initialize max // Initialize max
if !pointsYielded { if !pointsYielded {
max.Time = k max.Time = item.Timestamp
max.Val = val max.Val = val
max.Fields = itr.Fields() max.Fields = item.Fields
max.Tags = itr.Tags() max.Tags = item.Tags
pointsYielded = true pointsYielded = true
} }
current := max.Val current := max.Val
max.Val = math.Max(max.Val, val) max.Val = math.Max(max.Val, val)
// Check to see if the value changed, if so, update the fields/tags // Check to see if the value changed, if so, update the fields/tags
if current != max.Val { if current != max.Val {
max.Time = k max.Time = item.Timestamp
max.Fields = itr.Fields() max.Fields = item.Fields
max.Tags = itr.Tags() max.Tags = item.Tags
} }
} }
if pointsYielded { if pointsYielded {
@ -827,17 +845,17 @@ type spreadMapOutput struct {
} }
// MapSpread collects the values to pass to the reducer // MapSpread collects the values to pass to the reducer
func MapSpread(itr Iterator) interface{} { func MapSpread(input *MapInput) interface{} {
out := &spreadMapOutput{} out := &spreadMapOutput{}
pointsYielded := false pointsYielded := false
var val float64 var val float64
for k, v := itr.Next(); k != -1; k, v = itr.Next() { for _, item := range input.Items {
switch n := v.(type) { switch v := item.Value.(type) {
case float64: case float64:
val = n val = v
case int64: case int64:
val = float64(n) val = float64(v)
out.Type = Int64Type out.Type = Int64Type
} }
@ -888,19 +906,17 @@ func ReduceSpread(values []interface{}) interface{} {
} }
// MapStddev collects the values to pass to the reducer // MapStddev collects the values to pass to the reducer
func MapStddev(itr Iterator) interface{} { func MapStddev(input *MapInput) interface{} {
var values []float64 var a []float64
for _, item := range input.Items {
for k, v := itr.Next(); k != -1; k, v = itr.Next() { switch v := item.Value.(type) {
switch n := v.(type) {
case float64: case float64:
values = append(values, n) a = append(a, v)
case int64: case int64:
values = append(values, float64(n)) a = append(a, float64(v))
} }
} }
return a
return values
} }
// ReduceStddev computes the stddev of values. // ReduceStddev computes the stddev of values.
@ -948,29 +964,35 @@ type firstLastMapOutput struct {
// MapFirst collects the values to pass to the reducer // MapFirst collects the values to pass to the reducer
// This function assumes time ordered input // This function assumes time ordered input
func MapFirst(itr Iterator, fieldName string) interface{} { func MapFirst(input *MapInput, fieldName string) interface{} {
var fields map[string]interface{} if len(input.Items) == 0 {
k, v := itr.Next()
fields = itr.Fields()
if k == -1 {
return nil return nil
} }
k, v := input.Items[0].Timestamp, input.Items[0].Value
tags := input.Items[0].Tags
fields := input.Items[0].Fields
if n, ok := v.(map[string]interface{}); ok { if n, ok := v.(map[string]interface{}); ok {
v = n[fieldName] v = n[fieldName]
} }
nextk, nextv := itr.Next() // Find greatest value at same timestamp.
for _, item := range input.Items[1:] {
nextk, nextv := item.Timestamp, item.Value
if nextk != k {
break
}
if n, ok := nextv.(map[string]interface{}); ok { if n, ok := nextv.(map[string]interface{}); ok {
nextv = n[fieldName] nextv = n[fieldName]
} }
for nextk == k {
if greaterThan(nextv, v) { if greaterThan(nextv, v) {
fields = itr.Fields() fields = item.Fields
tags = item.Tags
v = nextv v = nextv
} }
nextk, nextv = itr.Next()
} }
return &firstLastMapOutput{Time: k, Value: v, Fields: fields, Tags: itr.Tags()} return &firstLastMapOutput{Time: k, Value: v, Fields: fields, Tags: tags}
} }
// ReduceFirst computes the first of value. // ReduceFirst computes the first of value.
@ -1014,31 +1036,33 @@ func ReduceFirst(values []interface{}) interface{} {
} }
// MapLast collects the values to pass to the reducer // MapLast collects the values to pass to the reducer
func MapLast(itr Iterator, fieldName string) interface{} { func MapLast(input *MapInput, fieldName string) interface{} {
out := &firstLastMapOutput{} out := &firstLastMapOutput{}
pointsYielded := false pointsYielded := false
for k, v := itr.Next(); k != -1; k, v = itr.Next() { for _, item := range input.Items {
if n, ok := v.(map[string]interface{}); ok { k, v := item.Timestamp, item.Value
v = n[fieldName] if m, ok := v.(map[string]interface{}); ok {
v = m[fieldName]
} }
// Initialize last // Initialize last
if !pointsYielded { if !pointsYielded {
out.Time = k out.Time = k
out.Value = v out.Value = v
out.Fields = itr.Fields() out.Fields = item.Fields
out.Tags = itr.Tags() out.Tags = item.Tags
pointsYielded = true pointsYielded = true
} }
if k > out.Time { if k > out.Time {
out.Time = k out.Time = k
out.Value = v out.Value = v
out.Fields = itr.Fields() out.Fields = item.Fields
out.Tags = itr.Tags() out.Tags = item.Tags
} else if k == out.Time && greaterThan(v, out.Value) { } else if k == out.Time && greaterThan(v, out.Value) {
out.Value = v out.Value = v
out.Fields = itr.Fields() out.Fields = item.Fields
out.Tags = itr.Tags() out.Tags = item.Tags
} }
} }
if pointsYielded { if pointsYielded {
@ -1455,7 +1479,7 @@ func (m *mapIter) Next() (time int64, value interface{}) {
} }
// MapTopBottom emits the top/bottom data points for each group by interval // MapTopBottom emits the top/bottom data points for each group by interval
func MapTopBottom(itr Iterator, limit int, fields []string, argCount int, callName string) interface{} { func MapTopBottom(input *MapInput, limit int, fields []string, argCount int, callName string) interface{} {
out := positionOut{callArgs: fields} out := positionOut{callArgs: fields}
out.points = make([]PositionPoint, 0, limit) out.points = make([]PositionPoint, 0, limit)
minheap := topBottomMapOut{ minheap := topBottomMapOut{
@ -1475,9 +1499,15 @@ func MapTopBottom(itr Iterator, limit int, fields []string, argCount int, callNa
// For each unique permutation of the tags given, // For each unique permutation of the tags given,
// select the max and then fall through to select top of those // select the max and then fall through to select top of those
// points // points
for k, v := itr.Next(); k != -1; k, v = itr.Next() { for _, item := range input.Items {
pp = PositionPoint{k, v, itr.Fields(), itr.Tags()} pp = PositionPoint{
tags := itr.Tags() Time: item.Timestamp,
Value: item.Value,
Fields: item.Fields,
Tags: item.Tags,
}
tags := item.Tags
// TODO in the future we need to send in fields as well // TODO in the future we need to send in fields as well
// this will allow a user to query on both fields and tags // this will allow a user to query on both fields and tags
// fields will take the priority over tags if there is a name collision // fields will take the priority over tags if there is a name collision
@ -1487,18 +1517,24 @@ func MapTopBottom(itr Iterator, limit int, fields []string, argCount int, callNa
tagmap[key] = pp tagmap[key] = pp
} }
} }
itr = &mapIter{
m: tagmap, items := make([]MapItem, 0, len(tagmap))
tmin: itr.TMin(), for _, p := range tagmap {
items = append(items, MapItem{Timestamp: p.Time, Value: p.Value, Fields: p.Fields, Tags: p.Tags})
}
input = &MapInput{
TMin: input.TMin,
Items: items,
} }
} }
for k, v := itr.Next(); k != -1; k, v = itr.Next() {
t := k for _, item := range input.Items {
if bt := itr.TMin(); bt > -1 { t := item.Timestamp
t = bt if input.TMin > -1 {
t = input.TMin
} }
if len(out.points) < limit { if len(out.points) < limit {
out.points = append(out.points, PositionPoint{t, v, itr.Fields(), itr.Tags()}) out.points = append(out.points, PositionPoint{t, item.Value, item.Fields, item.Tags})
if len(out.points) == limit { if len(out.points) == limit {
heap.Init(&minheap) heap.Init(&minheap)
} }
@ -1506,12 +1542,13 @@ func MapTopBottom(itr Iterator, limit int, fields []string, argCount int, callNa
// we're over the limit, so find out if we're bigger than the // we're over the limit, so find out if we're bigger than the
// smallest point in the set and eject it if we are // smallest point in the set and eject it if we are
minval := &out.points[0] minval := &out.points[0]
pp = PositionPoint{t, v, itr.Fields(), itr.Tags()} pp = PositionPoint{t, item.Value, item.Fields, item.Tags}
if minheap.positionPointLess(minval, &pp) { if minheap.positionPointLess(minval, &pp) {
minheap.insert(pp) minheap.insert(pp)
} }
} }
} }
// should only happen on empty iterator. // should only happen on empty iterator.
if len(out.points) == 0 { if len(out.points) == 0 {
return nil return nil
@ -1521,6 +1558,7 @@ func MapTopBottom(itr Iterator, limit int, fields []string, argCount int, callNa
// rid of another sort order. // rid of another sort order.
heap.Init(&minheap) heap.Init(&minheap)
} }
// minheap should now contain the largest/smallest values that were encountered // minheap should now contain the largest/smallest values that were encountered
// during iteration. // during iteration.
// //
@ -1533,10 +1571,12 @@ func MapTopBottom(itr Iterator, limit int, fields []string, argCount int, callNa
for len(out.points) > 0 { for len(out.points) > 0 {
p := out.points[0] p := out.points[0]
heap.Pop(&minheap) heap.Pop(&minheap)
// reslice so that we can get to the element just after the heap // reslice so that we can get to the element just after the heap
endslice := out.points[:len(out.points)+1] endslice := out.points[:len(out.points)+1]
endslice[len(endslice)-1] = p endslice[len(endslice)-1] = p
} }
// the ascending order is now in the result slice // the ascending order is now in the result slice
return result return result
} }
@ -1589,11 +1629,10 @@ func ReduceTopBottom(values []interface{}, c *influxql.Call) interface{} {
} }
// MapEcho emits the data points for each group by interval // MapEcho emits the data points for each group by interval
func MapEcho(itr Iterator) interface{} { func MapEcho(input *MapInput) interface{} {
var values []interface{} var values []interface{}
for _, item := range input.Items {
for k, v := itr.Next(); k != -1; k, v = itr.Next() { values = append(values, item.Value)
values = append(values, v)
} }
return values return values
} }
@ -1645,11 +1684,10 @@ func IsNumeric(c *influxql.Call) bool {
} }
// MapRawQuery is for queries without aggregates // MapRawQuery is for queries without aggregates
func MapRawQuery(itr Iterator) interface{} { func MapRawQuery(input *MapInput) interface{} {
var values []*rawQueryMapOutput var values []*rawQueryMapOutput
for k, v := itr.Next(); k != -1; k, v = itr.Next() { for _, item := range input.Items {
val := &rawQueryMapOutput{k, v} values = append(values, &rawQueryMapOutput{item.Timestamp, item.Value})
values = append(values, val)
} }
return values return values
} }

View File

@ -11,63 +11,15 @@ import (
import "sort" import "sort"
type testPoint struct { // type testPoint struct {
seriesKey string // time int64
time int64 // value interface{}
value interface{} // fields map[string]interface{}
fields map[string]interface{} // tags map[string]string
tags map[string]string // }
}
type testIterator struct {
values []testPoint
lastFields map[string]interface{}
lastTags map[string]string
nextFunc func() (timestamp int64, value interface{})
fieldsFunc func() map[string]interface{}
tagsFunc func() map[string]string
tMinFunc func() int64
}
func (t *testIterator) Next() (timestamp int64, value interface{}) {
if t.nextFunc != nil {
return t.nextFunc()
}
if len(t.values) > 0 {
v := t.values[0]
t.lastFields = t.values[0].fields
t.lastTags = t.values[0].tags
t.values = t.values[1:]
return v.time, v.value
}
return -1, nil
}
func (t *testIterator) Fields() map[string]interface{} {
if t.fieldsFunc != nil {
return t.fieldsFunc()
}
return t.lastFields
}
func (t *testIterator) Tags() map[string]string {
if t.tagsFunc != nil {
return t.tagsFunc()
}
return t.lastTags
}
func (t *testIterator) TMin() int64 {
if t.tMinFunc != nil {
return t.tMinFunc()
}
return -1
}
func TestMapMeanNoValues(t *testing.T) { func TestMapMeanNoValues(t *testing.T) {
iter := &testIterator{} if got := MapMean(&MapInput{}); got != nil {
if got := MapMean(iter); got != nil {
t.Errorf("output mismatch: exp nil got %v", got) t.Errorf("output mismatch: exp nil got %v", got)
} }
} }
@ -75,28 +27,30 @@ func TestMapMeanNoValues(t *testing.T) {
func TestMapMean(t *testing.T) { func TestMapMean(t *testing.T) {
tests := []struct { tests := []struct {
input []testPoint input *MapInput
output *meanMapOutput output *meanMapOutput
}{ }{
{ // Single point { // Single point
input: []testPoint{testPoint{"0", 1, 1.0, nil, nil}}, input: &MapInput{
Items: []MapItem{
{Timestamp: 1, Value: 1.0},
},
},
output: &meanMapOutput{1, 1, Float64Type}, output: &meanMapOutput{1, 1, Float64Type},
}, },
{ // Two points { // Two points
input: []testPoint{ input: &MapInput{
testPoint{"0", 1, 2.0, nil, nil}, Items: []MapItem{
testPoint{"0", 2, 8.0, nil, nil}, {Timestamp: 1, Value: float64(2.0)},
{Timestamp: 2, Value: float64(8.0)},
},
}, },
output: &meanMapOutput{2, 5.0, Float64Type}, output: &meanMapOutput{2, 5.0, Float64Type},
}, },
} }
for _, test := range tests { for _, test := range tests {
iter := &testIterator{ got := MapMean(test.input)
values: test.input,
}
got := MapMean(iter)
if got == nil { if got == nil {
t.Fatalf("MapMean(%v): output mismatch: exp %v got %v", test.input, test.output, got) t.Fatalf("MapMean(%v): output mismatch: exp %v got %v", test.input, test.output, got)
} }
@ -154,11 +108,6 @@ func TestReducePercentileNil(t *testing.T) {
} }
func TestMapDistinct(t *testing.T) { func TestMapDistinct(t *testing.T) {
const ( // prove that we're ignoring seriesKey
seriesKey1 = "1"
seriesKey2 = "2"
)
const ( // prove that we're ignoring time const ( // prove that we're ignoring time
timeId1 = iota + 1 timeId1 = iota + 1
timeId2 timeId2
@ -168,18 +117,18 @@ func TestMapDistinct(t *testing.T) {
timeId6 timeId6
) )
iter := &testIterator{ input := &MapInput{
values: []testPoint{ Items: []MapItem{
{seriesKey1, timeId1, uint64(1), nil, nil}, {Timestamp: timeId1, Value: uint64(1)},
{seriesKey1, timeId2, uint64(1), nil, nil}, {Timestamp: timeId2, Value: uint64(1)},
{seriesKey1, timeId3, "1", nil, nil}, {Timestamp: timeId3, Value: "1"},
{seriesKey2, timeId4, uint64(1), nil, nil}, {Timestamp: timeId4, Value: uint64(1)},
{seriesKey2, timeId5, float64(1.0), nil, nil}, {Timestamp: timeId5, Value: float64(1.0)},
{seriesKey2, timeId6, "1", nil, nil}, {Timestamp: timeId6, Value: "1"},
}, },
} }
values := MapDistinct(iter).(interfaceValues) values := MapDistinct(input).(interfaceValues)
if exp, got := 3, len(values); exp != got { if exp, got := 3, len(values); exp != got {
t.Errorf("Wrong number of values. exp %v got %v", exp, got) t.Errorf("Wrong number of values. exp %v got %v", exp, got)
@ -199,11 +148,7 @@ func TestMapDistinct(t *testing.T) {
} }
func TestMapDistinctNil(t *testing.T) { func TestMapDistinctNil(t *testing.T) {
iter := &testIterator{ values := MapDistinct(&MapInput{})
values: []testPoint{},
}
values := MapDistinct(iter)
if values != nil { if values != nil {
t.Errorf("Wrong values. exp nil got %v", spew.Sdump(values)) t.Errorf("Wrong values. exp nil got %v", spew.Sdump(values))
@ -307,11 +252,6 @@ func Test_distinctValues_Sort(t *testing.T) {
} }
func TestMapCountDistinct(t *testing.T) { func TestMapCountDistinct(t *testing.T) {
const ( // prove that we're ignoring seriesKey
seriesKey1 = "1"
seriesKey2 = "2"
)
const ( // prove that we're ignoring time const ( // prove that we're ignoring time
timeId1 = iota + 1 timeId1 = iota + 1
timeId2 timeId2
@ -322,19 +262,19 @@ func TestMapCountDistinct(t *testing.T) {
timeId7 timeId7
) )
iter := &testIterator{ input := &MapInput{
values: []testPoint{ Items: []MapItem{
{seriesKey1, timeId1, uint64(1), nil, nil}, {Timestamp: timeId1, Value: uint64(1)},
{seriesKey1, timeId2, uint64(1), nil, nil}, {Timestamp: timeId2, Value: uint64(1)},
{seriesKey1, timeId3, "1", nil, nil}, {Timestamp: timeId3, Value: "1"},
{seriesKey2, timeId4, uint64(1), nil, nil}, {Timestamp: timeId4, Value: uint64(1)},
{seriesKey2, timeId5, float64(1.0), nil, nil}, {Timestamp: timeId5, Value: float64(1.0)},
{seriesKey2, timeId6, "1", nil, nil}, {Timestamp: timeId6, Value: "1"},
{seriesKey2, timeId7, true, nil, nil}, {Timestamp: timeId7, Value: true},
}, },
} }
values := MapCountDistinct(iter).(map[interface{}]struct{}) values := MapCountDistinct(input).(map[interface{}]struct{})
if exp, got := 4, len(values); exp != got { if exp, got := 4, len(values); exp != got {
t.Errorf("Wrong number of values. exp %v got %v", exp, got) t.Errorf("Wrong number of values. exp %v got %v", exp, got)
@ -353,13 +293,7 @@ func TestMapCountDistinct(t *testing.T) {
} }
func TestMapCountDistinctNil(t *testing.T) { func TestMapCountDistinctNil(t *testing.T) {
iter := &testIterator{ if values := MapCountDistinct(&MapInput{}); values != nil {
values: []testPoint{},
}
values := MapCountDistinct(iter)
if values != nil {
t.Errorf("Wrong values. exp nil got %v", spew.Sdump(values)) t.Errorf("Wrong values. exp nil got %v", spew.Sdump(values))
} }
} }
@ -495,284 +429,223 @@ func TestMapTopBottom(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
skip bool skip bool
iter *testIterator input *MapInput
exp positionOut exp positionOut
call *influxql.Call call *influxql.Call
}{ }{
{ {
name: "top int64 - basic", name: "top int64 - basic",
iter: &testIterator{ input: &MapInput{
values: []testPoint{ TMin: -1,
{"", 10, int64(99), nil, map[string]string{"host": "a"}}, Items: []MapItem{
{"", 10, int64(53), nil, map[string]string{"host": "b"}}, {Timestamp: 10, Value: int64(53), Tags: map[string]string{"host": "a"}},
{"", 20, int64(88), nil, map[string]string{"host": "a"}}, {Timestamp: 20, Value: int64(88), Tags: map[string]string{"host": "a"}},
}, },
}, },
exp: positionOut{ exp: positionOut{
points: PositionPoints{ points: PositionPoints{
PositionPoint{10, int64(99), nil, map[string]string{"host": "a"}}, {20, int64(88), nil, map[string]string{"host": "a"}},
PositionPoint{20, int64(88), nil, map[string]string{"host": "a"}}, {10, int64(53), nil, map[string]string{"host": "a"}},
}, },
}, },
call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}}, call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}},
}, },
{
name: "top int64 - basic with tag",
iter: &testIterator{
values: []testPoint{
{"", 10, int64(99), nil, map[string]string{"host": "a"}},
{"", 20, int64(53), nil, map[string]string{"host": "b"}},
{"", 30, int64(88), nil, map[string]string{"host": "a"}},
},
},
exp: positionOut{
callArgs: []string{"host"},
points: PositionPoints{
PositionPoint{10, int64(99), nil, map[string]string{"host": "a"}},
PositionPoint{20, int64(53), nil, map[string]string{"host": "b"}},
},
},
call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.VarRef{Val: "host"}, &influxql.NumberLiteral{Val: 2}}},
},
{ {
name: "top int64 - tie on value, resolve based on time", name: "top int64 - tie on value, resolve based on time",
iter: &testIterator{ input: &MapInput{
values: []testPoint{ TMin: -1,
{"", 20, int64(99), nil, map[string]string{"host": "a"}}, Items: []MapItem{
{"", 10, int64(53), nil, map[string]string{"host": "a"}}, {Timestamp: 20, Value: int64(99), Tags: map[string]string{"host": "a"}},
{"", 10, int64(99), nil, map[string]string{"host": "a"}}, {Timestamp: 10, Value: int64(53), Tags: map[string]string{"host": "a"}},
{Timestamp: 10, Value: int64(99), Tags: map[string]string{"host": "a"}},
}, },
}, },
exp: positionOut{ exp: positionOut{
callArgs: []string{"host"}, callArgs: []string{"host"},
points: PositionPoints{ points: PositionPoints{
PositionPoint{10, int64(99), nil, map[string]string{"host": "a"}}, {10, int64(99), nil, map[string]string{"host": "a"}},
PositionPoint{20, int64(99), nil, map[string]string{"host": "a"}}, {20, int64(99), nil, map[string]string{"host": "a"}},
}, },
}, },
call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}}, call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}},
}, },
{
name: "top int64 - tie on value, time, resolve based on tags",
iter: &testIterator{
values: []testPoint{
{"", 10, int64(99), nil, map[string]string{"host": "b"}},
{"", 10, int64(99), nil, map[string]string{"host": "a"}},
{"", 20, int64(88), nil, map[string]string{"host": "a"}},
},
},
exp: positionOut{
callArgs: []string{"host"},
points: PositionPoints{
PositionPoint{10, int64(99), nil, map[string]string{"host": "a"}},
PositionPoint{10, int64(99), nil, map[string]string{"host": "b"}},
},
},
call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.VarRef{Val: "host"}, &influxql.NumberLiteral{Val: 2}}},
},
{ {
name: "top mixed numerics - ints", name: "top mixed numerics - ints",
iter: &testIterator{ input: &MapInput{
values: []testPoint{ TMin: -1,
{"", 10, int64(99), nil, map[string]string{"host": "a"}}, Items: []MapItem{
{"", 10, int64(53), nil, map[string]string{"host": "b"}}, {Timestamp: 10, Value: int64(99), Tags: map[string]string{"host": "a"}},
{"", 20, uint64(88), nil, map[string]string{"host": "a"}}, {Timestamp: 10, Value: int64(53), Tags: map[string]string{"host": "a"}},
{Timestamp: 20, Value: uint64(88), Tags: map[string]string{"host": "a"}},
}, },
}, },
exp: positionOut{ exp: positionOut{
points: PositionPoints{ points: PositionPoints{
PositionPoint{10, int64(99), nil, map[string]string{"host": "a"}}, {10, int64(99), nil, map[string]string{"host": "a"}},
PositionPoint{20, uint64(88), nil, map[string]string{"host": "a"}}, {20, uint64(88), nil, map[string]string{"host": "a"}},
}, },
}, },
call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}}, call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}},
}, },
{ {
name: "top mixed numerics - ints & floats", name: "top mixed numerics - ints & floats",
iter: &testIterator{ input: &MapInput{
values: []testPoint{ TMin: -1,
{"", 10, float64(99), nil, map[string]string{"host": "a"}}, Items: []MapItem{
{"", 10, int64(53), nil, map[string]string{"host": "b"}}, {Timestamp: 10, Value: float64(99), Tags: map[string]string{"host": "a"}},
{"", 20, uint64(88), nil, map[string]string{"host": "a"}}, {Timestamp: 10, Value: int64(53), Tags: map[string]string{"host": "a"}},
{Timestamp: 20, Value: uint64(88), Tags: map[string]string{"host": "a"}},
}, },
}, },
exp: positionOut{ exp: positionOut{
points: PositionPoints{ points: PositionPoints{
PositionPoint{10, float64(99), nil, map[string]string{"host": "a"}}, {10, float64(99), nil, map[string]string{"host": "a"}},
PositionPoint{20, uint64(88), nil, map[string]string{"host": "a"}}, {20, uint64(88), nil, map[string]string{"host": "a"}},
}, },
}, },
call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}}, call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}},
}, },
{ {
name: "top mixed numerics - ints, floats, & strings", name: "top mixed numerics - ints, floats, & strings",
iter: &testIterator{ input: &MapInput{
values: []testPoint{ TMin: -1,
{"", 10, float64(99), nil, map[string]string{"host": "a"}}, Items: []MapItem{
{"", 10, int64(53), nil, map[string]string{"host": "b"}}, {Timestamp: 10, Value: float64(99), Tags: map[string]string{"host": "a"}},
{"", 20, "88", nil, map[string]string{"host": "a"}}, {Timestamp: 10, Value: int64(53), Tags: map[string]string{"host": "a"}},
{Timestamp: 20, Value: "88", Tags: map[string]string{"host": "a"}},
}, },
}, },
exp: positionOut{ exp: positionOut{
points: PositionPoints{ points: PositionPoints{
PositionPoint{10, float64(99), nil, map[string]string{"host": "a"}}, {10, float64(99), nil, map[string]string{"host": "a"}},
PositionPoint{10, int64(53), nil, map[string]string{"host": "b"}}, {10, int64(53), nil, map[string]string{"host": "a"}},
}, },
}, },
call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}}, call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}},
}, },
{ {
name: "top bools", name: "top bools",
iter: &testIterator{ input: &MapInput{
values: []testPoint{ TMin: -1,
{"", 10, true, nil, map[string]string{"host": "a"}}, Items: []MapItem{
{"", 10, true, nil, map[string]string{"host": "b"}}, {Timestamp: 10, Value: true, Tags: map[string]string{"host": "a"}},
{"", 20, false, nil, map[string]string{"host": "a"}}, {Timestamp: 10, Value: true, Tags: map[string]string{"host": "a"}},
{Timestamp: 20, Value: false, Tags: map[string]string{"host": "a"}},
}, },
}, },
exp: positionOut{ exp: positionOut{
points: PositionPoints{ points: PositionPoints{
PositionPoint{10, true, nil, map[string]string{"host": "a"}}, {10, true, nil, map[string]string{"host": "a"}},
PositionPoint{10, true, nil, map[string]string{"host": "b"}}, {10, true, nil, map[string]string{"host": "a"}},
}, },
}, },
call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}}, call: &influxql.Call{Name: "top", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}},
}, },
{ {
name: "bottom int64 - basic", name: "bottom int64 - basic",
iter: &testIterator{ input: &MapInput{
values: []testPoint{ TMin: -1,
{"", 10, int64(99), nil, map[string]string{"host": "a"}}, Items: []MapItem{
{"", 10, int64(53), nil, map[string]string{"host": "b"}}, {Timestamp: 10, Value: int64(99), Tags: map[string]string{"host": "a"}},
{"", 20, int64(88), nil, map[string]string{"host": "a"}}, {Timestamp: 10, Value: int64(53), Tags: map[string]string{"host": "a"}},
{Timestamp: 20, Value: int64(88), Tags: map[string]string{"host": "a"}},
}, },
}, },
exp: positionOut{ exp: positionOut{
points: PositionPoints{ points: PositionPoints{
PositionPoint{10, int64(53), nil, map[string]string{"host": "b"}}, {10, int64(53), nil, map[string]string{"host": "a"}},
PositionPoint{20, int64(88), nil, map[string]string{"host": "a"}}, {20, int64(88), nil, map[string]string{"host": "a"}},
}, },
}, },
call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}}, call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}},
}, },
{
name: "bottom int64 - basic with tag",
iter: &testIterator{
values: []testPoint{
{"", 10, int64(20), nil, map[string]string{"host": "a"}},
{"", 20, int64(53), nil, map[string]string{"host": "b"}},
{"", 30, int64(30), nil, map[string]string{"host": "a"}},
},
},
exp: positionOut{
callArgs: []string{"host"},
points: PositionPoints{
PositionPoint{10, int64(20), nil, map[string]string{"host": "a"}},
PositionPoint{20, int64(53), nil, map[string]string{"host": "b"}},
},
},
call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.VarRef{Val: "host"}, &influxql.NumberLiteral{Val: 2}}},
},
{ {
name: "bottom int64 - tie on value, resolve based on time", name: "bottom int64 - tie on value, resolve based on time",
iter: &testIterator{ input: &MapInput{
values: []testPoint{ TMin: -1,
{"", 10, int64(53), nil, map[string]string{"host": "a"}}, Items: []MapItem{
{"", 20, int64(53), nil, map[string]string{"host": "a"}}, {Timestamp: 10, Value: int64(53), Tags: map[string]string{"host": "a"}},
{"", 20, int64(53), nil, map[string]string{"host": "a"}}, {Timestamp: 20, Value: int64(53), Tags: map[string]string{"host": "a"}},
{Timestamp: 20, Value: int64(53), Tags: map[string]string{"host": "a"}},
}, },
}, },
exp: positionOut{ exp: positionOut{
callArgs: []string{"host"}, callArgs: []string{"host"},
points: PositionPoints{ points: PositionPoints{
PositionPoint{10, int64(53), nil, map[string]string{"host": "a"}}, {10, int64(53), nil, map[string]string{"host": "a"}},
PositionPoint{20, int64(53), nil, map[string]string{"host": "a"}}, {20, int64(53), nil, map[string]string{"host": "a"}},
}, },
}, },
call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}}, call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}},
}, },
{
name: "bottom int64 - tie on value, time, resolve based on tags",
iter: &testIterator{
values: []testPoint{
{"", 10, int64(99), nil, map[string]string{"host": "b"}},
{"", 10, int64(99), nil, map[string]string{"host": "a"}},
{"", 20, int64(100), nil, map[string]string{"host": "a"}},
},
},
exp: positionOut{
callArgs: []string{"host"},
points: PositionPoints{
PositionPoint{10, int64(99), nil, map[string]string{"host": "a"}},
PositionPoint{10, int64(99), nil, map[string]string{"host": "b"}},
},
},
call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.VarRef{Val: "host"}, &influxql.NumberLiteral{Val: 2}}},
},
{ {
name: "bottom mixed numerics - ints", name: "bottom mixed numerics - ints",
iter: &testIterator{ input: &MapInput{
values: []testPoint{ TMin: -1,
{"", 10, int64(99), nil, map[string]string{"host": "a"}}, Items: []MapItem{
{"", 10, int64(53), nil, map[string]string{"host": "b"}}, {Timestamp: 10, Value: int64(99), Tags: map[string]string{"host": "a"}},
{"", 20, uint64(88), nil, map[string]string{"host": "a"}}, {Timestamp: 10, Value: int64(53), Tags: map[string]string{"host": "a"}},
{Timestamp: 20, Value: uint64(88), Tags: map[string]string{"host": "a"}},
}, },
}, },
exp: positionOut{ exp: positionOut{
points: PositionPoints{ points: PositionPoints{
PositionPoint{10, int64(53), nil, map[string]string{"host": "b"}}, {10, int64(53), nil, map[string]string{"host": "a"}},
PositionPoint{20, uint64(88), nil, map[string]string{"host": "a"}}, {20, uint64(88), nil, map[string]string{"host": "a"}},
}, },
}, },
call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}}, call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}},
}, },
{ {
name: "bottom mixed numerics - ints & floats", name: "bottom mixed numerics - ints & floats",
iter: &testIterator{ input: &MapInput{
values: []testPoint{ TMin: -1,
{"", 10, int64(99), nil, map[string]string{"host": "a"}}, Items: []MapItem{
{"", 10, float64(53), nil, map[string]string{"host": "b"}}, {Timestamp: 10, Value: int64(99), Tags: map[string]string{"host": "a"}},
{"", 20, uint64(88), nil, map[string]string{"host": "a"}}, {Timestamp: 10, Value: float64(53), Tags: map[string]string{"host": "a"}},
{Timestamp: 20, Value: uint64(88), Tags: map[string]string{"host": "a"}},
}, },
}, },
exp: positionOut{ exp: positionOut{
points: PositionPoints{ points: PositionPoints{
PositionPoint{10, float64(53), nil, map[string]string{"host": "b"}}, {10, float64(53), nil, map[string]string{"host": "a"}},
PositionPoint{20, uint64(88), nil, map[string]string{"host": "a"}}, {20, uint64(88), nil, map[string]string{"host": "a"}},
}, },
}, },
call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}}, call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}},
}, },
{ {
name: "bottom mixed numerics - ints, floats, & strings", name: "bottom mixed numerics - ints, floats, & strings",
iter: &testIterator{ input: &MapInput{
values: []testPoint{ TMin: -1,
{"", 10, float64(99), nil, map[string]string{"host": "a"}}, Items: []MapItem{
{"", 10, int64(53), nil, map[string]string{"host": "b"}}, {Timestamp: 10, Value: float64(99), Tags: map[string]string{"host": "a"}},
{"", 20, "88", nil, map[string]string{"host": "a"}}, {Timestamp: 10, Value: int64(53), Tags: map[string]string{"host": "a"}},
{Timestamp: 20, Value: "88", Tags: map[string]string{"host": "a"}},
}, },
}, },
exp: positionOut{ exp: positionOut{
points: PositionPoints{ points: PositionPoints{
PositionPoint{10, int64(53), nil, map[string]string{"host": "b"}}, {10, int64(53), nil, map[string]string{"host": "a"}},
PositionPoint{10, float64(99), nil, map[string]string{"host": "a"}}, {10, float64(99), nil, map[string]string{"host": "a"}},
}, },
}, },
call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}}, call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}},
}, },
{ {
name: "bottom bools", name: "bottom bools",
iter: &testIterator{ input: &MapInput{
values: []testPoint{ TMin: -1,
{"", 10, true, nil, map[string]string{"host": "a"}}, Items: []MapItem{
{"", 10, true, nil, map[string]string{"host": "b"}}, {Timestamp: 10, Value: true, Tags: map[string]string{"host": "a"}},
{"", 20, false, nil, map[string]string{"host": "a"}}, {Timestamp: 10, Value: true, Tags: map[string]string{"host": "a"}},
{Timestamp: 20, Value: false, Tags: map[string]string{"host": "a"}},
}, },
}, },
exp: positionOut{ exp: positionOut{
points: PositionPoints{ points: PositionPoints{
PositionPoint{20, false, nil, map[string]string{"host": "a"}}, {20, false, nil, map[string]string{"host": "a"}},
PositionPoint{10, true, nil, map[string]string{"host": "a"}}, {10, true, nil, map[string]string{"host": "a"}},
}, },
}, },
call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}}, call: &influxql.Call{Name: "bottom", Args: []influxql.Expr{&influxql.VarRef{Val: "field1"}, &influxql.NumberLiteral{Val: 2}}},
@ -787,7 +660,7 @@ func TestMapTopBottom(t *testing.T) {
limit := int(lit.Val) limit := int(lit.Val)
fields := topCallArgs(test.call) fields := topCallArgs(test.call)
values := MapTopBottom(test.iter, limit, fields, len(test.call.Args), test.call.Name).(PositionPoints) values := MapTopBottom(test.input, limit, fields, len(test.call.Args), test.call.Name).(PositionPoints)
t.Logf("Test: %s", test.name) t.Logf("Test: %s", test.name)
if exp, got := len(test.exp.points), len(values); exp != got { if exp, got := len(test.exp.points), len(values); exp != got {
t.Errorf("Wrong number of values. exp %v got %v", exp, got) t.Errorf("Wrong number of values. exp %v got %v", exp, got)

View File

@ -605,15 +605,25 @@ func (m *AggregateMapper) NextChunk() (interface{}, error) {
tsc.SelectFields = []string{m.fieldNames[i]} tsc.SelectFields = []string{m.fieldNames[i]}
tsc.SelectWhereFields = uniqueStrings([]string{m.fieldNames[i]}, m.whereFields) tsc.SelectWhereFields = uniqueStrings([]string{m.fieldNames[i]}, m.whereFields)
// Execute the map function which walks the entire interval, and aggregates the result. // Build a map input from the cursor.
mapValue := m.mapFuncs[i](&AggregateTagSetCursor{ input := &MapInput{
cursor: tsc, TMin: -1,
tmin: tmin, }
stmt: m.stmt, if len(m.stmt.Dimensions) > 0 && !m.stmt.HasTimeFieldSpecified() {
input.TMin = tmin
}
qmin: qmin, for k, v := tsc.Next(qmin, qmax); k != -1; k, v = tsc.Next(qmin, qmax) {
qmax: qmax, input.Items = append(input.Items, MapItem{
Timestamp: k,
Value: v,
Fields: tsc.Fields(),
Tags: tsc.Tags(),
}) })
}
// Execute the map function which walks the entire interval, and aggregates the result.
mapValue := m.mapFuncs[i](input)
output.Values[0].Value = append(output.Values[0].Value.([]interface{}), mapValue) output.Values[0].Value = append(output.Values[0].Value.([]interface{}), mapValue)
} }
@ -635,40 +645,6 @@ func (m *AggregateMapper) nextInterval() (start, end int64) {
return return
} }
// AggregateTagSetCursor wraps a standard tagSetCursor, such that the values it emits are aggregated by intervals.
type AggregateTagSetCursor struct {
cursor *TagSetCursor
qmin int64
qmax int64
tmin int64
stmt *influxql.SelectStatement
}
// Next returns the next aggregate value for the cursor.
func (a *AggregateTagSetCursor) Next() (time int64, value interface{}) {
return a.cursor.Next(a.qmin, a.qmax)
}
// Fields returns the current fields for the cursor
func (a *AggregateTagSetCursor) Fields() map[string]interface{} {
return a.cursor.Fields()
}
// Tags returns the current tags for the cursor
func (a *AggregateTagSetCursor) Tags() map[string]string { return a.cursor.Tags() }
// TMin returns the current floor time for the bucket being worked on
func (a *AggregateTagSetCursor) TMin() int64 {
if len(a.stmt.Dimensions) == 0 {
return -1
}
if !a.stmt.HasTimeFieldSpecified() {
return a.tmin
}
return -1
}
// uniqueStrings returns a slice of unique strings from all lists in a. // uniqueStrings returns a slice of unique strings from all lists in a.
func uniqueStrings(a ...[]string) []string { func uniqueStrings(a ...[]string) []string {
// Calculate unique set of strings. // Calculate unique set of strings.