Skip to content

Commit

Permalink
Add support for lexing, parsing Conditional Upsert
Browse files Browse the repository at this point in the history
  • Loading branch information
mangalaman93 committed Jul 11, 2019
1 parent d32df83 commit 3661a28
Show file tree
Hide file tree
Showing 5 changed files with 364 additions and 18 deletions.
3 changes: 3 additions & 0 deletions gql/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ var (
type Mutation struct {
Set []*api.NQuad
Del []*api.NQuad

// CondTree stores the condition of mutation (@if directive)
CondTree *FilterTree
}

// ParseUid parses the given string into an UID. This method returns with an error
Expand Down
61 changes: 54 additions & 7 deletions gql/parser_mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,43 @@ package gql
import (
"github.com/dgraph-io/dgo/protos/api"
"github.com/dgraph-io/dgraph/lex"

"github.com/pkg/errors"
)

// ParseIfDirective parses the if directive into a FilterTree
func ParseIfDirective(cond string) (*FilterTree, error) {
if cond == "" {
return nil, nil
}

lexer := lex.NewLexer(cond)
lexer.Run(lexIfDirective)
if err := lexer.ValidateResult(); err != nil {
return nil, err
}

// ===>@<=== if(...)
it := lexer.NewIterator()
if !it.Next() {
return nil, errors.Errorf("Empty if directive")
}
if item := it.Item(); item.Typ != itemAt {
return nil, errors.Errorf("Expected @, found [%v]", item.Val)
}

// @ ===>if<=== (...)
if !it.Next() {
return nil, errors.Errorf("Invalid if directive")
}
if item := it.Item(); item.Typ != itemName || item.Val != "if" {
return nil, errors.Errorf("Expected if, found [%v]", item.Val)
}

// @if ===>(...)<===
return parseFilter(it)
}

// 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) {
Expand All @@ -39,11 +73,11 @@ func ParseMutation(mutation string) (mu *api.Mutation, err error) {
item := it.Item()
switch item.Typ {
case itemUpsertBlock:
if mu, err = ParseUpsertBlock(it); err != nil {
if mu, err = parseUpsertBlock(it); err != nil {
return nil, err
}
case itemLeftCurl:
if mu, err = ParseMutationBlock(it); err != nil {
if mu, err = parseMutationBlock(it); err != nil {
return nil, err
}
default:
Expand All @@ -58,10 +92,11 @@ func ParseMutation(mutation string) (mu *api.Mutation, err error) {
return mu, nil
}

// ParseUpsertBlock parses the upsert block
func ParseUpsertBlock(it *lex.ItemIterator) (*api.Mutation, error) {
// parseUpsertBlock parses the upsert block
func parseUpsertBlock(it *lex.ItemIterator) (*api.Mutation, error) {
var mu *api.Mutation
var queryText string
var condText string
var queryFound bool

// ===>upsert<=== {...}
Expand All @@ -86,6 +121,7 @@ func ParseUpsertBlock(it *lex.ItemIterator) (*api.Mutation, error) {
return nil, errors.Errorf("Query op not found in upsert block")
} else {
mu.Query = queryText
mu.Cond = condText
return mu, nil
}

Expand All @@ -109,8 +145,19 @@ func ParseUpsertBlock(it *lex.ItemIterator) (*api.Mutation, error) {
if !it.Next() {
return nil, errors.Errorf("Unexpected end of upsert block")
}

// upsert { mutation ===>@if(...)<=== {....} query{...}}
item = it.Item()
if item.Typ == itemUpsertBlockOpContent {
condText = item.Val
if !it.Next() {
return nil, errors.Errorf("Unexpected end of upsert block")
}
}

// upsert @if(...) ===>{<=== ....}
var err error
if mu, err = ParseMutationBlock(it); err != nil {
if mu, err = parseMutationBlock(it); err != nil {
return nil, err
}

Expand All @@ -133,8 +180,8 @@ func ParseUpsertBlock(it *lex.ItemIterator) (*api.Mutation, error) {
return nil, errors.Errorf("Invalid upsert block")
}

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

item := it.Item()
Expand Down
87 changes: 76 additions & 11 deletions gql/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,34 @@ const (
itemMathOp
)

// lexIfDirective lexes the @if directive in a mutation block
func lexIfDirective(l *lex.Lexer) lex.StateFn {
l.Mode = lexIfDirective
for {
switch r := l.Next(); {
case r == lex.EOF:
l.Emit(lex.ItemEOF)
return nil
case isSpace(r) || lex.IsEndOfLine(r):
l.Ignore()
case r == '#':
return lexComment
case r == leftRound:
l.Emit(itemLeftRound)
l.AcceptRun(isSpace)
l.Ignore()
l.ArgDepth++
l.WhetherIf = true
return lexFuncOrArg
case r == at:
l.Emit(itemAt)
return lexDirectiveOrLangList
default:
return l.Errorf("Unrecognized character in lexText: %#U", r)
}
}
}

// lexIdentifyBlock identifies whether it is an upsert block
// If the block begins with "{" => mutation block
// Else if the block begins with "upsert" => upsert block
Expand Down Expand Up @@ -93,11 +121,7 @@ func lexIdentifyBlock(l *lex.Lexer) lex.StateFn {
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()
l.AcceptRun(isNameSuffix)
switch word := l.Input[l.Start:l.Pos]; word {
case "upsert":
l.Emit(itemUpsertBlock)
Expand Down Expand Up @@ -140,11 +164,7 @@ func lexUpsertBlock(l *lex.Lexer) lex.StateFn {
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()
l.AcceptRun(isNameSuffix)
word := l.Input[l.Start:l.Pos]
switch word {
case "query":
Expand Down Expand Up @@ -187,10 +207,48 @@ func lexBlockContent(l *lex.Lexer) lex.StateFn {
}
}

// lexIfContent lexes the whole of @if directive in a mutation block
func lexIfContent(l *lex.Lexer) lex.StateFn {
if r := l.Next(); r != at {
return l.Errorf("Expected [@], found; [%#U]", r)
}

l.AcceptRun(isNameSuffix)
word := l.Input[l.Start:l.Pos]
if word != "@if" {
return l.Errorf("Expected @if, found [%v]", word)
}

depth := 0
for {
switch l.Next() {
case lex.EOF:
return l.Errorf("Invalid if directive")
case quote:
if err := l.LexQuotedString(); err != nil {
return l.Errorf(err.Error())
}
case leftRound:
depth++
case rightRound:
depth--
if depth < 0 {
return l.Errorf("Unopened ) found in if directive")
} else if depth == 0 {
l.Emit(itemUpsertBlockOpContent)
return lexInsideMutation
}
}
}
}

func lexInsideMutation(l *lex.Lexer) lex.StateFn {
l.Mode = lexInsideMutation
for {
switch r := l.Next(); {
case r == at:
l.Backup()
return lexIfContent
case r == rightCurl:
l.Depth--
l.Emit(itemRightCurl)
Expand Down Expand Up @@ -304,6 +362,13 @@ func lexFuncOrArg(l *lex.Lexer) lex.StateFn {
return l.Errorf("Empty Argument")
}
if l.ArgDepth == 0 {
// TODO(Aman): We should make l.Mode a stack instead
// and avoid such conditions as below.
if l.WhetherIf {
l.WhetherIf = false
return lexIfDirective
}

return lexQuery // Filter directive is done.
}
case r == lex.EOF:
Expand Down Expand Up @@ -586,7 +651,7 @@ func lexOperationType(l *lex.Lexer) lex.StateFn {
l.Emit(itemOpType)
return lexInsideSchema
} else {
l.Errorf("Invalid operation type: %s", word)
return l.Errorf("Invalid operation type: %s", word)
}

return lexQuery
Expand Down
Loading

0 comments on commit 3661a28

Please sign in to comment.