Skip to content

Commit

Permalink
Add parser and lexer for upsert block
Browse files Browse the repository at this point in the history
  • Loading branch information
mangalaman93 committed May 22, 2019
1 parent 6be440b commit 1ebe573
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 39 deletions.
78 changes: 78 additions & 0 deletions gql/parse_upsert_test.go
@@ -0,0 +1,78 @@
package gql

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestMutationTxnBlock1(t *testing.T) {
query := `
query {
me(func: eq(age, 34)) {
uid
friend {
uid
age
}
}
}
`
_, err := ParseMutation(query)
require.Error(t, err)
require.Contains(t, err.Error(), "Invalid block: [query]")
}

func TestMutationTxnBlock2(t *testing.T) {
query := `
upsert {
query {
me(func: eq(age, 34)) {
uid
friend {
uid
age
}
}
}
}
}
`
_, err := ParseMutation(query)
require.Error(t, err)
require.Contains(t, err.Error(), "Too many right curl")
}

// Is this okay?
// - Doesn't contain mutation op inside Upsert block
// - uid and age are in the same line
func TestMutationTxnBlock3(t *testing.T) {
query := `Upsert{query{me(func:eq(age,34)){uidfriend{uid age}}}}}`
_, err := ParseMutation(query)
require.Error(t, err)
require.Contains(t, err.Error(), "Too many right curl")
}

func TestMutationTxnBlock4(t *testing.T) {
query := `
upsert {
query {
me(func: eq(age, 34)) {
uid
friend {
uid
age
}
}
}
mutation {
set {
"_:user1" <age> "45" .
}
}
}
`
_, err := ParseMutation(query)
require.Nil(t, err)
}
20 changes: 13 additions & 7 deletions gql/parser.go
Expand Up @@ -457,21 +457,25 @@ type Result struct {
Schema *pb.SchemaRequest
}

// Parse initializes and runs the lexer. It also constructs the GraphQuery subgraph
// from the lexed items.
// Parse initializes and runs the lexer and parser.
func Parse(r Request) (res Result, rerr error) {
query := r.Str
vmap := convertToVarMap(r.Variables)

lexer := lex.NewLexer(query)
lexer := lex.NewLexer(r.Str)
lexer.Run(lexTopLevel)
if err := lexer.ValidateResult(); err != nil {
return res, err
}

return ParseQuery(lexer.NewIterator(), r.Variables)
}

// ParseQuery parses the given query.
// It also constructs the GraphQuery subgraph from the lexed items.
func ParseQuery(it *lex.ItemIterator, vars map[string]string) (res Result, rerr error) {
vmap := convertToVarMap(vars)

var qu *GraphQuery
it := lexer.NewIterator()
fmap := make(fragmentMap)
loop:
for it.Next() {
item := it.Item()
switch item.Typ {
Expand Down Expand Up @@ -510,6 +514,8 @@ func Parse(r Request) (res Result, rerr error) {
return res, rerr
}
res.Query = append(res.Query, qu)
case itemRightCurl:
break loop
case itemName:
it.Prev()
if qu, rerr = getQuery(it); rerr != nil {
Expand Down
99 changes: 89 additions & 10 deletions gql/parser_mutation.go
Expand Up @@ -17,22 +17,105 @@
package gql

import (
"errors"
"fmt"

"github.com/dgraph-io/dgo/protos/api"
"github.com/dgraph-io/dgraph/lex"
"github.com/dgraph-io/dgraph/x"
)

func ParseMutation(mutation string) (*api.Mutation, error) {
// ParseMutation parses a block into a mutation. Returns an object with a mutation or
// an upsert block with mutation, otherwise returns nil with an error.
func ParseMutation(mutation string) (mu *api.Mutation, err error) {
lexer := lex.NewLexer(mutation)
lexer.Run(lexInsideMutation)
lexer.Run(lexIdentifyBlock)
if err := lexer.ValidateResult(); err != nil {
return nil, err
}

it := lexer.NewIterator()
var mu api.Mutation
if !it.Next() {
return nil, x.Errorf("Invalid mutation")
}

item := it.Item()
switch item.Typ {
case itemUpsertBlock:
if mu, err = ParseUpsertBlock(it); err != nil {
return nil, err
}
case itemLeftCurl:
if mu, err = ParseMutationBlock(it); err != nil {
return nil, err
}
default:
return nil, x.Errorf("Unexpected token: [%s]", item.Val)
}

// mutations must be enclosed in a single block.
if it.Next() && it.Item().Typ != lex.ItemEOF {
return nil, x.Errorf("Unexpected %s after the end of the block.", it.Item().Val)
}

return mu, nil
}

// ParseUpsertBlock parses the upsert block
func ParseUpsertBlock(it *lex.ItemIterator) (*api.Mutation, error) {
var mu *api.Mutation
var res Result

// ==>upsert<=== {...}
if !it.Next() {
return nil, errors.New("Invalid mutation")
return nil, x.Errorf("Unexpected end of upsert block")
}

// upsert ===>{<=== ....}
item := it.Item()
if item.Typ != itemLeftCurl {
return nil, x.Errorf("Expected { at the start of block. Got: [%s]", item.Val)
}

for it.Next() {
item = it.Item()
switch item.Typ {
// upsert {... ===>}<===
case itemRightCurl:
// TODO
fmt.Printf("ready to return, but don't know what to do with res: %+v\n", res)
return mu, nil

// upsert { ===>mutation<=== {...} query{...}}
// upsert { mutation{...} ===>query<==={...}}
case itemUpsertBlockOp:
var err error
if !it.Next() {
return nil, x.Errorf("Unexpected end of upsert block")
}
if item.Val == "query" {
if res, err = ParseQuery(it, nil); err != nil {
return nil, err
}
} else if item.Val == "mutation" {
if mu, err = ParseMutationBlock(it); err != nil {
return nil, err
}
} else {
return nil, x.Errorf("should not reach here")
}

default:
return nil, x.Errorf("unexpected token in upsert block [%s]", item.Val)
}
}

return nil, x.Errorf("Invalid upsert block")
}

// ParseMutationBlock parses the mutation block
func ParseMutationBlock(it *lex.ItemIterator) (*api.Mutation, error) {
var mu api.Mutation

item := it.Item()
if item.Typ != itemLeftCurl {
return nil, x.Errorf("Expected { at the start of block. Got: [%s]", item.Val)
Expand All @@ -44,10 +127,6 @@ func ParseMutation(mutation string) (*api.Mutation, error) {
continue
}
if item.Typ == itemRightCurl {
// mutations must be enclosed in a single block.
if it.Next() && it.Item().Typ != lex.ItemEOF {
return nil, x.Errorf("Unexpected %s after the end of the block.", it.Item().Val)
}
return &mu, nil
}
if item.Typ == itemMutationOp {
Expand All @@ -73,7 +152,7 @@ func parseMutationOp(it *lex.ItemIterator, op string, mu *api.Mutation) error {
}
parse = true
}
if item.Typ == itemMutationContent {
if item.Typ == itemMutationOpContent {
if !parse {
return x.Errorf("Mutation syntax invalid.")
}
Expand Down
4 changes: 2 additions & 2 deletions gql/parser_test.go
Expand Up @@ -1641,7 +1641,7 @@ func TestParseMutationError(t *testing.T) {
`
_, err := ParseMutation(query)
require.Error(t, err)
require.Equal(t, `Expected { at the start of block. Got: [mutation]`, err.Error())
require.Contains(t, err.Error(), `Invalid block: [mutation]`)
}

func TestParseMutationError2(t *testing.T) {
Expand All @@ -1656,7 +1656,7 @@ func TestParseMutationError2(t *testing.T) {
`
_, err := ParseMutation(query)
require.Error(t, err)
require.Equal(t, `Expected { at the start of block. Got: [set]`, err.Error())
require.Contains(t, err.Error(), `Invalid block: [set]`)
}

func TestParseMutationAndQueryWithComments(t *testing.T) {
Expand Down

0 comments on commit 1ebe573

Please sign in to comment.