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
1 change: 1 addition & 0 deletions graphql/e2e/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,7 @@ 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)

// error tests
t.Run("graphql completion on", graphQLCompletionOn)
Expand Down
53 changes: 53 additions & 0 deletions graphql/e2e/common/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,59 @@ func entitiesQuery(t *testing.T) {
DeleteGqlType(t, "Mission", missionDeleteFilter, 2, nil)

}

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))
}

func inFilterOnString(t *testing.T) {
addStateParams := &GraphQLParams{
Query: `mutation addState($name1: String!, $code1: String!, $name2: String!, $code2: String! ) {
Expand Down
9 changes: 6 additions & 3 deletions graphql/resolve/query_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,13 @@ func entitiesQuery(field schema.Query, authRw *authRewriter) ([]*gql.GraphQuery,
// ...
// }

if keyFieldIsID {
// If key field is of ID type but it is an external field,
// then it is stored in Dgraph as string type with Hash index
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 +1387,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
105 changes: 105 additions & 0 deletions graphql/schema/testdata/apolloservice/output/auth-directive.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,28 @@ input StringHashFilter {
# Generated Types
#######################

type AddTodoPayload {
todo(filter: TodoFilter, order: TodoOrder, first: Int, offset: Int): [Todo]
numUids: Int
}

type AddUserPayload {
user(filter: UserFilter, order: UserOrder, first: Int, offset: Int): [User]
numUids: Int
}

type DeleteTodoPayload {
todo(filter: TodoFilter, order: TodoOrder, first: Int, offset: Int): [Todo]
msg: String
numUids: Int
}

type DeleteUserPayload {
user(filter: UserFilter, order: UserOrder, first: Int, offset: Int): [User]
msg: String
numUids: Int
}

type TodoAggregateResult {
count: Int
titleMin: String
Expand All @@ -281,6 +303,16 @@ type TodoAggregateResult {
somethingPrivateMax: String
}

type UpdateTodoPayload {
todo(filter: TodoFilter, order: TodoOrder, first: Int, offset: Int): [Todo]
numUids: Int
}

type UpdateUserPayload {
user(filter: UserFilter, order: UserOrder, first: Int, offset: Int): [User]
numUids: Int
}

type UserAggregateResult {
count: Int
usernameMin: String
Expand Down Expand Up @@ -321,6 +353,22 @@ enum UserOrderable {
# Generated Inputs
#######################

input AddTodoInput {
title: String
text: String
isPublic: Boolean
dateCompleted: String
sharedWith: [UserRef]
owner: UserRef
somethingPrivate: String
pwd: String!
}

input AddUserInput {
username: String!
todos: [TodoRef]
}

input TodoFilter {
id: [ID!]
isPublic: Boolean
Expand All @@ -337,6 +385,41 @@ input TodoOrder {
then: TodoOrder
}

input TodoPatch {
title: String
text: String
isPublic: Boolean
dateCompleted: String
sharedWith: [UserRef]
owner: UserRef
somethingPrivate: String
pwd: String
}

input TodoRef {
id: ID
title: String
text: String
isPublic: Boolean
dateCompleted: String
sharedWith: [UserRef]
owner: UserRef
somethingPrivate: String
pwd: String
}

input UpdateTodoInput {
filter: TodoFilter!
set: TodoPatch
remove: TodoPatch
}

input UpdateUserInput {
filter: UserFilter!
set: UserPatch
remove: UserPatch
}

input UserFilter {
username: StringHashFilter
has: UserHasFilter
Expand All @@ -351,6 +434,15 @@ input UserOrder {
then: UserOrder
}

input UserPatch {
todos: [TodoRef]
}

input UserRef {
username: String
todos: [TodoRef]
}

#######################
# Generated Query
#######################
Expand All @@ -365,3 +457,16 @@ type Query {
aggregateUser(filter: UserFilter): UserAggregateResult
}

#######################
# Generated Mutations
#######################

type Mutation {
addTodo(input: [AddTodoInput!]!): AddTodoPayload
updateTodo(input: UpdateTodoInput!): UpdateTodoPayload
deleteTodo(filter: TodoFilter!): DeleteTodoPayload
addUser(input: [AddUserInput!]!): AddUserPayload
updateUser(input: UpdateUserInput!): UpdateUserPayload
deleteUser(filter: UserFilter!): DeleteUserPayload
}

Loading