Under what scenarios does Dgraph return an array in place of a string?

I have a schema in which one node references another, and sometimes when I query that node I get an array instead of a single string response. Unfortunately, lot of code will break if the value is sometimes a string and sometimes not. I’m not sure I can work around this, so I’d like to understand what scenarios may cause it so I may try to avoid it entirely. I’d say it happens about half the time.

Schema:

type Foo {
  bar
}
bar: uid .

type Bar {
  bar_name
}
bar_name: string @index(exact) .

A script that demonstrates it: demo.js · GitHub

I’m running Dgraph v21.03.0 in a single-alpha/single-zero configuration (in Docker).

with this you get an object instead of array.

[uid] .

with this above you get always an array.

Also reverse edges you always get arrays. No matter what.

why this line?

116		bar: 'uid(bar3)',

line 125, you are not using vars, you should use the main method instead.

        "bar_name": [
          "bar 2",
          "bar 3"
        ]

Looks like somehow your code transformed your bar_name into a list. Or maybe your code messup in the loop?

It doesn’t look like this is always happening (based on the demo script anyway).

Ok, I can work with this, or anything where it’s always one or the other.

I’m using 116 to assign the value of the bar3 query to the bar uid predicate, replacing the previous bar2 uid that was set there in line 84.

I’m not sure what you mean about line 125. I’m looking up every Foo to print its results (although there’s only one). The foo variable is out of scope at that point as the request that defined it was run on line 123.

I don’t know, running the script multiple times gives different output for the bar_name predicate. Do you see a problem in the loop?

This looks odd. It should be like:

		mu.setSetJson({
			'bar': [{'uid': 'uid(bar2)'}], #be careful here, make sure it is ONE to ONE
			'dgraph.type': 'Foo',
		});

As bar is an Edge connecting to another object. This feels like a bug. But for now, fix the syntax to the one I mentioned above.

Do you really need to pass 'dgraph.type': 'Foo' all the time? You should add it in the creation, not in an upsert. But if you are creating it during the upsert, It is fine. Just a small thing, not important.

Line 99

I’m not sure what you are doing in that line. You say “Switch to bar 3” is it a migration of nodes to other references? You should unlink the others if it is a migration.

Fix the syntax there.

Also, if you have a lot of foos foo as var(func: type(Foo)) you will link them all in that upsert. That might be there the issue.

Maybe is the mutation syntax. Check it and let me know.

I haven’t (until now) tried mutating the predicate using an array in setJson. I wouldn’t have expected it to work. However, the results are the same with all four options below:

mu.setSetJson({
  bar: 'uid(bar2)',
});

mu.setSetJson({
  bar: {uid: 'uid(bar2)'},
});

mu.setSetJson({
  bar: ['uid(bar2)'],
});

mu.setSetJson({
  bar: [{uid: 'uid(bar2)'}],
});

That is, I sometimes get an array response to the query in postCommit and sometimes not.

No not in this case, it’s just habit on my part. A long while back I had forgotten it in a few places and a lot of stuff broke so now I’ve just been explicit every time.

I don’t know what you mean – I’m trying to replace the value in the predicate. Do I need to do a delete mutation and then a set mutation whenever I want to replace a value in a scalar/uid predicate?

Yeah – in this case, I have one, so I could make a (mostly) minimally reproducible demo. I’m more explicit in my real code.

For now I guess I just need to check to see if the result is an array instead of the expected string and retry the query. This is concerning, though, because I don’t know how many retries I will need to run (and how long I’ll need to make users wait), but it is a workaround.

Btw, for future readers: removing the dgraph.type set in the preCommit function (in the gist) didn’t change the result – the query still sometimes returns a string and sometimes returns an array.

@MichelDiz I may have found a workaround, and I think this might mean there’s a bug. If I change my queries to:

query {
	foo(func: type(Foo)) {
		uid
		bar {
			uid <-- Added this
			bar_name
		}
	}
}

the bar_name result in Pre-Commit is always a string:

Pre-commit
{
  "foo": [
    {
      "uid": "0x19cb5b",
      "bar": {
        "uid": "0x19cb59",
        "bar_name": "bar 3"
      }
    }
  ]
}

You’ll see a similar result if you remove bar_name entirely and just query bar { uid } or bar { count(bar_name) } – basically any time you specify only one predicate or function in the braces.