Recurse after filter


(Akshay Kayastha) #1

I am experimenting with DGraph for a solution in our company. I came across a situation where I have a graph structure as shown below:

Now I need to perform the following actions:

Given:

  • blue_name
  • yellow_name
  • red_name

Steps To Perform:

  1. Filter blue node with blue_name and find its yellow nodes
  2. Filter yellow nodes with yellow_name and find their red nodes
  3. Filter ALL red nodes and its child red nodes with red_name

Now, I was able to reach till step three, when I realized I would need to recurse through all subsequent red nodes to find my target node. I tried using recurse but I realized I could not obtain that using multiple filters either (or maybe I am wrong). The example in the docs is fairly basic and does not apply to this scenario.

Can someone verify is this is doable with dgraph, and if so, guide me?


(Michel Conrado (Support Engineer)) #2

Please provide more details like Types, indexes and mutation examples (preferably in JSON). The quickest way to help is to understand its structure in practice. An abstract conversation takes longer to get answers.

Cheers.


(Akshay Kayastha) #3

Sure thing. This is the simplified schema that I am using currently.

<category>: uid @count  .
<brand>: uid @count  .
<content>: string @index(trigram)  .
<image>: string  .
<name>: string @index(trigram) .
<sublevel>: uid @count  .
<title>: string @index(term)  .

And these are the sample mutations:


{
  set {
    _:some_brand <name> "some_brand" .
    _:some_brand <category> _:some_brand_some_device .
    
    _:some_brand_some_device <name> "some_device" .
    _:some_brand_some_device <brand> _:some_brand .
    _:some_brand_some_device <sublevel> _:sublevel_1_1 .
    _:some_brand_some_device <sublevel> _:sublevel_1_2 .
    
    _:sublevel_1_2 <name> "sublevel_1_2" .
    _:sublevel_1_2 <title> "Sublevel Level 1.2" .
    
    
    _:sublevel_1_1 <name> "sublevel_1_1" .
    _:sublevel_1_1 <title> "Sublevel Level 1.1" .

    _:sublevel_1_1 <sublevel> _:sublevel_2_1 .
    _:sublevel_1_1 <sublevel> _:sublevel_2_2 .
    _:sublevel_1_1 <sublevel> _:sublevel_2_3 .
    _:sublevel_1_1 <sublevel> _:sublevel_2_4 .
    _:sublevel_1_1 <sublevel> _:sublevel_2_5 .
    
    _:sublevel_2_1 <name> "sublevel_2_1" .
    _:sublevel_2_1 <title> "Sublevel Level 2.1" .
    _:sublevel_2_2 <name> "sublevel_2_2" .
    _:sublevel_2_2 <title> "Sublevel Level 2.2" .
    _:sublevel_2_3 <name> "sublevel_2_3" .
    _:sublevel_2_3 <title> "Sublevel Level 2.3" .
    _:sublevel_2_4 <name> "sublevel_2_4" .
    _:sublevel_2_4 <title> "Sublevel Level 2.4" .
    _:sublevel_2_5 <name> "sublevel_2_5 .
    _:sublevel_2_5 <title> "Sublevel Level 2.5" .

    _:sublevel_2_1 <sublevel> _:sublevel_3_1 .

    _:sublevel_3_1 <name> "sublevel_3_1" .
    _:sublevel_3_1 <title> "Sublevel Level 3.1" .
    _:sublevel_3_1 <image> "/path/to/image.png" .
    _:sublevel_3_1 <content> "This is details about sublevel 3.1" .
  }
}

A short explanation for the above would be as follows:

  1. brand and device have a many-to-many mapping.
  2. A specific device of a specific brand may have several multi-tiered sublevels.

Now given that I have the name for a specific device, brand and sublevel, I need to return all the child predicates under it.

I tried a few approaches where I tried a filter for device at the root and ran a recurse on it, but I could not get it to filter for brand too. It seems I am missing something out @MichelDiz ?


(Michel Conrado (Support Engineer)) #4

I’m taking your case for now, sorry for the delay. I’m still a bit confused about the structure. But here follows a Query:

{
  f(func: has(category))  @recurse(depth: 5, loop: true){
    uid
    expand(_all_){expand(_all_)}
  }

}


(Michel Conrado (Support Engineer)) #5

From what you said, evaluating that you already know the name of the device, what is the brand and the sublevel of it. I imagined this query.

0x2739 = some_brand

{
  f(func: regexp(name, /^some_device.*$/)) @recurse(depth: 5, loop: true)@filter(uid_in(brand, 0x2739)) {
    uid
    name
		brand
  sublevel @filter(regexp(name, /^sublevel_1_2.*$/)) {expand(_all_)}}
}

Response

{
  "data": {
    "f": [
      {
        "uid": "0x2733",
        "name": "some_device",
        "brand": [
          {
            "uid": "0x2739",
            "name": "some_brand"
          }
        ],
        "sublevel": [
          {
            "uid": "0x273a",
            "name": "sublevel_1_2"
          }
        ]
      }
    ]
  }

(Akshay Kayastha) #6

Hi @MichelDiz,

Thanks for your response. This is indeed helpful. However, the query below does not work for the same thing:

{
  f(func: regexp(name, /^some_device.*$/)) @recurse(depth: 5, loop: true)@filter(uid_in(brand, 0x274f)) {
    uid
    name
    brand
  sublevel @filter(regexp(name, /^sublevel_2_1.*$/)) {expand(_all_)}}
}

Response

{
  "data": {
    "f": [
      {
        "uid": "0x2750",
        "name": "some_device",
        "brand": [
          {
            "uid": "0x274f",
            "name": "some_brand"
          }
        ]
      }
    ]
  },
  "extensions": {
    "server_latency": {
      "parsing_ns": 20209,
      "processing_ns": 4129009,
      "encoding_ns": 1221752
    },
    "txn": {
      "start_ts": 20415
    }
  }
}

As you can see, when filtering for one of the nested levels, the query fails to find the required node.


(Michel Conrado (Support Engineer)) #7

What version of Dgraph are you using? recently were introduced new features. I’m using v1.0.10-rc1

This query is the same as mine. It does not make sense. Unless the structure you gave me is different from the one you’re using.

But the most important thing is that uid_in is working. You do not necessarily need “brand” node. Because uid_in is already using it. Note that if you put a non-existent UID the query is empty.


(Akshay Kayastha) #8

I am using the latest docker image for dgraph: dgraph/dgraph:latest which has the following version:

Dgraph version   : v1.0.9
Commit SHA-1     : 22c13fce
Commit timestamp : 2018-10-02 16:45:53 -0700
Branch           : HEAD

Sorry for not explaining clerarly in the earlier response. Please note that I changed sublevel_1_2 to sublevel_2_1.
Your exact query does work on mine. Querying for sublevel nodes after the first level do not work however. (sublevel_2* and sublevel_3*)
Do you recommend trying out v1.0.10-rc1?


(Michel Conrado (Support Engineer)) #9

if mine q is working, no worries. But when expanding your structure with…

{
  f(func: regexp(name, /^some_device.*$/)) @filter(uid_in(brand, 0x2739)) {
    uid
    name
		brand {expand(_all_)}
  	sublevel #@filter(regexp(name, /^sublevel_1_2.*$/)) 
      {
      uid
      name
      title
      sublevel #@filter(regexp(name, /^sublevel_1_2.*$/))
        {
      uid
      name
      title
        sublevel #@filter(regexp(name, /^sublevel_1_2.*$/))
        {
      uid
      expand(_all_)
    }
    }
    }
  }
  }

We see a lot of levels to be filtered. So each level you have to add a predictable filter. Recurse can’t predict this for you.

Response

{
  "data": {
    "f": [
      {
        "uid": "0x2733",
        "name": "some_device",
        "brand": [
          {
            "name": "some_brand",
            "uid": "0x2739"
          }
        ],
        "sublevel": [
          {
            "uid": "0x2734",
            "name": "sublevel_1_1",
            "title": "Sublevel Level 1.1",
            "sublevel": [
              {
                "uid": "0x2732",
                "name": "sublevel_2_4",
                "title": "Sublevel Level 2.4"
              },
              {
                "uid": "0x2735",
                "name": "sublevel_2_3",
                "title": "Sublevel Level 2.3"
              },
              {
                "uid": "0x2736",
                "name": "sublevel_2_2",
                "title": "Sublevel Level 2.2"
              },
              {
                "uid": "0x2737",
                "name": "sublevel_2_5",
                "title": "Sublevel Level 2.5"
              },
              {
                "uid": "0x273b",
                "name": "sublevel_2_1",
                "title": "Sublevel Level 2.1",
                "sublevel": [
                  {
                    "uid": "0x2738",
                    "name": "sublevel_3_1",
                    "content": "This is details about sublevel 3.1",
                    "image": "/path/to/image.png",
                    "title": "Sublevel Level 3.1"
                  }
                ]
              }
            ]
          },
          {
            "uid": "0x273a",
            "name": "sublevel_1_2",
            "title": "Sublevel Level 1.2"
          }
        ]
      }
    ]
  }

BUT sublevel_2_1 is a level after the first sublevel so

Query without recurse


{
  f(func: regexp(name, /^some_device.*$/))  @filter(uid_in(brand, 0x2739)) {
    uid
    name
		brand {expand(_all_)}
  	sublevel @filter(regexp(name, /^sublevel_1_1.*$/)) 
      {
      uid
      name
      title
      sublevel @filter(regexp(name, /^sublevel_2_1.*$/))
        {
      uid
      name
      title
        sublevel #@filter(regexp(name, /^sublevel_1_2.*$/))
        {
      uid
      expand(_all_)
    }
    }
    }
  }
  }

Response

{
  "data": {
    "f": [
      {
        "uid": "0x2733",
        "name": "some_device",
        "brand": [
          {
            "name": "some_brand",
            "uid": "0x2739"
          }
        ],
        "sublevel": [
          {
            "uid": "0x2734",
            "name": "sublevel_1_1",
            "title": "Sublevel Level 1.1",
            "sublevel": [
              {
                "uid": "0x273b",
                "name": "sublevel_2_1",
                "title": "Sublevel Level 2.1",
                "sublevel": [
                  {
                    "uid": "0x2738",
                    "name": "sublevel_3_1",
                    "content": "This is details about sublevel 3.1",
                    "image": "/path/to/image.png",
                    "title": "Sublevel Level 3.1"
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }

(Akshay Kayastha) #10

I see.

I was hoping that since the nodes are of the same type, recurse would be able to reach a certain level (depth) without having to specify it. Do you think this is a feature that could be supported?


(Akshay Kayastha) #11

I finally was able to use query variables to sort of do this instead, and that works out great for me.

{ 
  var(func: regexp(name, /^some_brand.*$/i)) @filter(has(category)) {
    category @filter(has(sublevel) and regexp(name, /^some_device.*$/i)) {
      l1 as sublevel {
        l2 as sublevel {
          l3 as sublevel {
            l4 as sublevel {
              l5 as sublevel
            }
          }
        }
			}
    }
  }
  var(func: uid(l1,l2,l3,l4,l5)) @filter(regex(name, /^sublevel_2_1.*$/i)){
    a as uid
    pred as _predicate_
  }
  all(func: uid(a)) {
    expand(val(pred)) {
      expand(_all_)
    }
  }
}

Thank you @MichelDiz for your help!