Recurse at only specified predicates

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