Skip to content

Commit

Permalink
Add support for lexing and parsing upsert block
Browse files Browse the repository at this point in the history
  • Loading branch information
mangalaman93 committed May 20, 2019
1 parent 742a948 commit 45726c5
Show file tree
Hide file tree
Showing 4 changed files with 529 additions and 84 deletions.
70 changes: 63 additions & 7 deletions gql/parser_mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,40 @@
package gql

import (
"errors"

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

// ParseMutation parses a block of text into a mutation.
// Returns an object with a mutation or a transaction query
// with mutation, otherwise returns nil with an error.
func ParseMutation(mutation string) (*api.Mutation, error) {
lexer := lex.NewLexer(mutation)
lexer.Run(lexInsideMutation)
lexer.Run(lexIdentifyBlock)
it := lexer.NewIterator()
var mu api.Mutation
var inTxn bool

if !it.Next() {
return nil, errors.New("Invalid mutation")
return nil, x.Errorf("Invalid mutation")
}

item := it.Item()
// Inside txn{ ... } block.
// Here we switch into txn mode and try to fetch any query inside a txn block.
// If no query text is found, this txn is a no-op.
if item.Typ == itemMutationTxn {
var err error
// Get the query text: txn{ query { ... }}
mu.CondQuery, err = parseMutationCondQuery(it)
if err != nil {
return nil, err
}
inTxn = true
item = it.Item()
// fallthrough to regular mutation parsing.
}
if item.Typ != itemLeftCurl {
return nil, x.Errorf("Expected { at the start of block. Got: [%s]", item.Val)
}
Expand All @@ -45,7 +62,7 @@ func ParseMutation(mutation string) (*api.Mutation, error) {
}
if item.Typ == itemRightCurl {
// mutations must be enclosed in a single block.
if it.Next() && it.Item().Typ != lex.ItemEOF {
if !inTxn && 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
Expand All @@ -56,7 +73,46 @@ func ParseMutation(mutation string) (*api.Mutation, error) {
}
}
}
return nil, x.Errorf("Invalid mutation.")
return nil, x.Errorf("Invalid mutation")
}

// parseMutationCondQuery gets the text inside a txn query block. It is possible that there's
// no query to be found, in that case it's the caller's responsbility to fail.
// Returns the query text if any is found, otherwise an empty string with error.
func parseMutationCondQuery(it *lex.ItemIterator) (string, error) {
var query string
var parse bool
for it.Next() {
item := it.Item()
switch item.Typ {
case itemLeftCurl:
continue
case itemMutationOpContent:
if !parse {
return "", x.Errorf("Invalid query block.")
}
query = item.Val
case itemMutationTxnOp:
if item.Val == "query" {
if parse {
return "", x.Errorf("Too many query blocks in txn")
}
parse = true
continue
}
// TODO: mutation conditionals
if item.Val != "mutation" {
return "", x.Errorf("Invalid txn operator %q.", item.Val)
}
if !it.Next() {
return "", x.Errorf("Invalid mutation block")
}
return query, nil
default:
return "", x.Errorf("Unexpected %q inside of txn block.", item.Val)
}
}
return query, nil
}

// parseMutationOp parses and stores set or delete operation string in Mutation.
Expand All @@ -73,7 +129,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
117 changes: 59 additions & 58 deletions gql/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,7 @@ func TestParseQueryWithVarError1(t *testing.T) {
}
`
_, err := Parse(Request{Str: query})
require.NoError(t, err)
require.Error(t, err)
require.Contains(t, err.Error(), "Some variables are defined but not used")
}
Expand All @@ -871,6 +872,64 @@ func TestParseQueryWithVarError2(t *testing.T) {
require.Contains(t, err.Error(), "Some variables are used but not defined")
}

func TestParseMutationError(t *testing.T) {
query := `
mutation {
set {
<name> <is> <something> .
<hometown> <is> <san/francisco> .
}
delete {
<name> <is> <something-else> .
}
}
`
_, err := ParseMutation(query)
require.Error(t, err)
require.Equal(t, `Expected { at the start of block. Got: [mutation]`, err.Error())
}

func TestParseMutationError2(t *testing.T) {
query := `
set {
<name> <is> <something> .
<hometown> <is> <san/francisco> .
}
delete {
<name> <is> <something-else> .
}
`
_, err := ParseMutation(query)
require.Error(t, err)
require.Equal(t, `Expected { at the start of block. Got: [set]`, err.Error())
}

func TestParseMutationAndQueryWithComments(t *testing.T) {
query := `
# Mutation
mutation {
# Set block
set {
<name> <is> <something> .
<hometown> <is> <san/francisco> .
}
# Delete block
delete {
<name> <is> <something-else> .
}
}
# Query starts here.
query {
me(func: uid( 0x5)) { # now mention children
name # Name
hometown # hometown of the person
}
}
`
_, err := Parse(Request{Str: query})
require.Error(t, err)
}

func TestParseQueryFilterError1A(t *testing.T) {
query := `
{
Expand Down Expand Up @@ -1627,64 +1686,6 @@ func TestParseSchemaErrorMulti(t *testing.T) {
require.Contains(t, err.Error(), "Only one schema block allowed")
}

func TestParseMutationError(t *testing.T) {
query := `
mutation {
set {
<name> <is> <something> .
<hometown> <is> <san/francisco> .
}
delete {
<name> <is> <something-else> .
}
}
`
_, err := ParseMutation(query)
require.Error(t, err)
require.Equal(t, `Expected { at the start of block. Got: [mutation]`, err.Error())
}

func TestParseMutationError2(t *testing.T) {
query := `
set {
<name> <is> <something> .
<hometown> <is> <san/francisco> .
}
delete {
<name> <is> <something-else> .
}
`
_, err := ParseMutation(query)
require.Error(t, err)
require.Equal(t, `Expected { at the start of block. Got: [set]`, err.Error())
}

func TestParseMutationAndQueryWithComments(t *testing.T) {
query := `
# Mutation
mutation {
# Set block
set {
<name> <is> <something> .
<hometown> <is> <san/francisco> .
}
# Delete block
delete {
<name> <is> <something-else> .
}
}
# Query starts here.
query {
me(func: uid( 0x5)) { # now mention children
name # Name
hometown # hometown of the person
}
}
`
_, err := Parse(Request{Str: query})
require.Error(t, err)
}

func TestParseFragmentMultiQuery(t *testing.T) {
query := `
{
Expand Down
Loading

0 comments on commit 45726c5

Please sign in to comment.