Repository: timshannon/bolthold
Pull request #131 (2021-09-07 15:59:01)

Added nested field support for Aggregate queries


Patch:
diff --git a/aggregate.go b/aggregate.go
index b27ea13..ae62e81 100644
--- a/aggregate.go
+++ b/aggregate.go
@@ -66,17 +66,17 @@ func (a *aggregateResultSort) Swap(i, j int) {
 }
 func (a *aggregateResultSort) Less(i, j int) bool {
 	//reduction values are always pointers
-	iVal := a.reduction[i].Elem().FieldByName(a.sortby)
-	if !iVal.IsValid() {
-		panic(fmt.Sprintf("The field %s does not exist in the type %s", a.sortby, a.reduction[i].Type()))
+	iVal, err := fieldValue(a.reduction[i].Elem(), a.sortby)
+	if err != nil {
+		panic(err)
 	}
 
-	jVal := a.reduction[j].Elem().FieldByName(a.sortby)
-	if !jVal.IsValid() {
-		panic(fmt.Sprintf("The field %s does not exist in the type %s", a.sortby, a.reduction[j].Type()))
+	jVal, err := fieldValue(a.reduction[j].Elem(), a.sortby)
+	if err != nil {
+		panic(err)
 	}
 
-	c, err := compare(iVal.Interface(), jVal.Interface())
+	c, err := compare(iVal, jVal)
 	if err != nil {
 		panic(err)
 	}
@@ -144,12 +144,12 @@ func (a *AggregateResult) Sum(field string) float64 {
 	var sum float64
 
 	for i := range a.reduction {
-		fVal := a.reduction[i].Elem().FieldByName(field)
-		if !fVal.IsValid() {
-			panic(fmt.Sprintf("The field %s does not exist in the type %s", field, a.reduction[i].Type()))
+		fVal, err := fieldValue(a.reduction[i].Elem(), field)
+		if err != nil {
+			panic(err)
 		}
 
-		sum += tryFloat(fVal)
+		sum += tryFloat(reflect.ValueOf(fVal))
 	}
 
 	return sum
diff --git a/query.go b/query.go
index a4d5c8b..7869374 100644
--- a/query.go
+++ b/query.go
@@ -411,12 +411,11 @@ func (s *Store) aggregateQuery(source BucketSource, dataType interface{}, query
 			grouping := make([]reflect.Value, len(groupBy))
 
 			for i := range groupBy {
-				fVal := r.value.Elem().FieldByName(groupBy[i])
-				if !fVal.IsValid() {
-					return fmt.Errorf("The field %s does not exist in the type %s", groupBy[i], r.value.Type())
+				fVal, err := fieldValue(r.value.Elem(), groupBy[i])
+				if err != nil {
+					return err
 				}
-
-				grouping[i] = fVal
+				grouping[i] = reflect.ValueOf(fVal)
 			}
 
 			var err error


Test patch:
diff --git a/aggregate_test.go b/aggregate_test.go
index 5c70ca8..b81759a 100644
--- a/aggregate_test.go
+++ b/aggregate_test.go
@@ -519,3 +519,52 @@ func TestFindAggregateWithNoGroupBy(t *testing.T) {
 
 	})
 }
+
+func TestIssue130FindAggregateWithNestedGroupBy(t *testing.T) {
+	testWrap(t, func(store *bolthold.Store, t *testing.T) {
+
+		type NestedAggregateChild struct {
+			Grouping string
+			Value    int
+		}
+
+		type NestedAggregate struct {
+			Child NestedAggregateChild
+		}
+
+		for i := 0; i < 20; i++ {
+			var group = "group1"
+			if i%2 == 0 {
+				group = "group2"
+			}
+
+			store.Insert(i, NestedAggregate{
+				Child: NestedAggregateChild{
+					Grouping: group,
+					Value:    1,
+				},
+			})
+		}
+
+		result, err := store.FindAggregate(NestedAggregate{}, nil, "Child.Grouping")
+		ok(t, err)
+
+		equals(t, 2, len(result))
+
+		for i := range result {
+			equals(t, result[i].Count(), 10)
+			equals(t, result[i].Sum("Child.Value"), 10.0)
+			equals(t, result[i].Avg("Child.Value"), 1.0)
+
+			r := &NestedAggregate{}
+			result[i].Max("Child.Value", r)
+			equals(t, 1, r.Child.Value)
+
+			result[i].Min("Child.Value", r)
+			equals(t, 1, r.Child.Value)
+
+			result[i].Sort("Child.Value")
+		}
+
+	})
+}