mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-06 00:25:55 +00:00
Merge pull request #113 from molecula/minMaxFloatHandling
min and max should properly scale their output for decimal fields
This commit is contained in:
commit
ebab831a43
12 changed files with 3835 additions and 2358 deletions
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
98
executor.go
98
executor.go
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
73
field.go
73
field.go
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -50,6 +50,7 @@ message GroupCount{
|
|||
message ValCount {
|
||||
int64 Val = 1;
|
||||
int64 Count = 2;
|
||||
double FloatVal = 3;
|
||||
}
|
||||
|
||||
message ColumnAttrSet {
|
||||
|
|
|
|||
|
|
@ -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"}}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue