From 69c83890a4511677a17dcb53935fcedf12c8a875 Mon Sep 17 00:00:00 2001 From: Jatin Dev <64803093+JatinDevDG@users.noreply.github.com> Date: Sun, 18 Apr 2021 02:47:27 +0530 Subject: [PATCH] Feat(GraphQL): This PR allows @id field in interface to be unique across all the implementing types. (#7710) Currently, @id fields in the interface are unique only in one implementing type. But there are several use cases that require @id field to be unique across all the implementation types. Also currently. get a query on the interface can result in unexpected errors as we can have multiple implementing types have the same value for that @id field. Now we are allowing the @id field in the interface to be unique across all the implementing types. In order to do this, we have added a new argument interface of boolean type in the @id field. Whenever a @id field in interface type has an interface argument set then its value will be unique across all the implementing types. Users will get errors if they try to add a node with such a field and there is already a node with the same value of that field even in some other implementing types. This is true for other scenarios like adding nested value or while using upserts. If the interface argument is not present or its value is false then that field will be unique only for one implementing type. But such fields won't be allowed in argument to get query on interface in the future, see this PR also #7602 Example Schema, interface LibraryItem { refID: String! @id // This field is unique only for one implementing type itemID: String! @id(interface:true) // This field will be unique over all the implementing types inheriting this interface } type Book implements LibraryItem { title: String author: String } Related discuss Post: https://discuss.dgraph.io/t/uniqueness-for-id-fields-on-interface/13294 --- graphql/e2e/auth/add_mutation_test.go | 47 +- graphql/e2e/auth/auth_test.go | 2 +- graphql/e2e/auth/debug_off/debugoff_test.go | 62 +++ graphql/e2e/auth/schema.graphql | 19 + graphql/e2e/common/common.go | 2 + graphql/e2e/common/mutation.go | 203 +++++++ graphql/e2e/common/query.go | 57 ++ graphql/e2e/directives/schema.graphql | 32 ++ graphql/e2e/directives/schema_response.json | 192 +++++++ graphql/e2e/normal/schema.graphql | 33 ++ graphql/e2e/normal/schema_response.json | 192 +++++++ .../schema/apollo_service_response.graphql | 2 +- graphql/e2e/schema/generatedSchema.graphql | 2 +- graphql/resolve/add_mutation_test.yaml | 498 ++++++++++++++++++ graphql/resolve/mutation_rewriter.go | 141 ++++- graphql/resolve/query_test.yaml | 432 ++++++--------- graphql/resolve/schema.graphql | 298 ++++++----- graphql/schema/gqlschema.go | 29 +- graphql/schema/gqlschema_test.yml | 46 ++ graphql/schema/rules.go | 14 + .../output/auth-directive.graphql | 2 +- .../output/custom-directive.graphql | 2 +- .../output/extended-types.graphql | 2 +- .../output/generate-directive.graphql | 2 +- .../output/single-extended-type.graphql | 2 +- .../input/interface-with-id-directive.graphql | 3 +- .../output/apollo-federation.graphql | 2 +- .../output/auth-on-interfaces.graphql | 2 +- .../schemagen/output/authorization.graphql | 2 +- .../output/comments-and-descriptions.graphql | 2 +- ...custom-dql-query-with-subscription.graphql | 2 +- .../schemagen/output/custom-mutation.graphql | 2 +- .../output/custom-nested-types.graphql | 2 +- .../output/custom-query-mixed-types.graphql | 2 +- .../custom-query-not-dgraph-type.graphql | 2 +- .../custom-query-with-dgraph-type.graphql | 2 +- .../schemagen/output/deprecated.graphql | 2 +- ...e-on-concrete-type-with-interfaces.graphql | 2 +- ...-reverse-directive-with-interfaces.graphql | 2 +- .../output/field-with-id-directive.graphql | 2 +- .../field-with-multiple-@id-fields.graphql | 2 +- ...erse-predicate-in-dgraph-directive.graphql | 2 +- .../filter-cleanSchema-all-empty.graphql | 2 +- .../filter-cleanSchema-circular.graphql | 2 +- ...filter-cleanSchema-custom-mutation.graphql | 2 +- .../filter-cleanSchema-directLink.graphql | 2 +- .../output/generate-directive.graphql | 2 +- .../schemagen/output/geo-type.graphql | 2 +- ...se-with-interface-having-directive.graphql | 2 +- .../output/hasInverse-with-interface.graphql | 2 +- ...Inverse-with-type-having-directive.graphql | 2 +- .../schemagen/output/hasInverse.graphql | 2 +- .../hasInverse_withSubscription.graphql | 2 +- .../schemagen/output/hasfilter.graphql | 2 +- .../ignore-unsupported-directive.graphql | 2 +- .../output/interface-with-dgraph-pred.graphql | 2 +- .../interface-with-id-directive.graphql | 24 +- .../output/interface-with-no-ids.graphql | 2 +- ...interfaces-with-types-and-password.graphql | 2 +- .../output/interfaces-with-types.graphql | 2 +- .../schemagen/output/lambda-directive.graphql | 2 +- .../schemagen/output/language-tags.graphql | 2 +- .../no-id-field-with-searchables.graphql | 2 +- .../schemagen/output/no-id-field.graphql | 2 +- .../schemagen/output/password-type.graphql | 2 +- .../testdata/schemagen/output/random.graphql | 2 +- .../output/searchables-references.graphql | 2 +- .../schemagen/output/searchables.graphql | 2 +- .../output/single-type-with-enum.graphql | 2 +- .../schemagen/output/single-type.graphql | 2 +- ...ype-implements-multiple-interfaces.graphql | 2 +- .../schemagen/output/type-reference.graphql | 2 +- .../type-with-arguments-on-field.graphql | 2 +- ...e-with-custom-field-on-dgraph-type.graphql | 2 +- ...-with-custom-fields-on-remote-type.graphql | 2 +- .../output/type-without-orderables.graphql | 2 +- .../testdata/schemagen/output/union.graphql | 2 +- graphql/schema/wrappers.go | 56 +- 78 files changed, 1986 insertions(+), 510 deletions(-) diff --git a/graphql/e2e/auth/add_mutation_test.go b/graphql/e2e/auth/add_mutation_test.go index c2745804ae7..15f5af258eb 100644 --- a/graphql/e2e/auth/add_mutation_test.go +++ b/graphql/e2e/auth/add_mutation_test.go @@ -1035,7 +1035,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)) @@ -1152,3 +1152,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 908e8f2110d..20f9935c165 100644 --- a/graphql/e2e/auth/auth_test.go +++ b/graphql/e2e/auth/auth_test.go @@ -352,7 +352,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 f18354c057b..675ba395f9f 100644 --- a/graphql/e2e/auth/debug_off/debugoff_test.go +++ b/graphql/e2e/auth/debug_off/debugoff_test.go @@ -124,6 +124,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 := ioutil.ReadFile(schemaFile) diff --git a/graphql/e2e/auth/schema.graphql b/graphql/e2e/auth/schema.graphql index 4ec6785d2b9..14bba1b397e 100644 --- a/graphql/e2e/auth/schema.graphql +++ b/graphql/e2e/auth/schema.graphql @@ -848,6 +848,25 @@ type State @auth( country: Country } +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 e20bd21a092..19a8cecf30f 100644 --- a/graphql/e2e/common/common.go +++ b/graphql/e2e/common/common.go @@ -837,6 +837,7 @@ func RunAll(t *testing.T) { t.Run("query id directive with int64", idDirectiveWithInt64) t.Run("query filter ID values coercion to List", queryFilterWithIDInputCoercion) t.Run("query multiple language Fields", queryMultipleLangFields) + t.Run("query @id field with interface arg on interface", queryWithIDFieldAndInterfaceArg) // mutation tests t.Run("add mutation", addMutation) @@ -897,6 +898,7 @@ func RunAll(t *testing.T) { t.Run("multiple external Id's tests", multipleXidsTests) t.Run("Upsert Mutation Tests", upsertMutationTests) t.Run("Update language tag fields", updateLangTagFields) + 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 489056a768b..fe7eeed4260 100644 --- a/graphql/e2e/common/mutation.go +++ b/graphql/e2e/common/mutation.go @@ -6086,3 +6086,206 @@ func updateLangTagFields(t *testing.T) { testutil.CompareJSON(t, queryPersonExpected, string(gqlResponse.Data)) DeleteGqlType(t, "Person", map[string]interface{}{}, 1, 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 dbc60f4cb1d..19cdc6fee21 100644 --- a/graphql/e2e/common/query.go +++ b/graphql/e2e/common/query.go @@ -4010,3 +4010,60 @@ func queryMultipleLangFields(t *testing.T) { // Cleanup DeleteGqlType(t, "Person", map[string]interface{}{}, 3, nil) } + +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 80e4b27244f..25b6342feb3 100644 --- a/graphql/e2e/directives/schema.graphql +++ b/graphql/e2e/directives/schema.graphql @@ -389,3 +389,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 b43eabf666d..683460f7da3 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" @@ -1452,6 +1536,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 1834f4596a1..78d0011ca30 100644 --- a/graphql/e2e/normal/schema.graphql +++ b/graphql/e2e/normal/schema.graphql @@ -387,3 +387,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 95b64037e59..07f54ef8a07 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" @@ -1440,6 +1524,114 @@ } ], "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 854913f071c..d91fede0d74 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 26c14b07a4a..ca105a33d99 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 33c7135359a..bdbe2ca7951 100644 --- a/graphql/resolve/add_mutation_test.yaml +++ b/graphql/resolve/add_mutation_test.yaml @@ -1100,6 +1100,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 a12b9306be6..3d6b48de694 100644 --- a/graphql/resolve/mutation_rewriter.go +++ b/graphql/resolve/mutation_rewriter.go @@ -135,9 +135,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" }]) { @@ -151,7 +161,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 { @@ -1200,7 +1211,8 @@ func mutationFromFragment( } -func checkXIDExistsQuery(xidVariable, xidString, xidPredicate string, typ schema.Type) *gql.GraphQuery { +func checkXIDExistsQuery(xidVariable, xidString, xidPredicate string, typ schema.Type, + interfaceType schema.Type) *gql.GraphQuery { qry := &gql.GraphQuery{ Attr: xidVariable, Func: &gql.Function{ @@ -1212,6 +1224,13 @@ func checkXIDExistsQuery(xidVariable, xidString, xidPredicate string, typ schema }, Children: []*gql.GraphQuery{{Attr: "uid"}}, } + + // 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 } @@ -1375,44 +1394,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 { @@ -1465,7 +1519,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: @@ -1648,6 +1702,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. @@ -1726,7 +1794,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, retErrors @@ -1756,8 +1830,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) + + // 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. } } @@ -2296,3 +2381,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 0ffb235bfdd..569ee9dfee8 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,7 @@ } } -- - name: "Filter gets rewritten as @filter" +- name: "Filter gets rewritten as @filter" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" } }) { @@ -863,8 +835,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 +850,7 @@ } } -- - name: "Query with has Filter" +- name: "Query with has Filter" gqlquery: | query { queryTeacher(filter: {has: subject}) { @@ -895,8 +865,7 @@ } } -- - name: "has Filter with not" +- name: "has Filter with not" gqlquery: | query { queryTeacher(filter: { not : {has: subject } }) { @@ -911,8 +880,7 @@ } } -- - name: "has Filter with and" +- name: "has Filter with and" gqlquery: | query { queryTeacher(filter: {has: subject, and: {has: teaches } } ) { @@ -927,8 +895,7 @@ } } -- - name: "has Filter on list of fields" +- name: "has Filter on list of fields" gqlquery: | query { queryTeacher(filter: {has: [subject, teaches ] } ) { @@ -956,8 +923,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 +938,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 +953,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 +968,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 +983,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 +998,7 @@ } } -- - name: "Filter with 'or' object" +- name: "Filter with 'or' object" gqlquery: | query { queryAuthor(filter: { or: { name: { eq: "A. N. Author" } }} ) { @@ -1053,8 +1014,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 +1029,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 +1044,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 +1059,7 @@ } } -- - name: "Filter with 'not" +- name: "Filter with 'not" gqlquery: | query { queryAuthor(filter: { not: { reputation: { gt: 2.5 } } } ) { @@ -1117,8 +1074,7 @@ } } -- - name: "Filter with first" +- name: "Filter with first" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" } }, first: 10) { @@ -1133,8 +1089,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 +1104,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 +1119,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 +1135,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 +1150,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 +1165,7 @@ } } -- - name: "Deep filter" +- name: "Deep filter" gqlquery: | query { queryAuthor { @@ -1238,8 +1188,7 @@ } -- - name: "Deep filter with has filter" +- name: "Deep filter with has filter" gqlquery: | query { queryAuthor { @@ -1260,8 +1209,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 +1231,7 @@ } } -- - name: "Deep filter with has and other filters" +- name: "Deep filter with has and other filters" gqlquery: | query { queryAuthor { @@ -1305,8 +1252,7 @@ dgraph.uid : uid } } -- - name: "Deep filter with first" +- name: "Deep filter with first" gqlquery: | query { queryAuthor { @@ -1328,8 +1274,7 @@ } } -- - name: "Deep filter with order, first and offset" +- name: "Deep filter with order, first and offset" gqlquery: | query { queryAuthor { @@ -1351,8 +1296,7 @@ } } -- - name: "Deep filter with multiple order, first and offset" +- name: "Deep filter with multiple order, first and offset" gqlquery: | query { queryAuthor { @@ -1374,8 +1318,7 @@ } } -- - name: "Float with large exponentiation" +- name: "Float with large exponentiation" gqlquery: | query { queryAuthor(filter:{ reputation: { gt: 123456789.113 } }) { @@ -1390,8 +1333,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 +1348,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 +1363,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 +1378,7 @@ } } -- - name: "All String hash filters work" +- name: "All String hash filters work" gqlquery: | query { queryAuthor(filter: { name: { eq: "A. N. Author" } } ) { @@ -1454,8 +1393,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 +1408,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 +1423,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 +1439,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 +1454,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 +1470,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 +1485,7 @@ } } -- - name: "All String regexp filters work" +- name: "All String regexp filters work" gqlquery: | query { queryCountry(filter: { name: { regexp: "/.*ust.*/" }}) { @@ -1568,8 +1500,7 @@ } } -- - name: "Aggregate Query" +- name: "Aggregate Query" gqlquery: | query { aggregateCountry(filter: { name: { regexp: "/.*ust.*/" }}) { @@ -1595,8 +1526,7 @@ } } -- - name: "Skip directive" +- name: "Skip directive" gqlquery: | query ($skipTrue: Boolean!, $skipFalse: Boolean!) { getAuthor(id: "0x1") { @@ -1620,8 +1550,7 @@ } } -- - name: "Include directive" +- name: "Include directive" gqlquery: | query ($includeTrue: Boolean!, $includeFalse: Boolean!) { queryAuthor { @@ -1645,8 +1574,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 +1609,7 @@ } } -- - name: "Cascade directive on get query" +- name: "Cascade directive on get query" gqlquery: | query { getAuthor(id: "0x1") @cascade { @@ -1704,8 +1631,7 @@ } } -- - name: "Cascade directive on filter query" +- name: "Cascade directive on filter query" gqlquery: | query { queryAuthor @cascade { @@ -1727,8 +1653,7 @@ } } -- - name: "Cascade directive on query field" +- name: "Cascade directive on query field" gqlquery: | query { queryAuthor { @@ -1750,8 +1675,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 +1697,7 @@ } } -- - name: "Parameterized Cascade directive on filter query" +- name: "Parameterized Cascade directive on filter query" gqlquery: | query { queryAuthor @cascade(fields:["dob"]) { @@ -1798,8 +1721,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 +1745,7 @@ } } -- - name: "Parameterized Cascade directive on query field" +- name: "Parameterized Cascade directive on query field" gqlquery: | query { queryAuthor { @@ -1848,8 +1769,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 +1795,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 +1821,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 +1847,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 +1869,7 @@ } } -- - name: "parameterized cascade with interface Character" +- name: "parameterized cascade with interface Character" gqlquery: | query { queryCharacter @cascade(fields:["id","name"]) { @@ -1970,8 +1886,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 +1922,7 @@ } } -- - name: "getHuman which implements an interface" +- name: "getHuman which implements an interface" gqlquery: | query { getHuman(id: "0x1") { @@ -2030,8 +1944,7 @@ } } -- - name: "queryHuman which implements an interface" +- name: "queryHuman which implements an interface" gqlquery: | query { queryHuman { @@ -2053,8 +1966,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 +1979,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 +1992,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 +2012,7 @@ } } -- - name: "queryCharacter with fragment for human" +- name: "queryCharacter with fragment for human" gqlquery: | query { queryCharacter { @@ -2126,8 +2035,7 @@ } } -- - name: "queryCharacter with fragment on multiple types" +- name: "queryCharacter with fragment on multiple types" gqlquery: | query { queryCharacter { @@ -2154,8 +2062,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 +2087,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 +2102,7 @@ } } -- - name: "Between filter" +- name: "Between filter" gqlquery: | query { queryPost(filter: { numLikes: { between : { min :10, max: 20 }}}) { @@ -2214,8 +2119,7 @@ } } -- - name: "deep Between filter" +- name: "deep Between filter" gqlquery: | query{ queryAuthor(filter: {reputation: {between: {min:6.0, max: 7.2}}}){ @@ -2241,8 +2145,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 +2160,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 +2175,7 @@ } } -- - name: "Deep filter with id" +- name: "Deep filter with id" gqlquery: | query { queryAuthor { @@ -2296,8 +2197,7 @@ } } -- - name: "Deep filter with id in not key" +- name: "Deep filter with id in not key" gqlquery: | query { queryAuthor { @@ -2319,8 +2219,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 +2234,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 +2249,7 @@ } } -- - name: "Order at root node without UID." +- name: "Order at root node without UID." gqlquery: | query { queryAuthor(order: {asc: name}) { @@ -2367,8 +2264,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 +2280,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 +2295,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 +2310,7 @@ } } -- - name: "Get editor without supplying anything" +- name: "Get editor without supplying anything" gqlquery: | query { getEditor { @@ -2432,8 +2325,7 @@ } } -- - name: "Get editor using code" +- name: "Get editor using code" gqlquery: | query { getEditor(code: "tolstoy") { @@ -2448,8 +2340,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 +2355,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 +2370,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 +2385,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 +2400,7 @@ } } -- - name: "Query along reverse edge is converted appropriately" +- name: "Query along reverse edge is converted appropriately" gqlquery: | query { queryMovie { @@ -2535,8 +2422,7 @@ } } -- - name: "deprecated fields can be queried" +- name: "deprecated fields can be queried" gqlquery: | query { queryCategory { @@ -2552,8 +2438,7 @@ } } -- - name: "Password query" +- name: "Password query" gqlquery: | query { checkUserPassword(name: "user1", pwd: "Password") { @@ -2571,8 +2456,7 @@ } } -- - name: "Password query with alias" +- name: "Password query with alias" gqlquery: | query { verify : checkUserPassword(name: "user1", pwd: "Password") { @@ -2749,8 +2633,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 +2666,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 +2698,7 @@ dgraph.uid : uid } } -- - name: "Query with Same Alias" +- name: "Query with Same Alias" gqlquery: |- query { queryAuthor { @@ -2844,8 +2725,7 @@ dgraph.uid : uid } } -- - name: "Aggregate Query with multiple aliases" +- name: "Aggregate Query with multiple aliases" gqlquery: | query{ queryAuthor{ @@ -3124,8 +3004,7 @@ } } -- - name: "Count query at child level" +- name: "Count query at child level" gqlquery: | query { queryCountry { @@ -3144,8 +3023,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 +3067,7 @@ } } -- - name: "Count query at child level with filter" +- name: "Count query at child level with filter" gqlquery: | query { queryCountry { @@ -3216,8 +3093,7 @@ } } -- - name: "Deep child level get query with count" +- name: "Deep child level get query with count" gqlquery: | query { getAuthor(id: "0x1") { @@ -3241,8 +3117,7 @@ } } -- - name: "Aggregate Query with Sum and Avg" +- name: "Aggregate Query with Sum and Avg" gqlquery: | query { aggregateTweets() { @@ -3268,14 +3143,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 +3158,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 +3169,7 @@ } } } - dgquery: |- + dgquery: |- query { _entities(func: eq(Astronaut.id, "0x1", "0x2"), orderasc: Astronaut.id) @filter(type(Astronaut)) { dgraph.type @@ -3308,8 +3181,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 +3192,7 @@ } } } - dgquery: |- + dgquery: |- query { _entities(func: eq(SpaceShip.id, "0x1", "0x2"), orderasc: SpaceShip.id) @filter(type(SpaceShip)) { dgraph.type @@ -3332,8 +3204,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 +3229,7 @@ } } -- - name: "get query with multiple @id fields " +- name: "get query with multiple @id fields " gqlquery: | query { getBook(title: "GraphQL", ISBN: "001HB") { @@ -3384,8 +3254,7 @@ } } -- - name: "query language tag fields with filter and order" +- name: "query language tag fields with filter and order" gqlquery: | query { queryPerson(filter:{or:[{name:{eq:"Alice"}},{nameHi:{eq:"ऐलिस"}},{nameZh:{eq:"爱丽丝"}},{name_Untag_AnyLang:{eq:"Alice"}}]}, order: { asc: nameHi }) @@ -3411,8 +3280,7 @@ } } -- - name: "Query fields linked to reverse predicates in Dgraph" +- name: "Query fields linked to reverse predicates in Dgraph" gqlquery: | query { queryLinkX(filter:{f9:{eq: "Alice"}}) { @@ -3458,3 +3326,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 f54236f4f9b..fe94fd0cbef 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 { @@ -465,3 +465,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 c46a43d7735..cd5f668f49f 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 @@ -1961,18 +1962,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{ @@ -1982,6 +1979,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 7882481fc5f..d71026d719f 100644 --- a/graphql/schema/gqlschema_test.yml +++ b/graphql/schema/gqlschema_test.yml @@ -2934,6 +2934,18 @@ invalid_schemas: "locations": [ { "line": 3, "column": 19 } ] }, ] + - 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: | @@ -3444,3 +3456,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 c91bbe67cc1..128c2d066f0 100644 --- a/graphql/schema/rules.go +++ b/graphql/schema/rules.go @@ -2076,12 +2076,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 f9402efd440..d7d7bc31e3d 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 cab532ea364..95887b1a99a 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 96ce76f371f..5a307aa82c3 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 683ce84f0a5..4824b922930 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 5d486a6571e..b1b0b717541 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 fe85b12a1bd..eb53c911d9e 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 015dc6c2a6f..2c6258e6c6d 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 04e0c7f4e4c..493ee7664b6 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 a45e8241b70..710dc346b18 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 c58d9b55ed2..c16c541bb9f 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 870e60c55e2..5e3c8535a2c 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 339c3141d9a..334e1069a1e 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 ba54bc9edb0..c3745e5e13b 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 e8544d02bd1..6983582acde 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 c70eba52a85..907dc302ee3 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 965cfb97e75..fc1f189da9e 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 a9acb1557db..333d6b76e31 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 7ab7e16828c..f6f6004cb11 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 b223c5e7264..2d344d8d0de 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 5d669349ffc..6a757259216 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 af4254aa42a..d238ddca7a9 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 aa1c90632ac..b8b228d6691 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 0792f884486..b4c60f094b2 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 b10687cd7b0..5d4bfaba0d3 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 4a090608150..5c437c4d780 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 235c68bd89a..9af72b288ad 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 0df49f78dad..e5d0ed497f6 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 109fdf3591b..a725acd8c17 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 5228e55ba91..830047aa0dd 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 109fdf3591b..a725acd8c17 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 8a06f7223d5..6d6b9838741 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 09c31036f63..6af32f41653 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 f1305f00787..a4a5f2d43f7 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 972a73bb14c..e4153ca8dbc 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 19072f3acb4..5e56e9c5647 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 663a2cc7b8e..b1f45440701 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 d1f5a3e2df0..4a42ba75f7b 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 3f890b4592b..7d7b2091152 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 32d6abb1b25..e463bbafd4b 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 aae76e26023..3643c2871fe 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 index 4c198621631..e217582cce7 100755 --- a/graphql/schema/testdata/schemagen/output/language-tags.graphql +++ b/graphql/schema/testdata/schemagen/output/language-tags.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/no-id-field-with-searchables.graphql b/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql index 9cfdeba87ca..1180da0ce6c 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 e4aa4d7fc80..10d5e1b7116 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 368b2c325c4..34026afcbf6 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 7f3ae6caae3..1856c372f62 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 f2b10abad28..a7728bfe91c 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 331e38376f9..8688c2922d5 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 20aceec417f..c81033228e7 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 d398335ba8d..ece8c87c1f8 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 33b1dd36f9c..2a6a9825d4e 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 faab9408bfd..25a3363d5bf 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 c2cecc05787..e3792a43cb6 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 cde1c6483aa..5383a66ec5b 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 90d9c5e8d60..992d66ee50c 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 f223499c0c2..e503040ac90 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 dc726070c0c..60842caa54f 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 283e79fb286..000a4b31be4 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. @@ -2327,10 +2328,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" } @@ -3017,21 +3042,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.