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 31, 2019
1 parent d817a65 commit 680c279
Show file tree
Hide file tree
Showing 5 changed files with 533 additions and 35 deletions.
111 changes: 100 additions & 11 deletions gql/parser_mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,115 @@
package gql

import (
"errors"

"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 queryText string
var queryFound bool

// ==>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:
if mu == nil {
return nil, x.Errorf("empty mutation block")
} else if !queryFound {
return nil, x.Errorf("query op not found in upsert block")
} else {
mu.Query = queryText
return mu, nil
}

// upsert { ===>mutation<=== {...} query{...}}
// upsert { mutation{...} ===>query<==={...}}
case itemUpsertBlockOp:
if !it.Next() {
return nil, x.Errorf("Unexpected end of upsert block")
}
if item.Val == "query" {
if queryFound {
return nil, x.Errorf("multiple query ops inside upsert block")
}
queryFound = true
x.AssertTrue(it.Item().Typ == itemUpsertBlockOpContent)
queryText += it.Item().Val
} else if item.Val == "mutation" {
var err error
if mu, err = ParseMutationBlock(it); err != nil {
return nil, err
}
} else if item.Val == "fragment" {
x.AssertTrue(it.Item().Typ == itemUpsertBlockOpContent)
queryText += "fragment" + it.Item().Val
} 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 +137,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 +162,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
8 changes: 4 additions & 4 deletions gql/parser_test.go
Original file line number Diff line number Diff line change
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 Expand Up @@ -4362,10 +4362,10 @@ func TestParseMutationTooManyBlocks(t *testing.T) {
}{
set { _:b2 <reg> "b2 content" . }
}`,
errStr: "Unexpected { after the end of the block.",
errStr: "Unrecognized character in lexText",
},
{m: `{set { _:a1 <reg> "a1 content" . }} something`,
errStr: "Invalid operation type: something after the end of the block",
errStr: "Invalid operation type: something",
},
{m: `
# comments are ok
Expand Down
171 changes: 152 additions & 19 deletions gql/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
// Package gql is responsible for lexing and parsing a GraphQL query/mutation.
package gql

import "github.com/dgraph-io/dgraph/lex"
import (
"github.com/dgraph-io/dgraph/lex"
)

const (
leftCurl = '{'
Expand All @@ -43,29 +45,154 @@ const (

// Constants representing type of different graphql lexed items.
const (
itemText lex.ItemType = 5 + iota // plain text
itemLeftCurl // left curly bracket
itemRightCurl // right curly bracket
itemEqual // equals to symbol
itemName // [9] names
itemOpType // operation type
itemString // quoted string
itemLeftRound // left round bracket
itemRightRound // right round bracket
itemColon // Colon
itemAt // @
itemPeriod // .
itemDollar // $
itemRegex // /
itemBackslash // \
itemMutationOp // mutation operation
itemMutationContent // mutation content
itemText lex.ItemType = 5 + iota // plain text
itemLeftCurl // left curly bracket
itemRightCurl // right curly bracket
itemEqual // equals to symbol
itemName // [9] names
itemOpType // operation type
itemString // quoted string
itemLeftRound // left round bracket
itemRightRound // right round bracket
itemColon // Colon
itemAt // @
itemPeriod // .
itemDollar // $
itemRegex // /
itemBackslash // \
itemMutationOp // mutation operation (set, delete)
itemMutationOpContent // mutation operation content (inc. query)
itemUpsertBlock // mutation upsert block
itemUpsertBlockOp // mutation upsert block operations (query, mutate, fragment)
itemUpsertBlockOpContent // mutation upsert block operations' content
itemLeftSquare
itemRightSquare
itemComma
itemMathOp
)

// lexIdentifyBlock identifies whether it is an upsert block
// If the block begins with "{" => mutation block
// Else if the block begins with "upsert" => upsert block
func lexIdentifyBlock(l *lex.Lexer) lex.StateFn {
l.Mode = lexIdentifyBlock
for {
switch r := l.Next(); {
case isSpace(r) || lex.IsEndOfLine(r):
l.Ignore()
case isNameBegin(r):
return lexNameBlock
case r == leftCurl:
l.Backup()
return lexInsideMutation
case r == '#':
return lexComment
case r == lex.EOF:
return l.Errorf("Invalid mutation block")
default:
return l.Errorf("Unexpected character while identifying mutation block: %#U", r)
}
}
}

// lexNameBlock lexes the blocks, for now, only upsert block
func lexNameBlock(l *lex.Lexer) lex.StateFn {
for {
// The caller already checked isNameBegin, and absorbed one rune.
r := l.Next()
if isNameSuffix(r) {
continue
}
l.Backup()
switch word := l.Input[l.Start:l.Pos]; word {
case "upsert":
l.Emit(itemUpsertBlock)
return lexUpsertBlock
default:
return l.Errorf("Invalid block: [%s]", word)
}
}
}

// lexUpsertBlock lexes the upsert block
func lexUpsertBlock(l *lex.Lexer) lex.StateFn {
l.Mode = lexUpsertBlock
for {
switch r := l.Next(); {
case r == rightCurl:
l.BlockDepth--
l.Emit(itemRightCurl)
if l.BlockDepth == 0 {
return lexTopLevel
}
case r == leftCurl:
l.BlockDepth++
l.Emit(itemLeftCurl)
case isSpace(r) || lex.IsEndOfLine(r):
l.Ignore()
case isNameBegin(r):
return lexNameUpsertOp
case r == '#':
return lexComment
case r == lex.EOF:
return l.Errorf("Unclosed upsert block")
default:
return l.Errorf("Unrecognized character in upsert block: %#U", r)
}
}
}

// lexNameUpsertOp parses the operation names inside upsert block
func lexNameUpsertOp(l *lex.Lexer) lex.StateFn {
for {
// The caller already checked isNameBegin, and absorbed one rune.
r := l.Next()
if isNameSuffix(r) {
continue
}
l.Backup()
word := l.Input[l.Start:l.Pos]
switch word {
case "query":
l.Emit(itemUpsertBlockOp)
return lexBlockContent
case "mutation":
l.Emit(itemUpsertBlockOp)
return lexInsideMutation
case "fragment":
l.Emit(itemUpsertBlockOp)
return lexBlockContent
default:
return l.Errorf("Invalid operation type: %s", word)
}
}
}

// lexBlockContent lexes and absorbs the text inside a block (covered by braces).
func lexBlockContent(l *lex.Lexer) lex.StateFn {
depth := 0
for {
switch l.Next() {
case lex.EOF:
return l.Errorf("Unclosed block (matching braces not found)")
case quote:
if err := l.LexQuotedString(); err != nil {
return l.Errorf(err.Error())
}
case leftCurl:
depth++
case rightCurl:
depth--
if depth < 0 {
return l.Errorf("Unopened } found")
} else if depth == 0 {
l.Emit(itemUpsertBlockOpContent)
return lexUpsertBlock
}
}
}
}

func lexInsideMutation(l *lex.Lexer) lex.StateFn {
l.Mode = lexInsideMutation
for {
Expand Down Expand Up @@ -231,6 +358,12 @@ func lexFuncOrArg(l *lex.Lexer) lex.StateFn {
}

func lexTopLevel(l *lex.Lexer) lex.StateFn {
// TODO (Aman): we currently only have upsert block,
// this won't work if we have other blocks too.
if l.BlockDepth != 0 {
return lexUpsertBlock
}

l.Mode = lexTopLevel
Loop:
for {
Expand Down Expand Up @@ -401,7 +534,7 @@ func lexTextMutation(l *lex.Lexer) lex.StateFn {
continue
}
l.Backup()
l.Emit(itemMutationContent)
l.Emit(itemMutationOpContent)
break
}
return lexInsideMutation
Expand Down
Loading

0 comments on commit 680c279

Please sign in to comment.