Skip to content

Commit

Permalink
Fix(GraphQL): This PR add schema cleaning in GraphQL. (#6427)
Browse files Browse the repository at this point in the history
Fixes GRAPHQL-572

This PR adds schema cleaning in GraphQL which will remove empty links and types from generated GraphQL schema recursively.
  • Loading branch information
JatinDev543 authored Sep 15, 2020
1 parent 288eafb commit bec5322
Show file tree
Hide file tree
Showing 13 changed files with 1,133 additions and 18 deletions.
2 changes: 1 addition & 1 deletion graphql/admin/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import (
"bytes"
"context"
"encoding/json"

dgoapi "github.com/dgraph-io/dgo/v200/protos/api"

"github.com/dgraph-io/dgraph/edgraph"
"github.com/dgraph-io/dgraph/gql"
"github.com/dgraph-io/dgraph/graphql/resolve"
Expand Down
111 changes: 97 additions & 14 deletions graphql/schema/gqlschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,11 +577,88 @@ func completeSchema(sch *ast.Schema, definitions []string) {
}
}

func cleanupInput(sch *ast.Schema, def *ast.Definition, seen map[string]bool) {
// seen helps us avoid cycles
if def == nil || seen[def.Name] {
return
}

i := 0
// recursively walk over fields for an input type and delete those which are themselves empty.
for _, f := range def.Fields {
nt := f.Type.Name()
enum := sch.Types[nt] != nil && sch.Types[nt].Kind == "ENUM"
// Lets skip scalar types and enums.
if _, ok := scalarToDgraph[nt]; ok || enum {
def.Fields[i] = f
i++
continue
}

seen[def.Name] = true
cleanupInput(sch, sch.Types[nt], seen)
seen[def.Name] = false

// If after calling cleanup on an input type, it got deleted then it doesn't need to be
// in the fields for this type anymore.
if sch.Types[nt] == nil {
continue
}
def.Fields[i] = f
i++
}
def.Fields = def.Fields[:i]

if len(def.Fields) == 0 {
delete(sch.Types, def.Name)
}
}

func cleanSchema(sch *ast.Schema) {
// Let's go over inputs of the type TRef, TPatch and AddTInput and delete the ones which
// don't have field inside them.
for k := range sch.Types {
if strings.HasSuffix(k, "Ref") || strings.HasSuffix(k, "Patch") ||
(strings.HasPrefix(k, "Add") && strings.HasSuffix(k, "Input")) {
cleanupInput(sch, sch.Types[k], map[string]bool{})
}
}

// Let's go over mutations and cleanup those which don't have AddTInput defined in the schema
// anymore.
i := 0 // helps us overwrite the array with valid entries.
for _, field := range sch.Mutation.Fields {
custom := field.Directives.ForName("custom")
// We would only modify add type queries.
if custom != nil || !strings.HasPrefix(field.Name, "add") {
sch.Mutation.Fields[i] = field
i++
continue
}

// addT type mutations have an input which is AddTInput so if that doesn't exist anymore,
// we can delete the AddTPayload and also skip this mutation.
typ := field.Name[3:]
input := sch.Types["Add"+typ+"Input"]
if input == nil {
delete(sch.Types, "Add"+typ+"Payload")
continue
}
sch.Mutation.Fields[i] = field
i++

}
sch.Mutation.Fields = sch.Mutation.Fields[:i]
}

func addInputType(schema *ast.Schema, defn *ast.Definition) {
schema.Types["Add"+defn.Name+"Input"] = &ast.Definition{
Kind: ast.InputObject,
Name: "Add" + defn.Name + "Input",
Fields: getFieldsWithoutIDType(schema, defn),
field := getFieldsWithoutIDType(schema, defn)
if len(field) != 0 {
schema.Types["Add"+defn.Name+"Input"] = &ast.Definition{
Kind: ast.InputObject,
Name: "Add" + defn.Name + "Input",
Fields: field,
}
}
}

Expand All @@ -604,10 +681,12 @@ func addReferenceType(schema *ast.Schema, defn *ast.Definition) {
}
}

schema.Types[defn.Name+"Ref"] = &ast.Definition{
Kind: ast.InputObject,
Name: defn.Name + "Ref",
Fields: flds,
if len(flds) != 0 {
schema.Types[defn.Name+"Ref"] = &ast.Definition{
Kind: ast.InputObject,
Name: defn.Name + "Ref",
Fields: flds,
}
}
}

Expand Down Expand Up @@ -1040,11 +1119,12 @@ func addAddPayloadType(schema *ast.Schema, defn *ast.Definition) {
addFilterArgument(schema, qry)
addOrderArgument(schema, qry)
addPaginationArguments(qry)

schema.Types["Add"+defn.Name+"Payload"] = &ast.Definition{
Kind: ast.Object,
Name: "Add" + defn.Name + "Payload",
Fields: []*ast.FieldDefinition{qry, numUids},
if schema.Types["Add"+defn.Name+"Input"] != nil {
schema.Types["Add"+defn.Name+"Payload"] = &ast.Definition{
Kind: ast.Object,
Name: "Add" + defn.Name + "Payload",
Fields: []*ast.FieldDefinition{qry, numUids},
}
}
}

Expand Down Expand Up @@ -1235,6 +1315,7 @@ func addAddMutation(schema *ast.Schema, defn *ast.Definition) {
},
}
schema.Mutation.Fields = append(schema.Mutation.Fields, add)

}

func addUpdateMutation(schema *ast.Schema, defn *ast.Definition) {
Expand Down Expand Up @@ -1285,6 +1366,9 @@ func addDeleteMutation(schema *ast.Schema, defn *ast.Definition) {
}

func addMutations(schema *ast.Schema, defn *ast.Definition) {
if schema.Types["Add"+defn.Name+"Input"] == nil {
return
}
addAddMutation(schema, defn)
addUpdateMutation(schema, defn)
addDeleteMutation(schema, defn)
Expand Down Expand Up @@ -1385,7 +1469,6 @@ func getFieldsWithoutIDType(schema *ast.Schema, defn *ast.Definition) ast.FieldL
(!hasID(schema.Types[fld.Type.Name()]) && !hasXID(schema.Types[fld.Type.Name()])) {
continue
}

fldList = append(fldList, createField(schema, fld))
}

Expand Down
1 change: 1 addition & 0 deletions graphql/schema/schemagen.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ func NewHandler(input string, validateOnly bool) (Handler, error) {
headers := getAllowedHeaders(sch, defns, authHeader)
dgSchema := genDgSchema(sch, typesToComplete)
completeSchema(sch, typesToComplete)
cleanSchema(sch)

if len(sch.Query.Fields) == 0 && len(sch.Mutation.Fields) == 0 {
return nil, gqlerror.Errorf("No query or mutation found in the generated schema")
Expand Down
1 change: 0 additions & 1 deletion graphql/schema/schemagen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ func TestSchemaString(t *testing.T) {

_, err = FromString(newSchemaStr)
require.NoError(t, err)

outputFileName := outputDir + testFile.Name()
str2, err := ioutil.ReadFile(outputFileName)
require.NoError(t, err)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type X {
name: [Y]
f1: [Y] @dgraph(pred: "f1")
}

type Y {
f1: [X] @dgraph(pred: "~f1")
}

type Z {
add:[X]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type X{
f1: [Y] @dgraph(pred: "f1")
f3: [Z] @dgraph(pred: "~f3")
}

type Y{
f1: [X] @dgraph(pred: "~f1")
f2: [Z] @dgraph(pred: "f2")
}

type Z{
f2: [Y] @dgraph(pred: "~f2")
f3: [X] @dgraph(pred: "f3")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
type User {
id: ID!
name: String!
}

input UserInput {
name: String!
}

type Mutation {
addMyFavouriteUsers(input: [UserInput!]!): [User] @custom(http: {
url: "http://my-api.com",
method: "POST",
body: "{ data: $input }"
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type X {
f1: [Y] @dgraph(pred: "f1")
name: String
id: ID
}

type Y {
f2: [Z] @dgraph(pred: "~f2")
f1: [X] @dgraph(pred: "~f1")
}

type Z {
f2: [Y] @dgraph(pred: "f2")
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
type Author {
id: ID!
name: String! @search(by: [hash])
posts: [Post]
posts: [Post]
}

interface Post {
Expand All @@ -12,7 +12,7 @@ interface Post {
}

type Question implements Post {
answered: Boolean
answered: Boolean
}

type Answer implements Post {
Expand Down
Loading

0 comments on commit bec5322

Please sign in to comment.