Find nodes with edges to nodes filtered by type

So I am trying to find all nodes which have zero links to other nodes.

And this definitely works,

query {
  rootNodes(func: eq(dgraph.type,"Node"))
    @filter(eq( count(Links), 0 )) {
    uid
  }
}

But trying to filter the counter to only count links of a certain type, unfortunately fails completely:

query {
  rootNodes(func: eq(dgraph.type,"Node"))
    @filter(eq( count(Links @filter((eq(dgraph.type,"Node"))), 0 )) {
    uid
  }
}

With the error:

Only val/count/len allowed as function within another. Got: eq

Pretty sure that just doing

{
  count(Links @filter((eq(dgraph.type,"Node")))
}

is possible. So why is it impossible to filter with this value?
Is there some workaround like putting it in a variable and searching that?
What are my options here?

ok, after struggling for couple of hours with various options … found something that works.

query {
  ID as var(func:eq(dgraph.type,"Node")) {
    linksCount as count(Links @filter(eq(dgraph.type, "Node")))
  }

  rootNodes(func:uid(ID))
    @filter(eq(dgraph.type,"Node") AND eq(val(linksCount), 0)) {
    uid
    dgraph.type
    Links {
      uid
      dgraph.type
    }
  }
}

It would really be great if my original attempt would “just work” without pesky errors instead of this convolution.

And then I need to traverse through the graph, say using BFS. Then its a sequence of queries that go like this:

query {
  ID as var(func:type("Node")) {
    linksCount as count(Links @filter(type("Node") AND eq(Name, "nextNameInList")))
  }

  nodes(func:uid(ID))
    @filter(type("Node") AND gt(val(linksCount), 1)) {
    uid
    Name
  }
}

Assuming I have a LOT of nodes, I could add pagination in there. But it would probably be much easier to just query the whole graph and implement the BFS traversal in my own code than try and abuse dgraph like this. Seems that it was not built for traversal, as I couldn’t find any helpers to make it less painful.

Not sure about this syntax. I think this

count(Links) @filter(eq(dgraph.type,"Node"))

would be the right one. Feels odd tho.

Would you mind to share samples(e.g JSONs)? so I can follow what you’re doing. Just reading queries won’t help. It is kind of complicated to stop to analyze several cases just by reading queries. Samples would make easy to reproduce and explore solutions.

ok, I will try and provide a full example of what I am doing.

Suppose I have several nodes, A, B, C, D that have some relationship with each other:

set {
  _:A <dgraph.type> "Node" .
  _:A <name> "A" .
  _:B <dgraph.type> "Node" .
  _:B <name> "B" .
  _:C <dgraph.type> "Node" .
  _:C <name> "C" .
  _:D <dgraph.type> "Node" .
  _:D <name> "D" .

  _:B <Links> _:A .
  _:C <Links> _:A .
  _:C <Links> _:B .
  _:D <Links> _:C .
}

Assuming each such node represents a “class” and I have some rules to create instances from that class, I would want to do that in a breadth-first fashion. This means that first I need to find all the “root” nodes. Like so:

ID as var(func:type("Node")) {
  linkCount as count(Links @filter(type("Node")))
}
# find nodes no other node links to (i.e. root nodes)
rootNodes(func:uid(ID)) @filter(eq(val(linkCount), 0)) {
  name
  Links {
    name
  }
  Instances {
    name
  }
}

Now for each of these roots, I would create an instance and attach that like so:

upsert {
  query {
    nodeA as var(func: type("Node")) @filter(eq(name,"A"))
  }
  mutation {
    set {
      _:X <dgraph.type> "Instance" .
      _:X <name> "X" .
      uid(nodeA) <Instances> _:X .
    }
  }
}

And now the tricky part, I want to find the next level of nodes to create instances from - but I only want to find those nodes that have relationships to nodes that already have instances.

For example, this will find both “B” and “C”. Because each “B” and “C” has a link to “A” and then “A” has an instance created on it.

query {
  leafNodes(func:type("Node"))
    @cascade
    @filter(NOT has(Instances))
  {
    name
    Links @filter(ge(count(Instances),1)) {
      name
    }
  }
}

This is great! But “C” also has a link to “B”, which does not yet have an instance … and thus I cannot yet instantiate “C” because not all of its requirements have been met.

So I would want to see “C” in the query result only after both “A” and “B” have instances created, not before.

Try this

query {
  LF as var(func:type("Node")) @cascade @filter(NOT has(Instances)) {
    Links @filter(ge(count(Instances),1))
    }
  ExcludeThis as var(func:uid(LF)) @cascade {
    Links @filter(NOT has(Instances))
    }
  leafNodes(func:uid(LF)) @filter(NOT uid(ExcludeThis)){
    name
    Links {
      name
    }
  }
}