Recurse at only specified predicates

Hi all.

I have a simple schema here.

<name>: string .
<parent>: uid .
<child>: uid .

Given a specific name, I want to recursively get all the children, grandchildren…, but I only need the immediate parent.

Since I cannot use recurse at the predicate like

{
  tree(func: eq(name, "bob")) {
    child @recurse 
    parent 
    name
  }
}

I tried to use a variable

{
  var(func: eq(name, "bob")) {
    parent {
      parentName as name   
    }
  }
}

{
  tree(func: eq(name, "bob")) @recurse  {
    child
    val(parentName)
    name
  }
}

but the variable is added to the parent of bob, not bob.

Thank you for your help.

1 Like

Not sure how to construct it in a single query but you could try to have two different queries and combine the results in your application

{
   tree(func: eq(name, "bob")) {
    parent {
      name
    }
    name
  }
}

{
  children(func: eq(name, "bob")) @recurse  {
    child
  }
}

Thank you Martin. This does work. Is there a reason why recurse at an edge is not allowed? It seems like a common use case.

2 Likes

Not sure since I haven’t touched that part of the codebase. I’ll take a look tomorrow to see if it’s possible to get rid of that restriction.

Thank you. That would be great. Besides maybe slower, the other drawback of using two queries is that when there are multiple results, it requires merging the two query results manually by uid matching

I talked to Manish (the founder) and he said you would need two queries to do this.

Michel Conrado just pointed out to me that the following query would be more performant.

{
   tree(func: eq(name, "bob")) {
   p as uid
    parent {
      name
    }
    name
  }
}

{
  children(func: uid(p) ) @recurse  {
    child
  }
}

since the index only needs to be traversed once.

3 Likes

Also, if the height of your graph is constant, you could do it in a query with something like:

{
  tree(func: eq(name, "bob")) {
    child { expand(_all_) { expand(_all_) { expand(_all_) { expand(_all_) }}}}
    parent 
    name
  }
}

Again, this wouldn’t work if the number of expand(all) statements needed is not the same for every node but I am mentioning this in case it fits your use case.

Got it. Using uid to query children is a great idea. Thanks.

Hi @myokomu, I just starting looking at dgraph yesterday and decided to model a folder/file structure to get a feel for how it might work with hierarchical data. I’m still getting my head around things but figured some of my queries might help you because they seem closely related…

Sample Data Set
A basic tree structure like:

"Folder 1"
  | - "Folder 2"
        | - "Folder 3"
  | - "Folder 4"
  | - "Folder 5"
# Add Indexes
<name>: string @index(exact, term, trigram) .

# Configure Relationships (two-way)
<parent>: uid @count .
<children>: uid @count .

# Insert Data
{
  set {
    <_:node1> <name> "Folder 1" .
    <_:node2> <name> "Folder 2" .
    <_:node3> <name> "Folder 3" .
    <_:node4> <name> "Folder 4" .
    <_:node5> <name> "Folder 5" .
    
    <_:node1> <children> <_:node2> (type="unknown", date=2006-01-02T15:04:05) .
    <_:node2> <parent> <_:node1> (type="unknown", date=2006-01-02T15:04:05) .

    <_:node2> <children> <_:node3> (type="unknown", date=2006-01-02T15:04:05) .
    <_:node3> <parent> <_:node2> (type="unknown", date=2006-01-02T15:04:05) .

    <_:node1> <children> <_:node4> (type="unknown", date=2006-01-02T15:04:05) .
    <_:node4> <parent> <_:node1> (type="unknown", date=2006-01-02T15:04:05) .

    <_:node1> <children> <_:node5> (type="unknown", date=2006-01-02T15:04:05) .
    <_:node5> <parent> <_:node1> (type="unknown", date=2006-01-02T15:04:05) .
  }
}

The Queries
These were my initial attempts at bringing back the usual things you’d want from a Folder/File structure (or any hierarchy really). The key here was using @recurse.

# Immediate Children
{
  children(func: eq(name, "Folder 1")) @recurse(depth: 2) {
    uid
    name
    children(orderasc: name) {
      uid
      name
    }
  }
}

# Immediate Parent
{
  parent(func: eq(name, "Folder 3")) @recurse(depth: 2) {
    uid
    name
    parent(orderasc: name) {
      uid
      name
    }
  }
}

# All Descendants
{
  descendants(func: eq(name, "Folder 1")) @recurse {
    uid
    name
    children(orderasc: name) {
      uid
      name
    }
  }
}

# All Ancestors
{
  ancestors(func: eq(name, "Folder 3")) @recurse {
    uid
    name
    parent(orderasc: name) {
      uid
      name
    }
  }
}

One challenge I ran into was trying to find all siblings without making a mess of the data response and creating repetition/bloat. The @normalize enabled me to get what I wanted and actually helped me rewrite the two of the above.

# Siblings
{
  siblings(func: eq(name, "Folder 2")) @normalize {
    uid
    name
    parent {
      uid
      name
      children(orderasc: name) {
        uid: uid
        name: name
      }
    }
  }
}

# Immediate Children
{
  children(func: eq(name, "Folder 1")) @normalize {
    uid
    name
    children(orderasc: name) {
      uid: uid
      name: name
    }
  }
}

# Immediate Parent
{
  parent(func: eq(name, "Folder 3")) @normalize {
    uid
    name
    parent(orderasc: name) {
      uid: uid
      name: name
    }
  }
}

Taking it a step further I was able to put together an array forming the path’s crumbs which I felt could be useful…

# Path Crumbs (from parent)
{
  paths(func: eq(name, "Folder 1")) @normalize @recurse {
    uids: uid
    crumbs: name
    children(orderasc: name) {
      uid
      name
    }
  }
}

Thoughts
No idea how performant these types of queries are vs. the ones previously mentioned. Also I’m not sure that the @normalize option is conventional to use because you are effectively returning an array instead of a clear relationship with a central/start node, BUT in the case of siblings it was nice because it seemed like otherwise I would have to return the node its parent and then all its children which seemed unnecessary. Anyways, I figured I’d share these incase they help.


Jon

1 Like