How to detect bidirectional relationships?

What I want to do

I have a service graph which connect services and their dependencies, here’s the schema:

name: string @index(exact) .
dependsOn: [uid] @reverse .
type Service {
  name
  dependsOn
}

and sample data:

_:a <dgraph.type> "Service" .
_:a <name> "serviceA" .
_:b <dgraph.type> "Service" .
_:b <name> "serviceB" .
_:c <dgraph.type> "Service" .
_:c <name> "serviceC" .
_:d <dgraph.type> "Service" .
_:d <name> "serviceD" .

_:a <dependsOn> _:b .
_:a <dependsOn> _:c .
_:b <dependsOn> _:c .
_:c <dependsOn> _:b .
_:b <dependsOn> _:d .

I want to find all service pairs that depends on each other, serviceB and serviceC is what I expected in example data above.

What I did

I’ve tried many queries but none get work, here’s is one of my queries:

{
	q(func: eq(dgraph.type, "Service")) {
    uid
    name
    D as dependsOn {
      uid
      name
    }
    providesOf: ~dependsOn @filter(uid(D)) {
      uid
      name
    }
  }
}

result is:

{
    "q": [
      {
        "uid": "0xfffd8d67d85fdd75",
        "name": "serviceA",
        "dependsOn": [
          {
            "uid": "0xfffd8d67d85fdd76",
            "name": "serviceB"
          },
          {
            "uid": "0xfffd8d67d85fdd77",
            "name": "serviceC"
          }
        ]
      },
      {
        "uid": "0xfffd8d67d85fdd76",
        "name": "serviceB",
        "dependsOn": [
          {
            "uid": "0xfffd8d67d85fdd77",
            "name": "serviceC"
          },
          {
            "uid": "0xfffd8d67d85fdd78",
            "name": "serviceD"
          }
        ]
      },
      {
        "uid": "0xfffd8d67d85fdd77",
        "name": "serviceC",
        "dependsOn": [
          {
            "uid": "0xfffd8d67d85fdd76",
            "name": "serviceB"
          }
        ]
      },
      {
        "uid": "0xfffd8d67d85fdd78",
        "name": "serviceD"
      }
    ]
  }

What did I missunderstanding? Any suggestions? :rofl:

Dgraph metadata

dgraph version

[Decoder]: Using assembly version of decoder
Page Size: 4096

Dgraph version : v20.11.0
Dgraph codename : tchalla
Dgraph SHA-256 : 8acb886b24556691d7d74929817a4ac7d9db76bb8b77de00f44650931a16b6ac
Commit SHA-1 : c4245ad55
Commit timestamp : 2020-12-16 15:55:40 +0530
Branch : HEAD
Go version : go1.15.5
jemalloc enabled : true

For Dgraph official documentation, visit https://dgraph.io/docs/.
For discussions about Dgraph , visit http://discuss.dgraph.io.

Licensed variously under the Apache Public License 2.0 and Dgraph Community License.
Copyright 2015-2020 Dgraph Labs, Inc.

A workaround is run a query below for each service, but it not a beat solution clearly, I have to write a piece of program to do this.

query q($name: string) {
	S as q(func: eq(name, $name)) @cascade {
    uid
    name
    dependsOn {
      uid
      name
      dependsOn @filter(uid(S)) {
      	uid
        name
      }
    }
  }
}

I’m looking for a DQL only solution.

It should be like

{
  G as q(func: eq(dgraph.type, "Service")) {
    D as dependsOn
}

  q(func: uid(G)) {
    uid
    name
    dependsOn {
      uid
      name
    }
    providesOf: ~dependsOn @filter(uid(D)) {
      uid
      name
    }
  }
}

Another way

{
  G as q(func: eq(dgraph.type, "Service")) {
    D as dependsOn
}

  q(func: uid(G)) @filter(uid_in(<~dependsOn>, uid(D))) {
    uid
    name
    dependsOn {
      uid
      name
    }
    providesOf: ~dependsOn  {
      uid
      name
    }
  }
}

Unfortunately, both of two queries doesn’t return what I expected, I want find all service pairs that depends on each other (interdependence) only.

In your query, variable D is the collection of uids that any Service dependsOn, not for a particular one in the next query block.

From dgraph tour:

Variables evaluate to all uid’s matched in the query by the block they are defined against. In particular, note that, variables are uid lists, not the graph matched by the block, and that the variable evaluates to all uid’s the block matches for the whole query, not the uid’s matched by any one branch.

I don’t see in your query example particular filtering. So I tried to just do the same thing. Generally

Just add cascade to the query.

Yep, to collect a single UID you have to filter it. Or make sure that the edge has always only one UID.

How did you solve it on your end?

{
  G as var(func: eq(dgraph.type, "Service")) {
    D as dependsOn
  }

  q(func: uid(G)) @cascade {
    uid
    name
    interdependence: ~dependsOn @filter(uid(D)) {
      uid
      name
    }
  }
}

Run query above (just add @cascade) on data set I give at beginning, it returns:

{
  "data": {
    "q": [
      {
        "uid": "0x2",
        "name": "serviceB",
        "interdependence": [
          {
            "uid": "0x3",
            "name": "serviceC"
          }
        ]
      },
      {
        "uid": "0x3",
        "name": "serviceC",
        "interdependence": [
          {
            "uid": "0x2",
            "name": "serviceB"
          }
        ]
      },
      {
        "uid": "0x4",
        "name": "serviceD",
        "interdependence": [
          {
            "uid": "0x2",
            "name": "serviceB"
          }
        ]
      }
    ]
  }
}

Clearly, serviceD and serviceB doesn’t depends on each other.

I just run query below for every Service, it returns no result if a Service doesn’t have any interdependence relations. The disadvantages are obvious, I have to fetch all services first, and then run query for each of it. So, I’m looking for a DQL only solution.