diff --git a/gql/parser.go b/gql/parser.go index 39f5ddc7c29d4f27368b07ea349a7c21e7411d73..7892595072a48b3b1220892a9618df0d4f24a917 100644 --- a/gql/parser.go +++ b/gql/parser.go @@ -36,6 +36,11 @@ type GraphQuery struct { Children []*GraphQuery } +type Mutation struct { + Set string + Del string +} + func run(l *lex.Lexer) { for state := lexText; state != nil; { state = state(l) @@ -43,10 +48,11 @@ func run(l *lex.Lexer) { close(l.Items) // No more tokens. } -func Parse(input string) (gq *GraphQuery, rerr error) { +func Parse(input string) (gq *GraphQuery, mu *Mutation, rerr error) { l := lex.NewLexer(input) go run(l) + mu = nil gq = nil for item := range l.Items { if item.Typ == itemText { @@ -54,7 +60,13 @@ func Parse(input string) (gq *GraphQuery, rerr error) { } if item.Typ == itemOpType { if item.Val == "mutation" { - return nil, errors.New("Mutations not supported") + if mu != nil { + return nil, nil, errors.New("Only one mutation block allowed.") + } + mu, rerr = getMutation(l) + if rerr != nil { + return nil, nil, rerr + } } } if item.Typ == itemLeftCurl { @@ -62,16 +74,71 @@ func Parse(input string) (gq *GraphQuery, rerr error) { gq, rerr = getRoot(l) if rerr != nil { x.Err(glog, rerr).Error("While retrieving subgraph root") - return nil, rerr + return nil, nil, rerr } } else { if err := godeep(l, gq); err != nil { - return gq, err + return nil, nil, err } } } } - return gq, nil + return gq, mu, nil +} + +func getMutation(l *lex.Lexer) (mu *Mutation, rerr error) { + for item := range l.Items { + if item.Typ == itemText { + continue + } + if item.Typ == itemLeftCurl { + mu = new(Mutation) + } + if item.Typ == itemRightCurl { + return mu, nil + } + if item.Typ == itemMutationOp { + if err := parseMutationOp(l, item.Val, mu); err != nil { + return nil, err + } + } + } + return nil, errors.New("Invalid mutation.") +} + +func parseMutationOp(l *lex.Lexer, op string, mu *Mutation) error { + if mu == nil { + return errors.New("Mutation is nil.") + } + + parse := false + for item := range l.Items { + if item.Typ == itemText { + continue + } + if item.Typ == itemLeftCurl { + if parse { + return errors.New("Too many left curls in set mutation.") + } + parse = true + } + if item.Typ == itemMutationContent { + if !parse { + return errors.New("Mutation syntax invalid.") + } + if op == "set" { + mu.Set = item.Val + } else if op == "delete" { + mu.Del = item.Val + } else { + return errors.New("Invalid mutation operation.") + } + } + if item.Typ == itemRightCurl { + return nil + } + } + return errors.New("Invalid mutation formatting.") } func getRoot(l *lex.Lexer) (gq *GraphQuery, rerr error) { diff --git a/gql/parser_test.go b/gql/parser_test.go index d8be58c5c029197a6605607154efd9194ee3126d..c1c00ddaa60fe574e6f70608c19e10ca7825dbfb 100644 --- a/gql/parser_test.go +++ b/gql/parser_test.go @@ -18,6 +18,7 @@ package gql import ( "fmt" + "strings" "testing" ) @@ -41,7 +42,7 @@ func TestParse(t *testing.T) { } ` - gq, err := Parse(query) + gq, _, err := Parse(query) if err != nil { t.Error(err) } @@ -82,7 +83,7 @@ func TestParseXid(t *testing.T) { type.object.name } }` - gq, err := Parse(query) + gq, _, err := Parse(query) if err != nil { t.Error(err) return @@ -99,22 +100,6 @@ func TestParseXid(t *testing.T) { } } -func TestParse_error1(t *testing.T) { - query := ` - mutation { - me(_uid_:0x0a) { - name - } - } - ` - var err error - _, err = Parse(query) - t.Log(err) - if err == nil { - t.Error("Expected error") - } -} - func TestParse_error2(t *testing.T) { query := ` query { @@ -124,7 +109,7 @@ func TestParse_error2(t *testing.T) { } ` var err error - _, err = Parse(query) + _, _, err = Parse(query) t.Log(err) if err == nil { t.Error("Expected error") @@ -141,7 +126,7 @@ func TestParse_pass1(t *testing.T) { } } ` - gq, err := Parse(query) + gq, _, err := Parse(query) if err != nil { t.Error(err) } @@ -168,7 +153,7 @@ func TestParse_block(t *testing.T) { } } ` - gq, err := Parse(query) + gq, _, err := Parse(query) if err != nil { t.Error(err) } @@ -179,3 +164,134 @@ func TestParse_block(t *testing.T) { t.Error(err) } } + +func TestParseMutation(t *testing.T) { + query := ` + mutation { + set { + <name> <is> <something> . + <hometown> <is> <san francisco> . + } + delete { + <name> <is> <something-else> . + } + } + ` + _, mu, err := Parse(query) + if err != nil { + t.Error(err) + return + } + if strings.Index(mu.Set, "<name> <is> <something> .") == -1 { + t.Error("Unable to find mutation content.") + } + if strings.Index(mu.Set, "<hometown> <is> <san francisco> .") == -1 { + t.Error("Unable to find mutation content.") + } + if strings.Index(mu.Del, "<name> <is> <something-else> .") == -1 { + t.Error("Unable to find mutation content.") + } +} + +func TestParseMutation_error(t *testing.T) { + query := ` + mutation { + set { + <name> <is> <something> . + <hometown> <is> <san francisco> . + } + delete { + <name> <is> <something-else> . + } + ` + _, _, err := Parse(query) + if err == nil { + t.Error(err) + return + } + t.Log(err) +} + +func TestParseMutation_error2(t *testing.T) { + query := ` + mutation { + set { + <name> <is> <something> . + <hometown> <is> <san francisco> . + } + delete { + <name> <is> <something-else> . + } + } + mutation { + set { + another one? + } + } + + ` + _, _, err := Parse(query) + if err == nil { + t.Error(err) + return + } + t.Log(err) +} + +func TestParseMutationAndQuery(t *testing.T) { + query := ` + mutation { + set { + <name> <is> <something> . + <hometown> <is> <san francisco> . + } + delete { + <name> <is> <something-else> . + } + } + query { + me(_xid_: tomhanks) { + name + hometown + } + } + ` + gq, mu, err := Parse(query) + if err != nil { + t.Error(err) + return + } + + if mu == nil { + t.Error("mutation is nil") + return + } + if strings.Index(mu.Set, "<name> <is> <something> .") == -1 { + t.Error("Unable to find mutation content.") + } + if strings.Index(mu.Set, "<hometown> <is> <san francisco> .") == -1 { + t.Error("Unable to find mutation content.") + } + if strings.Index(mu.Del, "<name> <is> <something-else> .") == -1 { + t.Error("Unable to find mutation content.") + } + + if gq == nil { + t.Error("subgraph is nil") + return + } + if gq.XID != "tomhanks" { + t.Error("Expected: tomhanks. Got: %v", gq.XID) + return + } + if len(gq.Children) != 2 { + t.Errorf("Expected 2 children. Got: %v", len(gq.Children)) + return + } + if err := checkAttr(gq.Children[0], "name"); err != nil { + t.Error(err) + } + if err := checkAttr(gq.Children[1], "hometown"); err != nil { + t.Error(err) + } +} diff --git a/query/query_test.go b/query/query_test.go index c11f2b1aadc82909754b429757a1b9fc8856a8a7..44bbbd07c93bf4403f9ee393bdb913b7f6dcf999 100644 --- a/query/query_test.go +++ b/query/query_test.go @@ -190,7 +190,7 @@ func TestProcessGraph(t *testing.T) { } } ` - gq, err := gql.Parse(query) + gq, _, err := gql.Parse(query) if err != nil { t.Error(err) } @@ -282,7 +282,7 @@ func TestToJson(t *testing.T) { } ` - gq, err := gql.Parse(query) + gq, _, err := gql.Parse(query) if err != nil { t.Error(err) } diff --git a/server/main.go b/server/main.go index 5085ff9fdef228fa478b87c3cd792cd117d2008f..11c240aa6ed1d08e2132f0e86ab63faac5e92bd4 100644 --- a/server/main.go +++ b/server/main.go @@ -72,7 +72,7 @@ func queryHandler(w http.ResponseWriter, r *http.Request) { } glog.WithField("q", string(q)).Debug("Query received.") - gq, err := gql.Parse(string(q)) + gq, _, err := gql.Parse(string(q)) if err != nil { x.Err(glog, err).Error("While parsing query") x.SetStatus(w, x.E_INVALID_REQUEST, err.Error()) diff --git a/server/main_test.go b/server/main_test.go index 0c384f160c57d5b934f2ee43921a92b82295f601..010195130e0932fbebc2c57546c2476276643fe9 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -88,7 +88,7 @@ func TestQuery(t *testing.T) { defer closeAll(dir1, dir2, clog) // Parse GQL into internal query representation. - gq, err := gql.Parse(q0) + gq, _, err := gql.Parse(q0) if err != nil { t.Error(err) return @@ -184,7 +184,7 @@ func BenchmarkQuery(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - gq, err := gql.Parse(q1) + gq, _, err := gql.Parse(q1) if err != nil { b.Error(err) return