Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(GraphQL): Add support for Geo point type in Graphql. #6481

Merged
merged 25 commits into from
Oct 7, 2020
Merged
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
72df5bc
Add support for Geo point type in Graphql.
arijitAD Sep 17, 2020
b2ab70c
Query filter.
arijitAD Sep 23, 2020
8b19ad4
Temp
arijitAD Sep 25, 2020
c7abe00
Add and fix unit test.
arijitAD Sep 25, 2020
c34192e
Merge remote-tracking branch 'origin/master' into arijitAD/geo-point-…
arijitAD Sep 25, 2020
ccc3ddd
Fix test.
arijitAD Sep 25, 2020
3f6346f
Minor nit.
arijitAD Sep 25, 2020
b61c96b
Fix bugs and add E2E test.
arijitAD Sep 28, 2020
290ea93
Fix unit test.
arijitAD Sep 28, 2020
f5d4548
Fix schema E2E test.
arijitAD Sep 28, 2020
5cf4761
Fix GQL directives E2E tests.
arijitAD Sep 28, 2020
8192fa8
Fix deepsource errors.
arijitAD Sep 28, 2020
ca32d2e
Fix test and address comments.
arijitAD Sep 29, 2020
a94b4dd
Merge remote-tracking branch 'origin/master' into arijitAD/geo-point-…
arijitAD Sep 29, 2020
4d711a9
Fix E2E test.
arijitAD Sep 29, 2020
b5f9ee1
Fix test failures after merge.
arijitAD Sep 29, 2020
ed4d9bf
Merge remote-tracking branch 'origin/master' into arijitAD/geo-point-…
arijitAD Sep 30, 2020
7a2307e
Resolve merge conflict.
arijitAD Sep 30, 2020
fa30a59
Address comments.
arijitAD Sep 30, 2020
5a649ec
Address comments.
arijitAD Oct 1, 2020
f2782b7
Merge remote-tracking branch 'origin/master' into arijitAD/geo-point-…
arijitAD Oct 1, 2020
cfeff0f
Update the schema response for e2e tests
pawanrawal Oct 1, 2020
b65fcd4
Add E2E test for near filter.
arijitAD Oct 5, 2020
9fe33a8
Merge remote-tracking branch 'origin/master' into arijitAD/geo-point-…
arijitAD Oct 7, 2020
ba94862
Fix E2E test after merging.
arijitAD Oct 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions graphql/dgraph/graphquery.go
Original file line number Diff line number Diff line change
@@ -155,6 +155,9 @@ func writeFilterFunction(b *strings.Builder, f *gql.Function) {
x.Check2(b.WriteString(fmt.Sprintf("%s(%s)", f.Name, f.Args[0].Value)))
case len(f.Args) == 2:
x.Check2(b.WriteString(fmt.Sprintf("%s(%s, %s)", f.Name, f.Args[0].Value, f.Args[1].Value)))
case len(f.Args) == 3:
x.Check2(b.WriteString(fmt.Sprintf("%s(%s, %s, %s)", f.Name, f.Args[0].Value, f.Args[1].Value,
f.Args[2].Value)))
}
}

1 change: 1 addition & 0 deletions graphql/e2e/common/common.go
Original file line number Diff line number Diff line change
@@ -377,6 +377,7 @@ func RunAll(t *testing.T) {
t.Run("update mutation without set & remove", updateMutationWithoutSetRemove)
t.Run("Input coercing for int64 type", int64BoundaryTesting)
t.Run("Check cascade with mutation without ID field", checkCascadeWithMutationWithoutIDField)
t.Run("Geo type", mutationGeoType)

// error tests
t.Run("graphql completion on", graphQLCompletionOn)
43 changes: 43 additions & 0 deletions graphql/e2e/common/mutation.go
Original file line number Diff line number Diff line change
@@ -3746,3 +3746,46 @@ func nestedAddMutationWithHasInverse(t *testing.T) {
// cleanup
deleteGqlType(t, "Person1", map[string]interface{}{}, 3, nil)
}

func mutationGeoType(t *testing.T) {
addHotelParams := &GraphQLParams{
Query: `
mutation addHotel($hotel: AddHotelInput!) {
addHotel(input: [$hotel]) {
hotel {
name
location {
latitude
longitude
}
}
}
}`,
Variables: map[string]interface{}{"hotel": map[string]interface{}{
"name": "Taj Hotel",
"location": map[string]interface{}{
"latitude": 11.11,
"longitude": 22.22,
},
}},
}
gqlResponse := addHotelParams.ExecuteAsPost(t, GraphqlURL)
RequireNoGQLErrors(t, gqlResponse)

addHotelExpected := `
{
"addHotel": {
"hotel": [{
"name": "Taj Hotel",
"location": {
"latitude": 11.11,
"longitude": 22.22
}
}]
}
}`
testutil.CompareJSON(t, addHotelExpected, string(gqlResponse.Data))

// Cleanup
deleteGqlType(t, "Hotel", map[string]interface{}{}, 1, nil)
}
6 changes: 6 additions & 0 deletions graphql/e2e/directives/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# **Don't delete** Comments at top of schemas should work
# See: https://github.com/dgraph-io/dgraph/issues/4227

type Hotel {
id: ID!
name: String! @search(by: [exact])
location: Point @search
}

type Country {
# **Don't delete** Comments in types should work
id: ID! # **Don't delete** Comments in lines should work
6 changes: 6 additions & 0 deletions graphql/e2e/normal/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# **Don't delete** Comments at top of schemas should work
# See: https://github.com/dgraph-io/dgraph/issues/4227

type Hotel {
id: ID!
name: String! @search(by: [exact])
location: Point @search
}

type Country {
# **Don't delete** Comments in types should work
id: ID! # **Don't delete** Comments in in lines should work
20 changes: 20 additions & 0 deletions graphql/e2e/schema/generatedSchema.graphql
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@ enum DgraphIndex {
month
day
hour
geo
}

input AuthRule {
@@ -72,6 +73,25 @@ input CustomHTTP {
skipIntrospection: Boolean
}

type Point {
longitude: Float!
latitude: Float!
}

input PointRef {
longitude: Float!
latitude: Float!
}

input NearFilter {
distance: Float!
coordinate: PointRef!
}

input PointGeoFilter {
near: NearFilter!
}

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
33 changes: 33 additions & 0 deletions graphql/resolve/add_mutation_test.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
-
name: "Add mutation with geo"
gqlmutation: |
mutation addHotel($hotel: AddHotelInput!) {
addHotel(input: [$hotel]) {
hotel {
name
location {
latitude
longitude
}
}
}
}
gqlvariables: |
{ "hotel":
{ "name": "Taj Hotel",
"location": { "latitude": 11.11 , "longitude" : 22.22}
}
}
explanation: "A uid and type should get injected and all data transformed to
underlying Dgraph edge names"
dgmutations:
- setjson: |
{ "uid":"_:Hotel1",
"dgraph.type":["Hotel"],
"Hotel.name":"Taj Hotel",
"Hotel.location": {
"type": "Point",
"coordinates": [22.22, 11.11]
}
}

-
name: "Add mutation with variables"
gqlmutation: |
23 changes: 20 additions & 3 deletions graphql/resolve/mutation_rewriter.go
Original file line number Diff line number Diff line change
@@ -1165,9 +1165,26 @@ func rewriteObject(
// or giving the data to create the object as part of a deep mutation
// { "title": "...", "author": { "username": "new user", "dob": "...", ... }
// like here ^^
frags =
rewriteObject(ctx, typ, fieldDef.Type(), fieldDef, myUID, varGen,
withAdditionalDeletes, val, deepXID, xidMetadata)
if fieldDef.Type().IsGeo() {
// For Geo type, the mutation json is as follows:
// { "type": "Point", "coordinates": [11.11, 22.22]}
lat := val["latitude"]
long := val["longitude"]
frags = &mutationRes{
secondPass: []*mutationFragment{
newFragment(
map[string]interface{}{
"type": "Point",
"coordinates": []interface{}{long, lat},
},
),
},
}
} else {
frags =
rewriteObject(ctx, typ, fieldDef.Type(), fieldDef, myUID, varGen,
withAdditionalDeletes, val, deepXID, xidMetadata)
}

case []interface{}:
// This field is either:
46 changes: 37 additions & 9 deletions graphql/resolve/query_rewriter.go
Original file line number Diff line number Diff line change
@@ -751,7 +751,10 @@ func addSelectionSetFrom(
auth.varName = parentQryName
}

selectionAuth := addSelectionSetFrom(child, f, auth)
var selectionAuth []*gql.GraphQuery
if !f.Type().IsGeo() {
selectionAuth = addSelectionSetFrom(child, f, auth)
}
addedFields[f.Name()] = true

if len(f.SelectionSet()) > 0 && !auth.isWritingAuth && auth.hasAuthRules {
@@ -1057,15 +1060,40 @@ func buildFilter(typ schema.Type, filter map[string]interface{}) *gql.FilterTree
// OR
// numLikes: { le: 10 } -> le(Post.numLikes, 10)
fn, val := first(dgFunc)
ands = append(ands, &gql.FilterTree{
Func: &gql.Function{
Name: fn,
Args: []gql.Arg{
{Value: typ.DgraphPredicate(field)},
{Value: maybeQuoteArg(fn, val)},
switch fn {
case "near":
// For Geo type we have `near` filter which is written as follows:
// { near: { distance: 33.33, coordinate: { latitude: 11.11, longitude: 22.22 } } }
geoParams := val.(map[string]interface{})
distance := geoParams["distance"]

coordinate, _ := geoParams["coordinate"].(map[string]interface{})
lat := coordinate["latitude"]
long := coordinate["longitude"]

args := []gql.Arg{
{Value: typ.DgraphPredicate(field)},
{Value: fmt.Sprintf("[%v,%v]", long, lat)},
{Value: fmt.Sprintf("%v", distance)},
}

ands = append(ands, &gql.FilterTree{
Func: &gql.Function{
Name: fn,
Args: args,
},
},
})
})
default:
ands = append(ands, &gql.FilterTree{
Func: &gql.Function{
Name: fn,
Args: []gql.Arg{
{Value: typ.DgraphPredicate(field)},
{Value: maybeQuoteArg(fn, val)},
},
},
})
}
case []interface{}:
// ids: [ 0x123, 0x124 ] -> uid(0x123, 0x124)
ids := convertIDs(dgFunc)
1 change: 0 additions & 1 deletion graphql/resolve/query_test.go
Original file line number Diff line number Diff line change
@@ -56,7 +56,6 @@ func TestQueryRewriting(t *testing.T) {

for _, tcase := range tests {
t.Run(tcase.Name, func(t *testing.T) {

op, err := gqlSchema.Operation(
&schema.Request{
Query: tcase.GQLQuery,
21 changes: 21 additions & 0 deletions graphql/resolve/query_test.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
-
name: "Geo query"
gqlquery: |
query {
queryHotel(filter: { location: { near: { distance: 33.33, coordinate: { latitude: 11.11, longitude: 22.22} } } }) {
name
location {
latitude
longitude
}
}
}
dgquery: |-
query {
queryHotel(func: type(Hotel)) @filter(near(Hotel.location, [22.22,11.11], 33.33)) {
name : Hotel.name
location : Hotel.location
dgraph.uid : uid
}
}

-
name: "ID query"
gqlquery: |
42 changes: 42 additions & 0 deletions graphql/resolve/resolver.go
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"math"
@@ -1226,6 +1227,44 @@ func resolveCustomFields(fields []schema.Field, data interface{}) error {
return errs
}

// completeGeoObject builds a json GraphQL result object for the geo type.
// It returns a bracketed json object like { "longitude" : 12.32 , "latitude" : 123.32 }.
func completeGeoObject(
path []interface{},
fields []schema.Field,
val map[string]interface{}) ([]byte, x.GqlErrorList) {
var buf bytes.Buffer
x.Check2(buf.WriteRune('{'))

coordinate, _ := val["coordinates"].([]interface{})
comma := ""

longitude := fmt.Sprintf("%s", coordinate[0])
latitude := fmt.Sprintf("%s", coordinate[1])
for _, field := range fields {
x.Check2(buf.WriteString(comma))
x.Check2(buf.WriteRune('"'))
x.Check2(buf.WriteString(field.ResponseName()))
x.Check2(buf.WriteString(`": `))

if field.ResponseName() == "longitude" {
x.Check2(buf.WriteString(longitude))
} else if field.ResponseName() == "latitude" {
x.Check2(buf.WriteString(latitude))
} else {
return nil, x.GqlErrorList{&x.GqlError{
Message: "Invalid field for Geo type",
Locations: []x.Location{field.Location()},
Path: copyPath(path),
}}
}
comma = ","
}

x.Check2(buf.WriteRune('}'))
return buf.Bytes(), nil
}

// completeObject builds a json GraphQL result object for the current query level.
// It returns a bracketed json object like { f1:..., f2:..., ... }.
//
@@ -1367,6 +1406,9 @@ func completeValue(
Path: copyPath(path),
}}
}
if field.Type().IsGeo() {
return completeGeoObject(path, field.SelectionSet(), val)
}

return completeObject(path, field.SelectionSet(), val)
case []interface{}:
6 changes: 6 additions & 0 deletions graphql/resolve/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Test schema that contains an example of everything that's useful to
# test for query rewriting.

type Hotel {
id: ID!
name: String!
location: Point @search
}

type Country {
id: ID!
name: String! @search(by: [trigram, exact])
Loading