Cannot set value in [uid] predicate in golang: "Input for predicate "predicate_name" of type uid is scalar"

Hello everyone!
I have some trouble in connecting two nodes.

What I want to do

I try to make two nodes Device and Rule with connection o2m.
There is the code:

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

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

const schema = `rule.name: string @index(exact) .
device.name: string @index(exact) .
device.rules: [uid] .
`

type Rule struct {
	Uid  int    `json:"uid,omitempty"`
	Name string `json:"rule.name,omitempty"`
}
type Device struct {
	Uid   int    `json:"uid,omitempty"`
	Name  string `json:"device.name,omitempty"`
	Rules []int  `json:"device.rules,omitempty"`
}

var ctx = context.TODO()

func main() {
	// Set db connection
	conn, err := grpc.Dial("localhost:9080", grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	dgraphClient := dgo.NewDgraphClient(api.NewDgraphClient(conn))

	op := &api.Operation{
		Schema: schema,
	}
	err = dgraphClient.Alter(ctx, op)
	if err != nil {
		panic(err)
	}

	// Create rule
	txn := dgraphClient.NewTxn()
	defer txn.Discard(ctx)

	r := Rule{
		Uid:  1,
		Name: "Rule1",
	}

	pb, err := json.Marshal(r)
	if err != nil {
		log.Fatal(err)
	}

	mu := &api.Mutation{
		SetJson:   pb,
		CommitNow: true,
	}
	res, err := txn.Mutate(ctx, mu)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res)

	// Create Device
	txn = dgraphClient.NewTxn()
	defer txn.Discard(ctx)

	d := Device{
		Uid:   2,
		Name:  "Device1",
		Rules: []int{1},
	}

	pb, err = json.Marshal(d)
	if err != nil {
		log.Fatal(err)
	}

	mu = &api.Mutation{
		SetJson:   pb,
		CommitNow: true,
	}
	res, err = txn.Mutate(ctx, mu)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res)
}

The trouble begins when I try to set Device.Rules. There its text:

2021/06/04 15:24:26 rpc error: code = Unknown desc = Input for predicate "device.rules" of type uid is scalar. Edge: entity:2 attr:"\000\000\000\000\000\000\000\000device.rules" value:"\001\000\000\000\000\000\000\000" value_type:INT 
exit status 1

So how can I set Device.Rules uids?

What I did

This works absolutely fine when I do it in Ratel.

Dgraph metadata

dgraph version

v21.03.0

Hi - see the https://dgraph.io/docs/mutations/json-mutation-format/#edges-between-nodes on using the json mutation format to create edges.

TL;DR: you make a nested struct, not an integer field.

Thank you for your reply.
It is strange to me after using relational databases. Looks like this storing data are natural way for dgraph. But what if I want to add nodes without relations, but add them later. How should I link them later in that case? I mean add in database two (for example) separate nodes and after some time join them. I want to avoid data replication. (Will adding relations between for example rule and device create copy of rule for device?)
Also one node can have many relations with other nodes. So every node should store copy of it? I mean if we create nodes with same nested node for example many devices with one rule, is every device will store copy of rule ? Or dgraph under the hood use pointer on that node instead it copy ?

I would highly suggest using the protobuf NQuads interface within the go library to use a typed system of inserting data, as opposed to the SetJson member of the requests. (its basically a protobuf representation of the RDF format, if you prefer string building, you can use that).

With the NQuads typed interface, you can just set the ObjectId to the uid(eg: 0x123) you wish to point to within a NQuad object.

You can also use upserts to look up ids as well, if you had not read up on that as well.

I finally did it.
I can refer to dgo/example_set_object_test.go at master · dgraph-io/dgo · GitHub.
There every one who wonder can see how to link nodes.

There is my working prototype. Is that Go way to do this?

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

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

const schema = `rule.name: string @index(exact) .
device.name: string @index(exact) .
device.rules: [uid] .
`

type Rule struct {
	Uid  int    `json:"uid,omitempty"`
	Name string `json:"rule.name,omitempty"`
}
type Device struct {
	Uid   int    `json:"uid,omitempty"`
	Name  string `json:"device.name,omitempty"`
	Rules []Rule `json:"device.rules,omitempty"`
}

var ctx = context.TODO()

func main() {
	// Set db connection
	conn, err := grpc.Dial("localhost:9080", grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	dgraphClient := dgo.NewDgraphClient(api.NewDgraphClient(conn))

	op := &api.Operation{
		Schema: schema,
	}
	err = dgraphClient.Alter(ctx, op)
	if err != nil {
		panic(err)
	}

	// Create rule
	txn := dgraphClient.NewTxn()
	defer txn.Discard(ctx)

	r := Rule{
		Uid:  1,
		Name: "Rule1",
	}

	pb, err := json.Marshal(r)
	if err != nil {
		log.Fatal(err)
	}

	mu := &api.Mutation{
		SetJson:   pb,
		CommitNow: true,
	}
	res, err := txn.Mutate(ctx, mu)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res)

	// Create Device
	txn = dgraphClient.NewTxn()
	defer txn.Discard(ctx)

	d := Device{
		Uid:   2,
		Name:  "Device1",
		Rules: []Rule{{Uid: 1}},
	}

	pb, err = json.Marshal(d)
	if err != nil {
		log.Fatal(err)
	}

	mu = &api.Mutation{
		SetJson:   pb,
		CommitNow: true,
	}
	res, err = txn.Mutate(ctx, mu)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res)
}

As I understood it will not fully copy rule in the device but just write it uid in device.rules.

Yea if it works for you, go at it. Especially if you (somehow) know the UIDs you are connecting. Often users would have to look up a UID since they are not-so user controllable (eg: if you put in a big UID it will fail if the zero has not leased it out yet). I do not think(?) the json interface will allow you to upsert. (caveat: I do not use the json mutation method and may be wrong there)

1 Like

Thank You.