Skip to content

Multiple Mutations Create Invalid JSON with Duplicate Fields #9422

@escquery

Description

@escquery

Environment

  • Dgraph version: 24.1.2
  • dgo version: v240

Issue Summary

When using multiple mutations in a single transaction where the first mutation deletes an edge and the second mutation creates a new edge, the resulting JSON contains duplicate fields for the same edge predicate, even though the edge is defined as uid (not [uid]). This produces invalid JSON with two identical field names in the same object.

Test Schema

<name>: string @index(exact) .
<like>: uid @reverse .
<fruit>: string @index(exact) .

type Person {
	name
	like
}
type Fruit {
	fruit
}

Test Data

{
    set {
        _:person <name> "Tom" .
        _:person <dgraph.type> "Person" .
        _:apple <fruit> "apple" .
        _:apple <dgraph.type> "Fruit" .
        _:banana <fruit> "banana" .
        _:banana <dgraph.type> "Fruit" .
        _:person <like> _:apple .  
    }
}

Test Code

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/dgraph-io/dgo/v240"
	"github.com/dgraph-io/dgo/v240/protos/api"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func query() {
	conn, err := grpc.NewClient("localhost:9080", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	dg := dgo.NewDgraphClient(api.NewDgraphClient(conn))
	dql := `{
  q(func: eq(name, "Tom")) {
	uid
	like { uid fruit }
  }
}`
	req := &api.Request{
		Query: dql,
	}

	response, err := dg.NewTxn().Do(context.Background(), req)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%v\n", string(response.Json))
}

func mutation() {
	conn, err := grpc.NewClient("localhost:9080", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	dg := dgo.NewDgraphClient(api.NewDgraphClient(conn))
	dql := `{
	  person as var(func: eq(name, "Tom"))
	  banana as var(func: eq(fruit, "banana"))
	  }`
	mu1 := &api.Mutation{
		DelNquads: []byte(`uid(person) <like> * .`),
		// CommitNow: true,
	}
	mu2 := &api.Mutation{
		SetNquads: []byte(`uid(person) <like> uid(banana) .`),
		// CommitNow: true,
	}
	req := &api.Request{
		Query:     dql,
		Mutations: []*api.Mutation{mu1, mu2},
		CommitNow: true,
	}
	txn := dg.NewTxn()
	response, err := txn.Do(context.Background(), req)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%v\n", response)
}

func main() {
	mutation()
	query()
}

Actual Result

json:"{}"  txn:{start_ts:1071  commit_ts:1072  preds:"1-0-like"}  latency:{parsing_ns:48115  processing_ns:873283  encoding_ns:13938  assign_timestamp_ns:382101  total_ns:1412244}  metrics:{num_uids:{key:"_total"  value:2}  num_uids:{key:"fruit"  value:0}  num_uids:{key:"mutation_cost"  value:2}  num_uids:{key:"name"  value:0}}  hdrs:{key:"content-type"  value:{value:"application/grpc"}}  hdrs:{key:"dgraph-toucheduids"  value:{value:"2"}}
{"q":[{"uid":"0x1","like":{"uid":"0x2","fruit":"apple","uid":"0x3","fruit":"banana"}}]}

Problem Description

The query returns invalid JSON where the like object contains duplicate uid fields:

"like":{"uid":"0x2","fruit":"apple","uid":"0x3","fruit":"banana"}

This is invalid JSON since objects cannot have duplicate keys. The edge should only point to one UID (banana) after the mutation, not both the old (apple) and new (banana) values.

Expected Result

After the mutations, the query should return:

{"q":[{"uid":"0x1","like":{"uid":"0x3","fruit":"banana"}}]}

Additional Notes

  • When using only mu2 (set mutation) without mu1 (delete mutation), the behavior is correct
  • The issue only occurs when combining delete and set mutations in the same transaction
  • The like predicate is defined as uid (single value), not [uid] (list), so it should not contain multiple values

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions