Skip to content

Commit f81956b

Browse files
authored
Add JSON mutations to raw HTTP (#2396)
* Add json mutation to raw http client * Add a test for json mutation * Improve json mutation test * Skip internal JSON marshaling in http muatation handler and add test case for number parsing
1 parent 4f2f250 commit f81956b

File tree

3 files changed

+185
-7
lines changed

3 files changed

+185
-7
lines changed

dgraph/cmd/server/http.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,33 @@ func mutationHandler(w http.ResponseWriter, r *http.Request) {
165165
}
166166

167167
parseStart := time.Now()
168-
mu, err := gql.ParseMutation(string(m))
169-
if err != nil {
170-
x.SetStatus(w, x.ErrorInvalidRequest, err.Error())
171-
return
168+
169+
var mu *api.Mutation
170+
if mType := r.Header.Get("X-Dgraph-MutationType"); mType == "json" {
171+
// Parse JSON.
172+
ms := make(map[string]*skipJSONUnmarshal)
173+
err := json.Unmarshal(m, &ms)
174+
if err != nil {
175+
x.SetStatus(w, x.ErrorInvalidRequest, err.Error())
176+
return
177+
}
178+
179+
mu = &api.Mutation{}
180+
if setJSON, ok := ms["set"]; ok && setJSON != nil {
181+
mu.SetJson = setJSON.bs
182+
}
183+
if delJSON, ok := ms["delete"]; ok && delJSON != nil {
184+
mu.DeleteJson = delJSON.bs
185+
}
186+
} else {
187+
// Parse NQuads.
188+
mu, err = gql.ParseMutation(string(m))
189+
if err != nil {
190+
x.SetStatus(w, x.ErrorInvalidRequest, err.Error())
191+
return
192+
}
172193
}
194+
173195
parseEnd := time.Now()
174196

175197
// Maybe rename it so that default is CommitNow.
@@ -395,3 +417,13 @@ func alterHandler(w http.ResponseWriter, r *http.Request) {
395417
}
396418
w.Write(js)
397419
}
420+
421+
// skipJSONUnmarshal stores the raw bytes as is while JSON unmarshaling.
422+
type skipJSONUnmarshal struct {
423+
bs []byte
424+
}
425+
426+
func (sju *skipJSONUnmarshal) UnmarshalJSON(bs []byte) error {
427+
sju.bs = bs
428+
return nil
429+
}

dgraph/cmd/server/http_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func queryWithTs(q string, ts uint64) (string, uint64, error) {
6464
return string(output), startTs, err
6565
}
6666

67-
func mutationWithTs(m string, commitNow bool, ignoreIndexConflict bool,
67+
func mutationWithTs(m string, isJson bool, commitNow bool, ignoreIndexConflict bool,
6868
ts uint64) ([]string, uint64, error) {
6969
url := "/mutate"
7070
if ts != 0 {
@@ -76,6 +76,9 @@ func mutationWithTs(m string, commitNow bool, ignoreIndexConflict bool,
7676
return keys, 0, err
7777
}
7878

79+
if isJson {
80+
req.Header.Set("X-Dgraph-MutationType", "json")
81+
}
7982
if commitNow {
8083
req.Header.Set("X-Dgraph-CommitNow", "true")
8184
}
@@ -158,7 +161,7 @@ func TestTransactionBasic(t *testing.T) {
158161
}
159162
`
160163

161-
keys, mts, err := mutationWithTs(m1, false, true, ts)
164+
keys, mts, err := mutationWithTs(m1, false, false, true, ts)
162165
require.NoError(t, err)
163166
require.Equal(t, mts, ts)
164167
sort.Strings(keys)

dgraph/cmd/server/run_test.go

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,12 @@ func runQuery(q string) (string, error) {
165165
}
166166

167167
func runMutation(m string) error {
168-
_, _, err := mutationWithTs(m, true, false, 0)
168+
_, _, err := mutationWithTs(m, false, true, false, 0)
169+
return err
170+
}
171+
172+
func runJsonMutation(m string) error {
173+
_, _, err := mutationWithTs(m, true, true, false, 0)
169174
return err
170175
}
171176

@@ -671,7 +676,145 @@ func TestSchemaMutationCountAdd(t *testing.T) {
671676
output, err := runQuery(q1)
672677
require.NoError(t, err)
673678
require.JSONEq(t, `{"data": {"user":[{"name":"Alice"}]}}`, output)
679+
}
674680

681+
func TestJsonMutation(t *testing.T) {
682+
var q1 = `
683+
{
684+
q(func: has(name)) {
685+
uid
686+
name
687+
}
688+
}
689+
`
690+
var q2 = `
691+
{
692+
q(func: has(name)) {
693+
name
694+
}
695+
}
696+
`
697+
var m1 = `
698+
{
699+
"set": [
700+
{
701+
"name": "Alice"
702+
},
703+
{
704+
"name": "Bob"
705+
}
706+
]
707+
}
708+
`
709+
var m2 = `
710+
{
711+
"delete": [
712+
{
713+
"uid": "%s",
714+
"name": null
715+
}
716+
]
717+
}
718+
`
719+
var s1 = `
720+
name: string @index(exact) .
721+
`
722+
723+
schema.ParseBytes([]byte(""), 1)
724+
err := alterSchemaWithRetry(s1)
725+
require.NoError(t, err)
726+
727+
err = runJsonMutation(m1)
728+
require.NoError(t, err)
729+
730+
output, err := runQuery(q1)
731+
q1Result := map[string]interface{}{}
732+
require.NoError(t, json.Unmarshal([]byte(output), &q1Result))
733+
queryResults := q1Result["data"].(map[string]interface{})["q"].([]interface{})
734+
require.Equal(t, 2, len(queryResults))
735+
736+
var uid string
737+
count := 0
738+
for i := 0; i < 2; i++ {
739+
name := queryResults[i].(map[string]interface{})["name"].(string)
740+
if name == "Alice" {
741+
uid = queryResults[i].(map[string]interface{})["uid"].(string)
742+
count++
743+
} else {
744+
require.Equal(t, "Bob", name)
745+
}
746+
}
747+
require.Equal(t, 1, count)
748+
749+
err = runJsonMutation(fmt.Sprintf(m2, uid))
750+
require.NoError(t, err)
751+
752+
output, err = runQuery(q2)
753+
require.NoError(t, err)
754+
require.JSONEq(t, `{"data": {"q":[{"name":"Bob"}]}}`, output)
755+
}
756+
757+
func TestJsonMutationNumberParsing(t *testing.T) {
758+
var q1 = `
759+
{
760+
q(func: has(n1)) {
761+
n1
762+
n2
763+
}
764+
}
765+
`
766+
var m1 = `
767+
{
768+
"set": [
769+
{
770+
"n1": 9007199254740995,
771+
"n2": 9007199254740995.0
772+
}
773+
]
774+
}
775+
`
776+
777+
schema.ParseBytes([]byte(""), 1)
778+
err := runJsonMutation(m1)
779+
require.NoError(t, err)
780+
781+
output, err := runQuery(q1)
782+
var q1Result struct {
783+
Data struct {
784+
Q []map[string]interface{} `json:"q"`
785+
} `json:"data"`
786+
}
787+
buffer := bytes.NewBuffer([]byte(output))
788+
dec := json.NewDecoder(buffer)
789+
dec.UseNumber()
790+
require.NoError(t, dec.Decode(&q1Result))
791+
require.Equal(t, 1, len(q1Result.Data.Q))
792+
793+
n1, ok := q1Result.Data.Q[0]["n1"]
794+
require.True(t, ok)
795+
switch n1.(type) {
796+
case json.Number:
797+
n := n1.(json.Number)
798+
require.True(t, strings.Index(n.String(), ".") < 0)
799+
i, err := n.Int64()
800+
require.NoError(t, err)
801+
require.Equal(t, int64(9007199254740995), i)
802+
default:
803+
require.Fail(t, fmt.Sprintf("expected n1 of type int64, got %v (type %T)", n1, n1))
804+
}
805+
806+
n2, ok := q1Result.Data.Q[0]["n2"]
807+
require.True(t, ok)
808+
switch n2.(type) {
809+
case json.Number:
810+
n := n2.(json.Number)
811+
require.True(t, strings.Index(n.String(), ".") >= 0)
812+
f, err := n.Float64()
813+
require.NoError(t, err)
814+
require.Equal(t, 9007199254740995.0, f)
815+
default:
816+
require.Fail(t, fmt.Sprintf("expected n2 of type float64, got %v (type %T)", n2, n2))
817+
}
675818
}
676819

677820
func TestDeleteAll(t *testing.T) {

0 commit comments

Comments
 (0)