Skip to content

Commit

Permalink
Add parser and lexer for mutation using txn block
Browse files Browse the repository at this point in the history
  • Loading branch information
mangalaman93 committed May 21, 2019
1 parent 742a948 commit 11b7e59
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 32 deletions.
13 changes: 9 additions & 4 deletions gql/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,20 +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.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)
for it.Next() {
item := it.Item()
Expand Down
81 changes: 75 additions & 6 deletions gql/parser_mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,91 @@
package gql

import (
"errors"
"fmt"

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

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

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

item := it.Item()
switch item.Typ {
case itemMutationTxnBLock:
return ParseTxnBlock(it)
case itemLeftCurl:
return ParseMutationBlock(it)
default:
return nil, x.Errorf("Expected { at the start of block. Got: [%s]", item.Val)
}
}

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

// ==>txn<=== {...}
item := it.Item()
if item.Typ != itemMutationTxnBLock {
return nil, x.Errorf("Expected txn block. Got [%s]", item.Val)
}

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

for it.Next() {
item = it.Item()
switch item.Typ {
// txn {... ===>}<===
case rightCurl:
// TODO
fmt.Printf("ready to return, but don't know what to do with res: %+v\n", res)
return mu, nil
// txn { ===>mutation<=== {...} query{...}}
// txn { mutation{...} ===>query<==={...}}
case itemMutationTxnBlockOp:
var err error
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 txn block [%s]", item.Val)
}
}

return nil, x.Errorf("Invalid txn 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 Down Expand Up @@ -73,7 +142,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
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
141 changes: 122 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,125 @@ 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)
itemMutationTxnBLock // mutation txn block (txn)
itemMutationTxnBlockOp // mutation txn block operations (query, mutate)
itemLeftSquare
itemRightSquare
itemComma
itemMathOp
)

// lexIdentifyBlock identifies whether it is a mutation txn block
// If the block begins with "{" => mutation block
// Else if the block begins with "txn" => mutation txn 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 mutation txn 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 "txn":
l.Emit(itemMutationTxnBLock)
return lexMutationTxnBlock
default:
return l.Errorf("Invalid block: [%s]", word)
}
}
}

// lexMutationTxnBlock lexes the mutation txn block
func lexMutationTxnBlock(l *lex.Lexer) lex.StateFn {
l.Mode = lexMutationTxnBlock
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 lexNameTxnOp
case r == '#':
return lexComment
case r == lex.EOF:
return l.Errorf("Unclosed mutation txn action")
default:
return l.Errorf("Unrecognized character in lexMutationTxnBlock: %#U", r)
}
}
}

// lexNameTxnOp parses the operation names inside mutation txn block
func lexNameTxnOp(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(itemMutationTxnBlockOp)
return lexQuery
case "mutation":
l.Emit(itemMutationTxnBlockOp)
return lexInsideMutation
default:
return l.Errorf("Invalid operation type: %s", word)
}
}
}

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

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

l.Mode = lexTopLevel
Loop:
for {
Expand Down Expand Up @@ -401,7 +504,7 @@ func lexTextMutation(l *lex.Lexer) lex.StateFn {
continue
}
l.Backup()
l.Emit(itemMutationContent)
l.Emit(itemMutationOpContent)
break
}
return lexInsideMutation
Expand Down
5 changes: 4 additions & 1 deletion lex/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package lex
import (
"errors"
"fmt"
"reflect"
"runtime"
"unicode/utf8"

"github.com/dgraph-io/dgraph/x"
Expand Down Expand Up @@ -165,6 +167,7 @@ type Lexer struct {
Width int // Width of last rune read from input.
widthStack []*RuneWidth
items []Item // channel of scanned items.
BlockDepth int // nesting of blocks (e.g. mutation block inside txn block)
Depth int // nesting of {}
ArgDepth int // nesting of ()
Mode StateFn // Default state to go back to after reading a token.
Expand Down Expand Up @@ -194,7 +197,7 @@ func (l *Lexer) ValidateResult() error {
func (l *Lexer) Run(f StateFn) *Lexer {
for state := f; state != nil; {
// The following statement is useful for debugging.
//fmt.Printf("Func: %v\n", runtime.FuncForPC(reflect.ValueOf(state).Pointer()).Name())
fmt.Printf("Func: %v\n", runtime.FuncForPC(reflect.ValueOf(state).Pointer()).Name())
state = state(l)
}
return l
Expand Down

0 comments on commit 11b7e59

Please sign in to comment.