Skip to content

Commit 398318e

Browse files
committed
Add parser and lexer for upsert block (#3412)
1 parent bc70681 commit 398318e

File tree

6 files changed

+608
-34
lines changed

6 files changed

+608
-34
lines changed

gql/parser.go

+26-3
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,32 @@ type Result struct {
450450

451451
// Parse initializes and runs the lexer. It also constructs the GraphQuery subgraph
452452
// from the lexed items.
453-
func Parse(r Request) (res Result, rerr error) {
453+
func Parse(r Request) (Result, error) {
454+
return ParseWithNeedVars(r, nil)
455+
}
456+
457+
// ParseWithNeedVars performs parsing of a query with given needVars.
458+
//
459+
// The needVars parameter is passed in the case of upsert block.
460+
// For example, when parsing the query block inside -
461+
// upsert {
462+
// query {
463+
// me(func: eq(email, "[email protected]"), first: 1) {
464+
// v as uid
465+
// }
466+
// }
467+
//
468+
// mutation {
469+
// set {
470+
// uid(v) <name> "Some One" .
471+
// uid(v) <email> "[email protected]" .
472+
// }
473+
// }
474+
// }
475+
//
476+
// The variable name v needs to be passed through the needVars parameter. Otherwise, an error
477+
// is reported complaining that the variable v is defined but not used in the query block.
478+
func ParseWithNeedVars(r Request, needVars []string) (res Result, rerr error) {
454479
query := r.Str
455480
vmap := convertToVarMap(r.Variables)
456481

@@ -575,12 +600,10 @@ func checkDependency(vl []*Vars) error {
575600
if len(defines) != lenBefore {
576601
return errors.Errorf("Some variables are declared multiple times.")
577602
}
578-
579603
if len(defines) > len(needs) {
580604
return errors.Errorf("Some variables are defined but not used\nDefined:%v\nUsed:%v\n",
581605
defines, needs)
582606
}
583-
584607
if len(defines) < len(needs) {
585608
return errors.Errorf("Some variables are used but not defined\nDefined:%v\nUsed:%v\n",
586609
defines, needs)

gql/parser_mutation.go

+111-9
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,121 @@ import (
2222
"github.com/pkg/errors"
2323
)
2424

25-
func ParseMutation(mutation string) (*api.Mutation, error) {
25+
// ParseMutation parses a block into a mutation. Returns an object with a mutation or
26+
// an upsert block with mutation, otherwise returns nil with an error.
27+
func ParseMutation(mutation string) (mu *api.Mutation, err error) {
2628
lexer := lex.NewLexer(mutation)
27-
lexer.Run(lexInsideMutation)
29+
lexer.Run(lexIdentifyBlock)
30+
if err := lexer.ValidateResult(); err != nil {
31+
return nil, err
32+
}
33+
2834
it := lexer.NewIterator()
29-
var mu api.Mutation
35+
if !it.Next() {
36+
return nil, errors.Errorf("Invalid mutation")
37+
}
38+
39+
item := it.Item()
40+
switch item.Typ {
41+
case itemUpsertBlock:
42+
if mu, err = ParseUpsertBlock(it); err != nil {
43+
return nil, err
44+
}
45+
case itemLeftCurl:
46+
if mu, err = ParseMutationBlock(it); err != nil {
47+
return nil, err
48+
}
49+
default:
50+
return nil, errors.Errorf("Unexpected token: [%s]", item.Val)
51+
}
52+
53+
// mutations must be enclosed in a single block.
54+
if it.Next() && it.Item().Typ != lex.ItemEOF {
55+
return nil, errors.Errorf("Unexpected %s after the end of the block", it.Item().Val)
56+
}
57+
58+
return mu, nil
59+
}
3060

61+
// ParseUpsertBlock parses the upsert block
62+
func ParseUpsertBlock(it *lex.ItemIterator) (*api.Mutation, error) {
63+
var mu *api.Mutation
64+
var queryText string
65+
var queryFound bool
66+
67+
// ===>upsert<=== {...}
3168
if !it.Next() {
32-
return nil, errors.New("Invalid mutation")
69+
return nil, errors.Errorf("Unexpected end of upsert block")
70+
}
71+
72+
// upsert ===>{<=== ....}
73+
item := it.Item()
74+
if item.Typ != itemLeftCurl {
75+
return nil, errors.Errorf("Expected { at the start of block. Got: [%s]", item.Val)
76+
}
77+
78+
for it.Next() {
79+
item = it.Item()
80+
switch {
81+
// upsert {... ===>}<===
82+
case item.Typ == itemRightCurl:
83+
if mu == nil {
84+
return nil, errors.Errorf("Empty mutation block")
85+
} else if !queryFound {
86+
return nil, errors.Errorf("Query op not found in upsert block")
87+
} else {
88+
mu.Query = queryText
89+
return mu, nil
90+
}
91+
92+
// upsert { mutation{...} ===>query<==={...}}
93+
case item.Typ == itemUpsertBlockOp && item.Val == "query":
94+
if queryFound {
95+
return nil, errors.Errorf("Multiple query ops inside upsert block")
96+
}
97+
queryFound = true
98+
if !it.Next() {
99+
return nil, errors.Errorf("Unexpected end of upsert block")
100+
}
101+
item = it.Item()
102+
if item.Typ != itemUpsertBlockOpContent {
103+
return nil, errors.Errorf("Expecting brace, found '%s'", item.Val)
104+
}
105+
queryText += item.Val
106+
107+
// upsert { ===>mutation<=== {...} query{...}}
108+
case item.Typ == itemUpsertBlockOp && item.Val == "mutation":
109+
if !it.Next() {
110+
return nil, errors.Errorf("Unexpected end of upsert block")
111+
}
112+
var err error
113+
if mu, err = ParseMutationBlock(it); err != nil {
114+
return nil, err
115+
}
116+
117+
// upsert { mutation{...} ===>fragment<==={...}}
118+
case item.Typ == itemUpsertBlockOp && item.Val == "fragment":
119+
if !it.Next() {
120+
return nil, errors.Errorf("Unexpected end of upsert block")
121+
}
122+
item = it.Item()
123+
if item.Typ != itemUpsertBlockOpContent {
124+
return nil, errors.Errorf("Expecting brace, found '%s'", item.Val)
125+
}
126+
queryText += "fragment" + item.Val
127+
128+
default:
129+
return nil, errors.Errorf("Unexpected token in upsert block [%s]", item.Val)
130+
}
33131
}
132+
133+
return nil, errors.Errorf("Invalid upsert block")
134+
}
135+
136+
// ParseMutationBlock parses the mutation block
137+
func ParseMutationBlock(it *lex.ItemIterator) (*api.Mutation, error) {
138+
var mu api.Mutation
139+
34140
item := it.Item()
35141
if item.Typ != itemLeftCurl {
36142
return nil, errors.Errorf("Expected { at the start of block. Got: [%s]", item.Val)
@@ -42,10 +148,6 @@ func ParseMutation(mutation string) (*api.Mutation, error) {
42148
continue
43149
}
44150
if item.Typ == itemRightCurl {
45-
// mutations must be enclosed in a single block.
46-
if it.Next() && it.Item().Typ != lex.ItemEOF {
47-
return nil, errors.Errorf("Unexpected %s after the end of the block.", it.Item().Val)
48-
}
49151
return &mu, nil
50152
}
51153
if item.Typ == itemMutationOp {
@@ -71,7 +173,7 @@ func parseMutationOp(it *lex.ItemIterator, op string, mu *api.Mutation) error {
71173
}
72174
parse = true
73175
}
74-
if item.Typ == itemMutationContent {
176+
if item.Typ == itemMutationOpContent {
75177
if !parse {
76178
return errors.Errorf("Mutation syntax invalid.")
77179
}

gql/parser_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -1641,7 +1641,7 @@ func TestParseMutationError(t *testing.T) {
16411641
`
16421642
_, err := ParseMutation(query)
16431643
require.Error(t, err)
1644-
require.Equal(t, `Expected { at the start of block. Got: [mutation]`, err.Error())
1644+
require.Contains(t, err.Error(), `Invalid block: [mutation]`)
16451645
}
16461646

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

16621662
func TestParseMutationAndQueryWithComments(t *testing.T) {
@@ -4362,10 +4362,10 @@ func TestParseMutationTooManyBlocks(t *testing.T) {
43624362
}{
43634363
set { _:b2 <reg> "b2 content" . }
43644364
}`,
4365-
errStr: "Unexpected { after the end of the block.",
4365+
errStr: "Unrecognized character in lexText",
43664366
},
43674367
{m: `{set { _:a1 <reg> "a1 content" . }} something`,
4368-
errStr: "Invalid operation type: something after the end of the block",
4368+
errStr: "Invalid operation type: something",
43694369
},
43704370
{m: `
43714371
# comments are ok

0 commit comments

Comments
 (0)