Skip to content

Commit c8b73e3

Browse files
feat(GraphQL): add has filter support (#6258)
* feat(GraphQL): Add HasFilter in the Schema (#6168) * add HadFilter In the Schema * fix deepsource failure * fix Teamcity failure * fix TestCases * address Pawan's comments * address Pawan's Comments * feat(GraphQL): Add has filter in Query Rewriter (#6205) * add has filter in query_rewriter and add test case * remove space * change Predicate Format and add test cases * minor changes * remove spaces * change in testcases * address Pawan's comments * add more test cases * fix CI failure * add more test cases * feat(GraphQL): add e2e tests for has filter (#6247) * add test data * add e2e tests * remove spacing * change format of has query * change addPost mutation * fix test failures * fix has filter test failure
1 parent 7ac7af6 commit c8b73e3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1227
-16
lines changed

graphql/e2e/common/common.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,13 @@ func RunAll(t *testing.T) {
243243
t.Run("multiple search indexes wrong field", multipleSearchIndexesWrongField)
244244
t.Run("hash search", hashSearch)
245245
t.Run("deep filter", deepFilter)
246+
t.Run("deep has filter", deepHasFilter)
246247
t.Run("many queries", manyQueries)
247248
t.Run("query order at root", queryOrderAtRoot)
248249
t.Run("queries with error", queriesWithError)
249250
t.Run("date filters", dateFilters)
250251
t.Run("float filters", floatFilters)
252+
t.Run("has filters", hasFilters)
251253
t.Run("Int filters", int32Filters)
252254
t.Run("Int64 filters", int64Filters)
253255
t.Run("boolean filters", booleanFilters)

graphql/e2e/common/mutation.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,80 @@ func addPost(t *testing.T, authorID, countryID string,
780780
return result.AddPost.Post[0]
781781
}
782782

783+
func addPostWithNullText(t *testing.T, authorID, countryID string,
784+
executeRequest requestExecutor) *post {
785+
786+
addPostParams := &GraphQLParams{
787+
Query: `mutation addPost($post: AddPostInput!) {
788+
addPost(input: [$post]) {
789+
post( filter : {not :{has : text} }){
790+
postID
791+
title
792+
text
793+
isPublished
794+
tags
795+
author(filter: {has:country}) {
796+
id
797+
name
798+
country {
799+
id
800+
name
801+
}
802+
}
803+
}
804+
}
805+
}`,
806+
Variables: map[string]interface{}{"post": map[string]interface{}{
807+
"title": "No text",
808+
"isPublished": false,
809+
"numLikes": 0,
810+
"tags": []string{"no text", "null"},
811+
"author": map[string]interface{}{"id": authorID},
812+
}},
813+
}
814+
815+
addPostExpected := fmt.Sprintf(`{ "addPost": {
816+
"post": [{
817+
"postID": "_UID_",
818+
"title": "No text",
819+
"text": null,
820+
"isPublished": false,
821+
"tags": ["null","no text"],
822+
"numLikes": 0,
823+
"author": {
824+
"id": "%s",
825+
"name": "Test Author",
826+
"country": {
827+
"id": "%s",
828+
"name": "Testland"
829+
}
830+
}
831+
}]
832+
} }`, authorID, countryID)
833+
834+
gqlResponse := executeRequest(t, graphqlURL, addPostParams)
835+
RequireNoGQLErrors(t, gqlResponse)
836+
837+
var expected, result struct {
838+
AddPost struct {
839+
Post []*post
840+
}
841+
}
842+
err := json.Unmarshal([]byte(addPostExpected), &expected)
843+
require.NoError(t, err)
844+
err = json.Unmarshal([]byte(gqlResponse.Data), &result)
845+
require.NoError(t, err)
846+
847+
requireUID(t, result.AddPost.Post[0].PostID)
848+
849+
opt := cmpopts.IgnoreFields(post{}, "PostID")
850+
if diff := cmp.Diff(expected, result, opt); diff != "" {
851+
t.Errorf("result mismatch (-want +got):\n%s", diff)
852+
}
853+
854+
return result.AddPost.Post[0]
855+
}
856+
783857
func requirePost(
784858
t *testing.T,
785859
postID string,

graphql/e2e/common/query.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,43 @@ func deepFilter(t *testing.T) {
378378
}
379379
}
380380

381+
func deepHasFilter(t *testing.T) {
382+
newCountry := addCountry(t, postExecutor)
383+
newAuthor := addAuthor(t, newCountry.ID, postExecutor)
384+
newPost1 := addPostWithNullText(t, newAuthor.ID, newCountry.ID, postExecutor)
385+
newPost2 := addPost(t, newAuthor.ID, newCountry.ID, postExecutor)
386+
getAuthorParams := &GraphQLParams{
387+
Query: `query {
388+
queryAuthor(filter: { name: { eq: "Test Author" } }) {
389+
name
390+
posts(filter: {not :{ has : text } }) {
391+
title
392+
}
393+
}
394+
}`,
395+
}
396+
397+
gqlResponse := getAuthorParams.ExecuteAsPost(t, graphqlURL)
398+
RequireNoGQLErrors(t, gqlResponse)
399+
400+
var result struct {
401+
QueryAuthor []*author
402+
}
403+
err := json.Unmarshal([]byte(gqlResponse.Data), &result)
404+
require.NoError(t, err)
405+
require.Equal(t, 1, len(result.QueryAuthor))
406+
407+
expected := &author{
408+
Name: "Test Author",
409+
Posts: []*post{{Title: "No text"}},
410+
}
411+
412+
if diff := cmp.Diff(expected, result.QueryAuthor[0]); diff != "" {
413+
t.Errorf("result mismatch (-want +got):\n%s", diff)
414+
}
415+
cleanUp(t, []*country{newCountry}, []*author{newAuthor}, []*post{newPost1, newPost2})
416+
}
417+
381418
// manyQueries runs multiple queries in the one block. Internally, the GraphQL
382419
// server should run those concurrently, but the results should be returned in the
383420
// requested order. This makes sure those many test runs are reassembled correctly.
@@ -699,6 +736,22 @@ func int32Filters(t *testing.T) {
699736
}
700737
}
701738

739+
func hasFilters(t *testing.T) {
740+
newCountry := addCountry(t, postExecutor)
741+
newAuthor := addAuthor(t, newCountry.ID, postExecutor)
742+
newPost := addPostWithNullText(t, newAuthor.ID, newCountry.ID, postExecutor)
743+
744+
Filter := map[string]interface{}{"has": "text"}
745+
Expected := []*post{
746+
{Title: "GraphQL doco"},
747+
{Title: "Introducing GraphQL in Dgraph"},
748+
{Title: "Learning GraphQL in Dgraph"},
749+
{Title: "Random post"}}
750+
751+
postTest(t, Filter, Expected)
752+
cleanUp(t, []*country{newCountry}, []*author{newAuthor}, []*post{newPost})
753+
}
754+
702755
func int64Filters(t *testing.T) {
703756
cases := map[string]struct {
704757
Filter interface{}

graphql/resolve/query_rewriter.go

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,6 @@ func buildFilter(typ schema.Type, filter map[string]interface{}) *gql.FilterTree
971971

972972
var ands []*gql.FilterTree
973973
var or *gql.FilterTree
974-
975974
// Get a stable ordering so we generate the same thing each time.
976975
var keys []string
977976
for key := range filter {
@@ -1041,19 +1040,35 @@ func buildFilter(typ schema.Type, filter map[string]interface{}) *gql.FilterTree
10411040
},
10421041
})
10431042
case interface{}:
1043+
// has: comments -> has(Post.comments)
1044+
// OR
10441045
// isPublished: true -> eq(Post.isPublished, true)
10451046
// OR an enum case
10461047
// postType: Question -> eq(Post.postType, "Question")
1047-
fn := "eq"
1048-
ands = append(ands, &gql.FilterTree{
1049-
Func: &gql.Function{
1050-
Name: fn,
1051-
Args: []gql.Arg{
1052-
{Value: typ.DgraphPredicate(field)},
1053-
{Value: fmt.Sprintf("%v", dgFunc)},
1048+
switch field {
1049+
case "has":
1050+
fieldName := fmt.Sprintf("%v", dgFunc)
1051+
ands = append(ands, &gql.FilterTree{
1052+
Func: &gql.Function{
1053+
Name: field,
1054+
Args: []gql.Arg{
1055+
{Value: typ.DgraphPredicate(fieldName)},
1056+
},
10541057
},
1055-
},
1056-
})
1058+
})
1059+
1060+
default:
1061+
fn := "eq"
1062+
ands = append(ands, &gql.FilterTree{
1063+
Func: &gql.Function{
1064+
Name: fn,
1065+
Args: []gql.Arg{
1066+
{Value: typ.DgraphPredicate(field)},
1067+
{Value: fmt.Sprintf("%v", dgFunc)},
1068+
},
1069+
},
1070+
})
1071+
}
10571072
}
10581073
}
10591074
}

graphql/resolve/query_test.yaml

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,54 @@
167167
}
168168
}
169169
170+
-
171+
name: "Query with has Filter"
172+
gqlquery: |
173+
query {
174+
queryTeacher(filter: {has: subject}) {
175+
name
176+
}
177+
}
178+
dgquery: |-
179+
query {
180+
queryTeacher(func: type(Teacher)) @filter(has(Teacher.subject)) {
181+
name : People.name
182+
dgraph.uid : uid
183+
}
184+
}
185+
186+
-
187+
name: "has Filter with not"
188+
gqlquery: |
189+
query {
190+
queryTeacher(filter: { not : {has: subject } }) {
191+
name
192+
}
193+
}
194+
dgquery: |-
195+
query {
196+
queryTeacher(func: type(Teacher)) @filter(NOT (has(Teacher.subject))) {
197+
name : People.name
198+
dgraph.uid : uid
199+
}
200+
}
201+
202+
-
203+
name: "has Filter with and"
204+
gqlquery: |
205+
query {
206+
queryTeacher(filter: {has: subject, and: {has: teaches } } ) {
207+
name
208+
}
209+
}
210+
dgquery: |-
211+
query {
212+
queryTeacher(func: type(Teacher)) @filter((has(Teacher.teaches) AND has(Teacher.subject))) {
213+
name : People.name
214+
dgraph.uid : uid
215+
}
216+
}
217+
170218
-
171219
name: "Filters in same input object implies AND"
172220
gqlquery: |
@@ -199,6 +247,22 @@
199247
}
200248
}
201249
250+
-
251+
name: "has Filter with nested 'and'"
252+
gqlquery: |
253+
query {
254+
queryAuthor(filter: { name: { eq: "A. N. Author" }, and: { dob: { le: "2001-01-01" }, and: { has: country } } } ) {
255+
name
256+
}
257+
}
258+
dgquery: |-
259+
query {
260+
queryAuthor(func: type(Author)) @filter(((has(Author.country) AND le(Author.dob, "2001-01-01")) AND eq(Author.name, "A. N. Author"))) {
261+
name : Author.name
262+
dgraph.uid : uid
263+
}
264+
}
265+
202266
-
203267
name: "Filter with 'or'"
204268
gqlquery: |
@@ -401,6 +465,51 @@
401465
}
402466
403467
468+
-
469+
name: "Deep filter with has filter"
470+
gqlquery: |
471+
query {
472+
queryAuthor {
473+
name
474+
posts(filter: { has : tags }) {
475+
title
476+
}
477+
}
478+
}
479+
dgquery: |-
480+
query {
481+
queryAuthor(func: type(Author)) {
482+
name : Author.name
483+
posts : Author.posts @filter(has(Post.tags)) {
484+
title : Post.title
485+
dgraph.uid : uid
486+
}
487+
dgraph.uid : uid
488+
}
489+
}
490+
491+
-
492+
name: "Deep filter with has and other filters"
493+
gqlquery: |
494+
query {
495+
queryAuthor {
496+
name
497+
posts(filter:{ title : {anyofterms: "GRAPHQL"} , and : { has : tags } } ) {
498+
title
499+
}
500+
}
501+
}
502+
dgquery: |-
503+
query {
504+
queryAuthor(func: type(Author)) {
505+
name : Author.name
506+
posts : Author.posts @filter((has(Post.tags) AND anyofterms(Post.title, "GRAPHQL"))) {
507+
title : Post.title
508+
dgraph.uid : uid
509+
}
510+
dgraph.uid : uid
511+
}
512+
}
404513
-
405514
name: "Deep filter with first"
406515
gqlquery: |
@@ -1445,4 +1554,4 @@
14451554
queryThingOne(func: type(ThingOne)) {
14461555
dgraph.uid : uid
14471556
}
1448-
}
1557+
}

0 commit comments

Comments
 (0)