Merge pull request #113 from molecula/minMaxFloatHandling

min and max should properly scale their output for decimal fields
This commit is contained in:
Matthew Jaffee 2020-02-21 16:38:11 -06:00 committed by GitHub
commit ebab831a43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 3835 additions and 2358 deletions

View file

@ -1335,8 +1335,9 @@ func decodePairField(pb *internal.Pair) pilosa.PairField {
func decodeValCount(pb *internal.ValCount) pilosa.ValCount {
return pilosa.ValCount{
Val: pb.Val,
Count: pb.Count,
Val: pb.Val,
FloatVal: pb.FloatVal,
Count: pb.Count,
}
}
@ -1460,8 +1461,9 @@ func encodePairField(p pilosa.PairField) *internal.Pair {
func encodeValCount(vc pilosa.ValCount) *internal.ValCount {
return &internal.ValCount{
Val: vc.Val,
Count: vc.Count,
Val: vc.Val,
FloatVal: vc.FloatVal,
Count: vc.Count,
}
}

View file

@ -770,7 +770,8 @@ func (e *executor) executeSum(ctx context.Context, index string, c *pql.Call, sh
span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeSum")
defer span.Finish()
if field := c.Args["field"]; field == "" {
fieldName, ok := c.Args["field"].(string)
if !ok || fieldName == "" {
return ValCount{}, errors.New("Sum(): field required")
}
@ -798,6 +799,21 @@ func (e *executor) executeSum(ctx context.Context, index string, c *pql.Call, sh
if other.Count == 0 {
return ValCount{}, nil
}
// scale summed response into float if decimal field and this is
// not a remote query (we're about to return to original client).
if !opt.Remote {
field := e.Holder.Field(index, fieldName)
if field == nil {
return ValCount{}, ErrFieldNotFound
}
if field.Type() == FieldTypeDecimal {
if scale := field.Options().Scale; scale != 0 {
other.FloatVal = float64(other.Val) / math.Pow10(int(scale))
other.Val = 0
}
}
}
return other, nil
}
@ -1244,24 +1260,7 @@ func (e *executor) executeMinShard(ctx context.Context, index string, c *pql.Cal
return ValCount{}, nil
}
bsig := field.bsiGroup(fieldName)
if bsig == nil {
return ValCount{}, nil
}
fragment := e.Holder.fragment(index, fieldName, viewBSIGroupPrefix+fieldName, shard)
if fragment == nil {
return ValCount{}, nil
}
fmin, fcount, err := fragment.min(filter, bsig.BitDepth)
if err != nil {
return ValCount{}, err
}
return ValCount{
Val: int64(fmin) + bsig.Base,
Count: int64(fcount),
}, nil
return field.MinForShard(shard, filter)
}
// executeMaxShard calculates the max for bsiGroups on a shard.
@ -1282,24 +1281,7 @@ func (e *executor) executeMaxShard(ctx context.Context, index string, c *pql.Cal
return ValCount{}, nil
}
bsig := field.bsiGroup(fieldName)
if bsig == nil {
return ValCount{}, nil
}
fragment := e.Holder.fragment(index, fieldName, viewBSIGroupPrefix+fieldName, shard)
if fragment == nil {
return ValCount{}, nil
}
fmax, fcount, err := fragment.max(filter, bsig.BitDepth)
if err != nil {
return ValCount{}, err
}
return ValCount{
Val: int64(fmax) + bsig.Base,
Count: int64(fcount),
}, nil
return field.MaxForShard(shard, filter)
}
// executeMinRowShard returns the minimum row ID for a shard.
@ -4113,10 +4095,11 @@ func (sr *SignedRow) union(other SignedRow) SignedRow {
return ret
}
// ValCount represents a grouping of sum & count for Sum() and Average() calls.
// ValCount represents a grouping of sum & count for Sum() and Average() calls. Also Min, Max....
type ValCount struct {
Val int64 `json:"value"`
Count int64 `json:"count"`
Val int64 `json:"value"`
FloatVal float64 `json:"floatValue"`
Count int64 `json:"count"`
}
func (vc *ValCount) add(other ValCount) ValCount {
@ -4128,6 +4111,9 @@ func (vc *ValCount) add(other ValCount) ValCount {
// smaller returns the smaller of the two ValCounts.
func (vc *ValCount) smaller(other ValCount) ValCount {
if vc.FloatVal != 0 || other.FloatVal != 0 {
return vc.floatSmaller(other)
}
if vc.Count == 0 || (other.Val < vc.Val && other.Count > 0) {
return other
}
@ -4141,8 +4127,26 @@ func (vc *ValCount) smaller(other ValCount) ValCount {
}
}
func (vc *ValCount) floatSmaller(other ValCount) ValCount {
if vc.Count == 0 || (other.FloatVal < vc.FloatVal && other.Count > 0) {
return other
}
extra := int64(0)
if vc.FloatVal == other.FloatVal {
extra += other.Count
}
return ValCount{
FloatVal: vc.FloatVal,
Count: vc.Count + extra,
}
}
// larger returns the larger of the two ValCounts.
func (vc *ValCount) larger(other ValCount) ValCount {
if vc.FloatVal != 0 || other.FloatVal != 0 {
return vc.floatLarger(other)
}
if vc.Count == 0 || (other.Val > vc.Val && other.Count > 0) {
return other
}
@ -4156,6 +4160,20 @@ func (vc *ValCount) larger(other ValCount) ValCount {
}
}
func (vc *ValCount) floatLarger(other ValCount) ValCount {
if vc.Count == 0 || (other.FloatVal > vc.FloatVal && other.Count > 0) {
return other
}
extra := int64(0)
if vc.FloatVal == other.FloatVal {
extra += other.Count
}
return ValCount{
FloatVal: vc.FloatVal,
Count: vc.Count + extra,
}
}
func callArgBool(call *pql.Call, key string) (bool, error) {
value, ok := call.Args[key]
if !ok {

View file

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"strconv"
"strings"
"testing"
@ -381,3 +382,59 @@ func TestExecutor_GroupCountCondition(t *testing.T) {
}
})
}
func TestValCountComparisons(t *testing.T) {
tests := []struct {
name string
vc ValCount
other ValCount
expLarger ValCount
expSmaller ValCount
}{
{
name: "zero",
},
{
name: "ints",
vc: ValCount{Val: 10, Count: 1},
other: ValCount{Val: 3, Count: 2},
expLarger: ValCount{Val: 10, Count: 1},
expSmaller: ValCount{Val: 3, Count: 2},
},
{
name: "floats",
vc: ValCount{FloatVal: 10.2, Count: 1},
other: ValCount{FloatVal: 3.4, Count: 2},
expLarger: ValCount{FloatVal: 10.2, Count: 1},
expSmaller: ValCount{FloatVal: 3.4, Count: 2},
},
{
name: "intsEquality",
vc: ValCount{Val: 10, Count: 1},
other: ValCount{Val: 10, Count: 2},
expLarger: ValCount{Val: 10, Count: 3},
expSmaller: ValCount{Val: 10, Count: 3},
},
{
name: "floatsEquality",
vc: ValCount{FloatVal: 10.7, Count: 1},
other: ValCount{FloatVal: 10.7, Count: 2},
expLarger: ValCount{FloatVal: 10.7, Count: 3},
expSmaller: ValCount{FloatVal: 10.7, Count: 3},
},
}
for i, test := range tests {
t.Run(test.name+strconv.Itoa(i), func(t *testing.T) {
gotLarger := test.vc.larger(test.other)
if gotLarger != test.expLarger {
t.Fatalf("larger failed, expected:\n%+v\ngot:\n%+v", test.expLarger, gotLarger)
}
gotSmaller := test.vc.smaller(test.other)
if gotSmaller != test.expSmaller {
t.Fatalf("smaller failed, expected:\n%+v\ngot:\n%+v", test.expSmaller, gotSmaller)
}
})
}
}

View file

@ -4692,6 +4692,10 @@ func TestExecutor_Execute_MinMaxCountEqual(t *testing.T) {
t.Fatal(err)
}
if _, err := idx.CreateField("dec", pilosa.OptFieldTypeDecimal(3)); err != nil {
t.Fatal(err)
}
if _, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `
Set(0, f=3)
Set(1, f=3)
@ -4706,6 +4710,10 @@ func TestExecutor_Execute_MinMaxCountEqual(t *testing.T) {
Set(` + strconv.Itoa(2*ShardWidth+1) + `, f=3)
Set(0, x=3)
Set(1, x=3)
Set(0, dec=5.122)
Set(1, dec=12.985)
Set(2, dec=4.234)
Set(3, dec=12.985)
`}); err != nil {
t.Fatal(err)
@ -4734,6 +4742,29 @@ func TestExecutor_Execute_MinMaxCountEqual(t *testing.T) {
}
}
})
t.Run("MinDec", func(t *testing.T) {
tests := []struct {
filter string
exp float64
cnt int64
}{
{filter: ``, exp: 4.234, cnt: 1},
{filter: `Row(x=3)`, exp: 5.122, cnt: 1},
}
for i, tt := range tests {
var pql string
if tt.filter == "" {
pql = `Min(field=dec)`
} else {
pql = fmt.Sprintf(`Min(%s, field=dec)`, tt.filter)
}
if result, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(result.Results[0], pilosa.ValCount{FloatVal: tt.exp, Count: tt.cnt}) {
t.Fatalf("unexpected result, test %d: %s", i, spew.Sdump(result.Results[0]))
}
}
})
t.Run("Max", func(t *testing.T) {
tests := []struct {
@ -4757,6 +4788,30 @@ func TestExecutor_Execute_MinMaxCountEqual(t *testing.T) {
}
}
})
t.Run("MaxDec", func(t *testing.T) {
tests := []struct {
filter string
exp float64
cnt int64
}{
{filter: ``, exp: 12.985, cnt: 2},
{filter: `Row(x=3)`, exp: 12.985, cnt: 1},
}
for i, tt := range tests {
var pql string
if tt.filter == "" {
pql = `Max(field=dec)`
} else {
pql = fmt.Sprintf(`Max(%s, field=dec)`, tt.filter)
}
if result, err := c[0].API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: pql}); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(result.Results[0], pilosa.ValCount{FloatVal: tt.exp, Count: tt.cnt}) {
t.Fatalf("unexpected result, test %d: %s", i, spew.Sdump(result.Results[0]))
}
}
})
}
func TestExecutor_Execute_NoIndex(t *testing.T) {

View file

@ -1369,6 +1369,7 @@ func (f *Field) Sum(filter *Row, name string) (sum, count int64, err error) {
// FloatMin performs a Min query and converts the result to a float
// based on the field's configured scale.
// TODO: this and Min are probably worthless
func (f *Field) FloatMin(filter *Row, name string) (min float64, count int64, err error) {
bsig := f.bsiGroup(f.name)
if bsig == nil {
@ -1404,6 +1405,11 @@ func (f *Field) Min(filter *Row, name string) (min, count int64, err error) {
// FloatMax performs a max query and converts the result to a float
// based on the field's configured scale.
//
// TODO, this isn't really used, because it's kind of useless. It will
// only get the max among shards on this node, but all query execution
// already happens at the shard level and bypasses this entirely
// calling fragment.max instead.
func (f *Field) FloatMax(filter *Row, name string) (max float64, count int64, err error) {
bsig := f.bsiGroup(f.name)
if bsig == nil {
@ -1417,6 +1423,73 @@ func (f *Field) FloatMax(filter *Row, name string) (max float64, count int64, er
return max, count, err
}
func (f *Field) MaxForShard(shard uint64, filter *Row) (ValCount, error) {
bsig := f.bsiGroup(f.name)
if bsig == nil {
return ValCount{}, ErrBSIGroupNotFound
}
view := f.view(viewBSIGroupPrefix + f.name)
if view == nil {
return ValCount{}, nil
}
fragment := view.Fragment(shard)
if fragment == nil {
return ValCount{}, nil
}
max, cnt, err := fragment.max(filter, bsig.BitDepth)
if err != nil {
return ValCount{}, errors.Wrap(err, "calling fragment.max")
}
valCount := ValCount{Count: int64(cnt)}
if f.Options().Type == FieldTypeDecimal {
valCount.FloatVal = float64(max) / math.Pow10(int(bsig.Scale))
} else {
valCount.Val = max
}
return valCount, nil
}
// MinForShard returns the minimum value which appears in this shard
// (this field must be an Int or Decimal field). It also returns the
// number of times the minimum value appears.
func (f *Field) MinForShard(shard uint64, filter *Row) (ValCount, error) {
bsig := f.bsiGroup(f.name)
if bsig == nil {
return ValCount{}, ErrBSIGroupNotFound
}
view := f.view(viewBSIGroupPrefix + f.name)
if view == nil {
return ValCount{}, nil
}
fragment := view.Fragment(shard)
if fragment == nil {
return ValCount{}, nil
}
min, cnt, err := fragment.min(filter, bsig.BitDepth)
if err != nil {
return ValCount{}, errors.Wrap(err, "calling fragment.min")
}
valCount := ValCount{Count: int64(cnt)}
if f.Options().Type == FieldTypeDecimal {
valCount.FloatVal = float64(min) / math.Pow10(int(bsig.Scale))
} else {
valCount.Val = min
}
return valCount, nil
}
// Max returns the max for a field.
// An optional filtering row can be provided.
func (f *Field) Max(filter *Row, name string) (max, count int64, err error) {

View file

@ -21,6 +21,7 @@ import (
"os"
"path/filepath"
"reflect"
"strconv"
"testing"
"time"
@ -576,3 +577,155 @@ func TestBSIGroup_importValue(t *testing.T) {
}
}
}
func TestIntField_MinMaxForShard(t *testing.T) {
f := MustOpenField(OptFieldTypeInt(-100, 200))
options := &ImportOptions{}
for i, test := range []struct {
name string
columnIDs []uint64
values []int64
expMax ValCount
expMin ValCount
}{
{
name: "zero",
columnIDs: []uint64{},
values: []int64{},
},
{
name: "single",
columnIDs: []uint64{1},
values: []int64{10},
expMax: ValCount{Val: 10, Count: 1},
expMin: ValCount{Val: 10, Count: 1},
},
{
name: "twovals",
columnIDs: []uint64{1, 2},
values: []int64{10, 20},
expMax: ValCount{Val: 20, Count: 1},
expMin: ValCount{Val: 10, Count: 1},
},
{
name: "multiplecounts",
columnIDs: []uint64{1, 2, 3, 4, 5},
values: []int64{10, 20, 10, 10, 20},
expMax: ValCount{Val: 20, Count: 2},
expMin: ValCount{Val: 10, Count: 3},
},
{
name: "middlevals",
columnIDs: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
values: []int64{10, 20, 10, 10, 20, 11, 12, 11, 13, 11},
expMax: ValCount{Val: 20, Count: 2},
expMin: ValCount{Val: 10, Count: 3},
},
{
name: "middlevals",
columnIDs: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100000000, 100000001},
values: []int64{10, 20, 10, 10, 20, 11, 12, 11, 13, 11, 44, 1},
expMax: ValCount{Val: 20, Count: 2},
expMin: ValCount{Val: 10, Count: 3},
},
} {
t.Run(test.name+strconv.Itoa(i), func(t *testing.T) {
if err := f.importValue(test.columnIDs, test.values, options); err != nil {
t.Fatalf("test %d, importing values: %s", i, err.Error())
}
maxvc, err := f.MaxForShard(0, nil)
if err != nil {
t.Fatalf("getting max for shard: %v", err)
}
if maxvc != test.expMax {
t.Fatalf("max expected:\n%+v\ngot:\n%+v", test.expMax, maxvc)
}
minvc, err := f.MinForShard(0, nil)
if err != nil {
t.Fatalf("getting min for shard: %v", err)
}
if minvc != test.expMin {
t.Fatalf("min expected:\n%+v\ngot:\n%+v", test.expMin, minvc)
}
})
}
}
func TestDecimalField_MinMaxForShard(t *testing.T) {
f := MustOpenField(OptFieldTypeDecimal(3))
options := &ImportOptions{}
for i, test := range []struct {
name string
columnIDs []uint64
values []float64
expMax ValCount
expMin ValCount
}{
{
name: "zero",
columnIDs: []uint64{},
values: []float64{},
},
{
name: "single",
columnIDs: []uint64{1},
values: []float64{10.1},
expMax: ValCount{FloatVal: 10.1, Count: 1},
expMin: ValCount{FloatVal: 10.1, Count: 1},
},
{
name: "twovals",
columnIDs: []uint64{1, 2},
values: []float64{10.1, 20.2},
expMax: ValCount{FloatVal: 20.2, Count: 1},
expMin: ValCount{FloatVal: 10.1, Count: 1},
},
{
name: "multiplecounts",
columnIDs: []uint64{1, 2, 3, 4, 5},
values: []float64{10.1, 20.2, 10.1, 10.1, 20.2},
expMax: ValCount{FloatVal: 20.2, Count: 2},
expMin: ValCount{FloatVal: 10.1, Count: 3},
},
{
name: "middlevals",
columnIDs: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
values: []float64{10.1, 20.2, 10.1, 10.1, 20.2, 11, 12, 11, 13, 11},
expMax: ValCount{FloatVal: 20.2, Count: 2},
expMin: ValCount{FloatVal: 10.1, Count: 3},
},
{
name: "another shard",
columnIDs: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100000000, 100000001},
values: []float64{10.1, 20.2, 10.1, 10.1, 20.2, 11, 12, 11, 13, 11, 44.39, 0.23},
expMax: ValCount{FloatVal: 20.2, Count: 2},
expMin: ValCount{FloatVal: 10.1, Count: 3},
},
} {
t.Run(test.name+strconv.Itoa(i), func(t *testing.T) {
if err := f.importFloatValue(test.columnIDs, test.values, options); err != nil {
t.Fatalf("test %d, importing values: %s", i, err.Error())
}
maxvc, err := f.MaxForShard(0, nil)
if err != nil {
t.Fatalf("getting max for shard: %v", err)
}
if maxvc != test.expMax {
t.Fatalf("max expected:\n%+v\ngot:\n%+v", test.expMax, maxvc)
}
minvc, err := f.MinForShard(0, nil)
if err != nil {
t.Fatalf("getting min for shard: %v", err)
}
if minvc != test.expMin {
t.Fatalf("min expected:\n%+v\ngot:\n%+v", test.expMin, minvc)
}
})
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -50,6 +50,7 @@ message GroupCount{
message ValCount {
int64 Val = 1;
int64 Count = 2;
double FloatVal = 3;
}
message ColumnAttrSet {

View file

@ -777,16 +777,32 @@ func makeRows(resp pilosa.QueryResponse, logger logger.Logger) chan *pb.RowRespo
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_BoolVal{BoolVal: r}},
}}
case pilosa.ValCount:
ci := []*pb.ColumnInfo{
{Name: "value", Datatype: "int64"},
{Name: "count", Datatype: "int64"},
var ci []*pb.ColumnInfo
// ValCount can have a float or integeger value, but
// not both (as of this writing).
if r.FloatVal != 0 {
ci = []*pb.ColumnInfo{
{Name: "value", Datatype: "float64"},
{Name: "count", Datatype: "int64"},
}
results <- &pb.RowResponse{
Headers: ci,
Columns: []*pb.ColumnResponse{
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Float64Val{Float64Val: r.FloatVal}},
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: r.Count}},
}}
} else {
ci = []*pb.ColumnInfo{
{Name: "value", Datatype: "int64"},
{Name: "count", Datatype: "int64"},
}
results <- &pb.RowResponse{
Headers: ci,
Columns: []*pb.ColumnResponse{
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: r.Val}},
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: r.Count}},
}}
}
results <- &pb.RowResponse{
Headers: ci,
Columns: []*pb.ColumnResponse{
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: r.Val}},
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Int64Val{Int64Val: r.Count}},
}}
case pilosa.SignedRow:
// TODO: address the overflow issue with values outside the int64 range
ci := []*pb.ColumnInfo{{Name: r.Field(), Datatype: "int64"}}

View file

@ -35,6 +35,16 @@ func TestGRPC(t *testing.T) {
expHeaders []expHeader
expColumns [][]expColumn
}{
{
pilosa.ValCount{Val: 1, Count: 1},
[]expHeader{{"value", "int64"}, {"count", "int64"}},
[][]expColumn{{int64(1), int64(1)}},
},
{
pilosa.ValCount{FloatVal: 1.24, Count: 1},
[]expHeader{{"value", "float64"}, {"count", "int64"}},
[][]expColumn{{float64(1.24), int64(1)}},
},
// Row (uint64)
{
pilosa.NewRow(10, 11, 12),
@ -266,6 +276,11 @@ func TestGRPC(t *testing.T) {
if val != v {
t.Fatalf("test %d expected column val: %v but got: %v", ti, v, val)
}
case float64:
val := column.GetFloat64Val()
if val != v {
t.Fatalf("test %d expected column val: %v but got: %v", ti, v, val)
}
default:
t.Fatalf("test %d has unhandled data type: %T", ti, v)
}

View file

@ -298,6 +298,41 @@ func TestMain_GroupBy(t *testing.T) {
}
}
func TestMain_MinMaxFloat(t *testing.T) {
m := test.MustRunCommand()
defer m.Close()
// Create fields.
client := m.Client()
if err := client.CreateIndex(context.Background(), "i", pilosa.IndexOptions{}); err != nil && err != pilosa.ErrIndexExists {
t.Fatal(err)
}
if err := client.CreateFieldWithOptions(context.Background(), "i", "dec", pilosa.FieldOptions{Type: pilosa.FieldTypeDecimal, Scale: 3, Max: 100000}); err != nil {
t.Fatal(err)
}
query := `
Set(0, dec=1.32)
Set(1, dec=4.44)
`
// Set columns on row.
if _, err := m.Query("i", "", query); err != nil {
t.Fatal(err)
}
// Query row.
exp0 := pilosa.ValCount{FloatVal: 4.44, Count: 1}
exp1 := pilosa.ValCount{FloatVal: 1.32, Count: 1}
if res, err := m.QueryProtobuf("i", `Max(field=dec) Min(field=dec)`); err != nil {
t.Fatal(err)
} else if res.Results[0] != exp0 ||
res.Results[1] != exp1 {
t.Fatalf("unexpected results: %+v", res.Results)
}
}
// Ensure the host can be parsed.
func TestConfig_Parse_Host(t *testing.T) {
if c, err := ParseConfig(`bind = "local"`); err != nil {
@ -984,3 +1019,45 @@ func TestClusterExhaustingConnectionsImport(t *testing.T) {
t.Fatalf("setting lots of shards: %v", err)
}
}
func TestClusterMinMaxSumDecimal(t *testing.T) {
cluster := test.MustRunCluster(t, 3)
defer cluster.Close()
cmd := cluster[0]
cmd.MustCreateIndex(t, "testdec", pilosa.IndexOptions{Keys: true, TrackExistence: true})
cmd.MustCreateField(t, "testdec", "adec", pilosa.OptFieldTypeDecimal(2))
test.MustDo("POST", cluster[0].URL()+"/index/testdec/query", `
Set("a", adec=42.2)
Set("b", adec=11.12)
Set("c", adec=13.41)
Set("d", adec=99.87)
Set("e", adec=11.13)
Set("f", adec=12.12)
Set("g", adec=15.52)
Set("h", adec=100.22)
`)
result := test.MustDo("POST", cluster[0].URL()+"/index/testdec/query", "Sum(field=adec)")
if !strings.Contains(result.Body, `"floatValue":305.59`) {
t.Fatalf("expected float sum of 305.59, but got: '%s'", result.Body)
} else if !strings.Contains(result.Body, `"count":8`) {
t.Fatalf("expected count 8, but got: '%s'", result.Body)
}
result = test.MustDo("POST", cluster[0].URL()+"/index/testdec/query", "Max(field=adec)")
if !strings.Contains(result.Body, `"floatValue":100.22`) {
t.Fatalf("expected float max of 100.22, but got: '%s'", result.Body)
} else if !strings.Contains(result.Body, `"count":1`) {
t.Fatalf("expected count 1, but got: '%s'", result.Body)
}
result = test.MustDo("POST", cluster[0].URL()+"/index/testdec/query", "Min(field=adec)")
if !strings.Contains(result.Body, `"floatValue":11.12`) {
t.Fatalf("expected float min of 11.12, but got: '%s'", result.Body)
} else if !strings.Contains(result.Body, `"count":1`) {
t.Fatalf("expected count 1, but got: '%s'", result.Body)
}
}