Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Apollo): Expose mutations to the gateway. #7358

Merged
merged 10 commits into from
Jan 27, 2021
2 changes: 2 additions & 0 deletions graphql/e2e/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,8 @@ func RunAll(t *testing.T) {
t.Run("mutation id directive with int", idDirectiveWithIntMutation)
t.Run("mutation id directive with int64", idDirectiveWithInt64Mutation)
t.Run("mutation id directive with float", idDirectiveWithFloatMutation)
t.Run("add mutation on extended type with field of ID type as key field", addMutationOnExtendedTypeWithIDasKeyField)
t.Run("add mutation with deep extended type objects", addMutationWithDeepExtendedTypeObjects)

// error tests
t.Run("graphql completion on", graphQLCompletionOn)
Expand Down
131 changes: 131 additions & 0 deletions graphql/e2e/common/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4674,3 +4674,134 @@ func idDirectiveWithFloatMutation(t *testing.T) {

DeleteGqlType(t, "Section", map[string]interface{}{}, 4, nil)
}

func addMutationWithDeepExtendedTypeObjects(t *testing.T) {
varMap1 := map[string]interface{}{
"missionId": "Mission1",
"astronautId": "Astronaut1",
"des": "Apollo1",
}
addMissionParams := &GraphQLParams{
Query: `mutation addMission($missionId: String!, $astronautId: ID!, $des: String!) {
addMission(input: [{id: $missionId, designation: $des, crew: [{id: $astronautId}]}]) {
mission{
id
crew {
id
}
}
}
}
`,
Variables: varMap1,
}
gqlResponse := addMissionParams.ExecuteAsPost(t, GraphqlURL)
RequireNoGQLErrors(t, gqlResponse)

expectedJSON := `{
"addMission": {
"mission": [
{
"id": "Mission1",
"crew": [
{
"id": "Astronaut1",
"missions": [
{
"id": "Mission1"
}
]
}
]
}
]
}
}`
testutil.CompareJSON(t, expectedJSON, string(gqlResponse.Data))

varMap2 := map[string]interface{}{
"missionId": "Mission2",
"astronautId": "Astronaut1",
"des": "Apollo2",
}
addMissionParams.Variables = varMap2

gqlResponse1 := addMissionParams.ExecuteAsPost(t, GraphqlURL)
RequireNoGQLErrors(t, gqlResponse)

expectedJSON = `{
"addMission": {
"mission": [
{
"id": "Mission2",
"crew": [
{
"id": "Astronaut1",
"missions": [
{
"id": "Mission1"
},
{
"id": "Mission2"
}
]
}
]
}
]
}
}`
testutil.CompareJSON(t, expectedJSON, string(gqlResponse1.Data))
}

func addMutationOnExtendedTypeWithIDasKeyField(t *testing.T) {
addAstronautParams := &GraphQLParams{
Query: `mutation addAstronaut($id1: ID!, $missionId1: String!, $id2: ID!, $missionId2: String! ) {
addAstronaut(input: [{id: $id1, missions: [{id: $missionId1, designation: "Apollo1"}]}, {id: $id2, missions: [{id: $missionId2, designation: "Apollo2"}]}]) {
astronaut{
id
missions {
id
designation
}
}
}
}`,
Variables: map[string]interface{}{
"id1": "Astronaut1",
"missionId1": "Mission1",
"id2": "Astronaut2",
"missionId2": "Mission2",
},
}

gqlResponse := addAstronautParams.ExecuteAsPost(t, GraphqlURL)
RequireNoGQLErrors(t, gqlResponse)

expectedJSON := `{
"addAstronaut": {
"astronaut": [
{
"id": "Astronaut1",
"missions": [
{
"id": "Mission1",
"designation": "Apollo1"
}
]
},
{
"id": "Astronaut2",
"missions": [
{
"id": "Mission2",
"designation": "Apollo2"
}
]
}
]
}
}`

testutil.CompareJSON(t, expectedJSON, string(gqlResponse.Data))
}
4 changes: 3 additions & 1 deletion graphql/e2e/common/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package common
import (
"encoding/json"
"fmt"
"github.com/spf13/cast"
"io/ioutil"
"math/rand"
"net/http"
Expand All @@ -29,6 +28,8 @@ import (
"testing"
"time"

"github.com/spf13/cast"

"github.com/google/go-cmp/cmp/cmpopts"

"github.com/dgraph-io/dgraph/graphql/schema"
Expand Down Expand Up @@ -449,6 +450,7 @@ func entitiesQuery(t *testing.T) {
DeleteGqlType(t, "Mission", missionDeleteFilter, 2, nil)

}

func inFilterOnString(t *testing.T) {
addStateParams := &GraphQLParams{
Query: `mutation addState($name1: String!, $code1: String!, $name2: String!, $code2: String! ) {
Expand Down
4 changes: 1 addition & 3 deletions graphql/e2e/directives/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -307,15 +307,13 @@ type Section {

type Mission @key(fields: "id") {
id: String! @id
crew: [Astronaut]
crew: [Astronaut] @hasInverse(field: missions)
spaceShip: [SpaceShip]
designation: String!
startDate: String
endDate: String
}

# Todo(Minhaj): Add test case for entities resolver
# for objects of Astronaut type.
type Astronaut @key(fields: "id") @extends {
id: ID! @external
missions: [Mission]
Expand Down
4 changes: 1 addition & 3 deletions graphql/e2e/normal/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -308,15 +308,13 @@ type Section {

type Mission @key(fields: "id") {
id: String! @id
crew: [Astronaut]
crew: [Astronaut] @hasInverse(field: missions)
spaceShip: [SpaceShip]
designation: String!
startDate: String
endDate: String
}

# Todo(Minhaj): Add test case for entities resolver
# for objects of Astronaut type.
type Astronaut @key(fields: "id") @extends {
id: ID! @external
missions: [Mission]
Expand Down
10 changes: 7 additions & 3 deletions graphql/resolve/query_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,14 @@ func entitiesQuery(field schema.Query, authRw *authRewriter) ([]*gql.GraphQuery,
// ...
// }

if keyFieldIsID {
// If the key field is of ID type and is not an external field
// then we query it using the `uid` otherwise we treat it as string
// and query using `eq` function.
if keyFieldIsID && !typeDefn.Field(keyFieldName).IsExternal() {
addUIDFunc(dgQuery, convertIDs(keyFieldValueList))
} else {
addEqFunc(dgQuery, typeDefn.DgraphPredicate(keyFieldName), keyFieldValueList)
}

// AddTypeFilter in as the Filter to the Root the Query.
// Query will be like :-
// _entities(func: ...) @filter(type(typeName)) {
Expand Down Expand Up @@ -1386,7 +1388,9 @@ func addSelectionSetFrom(
Alias: generateUniqueDgraphAlias(f, fieldSeenCount),
}

if f.Type().Name() == schema.IDType {
// if field of IDType has @external directive then it means that
// it stored as String with Hash index internally in the dgraph.
if f.Type().Name() == schema.IDType && !f.IsExternal() {
child.Attr = "uid"
} else {
child.Attr = f.DgraphPredicate()
Expand Down
88 changes: 38 additions & 50 deletions graphql/schema/gqlschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -937,76 +937,64 @@ func completeSchema(sch *ast.Schema, definitions []string, apolloServiceQuery bo
continue
}
defn := sch.Types[key]
if apolloServiceQuery && hasExtends(defn) {
if defn.Kind == ast.Union {
// TODO: properly check the case of reverse predicates (~) with union members and clean
// them from unionRef or unionFilter as required.
addUnionReferenceType(sch, defn)
addUnionFilterType(sch, defn)
addUnionMemberTypeEnum(sch, defn)
continue
}

params := &GenerateDirectiveParams{
generateGetQuery: true,
generateFilterQuery: true,
generatePasswordQuery: true,
generateAggregateQuery: true,
generateAddMutation: true,
generateUpdateMutation: true,
generateDeleteMutation: true,
generateSubscription: false,
}
if !apolloServiceQuery {
if defn.Kind == ast.Union {
// TODO: properly check the case of reverse predicates (~) with union members and clean
// them from unionRef or unionFilter as required.
addUnionReferenceType(sch, defn)
addUnionFilterType(sch, defn)
addUnionMemberTypeEnum(sch, defn)
continue
}
if defn.Kind != ast.Interface && defn.Kind != ast.Object {
continue
}

if defn.Kind != ast.Interface && defn.Kind != ast.Object {
continue
}
params := parseGenerateDirectiveParams(defn)

// Common types to both Interface and Object.
addReferenceType(sch, defn)

params = parseGenerateDirectiveParams(defn)
if params.generateUpdateMutation {
addPatchType(sch, defn)
addUpdateType(sch, defn)
addUpdatePayloadType(sch, defn)
}

// Common types to both Interface and Object.
addReferenceType(sch, defn)
if params.generateDeleteMutation {
addDeletePayloadType(sch, defn)
}

switch defn.Kind {
case ast.Interface:
// addInputType doesn't make sense as interface is like an abstract class and we can't
// create objects of its type.
if params.generateUpdateMutation {
addPatchType(sch, defn)
addUpdateType(sch, defn)
addUpdatePayloadType(sch, defn)
addUpdateMutation(sch, defn)
}

if params.generateDeleteMutation {
addDeletePayloadType(sch, defn)
addDeleteMutation(sch, defn)
}

switch defn.Kind {
case ast.Interface:
// addInputType doesn't make sense as interface is like an abstract class and we can't
// create objects of its type.
if params.generateUpdateMutation {
addUpdateMutation(sch, defn)
}
if params.generateDeleteMutation {
addDeleteMutation(sch, defn)
}

case ast.Object:
// types and inputs needed for mutations
if params.generateAddMutation {
addInputType(sch, defn)
addAddPayloadType(sch, defn)
}
addMutations(sch, defn, params)
case ast.Object:
// types and inputs needed for mutations
if params.generateAddMutation {
addInputType(sch, defn)
addAddPayloadType(sch, defn)
}
addMutations(sch, defn, params)
}

// types and inputs needed for query and search
addFilterType(sch, defn)
addTypeOrderable(sch, defn)
addFieldFilters(sch, defn, apolloServiceQuery)
addAggregationResultType(sch, defn)
addQueries(sch, defn, params)
// Don't expose queries for the @extends type to the gateway
// as it is resolved through `_entities` resolver.
if !(apolloServiceQuery && hasExtends(defn)) {
addQueries(sch, defn, params)
}
addTypeHasFilter(sch, defn)
// We need to call this at last as aggregateFields
// should not be part of HasFilter or UpdatePayloadType etc.
Expand Down
Loading