Upsert using Go client creates duplicate nodes

I’m trying to do an upsert using Go client based on the official README and godoc, but it seems to be creating duplicate entries in the database o_O

This is a short snippet of my code:

	query := `
          query Node($xid: string){
		     node(func: eq(xid, $xid)) {
			      xid
		     }
          }
	`

	node := &Node{
		UID:   obj.UID().String(),
		Name:  obj.Name(),
		Kind:  obj.Kind(),
		Ns:    obj.Namespace(),
		DType: []string{"Object"},
	}

	pb, err := json.Marshal(node)
	if err != nil {
		return nil, err
	}

	mu := &api.Mutation{
		SetJson: pb,
	}

	req := &api.Request{
		Query:     query,
		Vars:      map[string]string{"$xid": obj.UID().String()},
		Mutations: []*api.Mutation{mu},
		CommitNow: true,
	}

	ctx := context.Background()
	txn := d.client.NewTxn()
	defer txn.Discard(ctx)

	if _, err := txn.Do(ctx, req); err != nil {
		return nil, err
	}

The idea is to create the Object only if it does not exist, but the above keeps creating duplicate entries. Am I missing something here? This is the JSON result

{
  "data": {
    "node": [
      {
        "xid": "objectUID",
        "name": "fooKind-objectX",
        "kind": "fooKind",
        "ns": "fooNS"
      },
      {
        "xid": "objectUID",
        "name": "fooKind-objectX",
        "kind": "fooKind",
        "ns": "fooNS"
      }
    ]
  },

Thanks

Can you provide the complete snippet?

From a first look, it looks your query block is not assigning any query variables. And the mutation is happening unconditionally since it is not using any variables.

You should assign a var in the query block and use that in mutation as shown in the examples

Sure, but I doubt it will make things any clearer

func (d *dgraph) Add(obj Object) (store.Node, error) {
	query := `
          query Node($xid: string){
		      node(func: eq(xid, $xid)) {
			      xid
		      }
          }
	`

	node := &Node{
		UID:       obj.UID().String(),
		Name:      obj.Name(),
		Kind:      obj.Kind(),
		Namespace: obj.Namespace(),
		DType:     []string{"Object"},
	}

	pb, err := json.Marshal(node)
	if err != nil {
		return nil, err
	}

	mu := &api.Mutation{
		SetJson: pb,
	}

	req := &api.Request{
		Query:     query,
		Vars:      map[string]string{"$xid": obj.UID().String()},
		Mutations: []*api.Mutation{mu},
		CommitNow: true,
	}

	ctx := context.Background()
	txn := d.client.NewTxn()
	defer txn.Discard(ctx)

	if _, err := txn.Do(ctx, req); err != nil {
		return nil, err
	}

	snode := entity.NewNode(node.UID)

	return snode, nil
}

For the completeness, this is the schema:

var Schema = `
	type Object {
		xid
		name
		kind
		namespace
	}

	xid: string @index(exact) .
	name: string @index(exact) .
	kind: string @index(exact) .
	namespace: string @index(exact) .
`

This was my suspicion, but I expect passing Vars: map[string]string{"$xid": obj.UID().String()}, to the api.Request automatically assigns the $xid to the query before execution the Mutation? Am I missing anything here?

I shall also add I’m using the following version of dgraph:

dgraph/standalone              v2.0.0-beta         38fd01d675aa        2 months ago        153MB

And the following version of Go client:

github.com/dgraph-io/dgo/v200 v200.0.0-20200402171935-2ec5bf1438b0

I am unaware of how Vars is used. I will need to learn about it.

However, in the meantime, can you assign a variable in the query block itself exactly as shown in the examples? That should just work.

Are you suggesting I should avoid marshalling into JSON completely and take the Nquads approach ?

I think you are using Upsert Block. Unfortunately there nos support for GraphQL Variables in Upsert Block.

Follow this ticket

No.
In the godocs https://godoc.org/github.com/dgraph-io/dgo#example-Txn-Mutate-Upsert , there is also an example for JSON Upsert. Please follow that.

Ah, I totally missed that! Ok, I will check this out later on and send an update, but I suspect @MichelDiz is right. I am using the GraphQL vars >_<

I also suspect, judging by the quick look at the code, without the GraphQL vars I’ll have to do some mad string concatenation to write the query ugh :sweat_smile: so the code itself will become a bit of an unwieldy mess, but I guess if there is no other way.

Ok, I just quickly wrote some code to test the @Paras’ suggestion to follow the godoc upsert JSON example did the trick.

I also feel @MichelDiz was right to highlight the GraphQL vars do not work with upserts at the moment – I really hope they will do, soon.

I’d like to accept both answers as solutions because I believe both are correct. I shall leave you two to decide who gets the green tick :wink:

Either way, thanks to both of you for pointing me in the right direction!

Done, as both answers are different valid solutions. Your answer holds the solution.

1 Like

For anyone following this thread, the answer turns to be this:

query := `
	{
		node(func: eq(xid, "` + obj.UID().String() + `")) {
			u as uid
		}
	}
	`

	node := &Node{
		UID:       "uid(u)",
		XID:       obj.UID().String(),
		Name:      obj.Name(),
		Kind:      obj.Kind(),
		Namespace: obj.Namespace(),
		CreatedAt: time.Now(),
		DType:     []string{"Object"},
	}

NOTE: the query variable is uid and not xid, which is then used for serialization (Dgraph creates a unique uid in the DB when the new object is created)