Skip to content

Commit 6c31f04

Browse files
authored
Add implementation for RunDQL for v25 (#9355)
1 parent 2168b01 commit 6c31f04

14 files changed

+339
-126
lines changed

dgraph/cmd/alpha/http.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ func mutationHandler(w http.ResponseWriter, r *http.Request) {
402402

403403
case "application/rdf":
404404
// Parse N-Quads.
405-
req, err = dql.ParseMutation(string(body))
405+
req, err = dql.ParseDQL(string(body))
406406
if err != nil {
407407
x.SetStatus(w, x.ErrorInvalidRequest, err.Error())
408408
return

dgraph/cmd/alpha/http_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ func TestContentTypeCharset(t *testing.T) {
934934
require.True(t, err != nil && strings.Contains(err.Error(), "Unsupported charset"))
935935

936936
_, err = mutationWithTs(
937-
mutationInp{body: `{}`, typ: "application/rdf; charset=utf-8", commitNow: true})
937+
mutationInp{body: `{ set {_:a <name> "alice" .}}`, typ: "application/rdf; charset=utf-8", commitNow: true})
938938
require.NoError(t, err)
939939

940940
_, err = mutationWithTs(

dql/dql.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* SPDX-FileCopyrightText: Hypermode Inc. <[email protected]>
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package dql
7+
8+
import (
9+
"github.com/dgraph-io/dgo/v240/protos/api"
10+
11+
"github.com/hypermodeinc/dgraph/v24/lex"
12+
)
13+
14+
func ParseDQL(dqlQuery string) (*api.Request, error) {
15+
var lexer lex.Lexer
16+
lexer.Reset(dqlQuery)
17+
lexer.Run(lexTopLevel)
18+
if err := lexer.ValidateResult(); err != nil {
19+
return nil, err
20+
}
21+
22+
it := lexer.NewIterator()
23+
if !it.Next() {
24+
return nil, it.Errorf("Invalid mutation")
25+
}
26+
27+
item := it.Item()
28+
switch item.Typ {
29+
case itemUpsertBlock:
30+
it.Prev()
31+
return parseUpsertBlock(it)
32+
33+
default:
34+
it.Next()
35+
item = it.Item()
36+
if item.Typ == itemMutationOp {
37+
it.Prev()
38+
it.Prev()
39+
mu, err := parseMutationBlock(it)
40+
if err != nil {
41+
return nil, err
42+
}
43+
return &api.Request{Mutations: []*api.Mutation{mu}}, nil
44+
}
45+
46+
it.Prev()
47+
it.Prev()
48+
return &api.Request{Query: dqlQuery}, nil
49+
}
50+
}

dql/dql_test.go

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* SPDX-FileCopyrightText: © Hypermode Inc. <[email protected]>
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package dql
7+
8+
import (
9+
"testing"
10+
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestParseDQL(t *testing.T) {
15+
testCases := []string{
16+
`{ set {_:a <name> "Alice" .}}`,
17+
"schema{}",
18+
`upsert {
19+
query {
20+
ns(func: eq(dgraph.namespace.name, "ns1")) {
21+
q as var
22+
dgraph.namespace.id
23+
}
24+
}
25+
26+
mutation {
27+
delete {
28+
uid(q) * * .
29+
}
30+
}
31+
}`,
32+
`{
33+
q(func: eq(dgraph.namespace.name, "ns1")) {
34+
uid
35+
dgraph.type
36+
dgraph.namespace.id
37+
}
38+
}`,
39+
` query test($a: int) {
40+
q(func: uid(0x1)) {
41+
x as count(uid)
42+
p : math(x + $a)
43+
}
44+
}`,
45+
`{
46+
me(func: uid(1)) {
47+
Upvote {
48+
u as Author
49+
}
50+
count(val(u))
51+
}
52+
}`,
53+
`{
54+
user(func: uid(0x0a)) {
55+
...fragmenta,...fragmentb
56+
friends {
57+
name
58+
}
59+
...fragmentc
60+
hobbies
61+
...fragmentd
62+
}
63+
me(func: uid(0x01)) {
64+
...fragmenta
65+
...fragmentb
66+
}
67+
}
68+
fragment fragmenta {
69+
name
70+
}
71+
fragment fragmentb {
72+
id
73+
}
74+
fragment fragmentc {
75+
name
76+
}
77+
fragment fragmentd {
78+
id
79+
}`,
80+
`{
81+
set {
82+
<name> <is> <something> .
83+
<hometown> <is> <san/francisco> .
84+
}
85+
delete {
86+
<name> <is> <something-else> .
87+
}
88+
}`,
89+
}
90+
91+
for _, tc := range testCases {
92+
_, err := ParseDQL(tc)
93+
require.NoError(t, err)
94+
}
95+
}

dql/parser_mutation.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
func ParseMutation(mutation string) (req *api.Request, err error) {
1616
var lexer lex.Lexer
1717
lexer.Reset(mutation)
18-
lexer.Run(lexIdentifyBlock)
18+
lexer.Run(lexTopLevel)
1919
if err := lexer.ValidateResult(); err != nil {
2020
return nil, err
2121
}

dql/parser_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -1960,7 +1960,7 @@ func TestParseMutationError(t *testing.T) {
19601960
`
19611961
_, err := ParseMutation(query)
19621962
require.Error(t, err)
1963-
require.Contains(t, err.Error(), `Invalid block: [mutation]`)
1963+
require.Contains(t, err.Error(), `Unexpected token: [mutation]`)
19641964
}
19651965

19661966
func TestParseMutationError2(t *testing.T) {
@@ -1975,7 +1975,7 @@ func TestParseMutationError2(t *testing.T) {
19751975
`
19761976
_, err := ParseMutation(query)
19771977
require.Error(t, err)
1978-
require.Contains(t, err.Error(), `Invalid block: [set]`)
1978+
require.Contains(t, err.Error(), `Invalid operation type: set`)
19791979
}
19801980

19811981
func TestParseMutationAndQueryWithComments(t *testing.T) {
@@ -4809,7 +4809,7 @@ func TestParseMutationTooManyBlocks(t *testing.T) {
48094809
}{
48104810
set { _:b2 <reg> "b2 content" . }
48114811
}`,
4812-
errStr: "Unrecognized character in lexText",
4812+
errStr: "Unexpected { after the end of the block",
48134813
},
48144814
{m: `{set { _:a1 <reg> "a1 content" . }} something`,
48154815
errStr: "Invalid operation type: something",

dql/state.go

+31-38
Original file line numberDiff line numberDiff line change
@@ -55,43 +55,6 @@ const (
5555
itemStar
5656
)
5757

58-
// lexIdentifyBlock identifies whether it is an upsert block
59-
// If the block begins with "{" => mutation block
60-
// Else if the block begins with "upsert" => upsert block
61-
func lexIdentifyBlock(l *lex.Lexer) lex.StateFn {
62-
l.Mode = lexIdentifyBlock
63-
for {
64-
switch r := l.Next(); {
65-
case isSpace(r) || lex.IsEndOfLine(r):
66-
l.Ignore()
67-
case isNameBegin(r):
68-
return lexNameBlock
69-
case r == leftCurl:
70-
l.Backup()
71-
return lexInsideMutation
72-
case r == '#':
73-
return lexComment
74-
case r == lex.EOF:
75-
return l.Errorf("Invalid mutation block")
76-
default:
77-
return l.Errorf("Unexpected character while identifying mutation block: %#U", r)
78-
}
79-
}
80-
}
81-
82-
// lexNameBlock lexes the blocks, for now, only upsert block
83-
func lexNameBlock(l *lex.Lexer) lex.StateFn {
84-
// The caller already checked isNameBegin, and absorbed one rune.
85-
l.AcceptRun(isNameSuffix)
86-
switch word := l.Input[l.Start:l.Pos]; word {
87-
case "upsert":
88-
l.Emit(itemUpsertBlock)
89-
return lexUpsertBlock
90-
default:
91-
return l.Errorf("Invalid block: [%s]", word)
92-
}
93-
}
94-
9558
// lexUpsertBlock lexes the upsert block
9659
func lexUpsertBlock(l *lex.Lexer) lex.StateFn {
9760
l.Mode = lexUpsertBlock
@@ -368,7 +331,7 @@ Loop:
368331
case r == leftCurl:
369332
l.Depth++ // one level down.
370333
l.Emit(itemLeftCurl)
371-
return lexQuery
334+
return lexIdentifyMutationOrQuery
372335
case r == rightCurl:
373336
return l.Errorf("Too many right curl")
374337
case r == lex.EOF:
@@ -396,6 +359,33 @@ Loop:
396359
return nil
397360
}
398361

362+
func lexIdentifyMutationOrQuery(l *lex.Lexer) lex.StateFn {
363+
l.Mode = lexIdentifyMutationOrQuery
364+
for {
365+
switch r := l.Next(); {
366+
case isSpace(r) || lex.IsEndOfLine(r):
367+
l.Ignore()
368+
case isNameBegin(r):
369+
l.AcceptRun(isNameSuffix)
370+
op := l.Input[l.Start:l.Pos]
371+
// If it is a mutation
372+
if op == "set" || op == "delete" || op == "del" {
373+
l.Emit(itemMutationOp)
374+
return lexInsideMutation
375+
}
376+
// else it is a query
377+
l.Emit(itemName)
378+
return lexQuery
379+
case r == '#':
380+
return lexComment
381+
case r == lex.EOF:
382+
return l.Errorf("Invalid DQL")
383+
default:
384+
return l.Errorf("Unexpected character while lexing DQL: %#U", r)
385+
}
386+
}
387+
}
388+
399389
// lexQuery lexes the input string and calls other lex functions.
400390
func lexQuery(l *lex.Lexer) lex.StateFn {
401391
l.Mode = lexQuery
@@ -592,6 +582,9 @@ func lexOperationType(l *lex.Lexer) lex.StateFn {
592582
case "schema":
593583
l.Emit(itemOpType)
594584
return lexInsideSchema
585+
case "upsert":
586+
l.Emit(itemUpsertBlock)
587+
return lexUpsertBlock
595588
default:
596589
return l.Errorf("Invalid operation type: %s", word)
597590
}

0 commit comments

Comments
 (0)