diff --git a/graphql/e2e/common/common.go b/graphql/e2e/common/common.go index a9763a10160..d5ff69e3084 100644 --- a/graphql/e2e/common/common.go +++ b/graphql/e2e/common/common.go @@ -645,11 +645,13 @@ func RunAll(t *testing.T) { t.Run("query aggregate without filter", queryAggregateWithoutFilter) t.Run("query aggregate with filter", queryAggregateWithFilter) t.Run("query aggregate on empty data", queryAggregateOnEmptyData) + t.Run("query aggregate on empty scalar data", queryAggregateOnEmptyData2) t.Run("query aggregate with alias", queryAggregateWithAlias) t.Run("query aggregate with repeated fields", queryAggregateWithRepeatedFields) t.Run("query aggregate at child level", queryAggregateAtChildLevel) t.Run("query aggregate at child level with filter", queryAggregateAtChildLevelWithFilter) t.Run("query aggregate at child level with empty data", queryAggregateAtChildLevelWithEmptyData) + t.Run("query aggregate at child level on empty scalar data", queryAggregateOnEmptyData3) t.Run("query aggregate at child level with multiple alias", queryAggregateAtChildLevelWithMultipleAlias) t.Run("query aggregate at child level with repeated fields", queryAggregateAtChildLevelWithRepeatedFields) t.Run("query aggregate and other fields at child level", queryAggregateAndOtherFieldsAtChildLevel) diff --git a/graphql/e2e/common/query.go b/graphql/e2e/common/query.go index 807cdefd7e0..270f219a62c 100644 --- a/graphql/e2e/common/query.go +++ b/graphql/e2e/common/query.go @@ -2855,6 +2855,68 @@ func queryAggregateOnEmptyData(t *testing.T) { string(gqlResponse.Data)) } +func queryAggregateOnEmptyData2(t *testing.T) { + queryPostParams := &GraphQLParams{ + Query: `query { + aggregateState (filter: {xcode : { eq : "nsw" }} ) { + count + capitalMax + capitalMin + xcodeMin + xcodeMax + } + }`, + } + + gqlResponse := queryPostParams.ExecuteAsPost(t, GraphqlURL) + RequireNoGQLErrors(t, gqlResponse) + testutil.CompareJSON(t, + `{ + "aggregateState": + { + "capitalMax": null, + "capitalMin": null, + "xcodeMin": "nsw", + "xcodeMax": "nsw", + "count": 1 + } + }`, + string(gqlResponse.Data)) +} + +func queryAggregateOnEmptyData3(t *testing.T) { + queryNumberOfStates := &GraphQLParams{ + Query: `query + { + queryCountry(filter: { name: { eq: "India" } }) { + name + ag : statesAggregate { + count + nameMin + capitalMax + capitalMin + } + } + }`, + } + gqlResponse := queryNumberOfStates.ExecuteAsPost(t, GraphqlURL) + RequireNoGQLErrors(t, gqlResponse) + testutil.CompareJSON(t, + ` + { + "queryCountry": [{ + "name": "India", + "ag": { + "count" : 3, + "nameMin": "Gujarat", + "capitalMax": null, + "capitalMin": null + } + }] + }`, + string(gqlResponse.Data)) +} + func queryAggregateWithoutFilter(t *testing.T) { queryPostParams := &GraphQLParams{ Query: `query { diff --git a/query/outputnode.go b/query/outputnode.go index b39c7c81b08..5b8c87e28b9 100644 --- a/query/outputnode.go +++ b/query/outputnode.go @@ -914,7 +914,7 @@ func (sg *SubGraph) addAggregations(enc *encoder, fj fastJsonNode) error { } // the aggregation didn't happen, most likely was called with unset vars. // See: query.go:fillVars - aggVal = types.Val{Tid: types.FloatID, Value: float64(0)} + // In this case we do nothing. The aggregate value in response will be returned as NULL. } if child.Params.Normalize && child.Params.Alias == "" { continue diff --git a/query/query.go b/query/query.go index 79657bd558a..fcc35a63200 100644 --- a/query/query.go +++ b/query/query.go @@ -990,11 +990,6 @@ func evalLevelAgg( // The aggregated value doesn't really belong to a uid, we put it in UidToVal map // corresponding to uid 0 to avoid defining another field in SubGraph. vals := doneVars[needsVar].Vals - if len(vals) == 0 { - mp = make(map[uint64]types.Val) - mp[0] = types.Val{Tid: types.FloatID, Value: 0.0} - return mp, nil - } ag := aggregator{ name: sg.SrcFunc.Name, @@ -1724,7 +1719,13 @@ func (sg *SubGraph) fillVars(mp map[string]varValue) error { if err := sg.replaceVarInFunc(); err != nil { return err } - lists = append(lists, sg.DestUIDs) + + if len(sg.DestUIDs.GetUids()) > 0 { + // Don't add sg.DestUIDs in case its size is 0. + // This is to avoiding adding nil (empty element) to lists. + lists = append(lists, sg.DestUIDs) + } + sg.DestUIDs = algo.MergeSorted(lists) return nil } diff --git a/query/query1_test.go b/query/query1_test.go index 80cf6ad044e..87f2be9515e 100644 --- a/query/query1_test.go +++ b/query/query1_test.go @@ -1473,7 +1473,7 @@ func TestAggregateRoot5(t *testing.T) { } ` js := processQueryNoErr(t, query) - require.JSONEq(t, `{"data": {"me":[{"sum(val(m))":0.000000}]}}`, js) + require.JSONEq(t, `{"data": {"me":[{"sum(val(m))":null}]}}`, js) } func TestAggregateRoot6(t *testing.T) { @@ -1519,6 +1519,66 @@ func TestAggregateRootError(t *testing.T) { require.Contains(t, err.Error(), "Only aggregated variables allowed within empty block.") } +func TestAggregateEmptyData(t *testing.T) { + + query := ` + { + var(func: anyofterms(name, "Non-Existent-Data")) { + a as age + } + + me() { + avg(val(a)) + min(val(a)) + max(val(a)) + } + } + ` + js := processQueryNoErr(t, query) + require.JSONEq(t, `{"data": {"me":[{"avg(val(a))":null},{"min(val(a))":null},{"max(val(a))":null}]}}`, js) +} + +func TestCountEmptyData(t *testing.T) { + + query := ` + { + me(func: anyofterms(name, "Non-Existent-Data")) { + a: count(uid) + } + } + ` + js := processQueryNoErr(t, query) + require.JSONEq(t, `{"data": {"me":[{"a":0}]}}`, js) +} + +func TestCountEmptyData2(t *testing.T) { + + query := ` + { + a as var(func: eq(name, "Michonne")) + me(func: uid(a)) { + c: count(friend) @filter(eq(name, "non-existent")) + } + } + ` + js := processQueryNoErr(t, query) + require.JSONEq(t, `{"data": {"me":[{"c":0}]}}`, js) +} + +func TestCountEmptyData3(t *testing.T) { + + query := ` + { + a as var(func: eq(name, "Michonne")) + me(func: uid(a)) { + c: count(friend2) + } + } + ` + js := processQueryNoErr(t, query) + require.JSONEq(t, `{"data": {"me":[]}}`, js) +} + func TestAggregateEmpty1(t *testing.T) { query := ` {