Skip to content

Commit

Permalink
graphql: Added feature to ask for numUids in GraphQL layer (#4762)
Browse files Browse the repository at this point in the history
Adds a query field NumUid in Add/Update/Delete Payload.
  • Loading branch information
harshil-goel authored Feb 14, 2020
1 parent 2fa518b commit ebbb91d
Show file tree
Hide file tree
Showing 26 changed files with 341 additions and 17 deletions.
1 change: 1 addition & 0 deletions graphql/e2e/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ func RunAll(t *testing.T) {
t.Run("error in multiple mutations", addMultipleMutationWithOneError)
t.Run("dgraph directive with reverse edge adds data correctly",
addMutationWithReverseDgraphEdge)
t.Run("numUids test", testNumUids)

// error tests
t.Run("graphql completion on", graphQLCompletionOn)
Expand Down
166 changes: 154 additions & 12 deletions graphql/e2e/common/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,7 @@ func filterInUpdate(t *testing.T) {
var result struct {
UpdateCountry struct {
Country []*country
NumUids int
}
}

Expand Down Expand Up @@ -1527,23 +1528,27 @@ func deletePost(
}

func deleteWrongID(t *testing.T) {
t.Skip()
// Skipping the test for now because wrong type of node while deleting is not an error.
// After Dgraph returns the number of nodes modified from upsert, modify this test to check
// count of nodes modified is 0.
//
// FIXME: Test cases : with a wrongID, a malformed ID "blah", and maybe a filter that
// doesn't match anything.
newCountry := addCountry(t, postExecutor)
newAuthor := addAuthor(t, newCountry.ID, postExecutor)

expectedData := `{ "deleteCountry": null }`
expectedErrors := x.GqlErrorList{
&x.GqlError{Message: `input: couldn't complete deleteCountry because ` +
fmt.Sprintf(`input: Node with id %s is not of type Country`, newAuthor.ID)}}
expectedData := `{ "deleteCountry": {
"msg": "Deleted",
"numUids": 0
} }`

filter := map[string]interface{}{"id": []string{newAuthor.ID}}
deleteCountry(t, filter, expectedData, expectedErrors)
deleteCountryParams := &GraphQLParams{
Query: `mutation deleteCountry($filter: CountryFilter!) {
deleteCountry(filter: $filter) {
msg
numUids
}
}`,
Variables: map[string]interface{}{"filter": filter},
}

gqlResponse := deleteCountryParams.ExecuteAsPost(t, graphqlURL)
require.JSONEq(t, expectedData, string(gqlResponse.Data))

cleanUp(t, []*country{newCountry}, []*author{newAuthor}, []*post{})
}
Expand Down Expand Up @@ -2606,3 +2611,140 @@ func addMutationWithReverseDgraphEdge(t *testing.T) {

cleanupMovieAndDirector(t, newMovie.ID, movieDirectorID)
}

func testNumUids(t *testing.T) {
newCountry := addCountry(t, postExecutor)

auth := &author{
Name: "New Author",
Country: newCountry,
Posts: []*post{
{
Title: "A New Post for testing numUids",
Text: "Text of new post",
Tags: []string{},
Category: &category{Name: "A Category"},
},
{
Title: "Another New Post for testing numUids",
Text: "Text of other new post",
Tags: []string{},
},
},
}

addAuthorParams := &GraphQLParams{
Query: `mutation addAuthor($author: [AddAuthorInput!]!) {
addAuthor(input: $author) {
numUids
author {
id
posts {
postID
}
}
}
}`,
Variables: map[string]interface{}{"author": []*author{auth}},
}

var result struct {
AddAuthor struct {
Author []*author
NumUids int
}
}

gqlResponse := postExecutor(t, graphqlURL, addAuthorParams)
requireNoGQLErrors(t, gqlResponse)

t.Run("Test numUID in add", func(t *testing.T) {
err := json.Unmarshal([]byte(gqlResponse.Data), &result)
require.NoError(t, err)
require.Equal(t, result.AddAuthor.NumUids, 4)
})

t.Run("Test numUID in update", func(t *testing.T) {
updatePostParams := &GraphQLParams{
Query: `mutation updatePosts($posts: UpdatePostInput!) {
updatePost(input: $posts) {
numUids
}
}`,
Variables: map[string]interface{}{"posts": map[string]interface{}{
"filter": map[string]interface{}{
"title": map[string]interface{}{
"anyofterms": "numUids",
},
},
"set": map[string]interface{}{
"numLikes": 999,
},
}},
}

gqlResponse = postExecutor(t, graphqlURL, updatePostParams)
requireNoGQLErrors(t, gqlResponse)

var updateResult struct {
UpdatePost struct {
Post []*post
NumUids int
}
}

err := json.Unmarshal([]byte(gqlResponse.Data), &updateResult)
require.NoError(t, err)
require.Equal(t, updateResult.UpdatePost.NumUids, 2)
})

t.Run("Test numUID in delete", func(t *testing.T) {
deleteAuthorParams := &GraphQLParams{
Query: `mutation deleteItems($authorFilter: AuthorFilter!,
$postFilter: PostFilter!) {
deleteAuthor(filter: $authorFilter) {
numUids
}
deletePost(filter: $postFilter) {
numUids
msg
}
}`,
Variables: map[string]interface{}{
"postFilter": map[string]interface{}{
"title": map[string]interface{}{
"anyofterms": "numUids",
},
},
"authorFilter": map[string]interface{}{
"id": []string{result.AddAuthor.Author[0].ID},
},
},
}
gqlResponse = postExecutor(t, graphqlURL, deleteAuthorParams)
requireNoGQLErrors(t, gqlResponse)

var deleteResult struct {
DeleteAuthor struct {
Msg string
NumUids int
}
DeletePost struct {
Msg string
NumUids int
}
}

err := json.Unmarshal([]byte(gqlResponse.Data), &deleteResult)
require.NoError(t, err)
require.Equal(t, deleteResult.DeleteAuthor.NumUids, 1)
require.Equal(t, deleteResult.DeleteAuthor.Msg, "")
require.Equal(t, deleteResult.DeletePost.NumUids, 2)
require.Equal(t, deleteResult.DeletePost.Msg, "Deleted")
})

cleanUp(t, []*country{newCountry}, result.AddAuthor.Author,
result.AddAuthor.Author[0].Posts)
}
47 changes: 46 additions & 1 deletion graphql/resolve/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package resolve

import (
"context"
"fmt"
"strings"

dgoapi "github.com/dgraph-io/dgo/v2/protos/api"
"github.com/dgraph-io/dgraph/gql"
Expand Down Expand Up @@ -157,6 +159,8 @@ type mutationResolver struct {
queryExecutor QueryExecutor
mutationExecutor MutationExecutor
resultCompleter ResultCompleter

numUids int
}

func (mr *mutationResolver) Resolve(
Expand All @@ -173,12 +177,48 @@ func (mr *mutationResolver) Resolve(
res, success, err := mr.rewriteAndExecute(ctx, mutation)

completed, err := mr.resultCompleter.Complete(ctx, mutation.QueryField(), res, err)

selSets := mutation.SelectionSet()
for _, selSet := range selSets {
if selSet.Name() != schema.NumUid {
continue
}

s := string(completed)
if strings.Contains(s, schema.NumUid) {
completed = []byte(strings.ReplaceAll(s, fmt.Sprintf(`"%s": null`,
schema.NumUid), fmt.Sprintf(`"%s": %d`, schema.NumUid,
mr.numUids)))

} else if s[len(s)-1] == '}' {
completed = []byte(fmt.Sprintf(`%s, "%s": %d}`, s[:len(s)-1],
schema.NumUid, mr.numUids))

} else {
completed = []byte(fmt.Sprintf(`%s, "%s": %d`, s,
schema.NumUid, mr.numUids))
}
break
}

return &Resolved{
Data: completed,
Err: err,
}, success
}

func (mr *mutationResolver) getNumUids(mutation schema.Mutation, assigned map[string]string,
result map[string]interface{}) {
switch mr.mutationRewriter.(type) {
case *addRewriter:
mr.numUids = len(assigned)

default:
mutated := extractMutated(result, mutation.ResponseName())
mr.numUids = len(mutated)
}
}

func (mr *mutationResolver) rewriteAndExecute(
ctx context.Context, mutation schema.Mutation) ([]byte, bool, error) {

Expand All @@ -194,6 +234,7 @@ func (mr *mutationResolver) rewriteAndExecute(
schema.GQLWrapLocationf(err, mutation.Location(), "mutation %s failed", mutation.Name())
}

mr.getNumUids(mutation, assigned, result)
var errs error
dgQuery, err := mr.mutationRewriter.FromMutationResult(mutation, assigned, result)
errs = schema.AppendGQLErrs(errs, schema.GQLWrapf(err,
Expand All @@ -217,6 +258,10 @@ func deleteCompletion() CompletionFunc {
return CompletionFunc(func(
ctx context.Context, field schema.Field, result []byte, err error) ([]byte, error) {

return []byte(`{ "msg": "Deleted" }`), err
if field.Name() == "msg" {
return []byte(`{ "msg": "Deleted" }`), err
}

return []byte(fmt.Sprintf(`{ "%s": null }`, schema.NumUid)), err
})
}
11 changes: 9 additions & 2 deletions graphql/schema/gqlschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const (
idDirective = "id"

deprecatedDirective = "deprecated"
NumUid = "numUids"

Typename = "__typename"

Expand Down Expand Up @@ -145,6 +146,11 @@ type searchTypeIndex struct {
dgIndex string
}

var numUids = &ast.FieldDefinition{
Name: NumUid,
Type: &ast.Type{NamedType: "Int"},
}

// search arg -> supported GraphQL type
// == supported Dgraph index -> GraphQL type it applies to
var supportedSearches = map[string]searchTypeIndex{
Expand Down Expand Up @@ -836,7 +842,7 @@ func addAddPayloadType(schema *ast.Schema, defn *ast.Definition) {
schema.Types["Add"+defn.Name+"Payload"] = &ast.Definition{
Kind: ast.Object,
Name: "Add" + defn.Name + "Payload",
Fields: []*ast.FieldDefinition{qry},
Fields: []*ast.FieldDefinition{qry, numUids},
}
}

Expand Down Expand Up @@ -869,7 +875,7 @@ func addUpdatePayloadType(schema *ast.Schema, defn *ast.Definition) {
Kind: ast.Object,
Name: "Update" + defn.Name + "Payload",
Fields: []*ast.FieldDefinition{
qry,
qry, numUids,
},
}
}
Expand All @@ -889,6 +895,7 @@ func addDeletePayloadType(schema *ast.Schema, defn *ast.Definition) {
NamedType: "String",
},
},
numUids,
},
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,17 @@ input StringHashFilter {

type AddTPayload {
t(filter: TFilter, order: TOrder, first: Int, offset: Int): [T]
numUids: Int
}

type DeleteTPayload {
msg: String
numUids: Int
}

type UpdateTPayload {
t(filter: TFilter, order: TOrder, first: Int, offset: Int): [T]
numUids: Int
}

#######################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ input StringHashFilter {

type AddAtypePayload {
atype(order: AtypeOrder, first: Int, offset: Int): [Atype]
numUids: Int
}

#######################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,34 +104,42 @@ input StringHashFilter {

type AddDirectorPayload {
director(filter: DirectorFilter, order: DirectorOrder, first: Int, offset: Int): [Director]
numUids: Int
}

type AddOscarMoviePayload {
oscarmovie(filter: OscarMovieFilter, order: OscarMovieOrder, first: Int, offset: Int): [OscarMovie]
numUids: Int
}

type DeleteDirectorPayload {
msg: String
numUids: Int
}

type DeleteMoviePayload {
msg: String
numUids: Int
}

type DeleteOscarMoviePayload {
msg: String
numUids: Int
}

type UpdateDirectorPayload {
director(filter: DirectorFilter, order: DirectorOrder, first: Int, offset: Int): [Director]
numUids: Int
}

type UpdateMoviePayload {
movie(filter: MovieFilter, order: MovieOrder, first: Int, offset: Int): [Movie]
numUids: Int
}

type UpdateOscarMoviePayload {
oscarmovie(filter: OscarMovieFilter, order: OscarMovieOrder, first: Int, offset: Int): [OscarMovie]
numUids: Int
}

#######################
Expand Down
Loading

0 comments on commit ebbb91d

Please sign in to comment.