diff --git a/graphql/e2e/auth/add_mutation_test.go b/graphql/e2e/auth/add_mutation_test.go index 8abe0d763c7..70be41f6b32 100644 --- a/graphql/e2e/auth/add_mutation_test.go +++ b/graphql/e2e/auth/add_mutation_test.go @@ -1002,7 +1002,7 @@ func TestUpsertMutationsWithRBAC(t *testing.T) { require.Error(t, gqlResponse.Errors) require.Equal(t, len(gqlResponse.Errors), 1) require.Contains(t, gqlResponse.Errors[0].Error(), - "GraphQL debug: id tweet1 already exists for field id inside type Tweets") + " GraphQL debug: id Tweets already exists for field id inside type tweet1") } else { common.RequireNoGQLErrors(t, gqlResponse) require.JSONEq(t, tcase.result, string(gqlResponse.Data)) @@ -1119,3 +1119,48 @@ func TestUpsertWithDeepAuth(t *testing.T) { filter = map[string]interface{}{"code": map[string]interface{}{"eq": "UK"}} common.DeleteGqlType(t, "State", filter, 1, nil) } + +func TestAddMutationWithAuthOnIDFieldHavingInterfaceArg(t *testing.T) { + // add Library Member + addLibraryMemberParams := &common.GraphQLParams{ + Query: `mutation addLibraryMember($input: [AddLibraryMemberInput!]!) { + addLibraryMember(input: $input, upsert: false) { + numUids + } + }`, + Variables: map[string]interface{}{"input": []interface{}{ + map[string]interface{}{ + "refID": "101", + "name": "Alice", + "readHours": "4d2hr", + }}, + }, + } + + gqlResponse := addLibraryMemberParams.ExecuteAsPost(t, common.GraphqlURL) + common.RequireNoGQLErrors(t, gqlResponse) + // add sports member should return error but in debug mode + // because interface type have auth rules defined on it + addSportsMemberParams := &common.GraphQLParams{ + Query: `mutation addSportsMember($input: [AddSportsMemberInput!]!) { + addSportsMember(input: $input, upsert: false) { + numUids + } + }`, + Variables: map[string]interface{}{"input": []interface{}{ + map[string]interface{}{ + "refID": "101", + "name": "Bob", + "plays": "football and cricket", + }}, + }, + } + + gqlResponse = addSportsMemberParams.ExecuteAsPost(t, common.GraphqlURL) + require.Contains(t, gqlResponse.Errors[0].Error(), + " GraphQL debug: id 101 already exists for field refID in some other"+ + " implementing type of interface Member") + + // cleanup + common.DeleteGqlType(t, "LibraryMember", map[string]interface{}{}, 1, nil) +} diff --git a/graphql/e2e/auth/auth_test.go b/graphql/e2e/auth/auth_test.go index f89bbbd510b..6d47eba7b47 100644 --- a/graphql/e2e/auth/auth_test.go +++ b/graphql/e2e/auth/auth_test.go @@ -354,7 +354,7 @@ func TestAddMutationWithXid(t *testing.T) { require.Error(t, gqlResponse.Errors) require.Equal(t, len(gqlResponse.Errors), 1) require.Contains(t, gqlResponse.Errors[0].Error(), - "GraphQL debug: id tweet1 already exists for field id inside type Tweets") + "GraphQL debug: id Tweets already exists for field id inside type tweet1") // Clear the tweet. tweet.DeleteByID(t, user, metaInfo) diff --git a/graphql/e2e/auth/debug_off/debugoff_test.go b/graphql/e2e/auth/debug_off/debugoff_test.go index 2ed6bdb9282..45edcb739de 100644 --- a/graphql/e2e/auth/debug_off/debugoff_test.go +++ b/graphql/e2e/auth/debug_off/debugoff_test.go @@ -122,6 +122,68 @@ func TestAddMutationWithXid(t *testing.T) { tweet.DeleteByID(t, user, metaInfo) } +func TestAddMutationWithAuthOnIDFieldHavingInterfaceArg(t *testing.T) { + + // add Library Member + addLibraryMemberParams := &common.GraphQLParams{ + Query: `mutation addLibraryMember($input: [AddLibraryMemberInput!]!) { + addLibraryMember(input: $input, upsert: false) { + numUids + } + }`, + Variables: map[string]interface{}{"input": []interface{}{ + map[string]interface{}{ + "refID": "101", + "name": "Alice", + "readHours": "4d2hr", + }}, + }, + } + + gqlResponse := addLibraryMemberParams.ExecuteAsPost(t, common.GraphqlURL) + common.RequireNoGQLErrors(t, gqlResponse) + + // add SportsMember should return error but in debug mode + // because interface type have auth rules defined on it + var resultLibraryMember struct { + AddLibraryMember struct { + NumUids int + } + } + err := json.Unmarshal(gqlResponse.Data, &resultLibraryMember) + require.NoError(t, err) + require.Equal(t, 1, resultLibraryMember.AddLibraryMember.NumUids) + + addSportsMemberParams := &common.GraphQLParams{ + Query: `mutation addSportsMember($input: [AddSportsMemberInput!]!) { + addSportsMember(input: $input, upsert: false) { + numUids + } + }`, + Variables: map[string]interface{}{"input": []interface{}{ + map[string]interface{}{ + "refID": "101", + "name": "Bob", + "plays": "football and cricket", + }}, + }, + } + + gqlResponse = addSportsMemberParams.ExecuteAsPost(t, common.GraphqlURL) + common.RequireNoGQLErrors(t, gqlResponse) + var resultSportsMember struct { + AddSportsMember struct { + NumUids int + } + } + err = json.Unmarshal(gqlResponse.Data, &resultSportsMember) + require.NoError(t, err) + require.Equal(t, 0, resultSportsMember.AddSportsMember.NumUids) + + // cleanup + common.DeleteGqlType(t, "LibraryMember", map[string]interface{}{}, 1, nil) +} + func TestMain(m *testing.M) { schemaFile := "../schema.graphql" schema, err := os.ReadFile(schemaFile) diff --git a/graphql/e2e/auth/schema.graphql b/graphql/e2e/auth/schema.graphql index 69a6146df7b..b9b928f655b 100644 --- a/graphql/e2e/auth/schema.graphql +++ b/graphql/e2e/auth/schema.graphql @@ -620,43 +620,43 @@ type Author { interface Post @secret(field: "pwd") @auth( password: { rule: "{$ROLE: { eq: \"Admin\" } }"}, query: { rule: """ - query($USER: String!) { + query($USER: String!) { queryPost{ author(filter: {name: {eq: $USER}}){ name } - } + } }""" }, add: { rule: """ - query($USER: String!) { + query($USER: String!) { queryPost{ author(filter: {name: {eq: $USER}}){ name } - } + } }""" }, delete: { rule: """ - query($USER: String!) { + query($USER: String!) { queryPost{ author(filter: {name: {eq: $USER}}){ name } - } + } }""" }, update: { rule: """ - query($USER: String!) { + query($USER: String!) { queryPost{ author(filter: {name: {eq: $USER}}){ name } - } + } }""" } ){ id: ID! text: String! @search(by: [exact]) topic: String datePublished: DateTime @search - author: Author! + author: Author! } interface MsgPost @auth( @@ -678,28 +678,28 @@ type Question implements Post @auth( } }""" }, query:{ rule: """ - query($ANS: Boolean!) { - queryQuestion(filter: { answered: $ANS } ) { - id - } + query($ANS: Boolean!) { + queryQuestion(filter: { answered: $ANS } ) { + id + } }""" }, add:{ rule: """ - query($ANS: Boolean!) { - queryQuestion(filter: { answered: $ANS } ) { - id - } + query($ANS: Boolean!) { + queryQuestion(filter: { answered: $ANS } ) { + id + } }""" }, delete:{ rule: """ - query($ANS: Boolean!) { - queryQuestion(filter: { answered: $ANS } ) { - id - } + query($ANS: Boolean!) { + queryQuestion(filter: { answered: $ANS } ) { + id + } }""" }, update:{ rule: """ - query($ANS: Boolean!) { - queryQuestion(filter: { answered: $ANS } ) { - id - } + query($ANS: Boolean!) { + queryQuestion(filter: { answered: $ANS } ) { + id + } }""" }, ){ answered: Boolean @search @@ -726,7 +726,7 @@ type Answer implements Post { interface A { id: ID! fieldA: String @search(by: [exact]) - random: String + random: String } type B implements A { @@ -735,16 +735,16 @@ type B implements A { type C implements A @auth( query:{ rule: """ - query($ANS: Boolean!) { - queryC(filter: { fieldC: $ANS } ) { - id - } + query($ANS: Boolean!) { + queryC(filter: { fieldC: $ANS } ) { + id + } }""" }, delete:{ rule: """ - query($ANS: Boolean!) { - queryC(filter: { fieldC: $ANS } ) { - id - } + query($ANS: Boolean!) { + queryC(filter: { fieldC: $ANS } ) { + id + } }""" } ){ fieldC: Boolean @search @@ -780,10 +780,10 @@ type Book @auth( type Mission @key(fields: "id") @auth( query:{ rule: """ - query($USER: String!) { - queryMission(filter: { supervisorName: {eq: $USER} } ) { - id - } + query($USER: String!) { + queryMission(filter: { supervisorName: {eq: $USER} } ) { + id + } }""" } ){ id: String! @id @@ -864,6 +864,25 @@ type Person name: String! } +interface Member @auth( + query: { rule: "{$ROLE: { eq: \"ADMIN\" } }" }, +){ + refID: String! @id (interface:true) + name: String! @id (interface:false) +} + +type SportsMember implements Member { + plays: String + playerRating: Int +} + +type LibraryMember implements Member { + interests: [String] + readHours: String +} + + + # union testing - start enum AnimalCategory { Fish diff --git a/graphql/e2e/common/common.go b/graphql/e2e/common/common.go index 7c50cb58ef6..a2884bcf4e4 100644 --- a/graphql/e2e/common/common.go +++ b/graphql/e2e/common/common.go @@ -868,6 +868,7 @@ func RunAll(t *testing.T) { t.Run("query id directive with int", idDirectiveWithInt) t.Run("query id directive with int64", idDirectiveWithInt64) t.Run("query filter ID values coercion to List", queryFilterWithIDInputCoercion) + t.Run("query @id field with interface arg on interface", queryWithIDFieldAndInterfaceArg) // mutation tests t.Run("add mutation", addMutation) @@ -927,6 +928,7 @@ func RunAll(t *testing.T) { t.Run("input coercion to list", inputCoerciontoList) t.Run("multiple external Id's tests", multipleXidsTests) t.Run("Upsert Mutation Tests", upsertMutationTests) + t.Run("add mutation with @id field and interface arg", addMutationWithIDFieldHavingInterfaceArg) // error tests t.Run("graphql completion on", graphQLCompletionOn) diff --git a/graphql/e2e/common/mutation.go b/graphql/e2e/common/mutation.go index a0df3104846..0233b10fda5 100644 --- a/graphql/e2e/common/mutation.go +++ b/graphql/e2e/common/mutation.go @@ -4820,7 +4820,7 @@ func filterInMutationsWithArrayForAndOr(t *testing.T) { query: `mutation { addpost1(input: [{title: "Dgraph", numLikes: 100}]) { post1(filter:{and:[{title:{eq: "Dgraph"}},{or:{numLikes:{eq: 100}}}]}) { - title + title numLikes } } @@ -5228,7 +5228,7 @@ func threeLevelDoubleXID(t *testing.T) { name region { id - name + name district { id name @@ -5692,7 +5692,7 @@ func multipleXidsTests(t *testing.T) { query: `mutation { addEmployer(input: [{ company: "Slash", worker: { reg_No: 3, emp_Id: "E04" } }]) { employer { - company + company worker { name reg_No @@ -6037,3 +6037,206 @@ func upsertMutationTests(t *testing.T) { filter = GetXidFilter("xcode", []interface{}{"S1", "S10"}) deleteState(t, filter, 2, nil) } + +func addMutationWithIDFieldHavingInterfaceArg(t *testing.T) { + + // add data successfully for different implementing types + tcases := []struct { + name string + query string + variables string + error string + }{ + { + name: "adding new Library member shouldn't return any error", + query: `mutation addLibraryMember($input: [AddLibraryMemberInput!]!) { + addLibraryMember(input: $input, upsert: false) { + libraryMember { + refID + } + } + }`, + variables: `{ + "input": { + "refID": "101", + "name": "Alice", + "itemsIssued": [ + "Intro to Go", + "Parallel Programming" + ], + "readHours": "4d2hr" + } + }`, + }, { + name: "update existing library member using upsert shouldn't return any error", + query: `mutation addLibraryMember($input: [AddLibraryMemberInput!]!) { + addLibraryMember(input: $input, upsert: true) { + libraryMember { + refID + } + } + }`, + variables: `{ + "input": { + "refID": "101", + "name": "Alice", + "itemsIssued": [ + "Intro to Go", + "Parallel Programming", + "Computer Architecture" + ], + "readHours": "5d3hr" + } + }`, + }, { + name: "adding new Sports Member shouldn't return any error", + query: `mutation addSportsMember($input: [AddSportsMemberInput!]!) { + addSportsMember(input: $input, upsert: false) { + sportsMember { + refID + } + } + }`, + variables: `{ + "input": { + "refID": "102", + "name": "Bob", + "teamID": "T01", + "teamName": "GraphQL", + "itemsIssued": [ + "2-Bats", + "1-football" + ], + "plays": "football and cricket" + } + }`, + }, { + name: "adding new Cricket Team shouldn't return any error", + query: `mutation addCricketTeam($input: [AddCricketTeamInput!]!) { + addCricketTeam(input: $input, upsert: false) { + cricketTeam { + teamID + } + } + }`, + variables: `{ + "input": { + "teamID": "T02", + "teamName": "Dgraph", + "numOfBatsmen": 5, + "numOfBowlers": 3 + } + }`, + }, { + name: "add new LibraryManager,linking to existing library Member", + query: `mutation addLibraryManager($input: [AddLibraryManagerInput!]!) { + addLibraryManager(input: $input, upsert: false) { + libraryManager { + name + } + } + }`, + variables: `{ + "input": { + "name": "Juliet", + "manages": { + "refID": "101" + } + } + }`, + }, { + name: "adding new Library member returns error as given id already exist in other node of type" + + " SportsMember which implements same interface", + query: `mutation addLibraryMember($input: [AddLibraryMemberInput!]!) { + addLibraryMember(input: $input, upsert: false) { + libraryMember { + refID + } + } + }`, + variables: `{ + "input": { + "refID": "102", + "name": "James", + "itemsIssued": [ + "Intro to C" + ], + "readHours": "1d2hr" + } + }`, + error: "couldn't rewrite mutation addLibraryMember because failed to rewrite mutation" + + " payload because id 102 already exists for field refID in some other implementing" + + " type of interface Member", + }, { + name: "adding new Cricket Team with upsert returns error as given id already exist" + + " in other node of type SportsMember which implements same interface", + query: `mutation addCricketTeam($input: [AddCricketTeamInput!]!) { + addCricketTeam(input: $input, upsert: true) { + cricketTeam { + teamID + } + } + }`, + variables: `{ + "input": { + "teamID": "T01", + "teamName": "Slash", + "numOfBatsmen": 5, + "numOfBowlers": 4 + } + }`, + error: "couldn't rewrite mutation addCricketTeam because failed to rewrite mutation" + + " payload because id T01 already exists for field teamID in some other" + + " implementing type of interface Team", + }, { + name: "adding new Library manager returns error when it try to links to LibraryMember" + + " but got id of some other implementing type which implements " + + "same interface as LibraryMember", + query: `mutation addLibraryManager($input: [AddLibraryManagerInput!]!) { + addLibraryManager(input: $input, upsert: false) { + libraryManager { + name + } + } + }`, + variables: `{ + "input": { + "name": "John", + "manages": { + "refID": "102" + } + } + }`, + error: "couldn't rewrite mutation addLibraryManager because failed to rewrite mutation" + + " payload because id 102 already exists for field refID in some other implementing" + + " type of interface Member", + }, + } + + for _, tcase := range tcases { + t.Run(tcase.name, func(t *testing.T) { + var vars map[string]interface{} + if tcase.variables != "" { + err := json.Unmarshal([]byte(tcase.variables), &vars) + require.NoError(t, err) + } + params := &GraphQLParams{ + Query: tcase.query, + Variables: vars, + } + resp := params.ExecuteAsPost(t, GraphqlURL) + if tcase.error != "" { + require.Equal(t, tcase.error, resp.Errors[0].Error()) + } else { + RequireNoGQLErrors(t, resp) + } + + }) + } + + // Cleanup + DeleteGqlType(t, "LibraryMember", map[string]interface{}{}, 1, nil) + DeleteGqlType(t, "SportsMember", map[string]interface{}{}, 1, nil) + DeleteGqlType(t, "CricketTeam", map[string]interface{}{}, 1, nil) + DeleteGqlType(t, "LibraryManager", map[string]interface{}{}, 1, nil) +} diff --git a/graphql/e2e/common/query.go b/graphql/e2e/common/query.go index f6def8f3ec6..f1704408d1d 100644 --- a/graphql/e2e/common/query.go +++ b/graphql/e2e/common/query.go @@ -522,10 +522,10 @@ func inFilterOnString(t *testing.T) { updateStateParams := &GraphQLParams{ Query: `mutation{ - updateState(input: { + updateState(input: { filter: { xcode: { in: ["abc", "def"]}}, - set: { + set: { capital: "Common Capital"} }){ state{ xcode @@ -2597,10 +2597,10 @@ func queryWithCascade(t *testing.T) { { name: "parameterized cascade on interface ", query: `query { - queryCharacter (filter: { appearsIn: { in: [EMPIRE] } }) @cascade(fields:["appearsIn"]){ + queryCharacter (filter: { appearsIn: { in: [EMPIRE] } }) @cascade(fields:["appearsIn"]){ name appearsIn - } + } }`, respData: `{ "queryCharacter": [ @@ -3238,7 +3238,7 @@ func queryAggregateOnEmptyData3(t *testing.T) { { "queryCountry": [{ "name": "India", - "ag": { + "ag": { "count" : 3, "nameMin": "Gujarat", "capitalMax": null, @@ -3289,7 +3289,7 @@ func queryAggregateWithAlias(t *testing.T) { cnt: count tmin : titleMin tmax: titleMax - navg : numLikesAvg + navg : numLikesAvg } }`, } @@ -3365,7 +3365,7 @@ func queryAggregateAtChildLevel(t *testing.T) { { "queryCountry": [{ "name": "India", - "ag": { + "ag": { "count" : 3, "__typename": "StateAggregateResult", "nameMin": "Gujarat" @@ -3377,7 +3377,7 @@ func queryAggregateAtChildLevel(t *testing.T) { func queryAggregateAtChildLevelWithFilter(t *testing.T) { queryNumberOfIndianStates := &GraphQLParams{ - Query: `query + Query: `query { queryCountry(filter: { name: { eq: "India" } }) { name @@ -3395,7 +3395,7 @@ func queryAggregateAtChildLevelWithFilter(t *testing.T) { { "queryCountry": [{ "name": "India", - "ag": { + "ag": { "count" : 2, "nameMin" : "Karnataka" } @@ -3406,7 +3406,7 @@ func queryAggregateAtChildLevelWithFilter(t *testing.T) { func queryAggregateAtChildLevelWithEmptyData(t *testing.T) { queryNumberOfIndianStates := &GraphQLParams{ - Query: `query + Query: `query { queryCountry(filter: { name: { eq: "India" } }) { name @@ -3461,7 +3461,7 @@ func queryAggregateAtChildLevelWithMultipleAlias(t *testing.T) { { "queryCountry": [{ "name": "India", - "ag1": { + "ag1": { "count" : 2, "nameMax" : "Maharashtra" }, @@ -3509,7 +3509,7 @@ func queryAggregateAtChildLevelWithRepeatedFields(t *testing.T) { func queryAggregateAndOtherFieldsAtChildLevel(t *testing.T) { queryNumberOfIndianStates := &GraphQLParams{ - Query: `query + Query: `query { queryCountry(filter: { name: { eq: "India" } }) { name @@ -3530,14 +3530,14 @@ func queryAggregateAndOtherFieldsAtChildLevel(t *testing.T) { { "queryCountry": [{ "name": "India", - "ag": { + "ag": { "count" : 3, "nameMin" : "Gujarat" }, "states": [ { "name": "Maharashtra" - }, + }, { "name": "Gujarat" }, @@ -3927,3 +3927,60 @@ func idDirectiveWithInt(t *testing.T) { }` require.JSONEq(t, expected, string(response.Data)) } + +func queryWithIDFieldAndInterfaceArg(t *testing.T) { + // add library member + addLibraryMemberParams := &GraphQLParams{ + Query: `mutation addLibraryMember($input: [AddLibraryMemberInput!]!) { + addLibraryMember(input: $input, upsert: false) { + libraryMember { + refID + } + } + }`, + Variables: map[string]interface{}{"input": []interface{}{ + map[string]interface{}{ + "refID": "101", + "name": "Alice", + "itemsIssued": []string{"Intro to Go", "Parallel Programming"}, + "readHours": "4d2hr", + }}, + }, + } + + gqlResponse := addLibraryMemberParams.ExecuteAsPost(t, GraphqlURL) + RequireNoGQLErrors(t, gqlResponse) + + queryMember := &GraphQLParams{ + Query: ` + query { + getMember(refID: "101") { + refID + name + itemsIssued + ... on LibraryMember { + readHours + } + } + }`, + } + + gqlResponse = queryMember.ExecuteAsPost(t, GraphqlURL) + RequireNoGQLErrors(t, gqlResponse) + queryPersonExpected := ` + { + "getMember": { + "refID": "101", + "name": "Alice", + "itemsIssued": [ + "Parallel Programming", + "Intro to Go" + ], + "readHours": "4d2hr" + } + }` + + require.JSONEq(t, queryPersonExpected, string(gqlResponse.Data)) + // Cleanup + DeleteGqlType(t, "LibraryMember", map[string]interface{}{}, 1, nil) +} diff --git a/graphql/e2e/directives/schema.graphql b/graphql/e2e/directives/schema.graphql index 64af79b7c9a..01529ca4e6c 100644 --- a/graphql/e2e/directives/schema.graphql +++ b/graphql/e2e/directives/schema.graphql @@ -377,3 +377,35 @@ type Employer { company: String! @id worker: [Worker] } + +interface Member { + refID: String! @id (interface:true) + name: String! @id + itemsIssued: [String] + fineAccumulated: Int +} + +interface Team { + teamID: String! @id (interface:true) + teamName: String! @id +} + +type LibraryMember implements Member { + interests: [String] + readHours: String +} + +type SportsMember implements Member & Team { + plays: String + playerRating: Int +} + +type CricketTeam implements Team { + numOfBatsmen: Int + numOfBowlers: Int +} + +type LibraryManager { + name: String! @id + manages: [LibraryMember] +} \ No newline at end of file diff --git a/graphql/e2e/directives/schema_response.json b/graphql/e2e/directives/schema_response.json index b1377b73c3f..edda361e792 100644 --- a/graphql/e2e/directives/schema_response.json +++ b/graphql/e2e/directives/schema_response.json @@ -67,6 +67,90 @@ "predicate": "author1.posts", "type": "uid" }, + { + "list": true, + "predicate": "LibraryManager.manages", + "type": "uid" + }, + { + "predicate": "LibraryMember.readHours", + "type": "string" + }, + { + "index": true, + "predicate": "Team.teamName", + "tokenizer": [ + "hash" + ], + "type": "string", + "upsert": true + }, + { + "predicate": "SportsMember.playerRating", + "type": "int" + }, + { + "list": true, + "predicate": "LibraryMember.interests", + "type": "string" + }, + { + "index": true, + "predicate": "Team.teamID", + "tokenizer": [ + "hash" + ], + "type": "string", + "upsert": true + }, + { + "index": true, + "predicate": "Member.name", + "tokenizer": [ + "hash" + ], + "type": "string", + "upsert": true + }, + { + "predicate": "CricketTeam.numOfBowlers", + "type": "int" + }, + { + "predicate": "CricketTeam.numOfBatsmen", + "type": "int" + }, + { + "index": true, + "predicate": "LibraryManager.name", + "tokenizer": [ + "hash" + ], + "type": "string", + "upsert": true + }, + { + "index": true, + "predicate": "Member.refID", + "tokenizer": [ + "hash" + ], + "type": "string", + "upsert": true + }, + { + "list": true, + "predicate": "Member.itemsIssued", + "type": "string" + }, + { + "predicate": "Member.fineAccumulated", + "type": "int" + }, + { + "predicate": "SportsMember.plays", + "type": "string" + }, { "predicate": "Astronaut.name", "type": "string" @@ -1427,6 +1511,114 @@ } ], "name": "Employer" + }, + { + "fields": [ + { + "name": "Member.name" + }, + { + "name": "Member.refID" + }, + { + "name": "Member.itemsIssued" + }, + { + "name": "Member.fineAccumulated" + } + ], + "name": "Member" + }, + { + "fields": [ + { + "name": "Team.teamName" + }, + { + "name": "Team.teamID" + }, + { + "name": "CricketTeam.numOfBowlers" + }, + { + "name": "CricketTeam.numOfBatsmen" + } + ], + "name": "CricketTeam" + }, + { + "fields": [ + { + "name": "LibraryManager.manages" + }, + { + "name": "LibraryManager.name" + } + ], + "name": "LibraryManager" + }, + { + "fields": [ + { + "name": "Team.teamName" + }, + { + "name": "Team.teamID" + } + ], + "name": "Team" + }, + { + "fields": [ + { + "name": "Team.teamName" + }, + { + "name": "SportsMember.playerRating" + }, + { + "name": "Team.teamID" + }, + { + "name": "Member.name" + }, + { + "name": "Member.refID" + }, + { + "name": "Member.itemsIssued" + }, + { + "name": "SportsMember.plays" + }, + { + "name": "Member.fineAccumulated" + } + ], + "name": "SportsMember" + }, + { + "fields": [ + { + "name": "LibraryMember.readHours" + }, + { + "name": "LibraryMember.interests" + }, + { + "name": "Member.name" + }, + { + "name": "Member.refID" + }, + { + "name": "Member.itemsIssued" + }, + { + "name": "Member.fineAccumulated" + } + ], + "name": "LibraryMember" } ] } diff --git a/graphql/e2e/normal/schema.graphql b/graphql/e2e/normal/schema.graphql index 347a282c4f4..1876dd77f6c 100644 --- a/graphql/e2e/normal/schema.graphql +++ b/graphql/e2e/normal/schema.graphql @@ -380,3 +380,36 @@ type Dataset { project: Project! name: String! @search(by: [hash]) } + + +interface Member { + refID: String! @id (interface:true) + name: String! @id + itemsIssued: [String] + fineAccumulated: Int +} + +interface Team { + teamID: String! @id (interface:true) + teamName: String! @id +} + +type LibraryMember implements Member { + interests: [String] + readHours: String +} + +type SportsMember implements Member & Team { + plays: String + playerRating: Int +} + +type CricketTeam implements Team { + numOfBatsmen: Int + numOfBowlers: Int +} + +type LibraryManager { + name: String! @id + manages: [LibraryMember] +} \ No newline at end of file diff --git a/graphql/e2e/normal/schema_response.json b/graphql/e2e/normal/schema_response.json index 5d6b0c5708b..594e59bdef6 100644 --- a/graphql/e2e/normal/schema_response.json +++ b/graphql/e2e/normal/schema_response.json @@ -17,6 +17,90 @@ "type": "string", "upsert": true }, + { + "list": true, + "predicate": "LibraryManager.manages", + "type": "uid" + }, + { + "predicate": "LibraryMember.readHours", + "type": "string" + }, + { + "index": true, + "predicate": "Team.teamName", + "tokenizer": [ + "hash" + ], + "type": "string", + "upsert": true + }, + { + "predicate": "SportsMember.playerRating", + "type": "int" + }, + { + "list": true, + "predicate": "LibraryMember.interests", + "type": "string" + }, + { + "index": true, + "predicate": "Team.teamID", + "tokenizer": [ + "hash" + ], + "type": "string", + "upsert": true + }, + { + "index": true, + "predicate": "Member.name", + "tokenizer": [ + "hash" + ], + "type": "string", + "upsert": true + }, + { + "predicate": "CricketTeam.numOfBowlers", + "type": "int" + }, + { + "predicate": "CricketTeam.numOfBatsmen", + "type": "int" + }, + { + "index": true, + "predicate": "LibraryManager.name", + "tokenizer": [ + "hash" + ], + "type": "string", + "upsert": true + }, + { + "index": true, + "predicate": "Member.refID", + "tokenizer": [ + "hash" + ], + "type": "string", + "upsert": true + }, + { + "list": true, + "predicate": "Member.itemsIssued", + "type": "string" + }, + { + "predicate": "Member.fineAccumulated", + "type": "int" + }, + { + "predicate": "SportsMember.plays", + "type": "string" + }, { "predicate": "post1.author", "type": "uid" @@ -1427,6 +1511,125 @@ } ], "name": "Employer" + }, + { + "fields": [ + { + "name": "Person.name" + }, + { + "name": "Person.profession" + } + ], + "name": "Person" + }, + { + "fields": [ + { + "name": "Member.name" + }, + { + "name": "Member.refID" + }, + { + "name": "Member.itemsIssued" + }, + { + "name": "Member.fineAccumulated" + } + ], + "name": "Member" + }, + { + "fields": [ + { + "name": "Team.teamName" + }, + { + "name": "Team.teamID" + }, + { + "name": "CricketTeam.numOfBowlers" + }, + { + "name": "CricketTeam.numOfBatsmen" + } + ], + "name": "CricketTeam" + }, + { + "fields": [ + { + "name": "LibraryManager.manages" + }, + { + "name": "LibraryManager.name" + } + ], + "name": "LibraryManager" + }, + { + "fields": [ + { + "name": "Team.teamName" + }, + { + "name": "Team.teamID" + } + ], + "name": "Team" + }, + { + "fields": [ + { + "name": "Team.teamName" + }, + { + "name": "SportsMember.playerRating" + }, + { + "name": "Team.teamID" + }, + { + "name": "Member.name" + }, + { + "name": "Member.refID" + }, + { + "name": "Member.itemsIssued" + }, + { + "name": "SportsMember.plays" + }, + { + "name": "Member.fineAccumulated" + } + ], + "name": "SportsMember" + }, + { + "fields": [ + { + "name": "LibraryMember.readHours" + }, + { + "name": "LibraryMember.interests" + }, + { + "name": "Member.name" + }, + { + "name": "Member.refID" + }, + { + "name": "Member.itemsIssued" + }, + { + "name": "Member.fineAccumulated" + } + ], + "name": "LibraryMember" } ] -} \ No newline at end of file +} diff --git a/graphql/e2e/schema/apollo_service_response.graphql b/graphql/e2e/schema/apollo_service_response.graphql index 4c176d478e7..c0383cdeec5 100644 --- a/graphql/e2e/schema/apollo_service_response.graphql +++ b/graphql/e2e/schema/apollo_service_response.graphql @@ -198,7 +198,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM diff --git a/graphql/e2e/schema/generatedSchema.graphql b/graphql/e2e/schema/generatedSchema.graphql index f4a15622aaf..6b9c37d5f31 100644 --- a/graphql/e2e/schema/generatedSchema.graphql +++ b/graphql/e2e/schema/generatedSchema.graphql @@ -179,7 +179,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/resolve/add_mutation_test.yaml b/graphql/resolve/add_mutation_test.yaml index cfaeffc3d3f..3253b5a40e1 100644 --- a/graphql/resolve/add_mutation_test.yaml +++ b/graphql/resolve/add_mutation_test.yaml @@ -1127,6 +1127,504 @@ } } +- + name: "Add mutation on implementation type which have inherited @id field with interface argument -1" + explanation: "This mutation will generate three existence queries two for xid - refID (one for interface and one + for implementing type) and one for xid - name" + gqlmutation: | + mutation addLibraryMember($input: AddLibraryMemberInput!) { + addLibraryMember(input: [$input], upsert: false) { + libraryMember { + refID + } + } + } + gqlvariables: | + { + "input": { + "refID": "101", + "name": "Alice", + "itemsIssued": [ + "Intro to Go", + "Parallel Programming" + ], + "readHours": "4d2hr" + } + } + dgquery: |- + query { + LibraryMember_1(func: eq(Member.name, "Alice")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_2(func: eq(Member.refID, "101")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_3(func: eq(Member.refID, "101")) @filter(type(Member)) { + uid + } + } + dgmutations: + - setjson: | + { + "LibraryMember.readHours": "4d2hr", + "Member.itemsIssued": [ + "Intro to Go", + "Parallel Programming" + ], + "Member.name": "Alice", + "Member.refID": "101", + "dgraph.type": [ + "LibraryMember", + "Member" + ], + "uid": "_:LibraryMember_2" + } + +- + name: "Add mutation on implementation type which have inherited @id field with interface argument -2" + explanation: "Node with refID:101 already exist in other implementing type of interface, mutation not allowed + in this case and we will return error" + gqlmutation: | + mutation addLibraryMember($input: AddLibraryMemberInput!) { + addLibraryMember(input: [$input], upsert: false) { + libraryMember { + refID + } + } + } + gqlvariables: | + { + "input": { + "refID": "101", + "name": "Alice", + "itemsIssued": [ + "Intro to Go", + "Parallel Programming" + ], + "readHours": "4d2hr" + } + } + dgquery: |- + query { + LibraryMember_1(func: eq(Member.name, "Alice")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_2(func: eq(Member.refID, "101")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_3(func: eq(Member.refID, "101")) @filter(type(Member)) { + uid + } + } + qnametouid: |- + { + "LibraryMember_3": "0x11" + } + error2: + { + "message": "failed to rewrite mutation payload because id 101 already exists for field refID + in some other implementing type of interface Member" + } + +- + name: "Add mutation on implementation type which have inherited @id field with interface argument -3" + explanation: "Node with refID:101 already exist in same mutated type, returns error " + gqlmutation: | + mutation addLibraryMember($input: AddLibraryMemberInput!) { + addLibraryMember(input: [$input], upsert: false) { + libraryMember { + refID + } + } + } + gqlvariables: | + { + "input": { + "refID": "101", + "name": "Alice", + "itemsIssued": [ + "Intro to Go", + "Parallel Programming" + ], + "readHours": "4d2hr" + } + } + dgquery: |- + query { + LibraryMember_1(func: eq(Member.name, "Alice")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_2(func: eq(Member.refID, "101")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_3(func: eq(Member.refID, "101")) @filter(type(Member)) { + uid + } + } + qnametouid: |- + { + "LibraryMember_2": "0x11" + } + error2: + { + "message": "failed to rewrite mutation payload because id 101 already exists for field + refID inside type LibraryMember" + } + +- + name: "Add upsert mutation on implementation type which have inherited @id field with interface argument -1" + explanation: "node with @id field doesn't exist in any of the implementing type, we will add the node" + gqlmutation: | + mutation addLibraryMember($input: AddLibraryMemberInput!) { + addLibraryMember(input: [$input], upsert: true) { + libraryMember { + refID + } + } + } + gqlvariables: | + { + "input": { + "refID": "101", + "name": "Alice", + "itemsIssued": [ + "Intro to Go", + "Parallel Programming" + ], + "readHours": "4d2hr" + } + } + dgquery: |- + query { + LibraryMember_1(func: eq(Member.name, "Alice")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_2(func: eq(Member.refID, "101")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_3(func: eq(Member.refID, "101")) @filter(type(Member)) { + uid + } + } + qnametouid: |- + { + "LibraryMember_1": "0x11" + } + dgquerysec: |- + query { + LibraryMember_1 as LibraryMember_1(func: uid(0x11)) @filter(type(LibraryMember)) { + uid + } + } + dgmutations: + - setjson: | + { + "LibraryMember.readHours": "4d2hr", + "Member.itemsIssued": [ + "Intro to Go", + "Parallel Programming" + ], + "uid": "uid(LibraryMember_1)" + } + cond: "@if(gt(len(LibraryMember_1), 0))" +- + name: "Add upsert mutation on implementation type which have inherited @id field with interface argument -2" + explanation: "node with @id field already exist in one of the implementing type, returns error" + gqlmutation: | + mutation addLibraryMember($input: AddLibraryMemberInput!) { + addLibraryMember(input: [$input], upsert: true) { + libraryMember { + refID + } + } + } + gqlvariables: | + { + "input": { + "refID": "101", + "name": "Alice", + "itemsIssued": [ + "Intro to Go", + "Parallel Programming" + ], + "readHours": "4d2hr" + } + } + dgquery: |- + query { + LibraryMember_1(func: eq(Member.name, "Alice")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_2(func: eq(Member.refID, "101")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_3(func: eq(Member.refID, "101")) @filter(type(Member)) { + uid + } + } + qnametouid: |- + { + "LibraryMember_3": "0x11" + } + error2: + { + "message": "failed to rewrite mutation payload because id 101 already exists for + field refID in some other implementing type of interface Member" + } + +- + name: "Add mutation with nested object which have inherited @id field with interface argument -1" + explanation: "There is no node with refID 101 of interface type or it's implementation type,hence will wii add + nested object and link that to parent object" + gqlmutation: | + mutation addLibraryManager($input: AddLibraryManagerInput!) { + addLibraryManager(input: [$input], upsert: false) { + libraryManager { + name + } + } + } + gqlvariables: | + { + "input": { + "name": "Alice", + "manages": [ + { + "refID": "101", + "name": "Bob", + "itemsIssued": [ + "Intro to Go", + "Parallel Programming" + ], + "readHours": "4d2hr" + } + ] + } + } + dgquery: |- + query { + LibraryManager_1(func: eq(LibraryManager.name, "Alice")) @filter(type(LibraryManager)) { + uid + } + LibraryMember_2(func: eq(Member.name, "Bob")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_3(func: eq(Member.refID, "101")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_4(func: eq(Member.refID, "101")) @filter(type(Member)) { + uid + } + } + dgmutations: + - setjson: | + { + "LibraryManager.manages": [ + { + "LibraryMember.readHours": "4d2hr", + "Member.itemsIssued": [ + "Intro to Go", + "Parallel Programming" + ], + "Member.name": "Bob", + "Member.refID": "101", + "dgraph.type": [ + "LibraryMember", + "Member" + ], + "uid": "_:LibraryMember_3" + } + ], + "LibraryManager.name": "Alice", + "dgraph.type": [ + "LibraryManager" + ], + "uid": "_:LibraryManager_1" + } + +- + name: "Add mutation with nested object which have inherited @id field with interface argument -2" + explanation: "node with refID 101 already exist in one of the implementing type other than mutated type,returns error" + gqlmutation: | + mutation addLibraryManager($input: AddLibraryManagerInput!) { + addLibraryManager(input: [$input], upsert: false) { + libraryManager { + name + } + } + } + gqlvariables: | + { + "input": { + "name": "Alice", + "manages": [ + { + "refID": "101", + "name": "Bob", + "itemsIssued": [ + "Intro to Go", + "Parallel Programming" + ], + "readHours": "4d2hr" + } + ] + } + } + dgquery: |- + query { + LibraryManager_1(func: eq(LibraryManager.name, "Alice")) @filter(type(LibraryManager)) { + uid + } + LibraryMember_2(func: eq(Member.name, "Bob")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_3(func: eq(Member.refID, "101")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_4(func: eq(Member.refID, "101")) @filter(type(Member)) { + uid + } + } + qnametouid: |- + { + "LibraryMember_4": "0x11" + } + error2: + { + "message": "failed to rewrite mutation payload because id 101 already exists for field + refID in some other implementing type of interface Member" + } + +- + name: "Add mutation with nested object which have inherited @id field with interface argument -3" + explanation: "node with refID 101 already exist for mutated type,link child node to parent" + gqlmutation: | + mutation addLibraryManager($input: AddLibraryManagerInput!) { + addLibraryManager(input: [$input], upsert: false) { + libraryManager { + name + } + } + } + gqlvariables: | + { + "input": { + "name": "Alice", + "manages": [ + { + "refID": "101", + "name": "Bob", + "itemsIssued": [ + "Intro to Go", + "Parallel Programming" + ], + "readHours": "4d2hr" + } + ] + } + } + dgquery: |- + query { + LibraryManager_1(func: eq(LibraryManager.name, "Alice")) @filter(type(LibraryManager)) { + uid + } + LibraryMember_2(func: eq(Member.name, "Bob")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_3(func: eq(Member.refID, "101")) @filter(type(LibraryMember)) { + uid + } + LibraryMember_4(func: eq(Member.refID, "101")) @filter(type(Member)) { + uid + } + } + qnametouid: |- + { + "LibraryMember_3": "0x11", + "LibraryMember_4": "0x11" + } + dgmutations: + - setjson: | + { + "LibraryManager.manages": [ + { + "uid":"0x11" + } + ], + "LibraryManager.name": "Alice", + "dgraph.type": [ + "LibraryManager" + ], + "uid": "_:LibraryManager_1" + } + +- + name: "Add mutation on implementation type which have inherited @id fields with interface argument from multiple interfaces" + explanation: "This mutation will generate six existence queries, 2 existence queries for each of the inherited @id fields + with interface arg and one for each @id field,none of the existence query return uid,so we successfully add the object in this case" + gqlmutation: | + mutation addSportsMember($input: AddSportsMemberInput!) { + addSportsMember(input: [$input], upsert: false) { + sportsMember { + refID + } + } + } + gqlvariables: | + { + "input": { + "refID": "101", + "name": "Alice", + "teamID": "T01", + "teamName": "GraphQL", + "itemsIssued": [ + "2-Bats", + "1-football" + ], + "plays": "football and cricket" + } + } + dgquery: |- + query { + SportsMember_1(func: eq(Member.name, "Alice")) @filter(type(SportsMember)) { + uid + } + SportsMember_2(func: eq(Member.refID, "101")) @filter(type(SportsMember)) { + uid + } + SportsMember_3(func: eq(Member.refID, "101")) @filter(type(Member)) { + uid + } + SportsMember_4(func: eq(Team.teamID, "T01")) @filter(type(SportsMember)) { + uid + } + SportsMember_5(func: eq(Team.teamID, "T01")) @filter(type(Team)) { + uid + } + SportsMember_6(func: eq(Team.teamName, "GraphQL")) @filter(type(SportsMember)) { + uid + } + } + dgmutations: + - setjson: | + { + "Member.itemsIssued": [ + "2-Bats", + "1-football" + ], + "Member.name": "Alice", + "Member.refID": "101", + "SportsMember.plays": "football and cricket", + "Team.teamID": "T01", + "Team.teamName": "GraphQL", + "dgraph.type": [ + "SportsMember", + "Member", + "Team" + ], + "uid": "_:SportsMember_6" + } + - name: "Add mutation using code on type which also has an ID field" gqlmutation: | diff --git a/graphql/resolve/mutation_rewriter.go b/graphql/resolve/mutation_rewriter.go index 4439e9a5727..5bc7677a4f6 100644 --- a/graphql/resolve/mutation_rewriter.go +++ b/graphql/resolve/mutation_rewriter.go @@ -131,9 +131,19 @@ func NewVariableGenerator() *VariableGenerator { func (v *VariableGenerator) Next(typ schema.Type, xidName, xidVal string, auth bool) string { // return previously allocated variable name for repeating xidVal var key string + flagAndXidName := xidName + + // We pass the xidName as "Int.xidName" to generate variable for existence query + // of interface type when id filed is inherited from interface and have interface Argument set + // Here we handle that case + if strings.Contains(flagAndXidName, ".") { + xidName = strings.Split(flagAndXidName, ".")[1] + } + if xidName == "" || xidVal == "" { key = typ.Name() } else { + // here we are using the assertion that field name or type name can't have "." in them // We add "." between values while generating key to removes duplicate xidError from below type of cases // mutation { // addABC(input: [{ ab: "cd", abc: "d" }]) { @@ -147,7 +157,8 @@ func (v *VariableGenerator) Next(typ schema.Type, xidName, xidVal string, auth b // ABC.ab.cd and ABC.abc.d // It also ensures that xids from different types gets different variable names // here we are using the assertion that field name or type name can't have "." in them - key = typ.FieldOriginatedFrom(xidName) + "." + xidName + "." + xidVal + xidType, _ := typ.FieldOriginatedFrom(xidName) + key = xidType.Name() + "." + flagAndXidName + "." + xidVal } if varName, ok := v.xidVarNameMap[key]; ok { @@ -1236,9 +1247,9 @@ func mutationFromFragment( } -func checkXIDExistsQuery( - xidVariable, xidString, xidPredicate string, typ schema.Type) *dql.GraphQuery { - qry := &dql.GraphQuery{ +func checkXIDExistsQuery(xidVariable, xidString, xidPredicate string, typ schema.Type, + interfaceType schema.Type) *gql.GraphQuery { + qry := &gql.GraphQuery{ Attr: xidVariable, Func: &dql.Function{ Name: "eq", @@ -1249,6 +1260,14 @@ func checkXIDExistsQuery( }, Children: []*dql.GraphQuery{{Attr: "uid"}, {Attr: "dgraph.type"}}, } + + // Below filter is added to generate existence query for interface + // If given xid field is inherited from interface and + // have interface arg set then we add interface type in filter + if interfaceType != nil { + typ = interfaceType + } + addTypeFilter(qry, typ) return qry } @@ -1406,46 +1425,79 @@ func rewriteObject( xidString, _ = extractVal(xidVal, xid.Name(), xid.Type().Name()) variable = varGen.Next(typ, xid.Name(), xidString, false) - // Three cases: - // 1. If the queryResult UID exists. Add a reference. + // If this xid field is inherited from interface and have interface argument set, we also + // have existence query for interface to make sure that this xid is unique across all + // implementation types of the interface. + // We have following cases + // 1. If the queryResult UID exists for any of existence query (type or interface), + // then add a reference. // 2. If the queryResult UID does not exist and this is the first time we are seeing // this. Then, return error. // 3. The queryResult UID does not exist. But, this could be a reference to an XID // node added during the mutation rewriting. This is handled by adding the new blank UID // to existenceQueryResult. - // Get whether node with XID exists or not from existenceQueriesResult - if uid, ok := idExistence[variable]; ok { + interfaceTyp, interfaceVar := interfaceVariable(typ, varGen, xid.Name(), xidString) + + // Get whether node with XID exists or not from existenceQueriesResults + _, interfaceUidExist := idExistence[interfaceVar] + typUid, typUidExist := idExistence[variable] + + if interfaceUidExist || typUidExist { // node with XID exists. This is a reference. - // We return an error if this is at toplevel. Else, we return the ID reference + // We return an error if this is at toplevel. Else, we return the ID reference if + // found node is of same type as xid field type. Because that node can be of some other + // type in case xidField is inherited from interface. if atTopLevel { if mutationType == AddWithUpsert { - // This means we are in Add Mutation with upsert: true. - // In this case, we don't return an error and continue updating this node. - // upsertVar is set to variable and srcUID is set to uid(variable) to continue - // updating this node. - upsertVar = variable - srcUID = fmt.Sprintf("uid(%s)", variable) + if typUidExist { + // This means we are in Add Mutation with upsert: true and node belong to + // same type as of the xid field. + // In this case, we don't return an error and continue updating this node. + // upsertVar is set to variable and srcUID is set to uid(variable) to continue + // updating this node. + upsertVar = variable + srcUID = fmt.Sprintf("uid(%s)", variable) + } else { + // if node is some other type as of xid Field then we can't upsert that + // and we returns error + retErrors = append(retErrors, xidErrorForInterfaceType(typ, xidString, xid, + interfaceTyp.Name())) + return nil, "", retErrors + } } else { // We return an error as we are at top level of non-upsert mutation and the XID exists. // We need to conceal the error because we might be leaking information to the user if it // tries to add duplicate data to the field with @id. var err error - if queryAuthSelector(typ) == nil { - err = x.GqlErrorf("id %s already exists "+ - "for field %s inside type %s", xidString, xid.Name(), typ.Name()) - } else { - // This error will only be reported in debug mode. - err = x.GqlErrorf("GraphQL debug: id %s already exists for "+ - "field %s inside type %s", xidString, xid.Name(), typ.Name()) + if typUidExist { + if queryAuthSelector(typ) == nil { + err = x.GqlErrorf("id %s already exists for field %s inside type %s", + xidString, xid.Name(), typ.Name()) + } else { + // This error will only be reported in debug mode. + err = x.GqlErrorf("GraphQL debug: id %s already exists for field %s"+ + " inside type %s", typ.Name(), xid.Name(), xidString) + } + retErrors = append(retErrors, err) + return nil, upsertVar, retErrors } - retErrors = append(retErrors, err) + + retErrors = append(retErrors, xidErrorForInterfaceType(typ, xidString, xid, + interfaceTyp.Name())) return nil, upsertVar, retErrors + } } else { // As we are not at top level, we return the XID reference. We don't update this node // further. - return asIDReference(ctx, uid, srcField, srcUID, varGen, mutationType == UpdateWithRemove), upsertVar, nil + if typUidExist { + return asIDReference(ctx, typUid, srcField, srcUID, varGen, + mutationType == UpdateWithRemove), upsertVar, nil + } + retErrors = append(retErrors, xidErrorForInterfaceType(typ, xidString, xid, + interfaceTyp.Name())) + return nil, upsertVar, retErrors } } else { @@ -1498,7 +1550,7 @@ func rewriteObject( // This is handled in the for loop above continue } else if mutationType == Add || mutationType == AddWithUpsert || !atTopLevel { - // When we reach this stage we are absoulutely sure that this is not a reference and is + // When we reach this stage we are absolutely sure that this is not a reference and is // a new node and one of the XIDs is missing. // There are two possibilities here: // 1. This is an Add Mutation or we are at some deeper level inside Update Mutation: @@ -1715,6 +1767,20 @@ func rewriteObject( return frag, upsertVar, retErrors } +func xidErrorForInterfaceType(typ schema.Type, xidString string, xid schema.FieldDefinition, + interfaceName string) error { + // TODO(GRAPHQL): currently we are checking typ of the mutated field for auth rules, + // But we need to check auth rule on implementing type for which we found existing node + // with same @id. + if queryAuthSelector(typ) == nil { + return x.GqlErrorf("id %s already exists for field %s in some other"+ + " implementing type of interface %s", xidString, xid.Name(), interfaceName) + } + // This error will only be reported in debug mode. + return x.GqlErrorf("GraphQL debug: id %s already exists for field %s in some other"+ + " implementing type of interface %s", xidString, xid.Name(), interfaceName) +} + // existenceQueries takes a GraphQL JSON object as obj and creates queries to find // out if referenced nodes by XID and UID exist or not. // This is done in recursive fashion using a dfs. @@ -1794,7 +1860,13 @@ func existenceQueries( if xidMetadata.variableObjMap[variable] != nil { // if we already encountered an object with same xid earlier, and this object is // considered a duplicate of the existing object, then return error. + if xidMetadata.isDuplicateXid(atTopLevel, variable, obj, srcField) { + // TODO(GRAPHQL): Add this error for inherited @id field with interface arg. + // Currently we don't return this error for the nested case when + // at both root and nested level we have same value of @id fields + // which have interface arg set and are inherited from same interface + // but are in different implementing type, we currently treat that as reference. err := errors.Errorf("duplicate XID found: %s", xidString) retErrors = append(retErrors, err) return nil, nil, retErrors @@ -1824,9 +1896,19 @@ func existenceQueries( // Add the corresponding existence query. As this is the first time we have // encountered this variable, the query is added only once per variable. - query := checkXIDExistsQuery(variable, xidString, xid.Name(), typ) + query := checkXIDExistsQuery(variable, xidString, xid.Name(), typ, nil) ret = append(ret, query) - retTypes = append(retTypes, typ.DgraphName()) + + // Add one more existence query if given xid field is inherited from interface and has + // interface argument set. This is added to ensure that this xid is unique across all the + // implementation of the interface. + interfaceTyp, varInterface := interfaceVariable(typ, varGen, + xid.Name(), xidString) + if interfaceTyp != nil { + queryInterface := checkXIDExistsQuery(varInterface, xidString, xid.Name(), + typ, interfaceTyp) + ret = append(ret, queryInterface) + } // Don't return just over here as there maybe more nodes in the children tree. } } @@ -2376,3 +2458,15 @@ func extractVal(xidVal interface{}, xidName, typeName string) (string, error) { "allowed as Xid", xidName, typeName) } } + +// This function will return interface type and variable for existence query on interface, +// if given xid is inherited from interface, otherwise it will return nil and empty string +func interfaceVariable(typ schema.Type, varGen *VariableGenerator, xidName string, + xidString string) (schema.Type, string) { + interfaceType, isInherited := typ.FieldOriginatedFrom(xidName) + fieldDef := typ.Field(xidName) + if isInherited && fieldDef.HasInterfaceArg() { + return interfaceType, varGen.Next(typ, "Int."+xidName, xidString, false) + } + return nil, "" +} diff --git a/graphql/resolve/query_test.yaml b/graphql/resolve/query_test.yaml index d5410eff615..919c4120802 100644 --- a/graphql/resolve/query_test.yaml +++ b/graphql/resolve/query_test.yaml @@ -30,8 +30,7 @@ } } -- - name: "in filter on string type" +- name: "in filter on string type" gqlquery: | query { queryState(filter: {code: {in: ["abc", "def", "ghi"]}}) { @@ -48,8 +47,7 @@ } } -- - name: "in filter on float type" +- name: "in filter on float type" gqlquery: | query { queryAuthor(filter: {reputation: {in: [10.3, 12.6, 13.6]}}) { @@ -66,8 +64,7 @@ } } -- - name: "in filter on datetime type" +- name: "in filter on datetime type" gqlquery: | query { queryAuthor(filter: {dob: {in: ["2001-01-01", "2002-02-01"]}}) { @@ -84,8 +81,7 @@ } } -- - name: "in filter on int type" +- name: "in filter on int type" gqlquery: | query { queryPost(filter: {numLikes: {in: [10, 15, 100]}}) { @@ -99,8 +95,7 @@ dgraph.uid : uid } } -- - name: "in filter on field which is of enum type" +- name: "in filter on field which is of enum type" gqlquery: | query{ queryVerification(filter: {prevStatus: {in: [ACTIVE, DEACTIVATED]}}){ @@ -117,8 +112,7 @@ } } -- - name: "in filter on field which is a List of enum type" +- name: "in filter on field which is a List of enum type" gqlquery: | query{ queryVerification(filter: {status: {in: [ACTIVE, DEACTIVATED]}}){ @@ -168,8 +162,7 @@ dgraph.uid : uid } } -- - name: "Point query near filter" +- name: "Point query near filter" gqlquery: | query { queryHotel(filter: { location: { near: { distance: 33.33, coordinate: { latitude: 11.11, longitude: 22.22} } } }) { @@ -189,8 +182,7 @@ } } -- - name: "Point query within filter" +- name: "Point query within filter" gqlquery: | query { queryHotel(filter: { location: { within: { polygon: { coordinates: [ { points: [{ latitude: 11.11, longitude: 22.22}, { latitude: 15.15, longitude: 16.16} , { latitude: 20.20, longitude: 21.21} ]}, { points: [{ latitude: 11.18, longitude: 22.28}, { latitude: 15.18, longitude: 16.18} , { latitude: 20.28, longitude: 21.28}]} ] } } } }) { @@ -210,8 +202,7 @@ } } -- - name: "Polygon query near filter" +- name: "Polygon query near filter" gqlquery: | query { queryHotel(filter: { area: { near: { distance: 33.33, coordinate: { latitude: 11.11, longitude: 22.22} } } }) { @@ -235,8 +226,7 @@ } } -- - name: "Polygon query within filter" +- name: "Polygon query within filter" gqlquery: | query { queryHotel(filter: { area: { within: { polygon: { coordinates: [ { points: [{ latitude: 11.11, longitude: 22.22}, { latitude: 15.15, longitude: 16.16} , { latitude: 20.20, longitude: 21.21} ]}, { points: [{ latitude: 11.18, longitude: 22.28}, { latitude: 15.18, longitude: 16.18} , { latitude: 20.28, longitude: 21.28}]} ] } } } }) { @@ -260,8 +250,7 @@ } } -- - name: "Polygon query contains polygon filter" +- name: "Polygon query contains polygon filter" gqlquery: | query { queryHotel(filter: { area: { contains: { polygon: { coordinates: [ { points: [{ latitude: 11.11, longitude: 22.22}, { latitude: 15.15, longitude: 16.16} , { latitude: 20.20, longitude: 21.21} ]}, { points: [{ latitude: 11.18, longitude: 22.28}, { latitude: 15.18, longitude: 16.18} , { latitude: 20.28, longitude: 21.28}]} ] } } } }) { @@ -285,8 +274,7 @@ } } -- - name: "Polygon query contains point filter" +- name: "Polygon query contains point filter" gqlquery: | query { queryHotel(filter: { area: { contains: { point: { latitude: 11.11, longitude: 22.22}} } }) { @@ -310,8 +298,7 @@ } } -- - name: "Polygon query intersect polygon filter" +- name: "Polygon query intersect polygon filter" gqlquery: | query { queryHotel(filter: { @@ -365,8 +352,7 @@ } } -- - name: "Polygon query intersect multi-polygon filter" +- name: "Polygon query intersect multi-polygon filter" gqlquery: | query { queryHotel(filter: { @@ -446,8 +432,7 @@ } } -- - name: "MultiPolygon query near filter" +- name: "MultiPolygon query near filter" gqlquery: | query { queryHotel(filter: { branches: { near: { distance: 33.33, coordinate: { latitude: 11.11, longitude: 22.22} } } }) { @@ -473,8 +458,7 @@ } } -- - name: "MultiPolygon query within filter" +- name: "MultiPolygon query within filter" gqlquery: | query { queryHotel(filter: { branches: { within: { polygon: { coordinates: [ { points: [{ latitude: 11.11, longitude: 22.22}, { latitude: 15.15, longitude: 16.16} , { latitude: 20.20, longitude: 21.21} ]}, { points: [{ latitude: 11.18, longitude: 22.28}, { latitude: 15.18, longitude: 16.18} , { latitude: 20.28, longitude: 21.28}]} ] } } } }) { @@ -500,8 +484,7 @@ } } -- - name: "MultiPolygon query contains polygon filter" +- name: "MultiPolygon query contains polygon filter" gqlquery: | query { queryHotel(filter: { branches: { contains: { polygon: { coordinates: [ { points: [{ latitude: 11.11, longitude: 22.22}, { latitude: 15.15, longitude: 16.16} , { latitude: 20.20, longitude: 21.21} ]}, { points: [{ latitude: 11.18, longitude: 22.28}, { latitude: 15.18, longitude: 16.18} , { latitude: 20.28, longitude: 21.28}]} ] } } } }) { @@ -527,8 +510,7 @@ } } -- - name: "MultiPolygon query contains point filter" +- name: "MultiPolygon query contains point filter" gqlquery: | query { queryHotel(filter: { branches: { contains: { point: { latitude: 11.11, longitude: 22.22}} } }) { @@ -554,8 +536,7 @@ } } -- - name: "MultiPolygon query intersect polygon filter" +- name: "MultiPolygon query intersect polygon filter" gqlquery: | query { queryHotel(filter: { @@ -611,8 +592,7 @@ } } -- - name: "MultiPolygon query intersect multi-polygon filter" +- name: "MultiPolygon query intersect multi-polygon filter" gqlquery: | query { queryHotel(filter: { @@ -694,8 +674,7 @@ } } -- - name: "ID query" +- name: "ID query" gqlquery: | query { getAuthor(id: "0x1") { @@ -710,8 +689,7 @@ } } -- - name: "Alias isn't ignored in query rewriting - get" +- name: "Alias isn't ignored in query rewriting - get" gqlquery: | query { author : getAuthor(id: "0x1") { @@ -733,8 +711,7 @@ } } -- - name: "Alias isn't ignored in query rewriting - query" +- name: "Alias isn't ignored in query rewriting - query" gqlquery: | query { author : queryAuthor { @@ -756,8 +733,7 @@ } } -- - name: "ID field gets transformed to uid" +- name: "ID field gets transformed to uid" gqlquery: | query { getAuthor(id: "0x1") { @@ -773,8 +749,7 @@ } } -- - name: "ID query with depth" +- name: "ID query with depth" gqlquery: | query { getAuthor(id: "0x1") { @@ -798,8 +773,7 @@ } } -- - name: "ID query deep" +- name: "ID query deep" gqlquery: | query { getAuthor(id: "0x1") { @@ -831,8 +805,7 @@ } } -- - name: "Query with no args is query for everything of that type" +- name: "Query with no args is query for everything of that type" gqlquery: | query { queryAuthor { @@ -847,8 +820,12 @@ } } +<<<<<<< HEAD - name: "eq Filter gets rewritten as root func" +======= +- name: "Filter gets rewritten as @filter" +>>>>>>> 69c83890a (Feat(GraphQL): This PR allows @id field in interface to be unique across all the implementing types. (#7710)) gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" } }) { @@ -863,8 +840,7 @@ } } -- - name: "Filter connectives with null values gets skipped " +- name: "Filter connectives with null values gets skipped " gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" },not:null }) { @@ -879,8 +855,7 @@ } } -- - name: "Query with has Filter" +- name: "Query with has Filter" gqlquery: | query { queryTeacher(filter: {has: subject}) { @@ -895,8 +870,7 @@ } } -- - name: "has Filter with not" +- name: "has Filter with not" gqlquery: | query { queryTeacher(filter: { not : {has: subject } }) { @@ -911,8 +885,7 @@ } } -- - name: "has Filter with and" +- name: "has Filter with and" gqlquery: | query { queryTeacher(filter: {has: subject, and: {has: teaches } } ) { @@ -927,8 +900,7 @@ } } -- - name: "has Filter on list of fields" +- name: "has Filter on list of fields" gqlquery: | query { queryTeacher(filter: {has: [subject, teaches ] } ) { @@ -956,8 +928,7 @@ dgraph.uid : uid } } -- - name: "Filters in same input object implies AND" +- name: "Filters in same input object implies AND" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" }, dob: { le: "2001-01-01" }, reputation: { gt: 2.5 } } ) { @@ -972,8 +943,7 @@ } } -- - name: "Filter with nested 'and'" +- name: "Filter with nested 'and'" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" }, and: { dob: { le: "2001-01-01" }, and: { reputation: { gt: 2.5 } } } } ) { @@ -988,8 +958,7 @@ } } -- - name: "has Filter with nested 'and'" +- name: "has Filter with nested 'and'" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" }, and: { dob: { le: "2001-01-01" }, and: { has: country } } } ) { @@ -1004,8 +973,7 @@ } } -- - name: "Filter with 'or'" +- name: "Filter with 'or'" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" }, or: { dob: { le: "2001-01-01" } } } ) { @@ -1020,8 +988,7 @@ } } -- - name: "Filter with 'or' array" +- name: "Filter with 'or' array" gqlquery: | query { queryAuthor(filter: { or: [ { name: { eq: "A. N. Author" } }, { dob: { le: "2001-01-01" } }] } ) { @@ -1036,8 +1003,7 @@ } } -- - name: "Filter with 'or' object" +- name: "Filter with 'or' object" gqlquery: | query { queryAuthor(filter: { or: { name: { eq: "A. N. Author" } }} ) { @@ -1053,8 +1019,7 @@ } -- - name: "Filter with implied and as well as 'or'" +- name: "Filter with implied and as well as 'or'" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" }, reputation: { gt: 2.5 }, or: { dob: { le: "2001-01-01" } } } ) { @@ -1069,8 +1034,7 @@ } } -- - name: "Filter with implied and nested in 'or'" +- name: "Filter with implied and nested in 'or'" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" }, or: { reputation: { gt: 2.5 }, dob: { le: "2001-01-01" } } } ) { @@ -1085,8 +1049,7 @@ } } -- - name: "Filter nested 'or'" +- name: "Filter nested 'or'" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" }, or: { reputation: { gt: 2.5 }, or: { dob: { le: "2001-01-01" } } } } ) { @@ -1101,8 +1064,7 @@ } } -- - name: "Filter with 'not" +- name: "Filter with 'not" gqlquery: | query { queryAuthor(filter: { not: { reputation: { gt: 2.5 } } } ) { @@ -1117,8 +1079,7 @@ } } -- - name: "Filter with first" +- name: "Filter with first" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" } }, first: 10) { @@ -1133,8 +1094,7 @@ } } -- - name: "Filter with first and offset" +- name: "Filter with first and offset" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" } }, first: 10, offset: 10) { @@ -1149,8 +1109,7 @@ } } -- - name: "Filter with order asc" +- name: "Filter with order asc" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" } }, order: { asc: reputation }) { @@ -1165,8 +1124,7 @@ } } -- - name: "Filter with order desc" +- name: "Filter with order desc" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" } }, order: { desc: reputation }) { @@ -1182,8 +1140,7 @@ } -- - name: "Filter with nested order" +- name: "Filter with nested order" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" } }, order: { desc: reputation, then: { asc: dob } }) { @@ -1198,8 +1155,7 @@ } } -- - name: "Filter with order, first and offset" +- name: "Filter with order, first and offset" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" } }, order: { desc: reputation }, first: 10, offset: 10) { @@ -1214,8 +1170,7 @@ } } -- - name: "Deep filter" +- name: "Deep filter" gqlquery: | query { queryAuthor { @@ -1238,8 +1193,7 @@ } -- - name: "Deep filter with has filter" +- name: "Deep filter with has filter" gqlquery: | query { queryAuthor { @@ -1260,8 +1214,7 @@ dgraph.uid : uid } } -- - name: "Deep filter with has filter on list of fields" +- name: "Deep filter with has filter on list of fields" gqlquery: | query { queryAuthor { @@ -1283,8 +1236,7 @@ } } -- - name: "Deep filter with has and other filters" +- name: "Deep filter with has and other filters" gqlquery: | query { queryAuthor { @@ -1305,8 +1257,7 @@ dgraph.uid : uid } } -- - name: "Deep filter with first" +- name: "Deep filter with first" gqlquery: | query { queryAuthor { @@ -1328,8 +1279,7 @@ } } -- - name: "Deep filter with order, first and offset" +- name: "Deep filter with order, first and offset" gqlquery: | query { queryAuthor { @@ -1351,8 +1301,7 @@ } } -- - name: "Deep filter with multiple order, first and offset" +- name: "Deep filter with multiple order, first and offset" gqlquery: | query { queryAuthor { @@ -1374,8 +1323,7 @@ } } -- - name: "Float with large exponentiation" +- name: "Float with large exponentiation" gqlquery: | query { queryAuthor(filter:{ reputation: { gt: 123456789.113 } }) { @@ -1390,8 +1338,7 @@ } } -- - name: "All Float filters work" +- name: "All Float filters work" gqlquery: | query { queryAuthor(filter: { reputation: { gt: 1.1 }, or: { reputation: { ge: 1.1 }, or: { reputation: { lt: 1.1 }, or: { reputation: { le: 1.1 }, or: { reputation: { eq: 1.1 } } } } } } ) { @@ -1406,8 +1353,7 @@ } } -- - name: "All DateTime filters work" +- name: "All DateTime filters work" gqlquery: | query { queryAuthor(filter: { dob: { gt: "2000-01-01" }, or: { dob: { ge: "2000-01-01" }, or: { dob: { lt: "2000-01-01" }, or: { dob: { le: "2000-01-01" }, or: { dob: { eq: "2000-01-01" } } } } } } ) { @@ -1422,8 +1368,7 @@ } } -- - name: "All Int filters work" +- name: "All Int filters work" gqlquery: | query { queryPost(filter: { numLikes: { gt: 10 }, or: { numLikes: { ge: 10 }, or: { numLikes: { lt: 10 }, or: { numLikes: { le: 10 }, or: { numLikes: { eq: 10 } } } } } } ) { @@ -1438,8 +1383,7 @@ } } -- - name: "All String hash filters work" +- name: "All String hash filters work" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" } } ) { @@ -1454,8 +1398,7 @@ } } -- - name: "All String exact filters work" +- name: "All String exact filters work" gqlquery: | query { queryCountry(filter: { name: { gt: "AAA" }, or: { name: { ge: "AAA" }, or: { name: { lt: "AAA" }, or: { name: { le: "AAA" }, or: { name: { eq: "AAA" } } } } } } ) { @@ -1470,8 +1413,7 @@ } } -- - name: "All String exact filters work with an array for OR" +- name: "All String exact filters work with an array for OR" gqlquery: | query { queryCountry(filter: { name: { gt: "AAA" }, or: [{ name: { ge: "AAA" }}, { name: { lt: "AAA" }}, { name: { le: "AAA" }}, { name: { eq: "AAA" } }] }) { @@ -1486,8 +1428,7 @@ } } -- - name: "All String exact filters work with an array for AND" +- name: "All String exact filters work with an array for AND" gqlquery: | query { queryCountry(filter: { name: { gt: "AAA" }, and: [{ name: { ge: "AAA" }}, { name: { lt: "AAA" }}, { name: { le: "AAA" }}, { name: { eq: "AAA" } }] }) { @@ -1503,8 +1444,7 @@ } -- - name: "Represent (A OR B) AND (C OR D)" +- name: "Represent (A OR B) AND (C OR D)" gqlquery: | query { queryCountry(filter: { and: [{ name: { gt: "AAA" }, or: { name: { lt: "XXX" }}}, { name: { gt : "CCC" }, or: { name: { lt: "MMM" }}}] }) { @@ -1519,8 +1459,7 @@ } } -- - name: "All String term filters work" +- name: "All String term filters work" gqlquery: | query { queryPost(filter: { title: { anyofterms: "GraphQL"}, or: { title: { allofterms: "GraphQL" } } } ) { @@ -1536,8 +1475,7 @@ } -- - name: "All String fulltext filters work" +- name: "All String fulltext filters work" gqlquery: | query { queryPost(filter: { text: { anyoftext: "GraphQL"}, or: { text: { alloftext: "GraphQL" } } } ) { @@ -1552,8 +1490,7 @@ } } -- - name: "All String regexp filters work" +- name: "All String regexp filters work" gqlquery: | query { queryCountry(filter: { name: { regexp: "/.*ust.*/" }}) { @@ -1568,8 +1505,7 @@ } } -- - name: "Aggregate Query" +- name: "Aggregate Query" gqlquery: | query { aggregateCountry(filter: { name: { regexp: "/.*ust.*/" }}) { @@ -1595,8 +1531,7 @@ } } -- - name: "Skip directive" +- name: "Skip directive" gqlquery: | query ($skipTrue: Boolean!, $skipFalse: Boolean!) { getAuthor(id: "0x1") { @@ -1620,8 +1555,7 @@ } } -- - name: "Include directive" +- name: "Include directive" gqlquery: | query ($includeTrue: Boolean!, $includeFalse: Boolean!) { queryAuthor { @@ -1645,8 +1579,7 @@ } } -- - name: "Include only fields for which skip is !false or include is true" +- name: "Include only fields for which skip is !false or include is true" variables: includeFalse: false includeTrue: true @@ -1681,8 +1614,7 @@ } } -- - name: "Cascade directive on get query" +- name: "Cascade directive on get query" gqlquery: | query { getAuthor(id: "0x1") @cascade { @@ -1704,8 +1636,7 @@ } } -- - name: "Cascade directive on filter query" +- name: "Cascade directive on filter query" gqlquery: | query { queryAuthor @cascade { @@ -1727,8 +1658,7 @@ } } -- - name: "Cascade directive on query field" +- name: "Cascade directive on query field" gqlquery: | query { queryAuthor { @@ -1750,8 +1680,7 @@ } } -- - name: "Cascade directive on root query and query field" +- name: "Cascade directive on root query and query field" gqlquery: | query { queryAuthor @cascade { @@ -1773,8 +1702,7 @@ } } -- - name: "Parameterized Cascade directive on filter query" +- name: "Parameterized Cascade directive on filter query" gqlquery: | query { queryAuthor @cascade(fields:["dob"]) { @@ -1798,8 +1726,7 @@ } } -- - name: "Parameterized Cascade directive on get query" +- name: "Parameterized Cascade directive on get query" gqlquery: | query { getAuthor(id: "0x1") @cascade(fields:["dob"]) { @@ -1823,8 +1750,7 @@ } } -- - name: "Parameterized Cascade directive on query field" +- name: "Parameterized Cascade directive on query field" gqlquery: | query { queryAuthor { @@ -1848,8 +1774,7 @@ } } -- - name: "Parameterized Cascade directive on root and query field" +- name: "Parameterized Cascade directive on root and query field" gqlquery: | query { queryAuthor @cascade(fields:["dob"]) { @@ -1875,8 +1800,7 @@ } } -- - name: "Parameterized Cascade directive with multiple parameters on root and query field" +- name: "Parameterized Cascade directive with multiple parameters on root and query field" gqlquery: | query { queryAuthor @cascade(fields:["dob","reputation","id"]) { @@ -1902,8 +1826,7 @@ } } -- - name: "Parameterized Cascade directive with argument at outer level which is not present in inner level " +- name: "Parameterized Cascade directive with argument at outer level which is not present in inner level " gqlquery: | query { queryAuthor @cascade(fields:["dob"]) { @@ -1929,8 +1852,7 @@ } } -- - name: "parameterized cascade with interface implementation Human" +- name: "parameterized cascade with interface implementation Human" gqlquery: | query { queryHuman @cascade(fields:["id","name","ename","dob"]) { @@ -1952,8 +1874,7 @@ } } -- - name: "parameterized cascade with interface Character" +- name: "parameterized cascade with interface Character" gqlquery: | query { queryCharacter @cascade(fields:["id","name"]) { @@ -1970,8 +1891,7 @@ } } -- - name: "Parameterized Cascade directive on root and nested field using variables" +- name: "Parameterized Cascade directive on root and nested field using variables" gqlquery: | query($fieldsRoot:[String],$fieldsDeep:[String]) { queryAuthor @cascade(fields: $fieldsRoot) { @@ -2007,8 +1927,7 @@ } } -- - name: "getHuman which implements an interface" +- name: "getHuman which implements an interface" gqlquery: | query { getHuman(id: "0x1") { @@ -2030,8 +1949,7 @@ } } -- - name: "queryHuman which implements an interface" +- name: "queryHuman which implements an interface" gqlquery: | query { queryHuman { @@ -2053,8 +1971,7 @@ } } -- - name: "Get Query on interface whose implementation contains Auth rules." +- name: "Get Query on interface whose implementation contains Auth rules." gqlquery: | query { getX(id: "0x1") { @@ -2067,8 +1984,7 @@ getX() } -- - name: "Query on interface whose implementation contains Auth rules." +- name: "Query on interface whose implementation contains Auth rules." gqlquery: | query { queryX { @@ -2081,8 +1997,7 @@ queryX() } -- - name: "filter with order for type which implements an interface" +- name: "filter with order for type which implements an interface" gqlquery: | query { queryHuman (filter: { name: { anyofterms: "GraphQL" } }, order: { asc: ename }) { @@ -2102,8 +2017,7 @@ } } -- - name: "queryCharacter with fragment for human" +- name: "queryCharacter with fragment for human" gqlquery: | query { queryCharacter { @@ -2126,8 +2040,7 @@ } } -- - name: "queryCharacter with fragment on multiple types" +- name: "queryCharacter with fragment on multiple types" gqlquery: | query { queryCharacter { @@ -2154,8 +2067,7 @@ } } -- - name: "fragment on interface implemented by type which implements multiple interfaces in query on some other interface" +- name: "fragment on interface implemented by type which implements multiple interfaces in query on some other interface" gqlquery: | query { queryCharacter { @@ -2180,8 +2092,7 @@ } } -- - name: "Filter with id uses uid func at root." +- name: "Filter with id uses uid func at root." gqlquery: | query { queryAuthor(filter: { id: ["0x1", "0x2"], and: { name: { eq: "A. N. Author" } }}) { @@ -2196,8 +2107,7 @@ } } -- - name: "Between filter" +- name: "Between filter" gqlquery: | query { queryPost(filter: { numLikes: { between : { min :10, max: 20 }}}) { @@ -2214,8 +2124,7 @@ } } -- - name: "deep Between filter" +- name: "deep Between filter" gqlquery: | query{ queryAuthor(filter: {reputation: {between: {min:6.0, max: 7.2}}}){ @@ -2241,8 +2150,7 @@ } } -- - name: "Filter with id inside and argument doesn't use uid func at root." +- name: "Filter with id inside and argument doesn't use uid func at root." gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" }, and: { id: ["0x1", "0x2"] }}) { @@ -2257,8 +2165,7 @@ } } -- - name: "Filter with id and not translates correctly.." +- name: "Filter with id and not translates correctly.." gqlquery: | query { queryAuthor(filter: { not: { id: ["0x1", "0x2"] }}) { @@ -2273,8 +2180,7 @@ } } -- - name: "Deep filter with id" +- name: "Deep filter with id" gqlquery: | query { queryAuthor { @@ -2296,8 +2202,7 @@ } } -- - name: "Deep filter with id in not key" +- name: "Deep filter with id in not key" gqlquery: | query { queryAuthor { @@ -2319,8 +2224,7 @@ } } -- - name: "Pagination and Order at root node with UID." +- name: "Pagination and Order at root node with UID." gqlquery: | query { queryAuthor(filter: { id: ["0x1", "0x2"] }, order: {asc: name}, first: 0, offset: 1 ) { @@ -2335,8 +2239,7 @@ } } -- - name: "Order at root node with UID." +- name: "Order at root node with UID." gqlquery: | query { queryAuthor(filter: { id: ["0x1", "0x2"] }, order: {asc: name}) { @@ -2351,8 +2254,7 @@ } } -- - name: "Order at root node without UID." +- name: "Order at root node without UID." gqlquery: | query { queryAuthor(order: {asc: name}) { @@ -2367,8 +2269,7 @@ } } -- - name: "Order and Pagination at root node without UID." +- name: "Order and Pagination at root node without UID." gqlquery: | query { queryAuthor(order: {asc: name}, first: 2, offset: 3) { @@ -2384,8 +2285,7 @@ } -- - name: "Filter with no valid id construct the right query with type func at root." +- name: "Filter with no valid id construct the right query with type func at root." gqlquery: | query { queryAuthor(filter: { id: ["alice", "bob"], and: { name: { eq: "A. N. Author" } }}) { @@ -2400,8 +2300,7 @@ } } -- - name: "Filter with id only includes valid id in dgquery." +- name: "Filter with id only includes valid id in dgquery." gqlquery: | query { queryAuthor(filter: { id: ["0x1", "bob"], and: { name: { eq: "A. N. Author" } }}) { @@ -2416,8 +2315,7 @@ } } -- - name: "Get editor without supplying anything" +- name: "Get editor without supplying anything" gqlquery: | query { getEditor { @@ -2432,8 +2330,7 @@ } } -- - name: "Get editor using code" +- name: "Get editor using code" gqlquery: | query { getEditor(code: "tolstoy") { @@ -2448,8 +2345,7 @@ } } -- - name: "Get editor using both code and id" +- name: "Get editor using both code and id" gqlquery: | query { getEditor(code: "tolstoy", id: "0x1") { @@ -2464,8 +2360,7 @@ } } -- - name: "Get with XID where no ID in type" +- name: "Get with XID where no ID in type" gqlquery: | query { getState(code: "NSW") { @@ -2480,8 +2375,7 @@ } } -- - name: "Query editor using code" +- name: "Query editor using code" gqlquery: | query { queryEditor(filter: { code: { eq: "editor" }, and: { name: { eq: "A. N. Editor" }}}) { @@ -2496,8 +2390,7 @@ } } -- - name: "Query editor using code and uid" +- name: "Query editor using code and uid" gqlquery: | query { queryEditor(filter: { id: ["0x1"], and: { code: { eq: "editor"}}}) { @@ -2512,8 +2405,7 @@ } } -- - name: "Query along reverse edge is converted appropriately" +- name: "Query along reverse edge is converted appropriately" gqlquery: | query { queryMovie { @@ -2535,8 +2427,7 @@ } } -- - name: "deprecated fields can be queried" +- name: "deprecated fields can be queried" gqlquery: | query { queryCategory { @@ -2552,8 +2443,7 @@ } } -- - name: "Password query" +- name: "Password query" gqlquery: | query { checkUserPassword(name: "user1", pwd: "Password") { @@ -2571,8 +2461,7 @@ } } -- - name: "Password query with alias" +- name: "Password query with alias" gqlquery: | query { verify : checkUserPassword(name: "user1", pwd: "Password") { @@ -2749,8 +2638,7 @@ dgraph.uid : uid } } -- - name: "querying a non-inbuiltType field multiple times with different aliases should reflect in rewriting" +- name: "querying a non-inbuiltType field multiple times with different aliases should reflect in rewriting" gqlquery: |- query { queryAuthor { @@ -2783,8 +2671,7 @@ } } -- - name: "querying field multiple times with different aliases and same filters" +- name: "querying field multiple times with different aliases and same filters" gqlquery: |- query { queryAuthor { @@ -2816,8 +2703,7 @@ dgraph.uid : uid } } -- - name: "Query with Same Alias" +- name: "Query with Same Alias" gqlquery: |- query { queryAuthor { @@ -2844,8 +2730,7 @@ dgraph.uid : uid } } -- - name: "Aggregate Query with multiple aliases" +- name: "Aggregate Query with multiple aliases" gqlquery: | query{ queryAuthor{ @@ -3124,8 +3009,7 @@ } } -- - name: "Count query at child level" +- name: "Count query at child level" gqlquery: | query { queryCountry { @@ -3144,8 +3028,7 @@ } } -- - name: "Aggregate query at child level with filter and multiple aggregate fields" +- name: "Aggregate query at child level with filter and multiple aggregate fields" gqlquery: | query { queryCountry { @@ -3189,8 +3072,7 @@ } } -- - name: "Count query at child level with filter" +- name: "Count query at child level with filter" gqlquery: | query { queryCountry { @@ -3216,8 +3098,7 @@ } } -- - name: "Deep child level get query with count" +- name: "Deep child level get query with count" gqlquery: | query { getAuthor(id: "0x1") { @@ -3241,8 +3122,7 @@ } } -- - name: "Aggregate Query with Sum and Avg" +- name: "Aggregate Query with Sum and Avg" gqlquery: | query { aggregateTweets() { @@ -3268,14 +3148,13 @@ } } -- - name: "query using single ID in filter" +- name: "query using single ID in filter" gqlquery: | - query { - queryAuthor(filter:{id: "0x1"}) { - name - } + query { + queryAuthor(filter:{id: "0x1"}) { + name } + } dgquery: |- query { queryAuthor(func: uid(0x1)) @filter(type(Author)) { @@ -3284,8 +3163,7 @@ } } -- - name: "entities query for extended type having @key field of ID type" +- name: "entities query for extended type having @key field of ID type" gqlquery: | query { _entities(representations: [{__typename: "Astronaut", id: "0x1" },{__typename: "Astronaut", id: "0x2" }]) { @@ -3296,7 +3174,7 @@ } } } - dgquery: |- + dgquery: |- query { _entities(func: eq(Astronaut.id, "0x1", "0x2"), orderasc: Astronaut.id) @filter(type(Astronaut)) { dgraph.type @@ -3308,8 +3186,7 @@ } } -- - name: "entities query for extended type having @key field of string type with @id directive" +- name: "entities query for extended type having @key field of string type with @id directive" gqlquery: | query { _entities(representations: [{__typename: "SpaceShip", id: "0x1" },{__typename: "SpaceShip", id: "0x2" }]) { @@ -3320,7 +3197,7 @@ } } } - dgquery: |- + dgquery: |- query { _entities(func: eq(SpaceShip.id, "0x1", "0x2"), orderasc: SpaceShip.id) @filter(type(SpaceShip)) { dgraph.type @@ -3332,8 +3209,7 @@ } } -- - name: "get query with multiple @id and an ID field" +- name: "get query with multiple @id and an ID field" gqlquery: | query { getBook(id: "0x1", title: "GraphQL", ISBN: "001HB") { @@ -3358,8 +3234,7 @@ } } -- - name: "get query with multiple @id fields " +- name: "get query with multiple @id fields " gqlquery: | query { getBook(title: "GraphQL", ISBN: "001HB") { @@ -3431,3 +3306,27 @@ dgraph.uid : uid } } + +- name: "get query on interface with @id field having interface argument set" + gqlquery: | + query { + getMember(refID: "101") { + refID + name + fineAccumulated + ... on SportsMember { + plays + } + } + } + dgquery: |- + query { + getMember(func: eq(Member.refID, "101")) @filter(type(Member)) { + dgraph.type + Member.refID : Member.refID + Member.name : Member.name + Member.fineAccumulated : Member.fineAccumulated + SportsMember.plays : SportsMember.plays + dgraph.uid : uid + } + } diff --git a/graphql/resolve/schema.graphql b/graphql/resolve/schema.graphql index 986010dff8c..cc5ffa25db9 100644 --- a/graphql/resolve/schema.graphql +++ b/graphql/resolve/schema.graphql @@ -10,169 +10,169 @@ type Hotel { } type Country { - id: ID! - name: String! @search(by: [trigram, exact]) - states: [State] @hasInverse(field: country) + id: ID! + name: String! @search(by: [trigram, exact]) + states: [State] @hasInverse(field: country) } type State { - code: String! @id - country: Country - name: String! - capital: String + code: String! @id + country: Country + name: String! + capital: String } type Author { - id: ID! - name: String! @search(by: [hash]) - dob: DateTime @search - reputation: Float @search - country: Country - posts: [Post!] @hasInverse(field: author) + id: ID! + name: String! @search(by: [hash]) + dob: DateTime @search + reputation: Float @search + country: Country + posts: [Post!] @hasInverse(field: author) } type Editor { - id: ID! - code: String! @id - name: String! @search(by: [hash]) + id: ID! + code: String! @id + name: String! @search(by: [hash]) } type Post { - postID: ID! - title: String! @search(by: [term]) - text: String @search(by: [fulltext]) - tags: [String] @search(by: [exact]) - numLikes: Int @search - isPublished: Boolean @search - postType: [PostType] @search - author: Author! - category: Category @hasInverse(field: posts) - comments: [Comment] - ps: PostSecret + postID: ID! + title: String! @search(by: [term]) + text: String @search(by: [fulltext]) + tags: [String] @search(by: [exact]) + numLikes: Int @search + isPublished: Boolean @search + postType: [PostType] @search + author: Author! + category: Category @hasInverse(field: posts) + comments: [Comment] + ps: PostSecret } type PostSecret { - title: String! @id + title: String! @id } type Category { - id: ID - name: String - posts: [Post] - iAmDeprecated: String @deprecated(reason: "because") + id: ID + name: String + posts: [Post] + iAmDeprecated: String @deprecated(reason: "because") } enum PostType { - Fact - Question - Opinion + Fact + Question + Opinion } interface Character { - id: ID! - name: String! @search + id: ID! + name: String! @search } interface Employee { - ename: String! + ename: String! } type Director implements Character { - movies: [String!] + movies: [String!] } type Human implements Character & Employee { - dob: DateTime - female: Boolean + dob: DateTime + female: Boolean } # just for testing filters on enum types type Verification { - name: String @search(by: [exact]) - status: [Status!]! @search(by: [exact]) - prevStatus: Status! @search + name: String @search(by: [exact]) + status: [Status!]! @search(by: [exact]) + prevStatus: Status! @search } enum Status { - ACTIVE - INACTIVE - DEACTIVATED + ACTIVE + INACTIVE + DEACTIVATED } # just for testing singluar (non-list) edges in both directions type House { - id: ID! - owner: Owner @hasInverse(field: house) + id: ID! + owner: Owner @hasInverse(field: house) } type Owner { - id: ID! - house: House + id: ID! + house: House } # for testing ~reverse predicate in @dgraph directive type Movie { - id: ID! - name: String! - director: [MovieDirector] @dgraph(pred: "~directed.movies") + id: ID! + name: String! + director: [MovieDirector] @dgraph(pred: "~directed.movies") } type MovieDirector { - id: ID! - name: String! - directed: [Movie] @dgraph(pred: "directed.movies") + id: ID! + name: String! + directed: [Movie] @dgraph(pred: "directed.movies") } type Lab { - name: String! @id - computers: [Computer] + name: String! @id + computers: [Computer] } # just for testing XID remove in list type Computer { - owners: [ComputerOwner!] - name: String! @id + owners: [ComputerOwner!] + name: String! @id } type ComputerOwner { - name: String! @id - nickName: String - computers: Computer! @hasInverse(field: owners) + name: String! @id + nickName: String + computers: Computer! @hasInverse(field: owners) } type User @secret(field: "pwd") { - name: String! @id + name: String! @id } # For testing duplicate XID in single mutation type District { - code: String! @id - name: String! - cities: [City] @hasInverse(field: district) + code: String! @id + name: String! + cities: [City] @hasInverse(field: district) } type City { - id: ID! - name: String! - district: District + id: ID! + name: String! + district: District } # For testing duplicate XID in single mutation for interface interface People { - id: ID! - xid: String! @id - name: String! + id: ID! + xid: String! @id + name: String! } type Teacher implements People { - subject: String - teaches: [Student] + subject: String + teaches: [Student] } type Student implements People { - taughtBy: [Teacher] @hasInverse(field: "teaches") + taughtBy: [Teacher] @hasInverse(field: "teaches") } type Comment { @@ -180,64 +180,64 @@ type Comment { author: String! title: String content: String @custom(http: { - url: "http://api-gateway.com/post/", - method: "GET", - operation: "batch", - body: "{ myId: $id, theAuthor: $author}", - forwardHeaders: ["X-App-Token"]}) + url: "http://api-gateway.com/post/", + method: "GET", + operation: "batch", + body: "{ myId: $id, theAuthor: $author}", + forwardHeaders: ["X-App-Token"]}) url: String! @id ups: Int! downs: Int relatedUsers: [User] @custom(http: { - url: "http://api-gateway.com/relatedPosts", - method: "POST", - operation: "single", - body: "{ myId: $url }"}) + url: "http://api-gateway.com/relatedPosts", + method: "POST", + operation: "single", + body: "{ myId: $url }"}) } type Query { - myFavoriteMovies(id: ID!, name: String!, num: Int): [Movie] @custom(http: { - url: "http://myapi.com/favMovies/$id?name=$name&num=$num", - method: "GET" - }) + myFavoriteMovies(id: ID!, name: String!, num: Int): [Movie] @custom(http: { + url: "http://myapi.com/favMovies/$id?name=$name&num=$num", + method: "GET" + }) - myFavoriteMoviesPart2(id: ID!, name: String!, num: Int): [Movie] @custom(http: { - url: "http://myapi.com/favMovies/$id?name=$name&num=$num", - method: "POST", - body: "{ id: $id, name: $name, director: { number: $num }}", - forwardHeaders: ["X-App-Token", "Auth0-token"] - }) + myFavoriteMoviesPart2(id: ID!, name: String!, num: Int): [Movie] @custom(http: { + url: "http://myapi.com/favMovies/$id?name=$name&num=$num", + method: "POST", + body: "{ id: $id, name: $name, director: { number: $num }}", + forwardHeaders: ["X-App-Token", "Auth0-token"] + }) } input MovieDirectorInput { - id: ID - name: String - directed: [MovieInput] + id: ID + name: String + directed: [MovieInput] } input MovieInput { - id: ID - name: String - director: [MovieDirectorInput] + id: ID + name: String + director: [MovieDirectorInput] } type Mutation { - createMyFavouriteMovies(input: [MovieInput!]): [Movie] @custom(http: { - url: "http://myapi.com/favMovies", - method: "POST", - body: "{ movies: $input}", - forwardHeaders: ["X-App-Token", "Auth0-token"] - }) - updateMyFavouriteMovie(id: ID!, input: MovieInput!): Movie @custom(http: { - url: "http://myapi.com/favMovies/$id", - method: "PATCH", - body: "{ movie: $input}" - }) - deleteMyFavouriteMovie(id: ID!): Movie @custom(http: { - url: "http://myapi.com/favMovies/$id", - method: "DELETE" - }) + createMyFavouriteMovies(input: [MovieInput!]): [Movie] @custom(http: { + url: "http://myapi.com/favMovies", + method: "POST", + body: "{ movies: $input}", + forwardHeaders: ["X-App-Token", "Auth0-token"] + }) + updateMyFavouriteMovie(id: ID!, input: MovieInput!): Movie @custom(http: { + url: "http://myapi.com/favMovies/$id", + method: "PATCH", + body: "{ movie: $input}" + }) + deleteMyFavouriteMovie(id: ID!): Movie @custom(http: { + url: "http://myapi.com/favMovies/$id", + method: "DELETE" + }) } type Message { @@ -252,33 +252,33 @@ interface X { } type Y implements X @auth( query: { rule: """ - query($USER: String!) { - queryY(filter: { username: { eq: $USER } }) { - __typename - } - } + query($USER: String!) { + queryY(filter: { username: { eq: $USER } }) { + __typename + } + } """ } ){ - userRole: String @search(by: [hash]) + userRole: String @search(by: [hash]) } type Post1 { - id: String! @id - content: String - comments: [Comment1] + id: String! @id + content: String + comments: [Comment1] } type Person1 { - id: String! @id - friends: [Person1] @hasInverse(field: friends) - closeFriends: [Person1] @hasInverse(field: closeFriends) - name: String! @id + id: String! @id + friends: [Person1] @hasInverse(field: friends) + closeFriends: [Person1] @hasInverse(field: closeFriends) + name: String! @id } type Comment1 { - id: String! @id - message: String - replies: [Comment1] + id: String! @id + message: String + replies: [Comment1] } type Tweets { @@ -460,3 +460,35 @@ type LinkZ { f7: String! @id f4: [LinkX] @dgraph(pred: "link") } + +interface Member { + refID: String! @id (interface:true) + name: String! @id + itemsIssued: [String] + fineAccumulated: Int +} + +interface Team { + teamID: String! @id (interface:true) + teamName: String! @id +} + +type LibraryMember implements Member { + interests: [String] + readHours: String +} + +type SportsMember implements Member & Team { + plays: String + playerRating: Int +} + +type CricketTeam implements Team { + numOfBatsmans: Int + numOfBowlers: Int +} + +type LibraryManager { + name: String! @id + manages: [LibraryMember] +} \ No newline at end of file diff --git a/graphql/schema/gqlschema.go b/graphql/schema/gqlschema.go index 920096aacd5..c2398514260 100644 --- a/graphql/schema/gqlschema.go +++ b/graphql/schema/gqlschema.go @@ -39,6 +39,7 @@ const ( dgraphPredArg = "pred" idDirective = "id" + idDirectiveInterfaceArg = "interface" subscriptionDirective = "withSubscription" secretDirective = "secret" authDirective = "auth" @@ -276,7 +277,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( @@ -307,7 +308,7 @@ directive @generate( directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM @@ -1968,18 +1969,14 @@ func addGetQuery(schema *ast.Schema, defn *ast.Definition, providesTypeMap map[s }, }) } + if hasXIDField { - if defn.Kind == "INTERFACE" { - qry.Directives = append( - qry.Directives, &ast.Directive{Name: deprecatedDirective, - Arguments: ast.ArgumentList{&ast.Argument{Name: "reason", - Value: &ast.Value{Raw: "@id argument for get query on interface is being deprecated, " + - "it will be removed in v21.11.0, " + - "please update your query to not use that argument", - Kind: ast.StringValue}}}}) - } + var idWithoutUniqueArgExists bool for _, fld := range defn.Fields { if hasIDDirective(fld) { + if !hasInterfaceArg(fld) { + idWithoutUniqueArgExists = true + } qry.Arguments = append(qry.Arguments, &ast.ArgumentDefinition{ Name: fld.Name, Type: &ast.Type{ @@ -1989,6 +1986,16 @@ func addGetQuery(schema *ast.Schema, defn *ast.Definition, providesTypeMap map[s }) } } + if defn.Kind == "INTERFACE" && idWithoutUniqueArgExists { + qry.Directives = append( + qry.Directives, &ast.Directive{Name: deprecatedDirective, + Arguments: ast.ArgumentList{&ast.Argument{Name: "reason", + Value: &ast.Value{Raw: "@id argument for get query on interface is being" + + " deprecated. Only those @id fields which have interface argument" + + " set to true will be available in getQuery argument on interface" + + " post v21.11.0, please update your schema accordingly.", + Kind: ast.StringValue}}}}) + } } schema.Query.Fields = append(schema.Query.Fields, qry) subs := defn.Directives.ForName(subscriptionDirective) diff --git a/graphql/schema/gqlschema_test.yml b/graphql/schema/gqlschema_test.yml index ba435b69366..a7458e1d616 100644 --- a/graphql/schema/gqlschema_test.yml +++ b/graphql/schema/gqlschema_test.yml @@ -2889,6 +2889,18 @@ invalid_schemas: { "message": "Type TwitterUser; @lambdaOnMutate directive not allowed along with @remote directive.", "locations": [{"line": 1, "column": 27}]} ] + - name: "@id field can't have interface argument when it's defined inside a type" + input: | + type Person { + name: String! @id(interface:true) + age: Int + } + errlist: [ + { "message": "Type Person; Field name: @id field with interface argument + can only be defined in interface,not in Type", + "locations": [ { "line": 2, "column": 18 } ] }, + ] + valid_schemas: - name: "Multiple fields with @id directive should be allowed" input: | @@ -3379,3 +3391,37 @@ valid_schemas: type Z { f4: [X] @dgraph(pred: "link") } + + - name: "valid schema with @id directive having interface argument in interface" + input: | + interface Member { + refID: String! @id(interface: true) + name: String! @id + itemsIssued: [String] + fineAccumulated: Int + } + + interface Team { + teamID: String! @id(interface: true) + teamName: String! @id + } + + type LibraryMember implements Member { + interests: [String] + readHours: String + } + + type SportsMember implements Member & Team { + plays: String + playerRating: Int + } + + type CricketTeam implements Team { + numOfBatsmans: Int + numOfBowlers: Int + } + + type LibraryManager { + name: String! @id + manages: [LibraryMember] + } diff --git a/graphql/schema/rules.go b/graphql/schema/rules.go index f835a2e4d8e..c6979234b6e 100644 --- a/graphql/schema/rules.go +++ b/graphql/schema/rules.go @@ -2074,12 +2074,26 @@ func idValidation(sch *ast.Schema, if field.Type.String() == "String!" || field.Type.String() == "Int!" || field.Type.String() == "Int64!" { + + var inherited bool + for _, implements := range sch.Implements[typ.Name] { + if implements.Fields.ForName(field.Name) != nil { + inherited = true + } + } + if typ.Kind != "INTERFACE" && hasInterfaceArg(field) && !inherited { + return []*gqlerror.Error{gqlerror.ErrorPosf( + dir.Position, + "Type %s; Field %s: @id field with interface argument can only be defined"+ + " in interface,not in Type", typ.Name, field.Name)} + } return nil } return []*gqlerror.Error{gqlerror.ErrorPosf( dir.Position, "Type %s; Field %s: with @id directive must be of type String!, Int! or Int64!, not %s", typ.Name, field.Name, field.Type.String())} + } func apolloKeyValidation(sch *ast.Schema, typ *ast.Definition) gqlerror.List { diff --git a/graphql/schema/testdata/apolloservice/output/auth-directive.graphql b/graphql/schema/testdata/apolloservice/output/auth-directive.graphql index e92aef33b21..1d3488c0b27 100644 --- a/graphql/schema/testdata/apolloservice/output/auth-directive.graphql +++ b/graphql/schema/testdata/apolloservice/output/auth-directive.graphql @@ -192,7 +192,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM diff --git a/graphql/schema/testdata/apolloservice/output/custom-directive.graphql b/graphql/schema/testdata/apolloservice/output/custom-directive.graphql index 137953258e2..0ffe521b151 100644 --- a/graphql/schema/testdata/apolloservice/output/custom-directive.graphql +++ b/graphql/schema/testdata/apolloservice/output/custom-directive.graphql @@ -184,7 +184,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM diff --git a/graphql/schema/testdata/apolloservice/output/extended-types.graphql b/graphql/schema/testdata/apolloservice/output/extended-types.graphql index 852620a45fb..31024cd9323 100644 --- a/graphql/schema/testdata/apolloservice/output/extended-types.graphql +++ b/graphql/schema/testdata/apolloservice/output/extended-types.graphql @@ -198,7 +198,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM diff --git a/graphql/schema/testdata/apolloservice/output/generate-directive.graphql b/graphql/schema/testdata/apolloservice/output/generate-directive.graphql index 9330e483219..fd38dab07b9 100644 --- a/graphql/schema/testdata/apolloservice/output/generate-directive.graphql +++ b/graphql/schema/testdata/apolloservice/output/generate-directive.graphql @@ -194,7 +194,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM diff --git a/graphql/schema/testdata/apolloservice/output/single-extended-type.graphql b/graphql/schema/testdata/apolloservice/output/single-extended-type.graphql index acf1c5611e8..0704e8fd6f3 100644 --- a/graphql/schema/testdata/apolloservice/output/single-extended-type.graphql +++ b/graphql/schema/testdata/apolloservice/output/single-extended-type.graphql @@ -179,7 +179,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM diff --git a/graphql/schema/testdata/schemagen/input/interface-with-id-directive.graphql b/graphql/schema/testdata/schemagen/input/interface-with-id-directive.graphql index 13cfc0547c8..6520d123ec7 100644 --- a/graphql/schema/testdata/schemagen/input/interface-with-id-directive.graphql +++ b/graphql/schema/testdata/schemagen/input/interface-with-id-directive.graphql @@ -1,5 +1,6 @@ interface LibraryItem { - refID: String! @id + refID: String! @id(interface:false) + itemID: String! @id(interface:true) } type Book implements LibraryItem { diff --git a/graphql/schema/testdata/schemagen/output/apollo-federation.graphql b/graphql/schema/testdata/schemagen/output/apollo-federation.graphql index 9b2891e8d03..91642aebb01 100644 --- a/graphql/schema/testdata/schemagen/output/apollo-federation.graphql +++ b/graphql/schema/testdata/schemagen/output/apollo-federation.graphql @@ -214,7 +214,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/auth-on-interfaces.graphql b/graphql/schema/testdata/schemagen/output/auth-on-interfaces.graphql index f8451784789..ea87104b74f 100644 --- a/graphql/schema/testdata/schemagen/output/auth-on-interfaces.graphql +++ b/graphql/schema/testdata/schemagen/output/auth-on-interfaces.graphql @@ -196,7 +196,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/authorization.graphql b/graphql/schema/testdata/schemagen/output/authorization.graphql index 97ec90740b4..8d8a2da3d46 100644 --- a/graphql/schema/testdata/schemagen/output/authorization.graphql +++ b/graphql/schema/testdata/schemagen/output/authorization.graphql @@ -192,7 +192,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql b/graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql index 08987e9adb8..cf11ecc3856 100755 --- a/graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql +++ b/graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql @@ -205,7 +205,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/custom-dql-query-with-subscription.graphql b/graphql/schema/testdata/schemagen/output/custom-dql-query-with-subscription.graphql index a3fc18824ff..a6c89246ef5 100755 --- a/graphql/schema/testdata/schemagen/output/custom-dql-query-with-subscription.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-dql-query-with-subscription.graphql @@ -193,7 +193,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/custom-mutation.graphql b/graphql/schema/testdata/schemagen/output/custom-mutation.graphql index cf2bc952534..1b48735de16 100644 --- a/graphql/schema/testdata/schemagen/output/custom-mutation.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-mutation.graphql @@ -183,7 +183,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/custom-nested-types.graphql b/graphql/schema/testdata/schemagen/output/custom-nested-types.graphql index 33695d23be3..7184d90e5a8 100755 --- a/graphql/schema/testdata/schemagen/output/custom-nested-types.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-nested-types.graphql @@ -200,7 +200,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/custom-query-mixed-types.graphql b/graphql/schema/testdata/schemagen/output/custom-query-mixed-types.graphql index a38f3ea8bbf..d441721cf27 100644 --- a/graphql/schema/testdata/schemagen/output/custom-query-mixed-types.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-query-mixed-types.graphql @@ -184,7 +184,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql b/graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql index c605b43a8e7..bbc666a87e1 100755 --- a/graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql @@ -183,7 +183,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql b/graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql index c19a37615cd..a38c50561d8 100755 --- a/graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql @@ -179,7 +179,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/deprecated.graphql b/graphql/schema/testdata/schemagen/output/deprecated.graphql index df19d26c92a..52cb4ea15d4 100755 --- a/graphql/schema/testdata/schemagen/output/deprecated.graphql +++ b/graphql/schema/testdata/schemagen/output/deprecated.graphql @@ -179,7 +179,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql b/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql index 718771ade43..b1af0955121 100755 --- a/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql +++ b/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql @@ -196,7 +196,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql b/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql index 86c2966c11f..44da4eec782 100755 --- a/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql +++ b/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql @@ -196,7 +196,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql b/graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql index 9157745c0b7..47653c74222 100755 --- a/graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql @@ -193,7 +193,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/field-with-multiple-@id-fields.graphql b/graphql/schema/testdata/schemagen/output/field-with-multiple-@id-fields.graphql index 0ede01e438a..d8b93ef7b08 100755 --- a/graphql/schema/testdata/schemagen/output/field-with-multiple-@id-fields.graphql +++ b/graphql/schema/testdata/schemagen/output/field-with-multiple-@id-fields.graphql @@ -193,7 +193,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql b/graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql index 3316b65d97b..f2145184ee6 100755 --- a/graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql @@ -188,7 +188,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-all-empty.graphql b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-all-empty.graphql index 3650c045774..64d88e15dd9 100644 --- a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-all-empty.graphql +++ b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-all-empty.graphql @@ -191,7 +191,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-circular.graphql b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-circular.graphql index 001091f2c1f..fd2ae029d78 100644 --- a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-circular.graphql +++ b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-circular.graphql @@ -195,7 +195,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-custom-mutation.graphql b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-custom-mutation.graphql index b72b0056457..480d42cc906 100644 --- a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-custom-mutation.graphql +++ b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-custom-mutation.graphql @@ -183,7 +183,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-directLink.graphql b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-directLink.graphql index a6848b2e499..ce22f674d5a 100644 --- a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-directLink.graphql +++ b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-directLink.graphql @@ -193,7 +193,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/generate-directive.graphql b/graphql/schema/testdata/schemagen/output/generate-directive.graphql index e5ed4c5b2ba..046c376fd08 100644 --- a/graphql/schema/testdata/schemagen/output/generate-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/generate-directive.graphql @@ -194,7 +194,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/geo-type.graphql b/graphql/schema/testdata/schemagen/output/geo-type.graphql index 858da149138..7b007dbea08 100644 --- a/graphql/schema/testdata/schemagen/output/geo-type.graphql +++ b/graphql/schema/testdata/schemagen/output/geo-type.graphql @@ -185,7 +185,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql b/graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql index 724e975f5e3..2a6e74d672c 100755 --- a/graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql @@ -204,7 +204,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql b/graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql index 68d762315a4..7262833b462 100755 --- a/graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql @@ -206,7 +206,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql b/graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql index 724e975f5e3..2a6e74d672c 100755 --- a/graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql @@ -204,7 +204,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/hasInverse.graphql b/graphql/schema/testdata/schemagen/output/hasInverse.graphql index ebf0fac6ef3..e550998e55a 100755 --- a/graphql/schema/testdata/schemagen/output/hasInverse.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse.graphql @@ -185,7 +185,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql b/graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql index 8106fa755a4..08cebd56299 100755 --- a/graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql @@ -185,7 +185,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/hasfilter.graphql b/graphql/schema/testdata/schemagen/output/hasfilter.graphql index 07084830a4c..e5adcc1acc6 100644 --- a/graphql/schema/testdata/schemagen/output/hasfilter.graphql +++ b/graphql/schema/testdata/schemagen/output/hasfilter.graphql @@ -187,7 +187,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql b/graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql index 8b401ca912b..0d50f4fcb38 100755 --- a/graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql @@ -186,7 +186,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/interface-with-dgraph-pred.graphql b/graphql/schema/testdata/schemagen/output/interface-with-dgraph-pred.graphql index 1bd51e0ccb5..a604dd070c5 100644 --- a/graphql/schema/testdata/schemagen/output/interface-with-dgraph-pred.graphql +++ b/graphql/schema/testdata/schemagen/output/interface-with-dgraph-pred.graphql @@ -195,7 +195,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql b/graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql index 7efd61c5558..db2e3d3be50 100755 --- a/graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql @@ -3,11 +3,13 @@ ####################### interface LibraryItem { - refID: String! @id + refID: String! @id(interface: false) + itemID: String! @id(interface: true) } type Book implements LibraryItem { - refID: String! @id + refID: String! @id(interface: false) + itemID: String! @id(interface: true) title: String author: String } @@ -189,7 +191,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( @@ -297,6 +299,8 @@ type BookAggregateResult { count: Int refIDMin: String refIDMax: String + itemIDMin: String + itemIDMax: String titleMin: String titleMax: String authorMin: String @@ -329,6 +333,8 @@ type LibraryItemAggregateResult { count: Int refIDMin: String refIDMax: String + itemIDMin: String + itemIDMax: String } type UpdateBookPayload { @@ -347,12 +353,14 @@ type UpdateLibraryPayload { enum BookHasFilter { refID + itemID title author } enum BookOrderable { refID + itemID title author } @@ -363,10 +371,12 @@ enum LibraryHasFilter { enum LibraryItemHasFilter { refID + itemID } enum LibraryItemOrderable { refID + itemID } ####################### @@ -375,6 +385,7 @@ enum LibraryItemOrderable { input AddBookInput { refID: String! + itemID: String! title: String author: String } @@ -385,6 +396,7 @@ input AddLibraryInput { input BookFilter { refID: StringHashFilter + itemID: StringHashFilter has: [BookHasFilter] and: [BookFilter] or: [BookFilter] @@ -404,6 +416,7 @@ input BookPatch { input BookRef { refID: String + itemID: String title: String author: String } @@ -417,6 +430,7 @@ input LibraryFilter { input LibraryItemFilter { refID: StringHashFilter + itemID: StringHashFilter has: [LibraryItemHasFilter] and: [LibraryItemFilter] or: [LibraryItemFilter] @@ -458,10 +472,10 @@ input UpdateLibraryInput { ####################### type Query { - getLibraryItem(refID: String!): LibraryItem @deprecated(reason: "@id argument for get query on interface is being deprecated, it will be removed in v21.11.0, please update your query to not use that argument") + getLibraryItem(refID: String, itemID: String): LibraryItem @deprecated(reason: "@id argument for get query on interface is being deprecated. Only those @id fields which have interface argument set to true will be available in getQuery argument on interface post v21.11.0, please update your schema accordingly.") queryLibraryItem(filter: LibraryItemFilter, order: LibraryItemOrder, first: Int, offset: Int): [LibraryItem] aggregateLibraryItem(filter: LibraryItemFilter): LibraryItemAggregateResult - getBook(refID: String!): Book + getBook(refID: String, itemID: String): Book queryBook(filter: BookFilter, order: BookOrder, first: Int, offset: Int): [Book] aggregateBook(filter: BookFilter): BookAggregateResult queryLibrary(filter: LibraryFilter, first: Int, offset: Int): [Library] diff --git a/graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql b/graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql index e50390d6bc7..90161b27860 100755 --- a/graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql +++ b/graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql @@ -189,7 +189,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql b/graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql index 615784ab6d6..2314846daa9 100755 --- a/graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql +++ b/graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql @@ -214,7 +214,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql b/graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql index 224f014b327..53da87263b2 100755 --- a/graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql +++ b/graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql @@ -214,7 +214,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/lambda-directive.graphql b/graphql/schema/testdata/schemagen/output/lambda-directive.graphql index 41747a6769a..8df251f64a7 100644 --- a/graphql/schema/testdata/schemagen/output/lambda-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/lambda-directive.graphql @@ -181,7 +181,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/language-tags.graphql b/graphql/schema/testdata/schemagen/output/language-tags.graphql new file mode 100755 index 00000000000..e217582cce7 --- /dev/null +++ b/graphql/schema/testdata/schemagen/output/language-tags.graphql @@ -0,0 +1,517 @@ +####################### +# Input Schema +####################### + +interface Node { + f1: String +} + +type Person implements Node { + f1: String + f1Hi: String @dgraph(pred: "Node.f1@hi") + f2: String @dgraph(pred: "T.f@no") + f3: String @dgraph(pred: "f3@en") + name: String! @id + nameHi: String @dgraph(pred: "Person.name@hi") @search(by: [term,exact]) + nameEn: String @dgraph(pred: "Person.name@en") @search(by: [regexp]) + nameHiEn: String @dgraph(pred: "Person.name@hi:en") + nameHi_En_Untag: String @dgraph(pred: "Person.name@hi:en:.") + name_Untag_AnyLang: String @dgraph(pred: "Person.name@.") + address: String @search(by: [fulltext]) + addressHi: String @dgraph(pred: "Person.address@hi") + professionEn: String @dgraph(pred: "Person.profession@en") +} + +####################### +# Extended Definitions +####################### + +""" +The Int64 scalar type represents a signed 64‐bit numeric non‐fractional value. +Int64 can represent values in range [-(2^63),(2^63 - 1)]. +""" +scalar Int64 + +""" +The DateTime scalar type represents date and time as a string in RFC3339 format. +For example: "1985-04-12T23:20:50.52Z" represents 20 minutes and 50.52 seconds after the 23rd hour of April 12th, 1985 in UTC. +""" +scalar DateTime + +input IntRange{ + min: Int! + max: Int! +} + +input FloatRange{ + min: Float! + max: Float! +} + +input Int64Range{ + min: Int64! + max: Int64! +} + +input DateTimeRange{ + min: DateTime! + max: DateTime! +} + +input StringRange{ + min: String! + max: String! +} + +enum DgraphIndex { + int + int64 + float + bool + hash + exact + term + fulltext + trigram + regexp + year + month + day + hour + geo +} + +input AuthRule { + and: [AuthRule] + or: [AuthRule] + not: AuthRule + rule: String +} + +enum HTTPMethod { + GET + POST + PUT + PATCH + DELETE +} + +enum Mode { + BATCH + SINGLE +} + +input CustomHTTP { + url: String! + method: HTTPMethod! + body: String + graphql: String + mode: Mode + forwardHeaders: [String!] + secretHeaders: [String!] + introspectionHeaders: [String!] + skipIntrospection: Boolean +} + +type Point { + longitude: Float! + latitude: Float! +} + +input PointRef { + longitude: Float! + latitude: Float! +} + +input NearFilter { + distance: Float! + coordinate: PointRef! +} + +input PointGeoFilter { + near: NearFilter + within: WithinFilter +} + +type PointList { + points: [Point!]! +} + +input PointListRef { + points: [PointRef!]! +} + +type Polygon { + coordinates: [PointList!]! +} + +input PolygonRef { + coordinates: [PointListRef!]! +} + +type MultiPolygon { + polygons: [Polygon!]! +} + +input MultiPolygonRef { + polygons: [PolygonRef!]! +} + +input WithinFilter { + polygon: PolygonRef! +} + +input ContainsFilter { + point: PointRef + polygon: PolygonRef +} + +input IntersectsFilter { + polygon: PolygonRef + multiPolygon: MultiPolygonRef +} + +input PolygonGeoFilter { + near: NearFilter + within: WithinFilter + contains: ContainsFilter + intersects: IntersectsFilter +} + +input GenerateQueryParams { + get: Boolean + query: Boolean + password: Boolean + aggregate: Boolean +} + +input GenerateMutationParams { + add: Boolean + update: Boolean + delete: Boolean +} + +directive @hasInverse(field: String!) on FIELD_DEFINITION +directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION +directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION +directive @secret(field: String!, pred: String) on OBJECT | INTERFACE +directive @auth( + password: AuthRule + query: AuthRule, + add: AuthRule, + update: AuthRule, + delete: AuthRule) on OBJECT | INTERFACE +directive @custom(http: CustomHTTP, dql: String) on FIELD_DEFINITION +directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM +directive @remoteResponse(name: String) on FIELD_DEFINITION +directive @cascade(fields: [String]) on FIELD +directive @lambda on FIELD_DEFINITION +directive @lambdaOnMutate(add: Boolean, update: Boolean, delete: Boolean) on OBJECT | INTERFACE +directive @cacheControl(maxAge: Int!) on QUERY +directive @generate( + query: GenerateQueryParams, + mutation: GenerateMutationParams, + subscription: Boolean) on OBJECT | INTERFACE + +input IntFilter { + eq: Int + in: [Int] + le: Int + lt: Int + ge: Int + gt: Int + between: IntRange +} + +input Int64Filter { + eq: Int64 + in: [Int64] + le: Int64 + lt: Int64 + ge: Int64 + gt: Int64 + between: Int64Range +} + +input FloatFilter { + eq: Float + in: [Float] + le: Float + lt: Float + ge: Float + gt: Float + between: FloatRange +} + +input DateTimeFilter { + eq: DateTime + in: [DateTime] + le: DateTime + lt: DateTime + ge: DateTime + gt: DateTime + between: DateTimeRange +} + +input StringTermFilter { + allofterms: String + anyofterms: String +} + +input StringRegExpFilter { + regexp: String +} + +input StringFullTextFilter { + alloftext: String + anyoftext: String +} + +input StringExactFilter { + eq: String + in: [String] + le: String + lt: String + ge: String + gt: String + between: StringRange +} + +input StringHashFilter { + eq: String + in: [String] +} + +####################### +# Generated Types +####################### + +type AddPersonPayload { + person(filter: PersonFilter, order: PersonOrder, first: Int, offset: Int): [Person] + numUids: Int +} + +type DeleteNodePayload { + node(filter: NodeFilter, order: NodeOrder, first: Int, offset: Int): [Node] + msg: String + numUids: Int +} + +type DeletePersonPayload { + person(filter: PersonFilter, order: PersonOrder, first: Int, offset: Int): [Person] + msg: String + numUids: Int +} + +type NodeAggregateResult { + count: Int + f1Min: String + f1Max: String +} + +type PersonAggregateResult { + count: Int + f1Min: String + f1Max: String + f1HiMin: String + f1HiMax: String + f2Min: String + f2Max: String + f3Min: String + f3Max: String + nameMin: String + nameMax: String + nameHiMin: String + nameHiMax: String + nameEnMin: String + nameEnMax: String + nameHiEnMin: String + nameHiEnMax: String + nameHi_En_UntagMin: String + nameHi_En_UntagMax: String + name_Untag_AnyLangMin: String + name_Untag_AnyLangMax: String + addressMin: String + addressMax: String + addressHiMin: String + addressHiMax: String + professionEnMin: String + professionEnMax: String +} + +type UpdateNodePayload { + node(filter: NodeFilter, order: NodeOrder, first: Int, offset: Int): [Node] + numUids: Int +} + +type UpdatePersonPayload { + person(filter: PersonFilter, order: PersonOrder, first: Int, offset: Int): [Person] + numUids: Int +} + +####################### +# Generated Enums +####################### + +enum NodeHasFilter { + f1 +} + +enum NodeOrderable { + f1 +} + +enum PersonHasFilter { + f1 + f1Hi + f2 + f3 + name + nameHi + nameEn + name_Untag_AnyLang + address + addressHi + professionEn +} + +enum PersonOrderable { + f1 + f1Hi + f2 + f3 + name + nameHi + nameEn + name_Untag_AnyLang + address + addressHi + professionEn +} + +####################### +# Generated Inputs +####################### + +input AddPersonInput { + f1: String + f1Hi: String + f2: String + f3: String + name: String! + nameHi: String + nameEn: String + address: String + addressHi: String + professionEn: String +} + +input NodeFilter { + has: [NodeHasFilter] + and: [NodeFilter] + or: [NodeFilter] + not: NodeFilter +} + +input NodeOrder { + asc: NodeOrderable + desc: NodeOrderable + then: NodeOrder +} + +input NodePatch { + f1: String +} + +input PersonFilter { + name: StringHashFilter + nameHi: StringExactFilter_StringTermFilter + nameEn: StringRegExpFilter + address: StringFullTextFilter + has: [PersonHasFilter] + and: [PersonFilter] + or: [PersonFilter] + not: PersonFilter +} + +input PersonOrder { + asc: PersonOrderable + desc: PersonOrderable + then: PersonOrder +} + +input PersonPatch { + f1: String + f1Hi: String + f2: String + f3: String + nameHi: String + nameEn: String + address: String + addressHi: String + professionEn: String +} + +input PersonRef { + f1: String + f1Hi: String + f2: String + f3: String + name: String + nameHi: String + nameEn: String + address: String + addressHi: String + professionEn: String +} + +input StringExactFilter_StringTermFilter { + eq: String + in: [String] + le: String + lt: String + ge: String + gt: String + between: StringRange + allofterms: String + anyofterms: String +} + +input UpdateNodeInput { + filter: NodeFilter! + set: NodePatch + remove: NodePatch +} + +input UpdatePersonInput { + filter: PersonFilter! + set: PersonPatch + remove: PersonPatch +} + +####################### +# Generated Query +####################### + +type Query { + queryNode(filter: NodeFilter, order: NodeOrder, first: Int, offset: Int): [Node] + aggregateNode(filter: NodeFilter): NodeAggregateResult + getPerson(name: String!): Person + queryPerson(filter: PersonFilter, order: PersonOrder, first: Int, offset: Int): [Person] + aggregatePerson(filter: PersonFilter): PersonAggregateResult +} + +####################### +# Generated Mutations +####################### + +type Mutation { + updateNode(input: UpdateNodeInput!): UpdateNodePayload + deleteNode(filter: NodeFilter!): DeleteNodePayload + addPerson(input: [AddPersonInput!]!, upsert: Boolean): AddPersonPayload + updatePerson(input: UpdatePersonInput!): UpdatePersonPayload + deletePerson(filter: PersonFilter!): DeletePersonPayload +} + diff --git a/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql b/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql index 41a8008c643..5430dbf9e17 100755 --- a/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql +++ b/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql @@ -178,7 +178,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/no-id-field.graphql b/graphql/schema/testdata/schemagen/output/no-id-field.graphql index 68e809c5a0b..1976d50b845 100755 --- a/graphql/schema/testdata/schemagen/output/no-id-field.graphql +++ b/graphql/schema/testdata/schemagen/output/no-id-field.graphql @@ -191,7 +191,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/password-type.graphql b/graphql/schema/testdata/schemagen/output/password-type.graphql index a994bbf1f83..cba6c170459 100755 --- a/graphql/schema/testdata/schemagen/output/password-type.graphql +++ b/graphql/schema/testdata/schemagen/output/password-type.graphql @@ -179,7 +179,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/random.graphql b/graphql/schema/testdata/schemagen/output/random.graphql index 8b365c84d9d..117b334faef 100644 --- a/graphql/schema/testdata/schemagen/output/random.graphql +++ b/graphql/schema/testdata/schemagen/output/random.graphql @@ -197,7 +197,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM diff --git a/graphql/schema/testdata/schemagen/output/searchables-references.graphql b/graphql/schema/testdata/schemagen/output/searchables-references.graphql index d98e8468a98..a1d33610f5a 100755 --- a/graphql/schema/testdata/schemagen/output/searchables-references.graphql +++ b/graphql/schema/testdata/schemagen/output/searchables-references.graphql @@ -189,7 +189,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/searchables.graphql b/graphql/schema/testdata/schemagen/output/searchables.graphql index 71764a8ff6d..8e21af2ddcd 100755 --- a/graphql/schema/testdata/schemagen/output/searchables.graphql +++ b/graphql/schema/testdata/schemagen/output/searchables.graphql @@ -209,7 +209,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql b/graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql index bd33c6a0166..dc67bd55967 100755 --- a/graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql +++ b/graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql @@ -187,7 +187,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/single-type.graphql b/graphql/schema/testdata/schemagen/output/single-type.graphql index c24c2a07ca4..ffd93575abf 100755 --- a/graphql/schema/testdata/schemagen/output/single-type.graphql +++ b/graphql/schema/testdata/schemagen/output/single-type.graphql @@ -182,7 +182,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql b/graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql index ae26f7520a4..b789d8551f7 100755 --- a/graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql +++ b/graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql @@ -196,7 +196,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/type-reference.graphql b/graphql/schema/testdata/schemagen/output/type-reference.graphql index c61259d84f0..721497a18fd 100755 --- a/graphql/schema/testdata/schemagen/output/type-reference.graphql +++ b/graphql/schema/testdata/schemagen/output/type-reference.graphql @@ -186,7 +186,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/type-with-arguments-on-field.graphql b/graphql/schema/testdata/schemagen/output/type-with-arguments-on-field.graphql index c4e9d509fe9..192482c12fa 100644 --- a/graphql/schema/testdata/schemagen/output/type-with-arguments-on-field.graphql +++ b/graphql/schema/testdata/schemagen/output/type-with-arguments-on-field.graphql @@ -187,7 +187,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/type-with-custom-field-on-dgraph-type.graphql b/graphql/schema/testdata/schemagen/output/type-with-custom-field-on-dgraph-type.graphql index fdd9e835668..7e4c71914f7 100644 --- a/graphql/schema/testdata/schemagen/output/type-with-custom-field-on-dgraph-type.graphql +++ b/graphql/schema/testdata/schemagen/output/type-with-custom-field-on-dgraph-type.graphql @@ -186,7 +186,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/type-with-custom-fields-on-remote-type.graphql b/graphql/schema/testdata/schemagen/output/type-with-custom-fields-on-remote-type.graphql index fcc386231e2..e85ea68b041 100644 --- a/graphql/schema/testdata/schemagen/output/type-with-custom-fields-on-remote-type.graphql +++ b/graphql/schema/testdata/schemagen/output/type-with-custom-fields-on-remote-type.graphql @@ -186,7 +186,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/type-without-orderables.graphql b/graphql/schema/testdata/schemagen/output/type-without-orderables.graphql index eedaf08b447..ff78a20fbb1 100644 --- a/graphql/schema/testdata/schemagen/output/type-without-orderables.graphql +++ b/graphql/schema/testdata/schemagen/output/type-without-orderables.graphql @@ -181,7 +181,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/testdata/schemagen/output/union.graphql b/graphql/schema/testdata/schemagen/output/union.graphql index 3a02426ae4d..33eaa4c6c54 100644 --- a/graphql/schema/testdata/schemagen/output/union.graphql +++ b/graphql/schema/testdata/schemagen/output/union.graphql @@ -228,7 +228,7 @@ input GenerateMutationParams { directive @hasInverse(field: String!) on FIELD_DEFINITION directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION -directive @id on FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION directive @secret(field: String!, pred: String) on OBJECT | INTERFACE directive @auth( diff --git a/graphql/schema/wrappers.go b/graphql/schema/wrappers.go index d07d85b73ff..40239982a55 100644 --- a/graphql/schema/wrappers.go +++ b/graphql/schema/wrappers.go @@ -249,7 +249,7 @@ type Type interface { Interfaces() []string ImplementingTypes() []Type EnsureNonNulls(map[string]interface{}, string) error - FieldOriginatedFrom(fieldName string) string + FieldOriginatedFrom(fieldName string) (Type, bool) AuthRules() *TypeAuth IsGeo() bool IsAggregateResult() bool @@ -269,6 +269,7 @@ type FieldDefinition interface { IsID() bool IsExternal() bool HasIDDirective() bool + HasInterfaceArg() bool Inverse() FieldDefinition WithMemberType(string) FieldDefinition // TODO - It might be possible to get rid of ForwardEdge and just use Inverse() always. @@ -2320,10 +2321,34 @@ func (fd *fieldDefinition) HasIDDirective() bool { } func hasIDDirective(fd *ast.FieldDefinition) bool { - id := fd.Directives.ForName("id") + id := fd.Directives.ForName(idDirective) return id != nil } +func (fd *fieldDefinition) HasInterfaceArg() bool { + if fd.fieldDef == nil { + return false + } + return hasInterfaceArg(fd.fieldDef) +} + +func hasInterfaceArg(fd *ast.FieldDefinition) bool { + if !hasIDDirective(fd) { + return false + } + interfaceArg := fd.Directives.ForName(idDirective).Arguments.ForName(idDirectiveInterfaceArg) + if interfaceArg == nil { + return false + } + + value, _ := interfaceArg.Value.Value(nil) + if val, ok := value.(bool); ok && val { + return true + } + + return false +} + func isID(fd *ast.FieldDefinition) bool { return fd.Type.Name() == "ID" } @@ -3014,21 +3039,34 @@ func SubstituteVarsInBody(jsonTemplate interface{}, variables map[string]interfa return jsonTemplate } -// FieldOriginatedFrom returns the name of the interface from which given field was inherited. -// If the field wasn't inherited, but belonged to this type, this type's name is returned. -// Otherwise, empty string is returned. -func (t *astType) FieldOriginatedFrom(fieldName string) string { +// FieldOriginatedFrom returns the interface from which given field was inherited. +// If the field wasn't inherited, but belonged to this type,then type is returned. +// Otherwise, nil is returned. Along with type definition we return boolean flag true if field +// is inherited from interface. +func (t *astType) FieldOriginatedFrom(fieldName string) (Type, bool) { + + astTyp := &astType{ + inSchema: t.inSchema, + dgraphPredicate: t.dgraphPredicate, + } + for _, implements := range t.inSchema.schema.Implements[t.Name()] { if implements.Fields.ForName(fieldName) != nil { - return implements.Name + astTyp.typ = &ast.Type{ + NamedType: implements.Name, + } + return astTyp, true } } if t.inSchema.schema.Types[t.Name()].Fields.ForName(fieldName) != nil { - return t.Name() + astTyp.typ = &ast.Type{ + NamedType: t.inSchema.schema.Types[t.Name()].Name, + } + return astTyp, false } - return "" + return nil, false } // buildGraphqlRequestFields will build graphql request body from ast.