diff --git a/executor.go b/executor.go index 69d96928f..a46ca6d2a 100644 --- a/executor.go +++ b/executor.go @@ -1825,10 +1825,14 @@ func (e *executor) executeSumCountShard(ctx context.Context, qcx *Qcx, index str if err != nil { return ValCount{}, errors.Wrap(err, "computing sum") } - return ValCount{ + out := ValCount{ Val: int64(vsum) + (int64(vcount) * bsig.Base), Count: int64(vcount), - }, nil + } + if field.Type() == FieldTypeDecimal { + out.FloatVal = float64(int64(vsum)+(int64(vcount)*bsig.Base)) / math.Pow(10, float64(bsig.Scale)) + } + return out, nil } // executeMinShard calculates the min for bsiGroups on a shard. @@ -3044,6 +3048,13 @@ func (e *executor) executeGroupBy(ctx context.Context, qcx *Qcx, index string, c aggType = "aggregate" } } + for _, res := range results { + if res.DecimalAgg != 0 && aggType == "sum" { + aggType = "decimalSum" + break + } + } + return NewGroupCounts(aggType, results...), nil } @@ -3132,9 +3143,10 @@ func (fr FieldRow) String() string { type aggregateType int const ( - nilAggregate aggregateType = 0 - sumAggregate aggregateType = 1 - distinctAggregate aggregateType = 2 + nilAggregate aggregateType = 0 + sumAggregate aggregateType = 1 + distinctAggregate aggregateType = 2 + decimalSumAggregate aggregateType = 3 ) // GroupCounts is a list of GroupCount. @@ -3152,6 +3164,8 @@ func (g *GroupCounts) AggregateColumn() string { return "sum" case distinctAggregate: return "aggregate" + case decimalSumAggregate: + return "decimalSum" default: return "" } @@ -3176,6 +3190,8 @@ func NewGroupCounts(agg string, groups ...GroupCount) *GroupCounts { aggType = sumAggregate case "aggregate": aggType = distinctAggregate + case "decimalSum": + aggType = decimalSumAggregate case "": aggType = nilAggregate default: @@ -3246,42 +3262,57 @@ func (g *GroupCounts) MarshalJSON() ([]byte, error) { if len(groups) == 0 { return []byte("[]"), nil } + switch g.aggregateType { case sumAggregate: counts = *(*[]groupCountSum)(unsafe.Pointer(&groups)) case distinctAggregate: counts = *(*[]groupCountAggregate)(unsafe.Pointer(&groups)) + case decimalSumAggregate: + counts = *(*[]groupCountDecimalSum)(unsafe.Pointer(&groups)) } return json.Marshal(counts) } // GroupCount represents a result item for a group by query. type GroupCount struct { - Group []FieldRow `json:"group"` - Count uint64 `json:"count"` - Agg int64 `json:"-"` + Group []FieldRow `json:"group"` + Count uint64 `json:"count"` + Agg int64 `json:"-"` + DecimalAgg float64 `json:"-"` } type groupCountSum struct { - Group []FieldRow `json:"group"` - Count uint64 `json:"count"` - Agg int64 `json:"sum"` + Group []FieldRow `json:"group"` + Count uint64 `json:"count"` + Agg int64 `json:"sum"` + DecimalAgg float64 `json:"-"` } type groupCountAggregate struct { - Group []FieldRow `json:"group"` - Count uint64 `json:"count"` - Agg int64 `json:"aggregate"` + Group []FieldRow `json:"group"` + Count uint64 `json:"count"` + Agg int64 `json:"aggregate"` + DecimalAgg float64 `json:"-"` +} + +type groupCountDecimalSum struct { + Group []FieldRow `json:"group"` + Count uint64 `json:"count"` + Agg int64 `json:"-"` + DecimalAgg float64 `json:"sum"` } var _ GroupCount = GroupCount(groupCountSum{}) var _ GroupCount = GroupCount(groupCountAggregate{}) +var _ GroupCount = GroupCount(groupCountDecimalSum{}) func (g *GroupCount) Clone() (r *GroupCount) { r = &GroupCount{ - Group: make([]FieldRow, len(g.Group)), - Count: g.Count, - Agg: g.Agg, + Group: make([]FieldRow, len(g.Group)), + Count: g.Count, + Agg: g.Agg, + DecimalAgg: g.DecimalAgg, } for i := range g.Group { r.Group[i] = *(g.Group[i].Clone()) @@ -3306,6 +3337,7 @@ func mergeGroupCounts(a, b []GroupCount, limit int) []GroupCount { case 0: a[i].Count += b[j].Count a[i].Agg += b[j].Agg + a[i].DecimalAgg += b[j].DecimalAgg ret = append(ret, a[i]) i++ j++ @@ -7939,6 +7971,7 @@ func (gbi *groupByIterator) Next(ctx context.Context) (ret GroupCount, done bool } ret.Count = uint64(result.Count) ret.Agg = result.Val + ret.DecimalAgg = result.FloatVal } } if ret.Count == 0 { diff --git a/executor_test.go b/executor_test.go index 8440adb9d..414bf47b5 100644 --- a/executor_test.go +++ b/executor_test.go @@ -38,7 +38,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/molecula/featurebase/v2" + pilosa "github.com/molecula/featurebase/v2" "github.com/molecula/featurebase/v2/boltdb" "github.com/molecula/featurebase/v2/disco" "github.com/molecula/featurebase/v2/http" @@ -5151,6 +5151,8 @@ func TestExecutor_GroupByStrings(t *testing.T) { c.CreateField(t, "istring", pilosa.IndexOptions{Keys: true}, "v", pilosa.OptFieldTypeInt(0, 1000)) c.CreateField(t, "istring", pilosa.IndexOptions{Keys: true}, "vv", pilosa.OptFieldTypeInt(0, 1000)) c.CreateField(t, "istring", pilosa.IndexOptions{Keys: true}, "nv", pilosa.OptFieldTypeInt(-1000, 1000)) + c.CreateField(t, "istring", pilosa.IndexOptions{Keys: true}, "dv", pilosa.OptFieldTypeDecimal(2)) + c.CreateField(t, "istring", pilosa.IndexOptions{Keys: true}, "ndv", pilosa.OptFieldTypeDecimal(1)) if err := c.GetNode(0).API.Import(context.Background(), nil, &pilosa.ImportRequest{ Index: "istring", @@ -5167,6 +5169,8 @@ func TestExecutor_GroupByStrings(t *testing.T) { var v1, v2, v3, v4, v5, v6, v7, v8, v9, v10 int64 = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 var nv1, nv2, nv3, nv4 int64 = -1, -2, -3, -4 + var dv1, dv2, dv3, dv4, dv5, dv6, dv7, dv8, dv9, dv10 int64 = 111, 222, 333, 444, 555, 666, 777, 888, 999, 1000 + var ndv1, ndv2, ndv3, ndv4, ndv5, ndv6, ndv7, ndv8, ndv9, ndv10 int64 = -111, -222, -333, -444, -555, -666, -777, -888, -999, -1000 if err := m0.API.ImportValue(context.Background(), qcx, &pilosa.ImportValueRequest{ Index: "istring", Field: "v", @@ -5197,6 +5201,26 @@ func TestExecutor_GroupByStrings(t *testing.T) { t.Fatalf("importing: %v", err) } + if err := m0.API.ImportValue(context.Background(), qcx, &pilosa.ImportValueRequest{ + Index: "istring", + Field: "dv", + Shard: 0, + ColumnKeys: []string{"c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10"}, + Values: []int64{dv1, dv2, dv3, dv4, dv5, dv6, dv7, dv8, dv9, dv10}, + }); err != nil { + t.Fatalf("importing: %v", err) + } + + if err := m0.API.ImportValue(context.Background(), qcx, &pilosa.ImportValueRequest{ + Index: "istring", + Field: "ndv", + Shard: 0, + ColumnKeys: []string{"c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10"}, + Values: []int64{ndv1, ndv2, ndv3, ndv4, ndv5, ndv6, ndv7, ndv8, ndv9, ndv10}, + }); err != nil { + t.Fatalf("importing: %v", err) + } + tests := []struct { query string expected []pilosa.GroupCount @@ -5221,6 +5245,20 @@ func TestExecutor_GroupByStrings(t *testing.T) { {Group: []pilosa.FieldRow{{Field: "generals", RowID: 2, RowKey: "r2"}}, Count: 5, Agg: 30}, }, }, + { + query: "GroupBy(Rows(generals), aggregate=Sum(field=dv))", + expected: []pilosa.GroupCount{ + {Group: []pilosa.FieldRow{{Field: "generals", RowID: 1, RowKey: "r1"}}, Count: 5, Agg: 2775, DecimalAgg: 27.75}, + {Group: []pilosa.FieldRow{{Field: "generals", RowID: 2, RowKey: "r2"}}, Count: 5, Agg: 3220, DecimalAgg: 32.20}, + }, + }, + { + query: "GroupBy(Rows(generals), aggregate=Sum(field=ndv))", + expected: []pilosa.GroupCount{ + {Group: []pilosa.FieldRow{{Field: "generals", RowID: 1, RowKey: "r1"}}, Count: 5, Agg: -2775, DecimalAgg: -277.5}, + {Group: []pilosa.FieldRow{{Field: "generals", RowID: 2, RowKey: "r2"}}, Count: 5, Agg: -3220, DecimalAgg: -322.0}, + }, + }, { query: "GroupBy(Rows(generals), aggregate=Sum(field=v), having=Condition(sum>25))", expected: []pilosa.GroupCount{