Find all inbound outbound edges

Hi there.

How can I find out all edges in and out bound to an vertice?

Take it for example if have a “contact_person” vertice which can be bound to and from any node using an arbitrary number of “edge types” like “know”, “known_by”, “employee_of”, “has_purchased”, etc…, and such relations could not be predicted whether they exists or not beforehand.

Now I have “joe” and “mary” as “contact_person” each one with a dynamic number of edges.

How do I do to find out what edges each one have, in and out bound do it?

Thanks!

3 Likes

Not sure I fully understand the question but using the @reverse directive in the schema for these edges, you can then use ~ in front of the field name to see inbound edges.

query {
    hey(func: uid(JOEUID)) {
        known_by { ... }
        ~known_by { ... }
    }
}

In the first known_by it says JOE is known by { xyz }, the second one means JOE knows… That way, you do not need knownBy and knows edges.

Hi. Thanks for your response, but no, this is not what I need.

I do not know how many, and how are the edges that a specific node may have, and thats what I need to discover.

Much like the “_predicate_” directive I would need a “_edges_” one, or maybe a set of “_edges_”, “_inbound_edges_” and “_outbound_edges_” to retrieve all edges of a node or only those in one direction.

Doesn’t expand(all) { expand(all) } do the trick?

No. It doesnt. Imagine that given an uid, you need now to list all edges point to and from it, along with its “names”.

1 Like

The concept of Edges is “Subject, Predicate, Object, Label”. In this case “expand (all)” would be the correct one to locate all Edges and their relationships.
Combining with “~ reversedEdge” and Subs “expand (all) {expand (all)}”.

If you want to list predicates. Just _predicate_ can do this like for example:

{
  director(func: eq(name@en, "Geoffrey Rush")) {
    _predicate_
    actor.film {
      _predicate_
  ~maybeReversed {
      _predicate_
      }
    }
  }
}

Do the following. Build a mutation close to what the model you want, and then build a supposed response you want to receive. To understand better.

Hi. Thanks for your responses, but I think you are not getting my point.

Proposal: I have a node uid 0x01 that has some (maybe one, maybe fifty) relationships with other nodes. I do not know how many, and I do not know to which other nodes.

Problem: Build a list with the names of each relationship the node 0x01 has.

Obs 1: I dont care about the other nodes, only about the edges.
Obs 2: It does not need to go deep, only direct edges will suffice.

So you want to expand only predicates that have a relationship. It is? I do not think Dgraph has a filter for that.

At least you need to know them all.

You can use inverse Expand (all). In theory you should already know which predicates that generate relationships (all of possible ones). So you can build a query with all possible predicates. What does not expand is because there is no relation. The Dgraph will not return Edges that has no reaction.

{
  director (func: eq (name @ en, "Geoffrey Rush")) {

   ~ friendOf {
            expand (_all_)
  }
   ~ OnwnerOf {
            expand (_all_)
  }
   ~ DirectorOf {
            expand (_all_)
  }
   ~ PresidentOf {
            expand (_all_)
  }
}

All predicate must exist @reverse, and not all of them will expand if they do not exist. Now if you want to create lists you can do this: (Just an idea)

{
 var (func: uid (0x1)) {

   ~ friendOf {
            friend as uid # name
  }
   ~ OnwnerOf {
            Objetc as uid # name
  }
   ~ DirectorOf {
            Film as uid # film.name
  }
   ~ PresidentOf {
            Company as uid # Company.name
  }

myList1 (func: uid (friend)) {
name
}
myList3 (func: uid (Objetc)) {
name
}
myList4 (func: uid (Movie)) {
film.name
}
myList5 (func: uid (Company)) {
Company.name
}
}

Some of these lists will come empty if relations do not exist. If not, give some more details as I said above.

Take a lonk on this https://docs.dgraph.io/query-language#expand-predicates

I know it’s not related

{
  var(func: eq(name@en, "Lost in Translation")) {
    pred as _predicate_
    # expand(_all_) { expand(_all_)}
  }

  director(func: eq(name@en, "Lost in Translation")) {
    name@.
    expand(val(pred)) {
      expand(_all_)
    }
  }
}
1 Like

Well, I’ll do some tests here but I think its a bump.

For one hand I have the power to relate any piece of information to every other to my heart contents.

Then I can freely create new relationships without the hassle of creating M:N tables and doing insane JOINS (SQL).

But when it comes to get back the info stored, I just cant. For dynamic structural data systems this kind of feature is a must. And the info stored as inferred relationships, which is often lost or hard to get on a SQL db is exactly the power one seeks in a graph database, after all.

Some parts of DGraph seems in a “almost” state to me. I mean, if you do all your queries by hand and know exactly what you have, its ok, but when you go more “dynamic” with things it falls short.

I do think that implementing those _edges_ directives should’nt be that hard and it would be a great tool.

Thanks, I’ll report after the tests.

2 Likes

Actually, this do explode all my data to me:

{
    q(func: has(__is_users)) {
        expand(_all_) { 
          expand(_all_) {
            expand(_all_) {
              expand(_all_)
            }
          }
        } 
    }
}

But, as in my question about uids, this left to me the work to parse all trough the data, when I could had received just the plain edge list for each __is_user in the query.

Did not solved my question, but its a starting point.

Just to make it clear, this query:

{
    q(func: has(__is_users)) {
	    _predicate_
    }
}

Returns me this:

{
  "data": {
    "q": [
      {
        "_predicate_": [
          "_updated_uid",
          "_created_uid",
          "user",
          "pessoas",
          "_created_time",
          "confirm",
          "_created_by",
          "__is_users",
          "pwd",
          "_updated_by",
          "_updated_time"
        ]
      }
    ]
  },
  ...

Ok, thats good. So, please let this query:

{
    q(func: has(__is_users)) {
	    _edges_
    }
}

Return me this:

{
  "data": {
    "q": [
      {
        "uid": 0x1
        "_edges_": [
          "knows",
          "employee_of",
          "friend_of"
        ]
      },
      {
        "uid": 0x2
        "_edges_": [
          "knows",
          "son_of",
          "married_with",
          "employee_of"
       ]
      }
    ]
  },

expand (all) I think it’s very unplanned (result too variable.), I find it much better to develop solid query patterns. Let it expanding out everything. This way you create unforeseen JSON formats. Not every application would know how to handle it dynamically.

I would recommend expand (all) only to explore relationships between nodes still in the planning stage.

I do not know, I think if the DB Dgraph administrator knows how to plans well, he does not need features like that. Creates an entire directive just to know when a Node has relations with other nodes through Edges seems to me a wasted time. Being that you can create a simple pattern on your application.

Dgraph was created inspired by GraphQL. There are no such things in GraphQL (not that I know of, after all GraphQL is not a DB.), everything is solved internally in GraphQL for DBs.

If you demonstrate real usability convincing the Engineers and defend(uphold) it as a feature. Who knows. Maybe it’s not complicated to do in a short time, could be a “filter” for the directive predicate like:

  _predicate_ @filter (has (uid)) # would return predicates that have relations only.
  _predicate_ @filter (has (int))
  _predicate_ @filter (has (string))

See (the rodamap link) there are many features to come out yet for Dgraph. In the short term I do not know if your need would come easy.

But it is right there my point: you are not considering that a beast like DGraph allows you to create some really nifty sneaky clever dynamic apps, with possibilities of grow much beyond regular SQL based systems.

I could PLAN an app to happily work with NON beforehand PLANNED relationships. If only I had a little more info, or access, or control whatever, over my lets say, metadata. Intelligent apps, growing with the user, and evolving as its been used, thats what I’m talking about and thats why I’m “here”.

Now, this DO include the need of a really smart planing of your schema and data, but goes much beyond that. You’ll need to build logic that needs be able to figure your metadata out on the fly.

DGraph, please: think dynamic, show me the meta. =]

Thanks.

2 Likes

P.S: predicate filter is nice, but I dont think it could deliver what inbound_edges and outbound_edges could. Why not have both? =]

1 Like

But if you expand(_all_) then unless you use arrays of scalar values in your schema, if you find an array for a given predicate, it is an edge pointing to n other nodes. And if your source node doesn’t have relationship “relatesTo”, then it wouldn’t appear.

If you use arrays of scalars, then ignore the above as an array for a given JSON field could be either an array of nodes, or an array of strings, integers, etc. And if your do not know in advance, then you are unable to filter out upon receiving the JSON result.

This feature can be super useful for introspecting data interactively.

This ask is trivial to do with Cypher:

MATCH (p)-[edge *]->(m) where id(m) = 150 return p, edge, m

I can not seem to figure out if this is possible in DGraph. This seems like a rather trivial usecase for querying underlying triples.

As a newbie to DGraph it is certainly difficult to work with without a way to introspect edges in both directions without knowing what those edges are beforehand.

It feels like something like this, should work but it sadly does not…
(assuming you want to find all of the edges leading into node with uid of 0xff)

{
  result(func: has(dgraph.type)) {
    uid
    expand(_all_) @filter(uid(0xff)) {
      uid
    }
  }
}
2 Likes

Also new to Dgraph, and having a lot of trouble determining if this is possible or not. It seems like a reasonable desire: sometimes you’ve been handed a dataset and need to discover its shape. Just looking at the schema isn’t enough, because edges that point to a UID don’t tell you the type of the actual node behind that UID (at least with the large movie database given in the Tour).

No amount of expand calls seems to help. I’m basically forced to guess what’s at the other end, making call after call to each of the available predicate names to see what works. Is there truly no alternative?

I think there is a massive amount of misunderstanding / people talking over each other in this thread. So please allow me to spend some time to clarify. I apologize in advance if I seem condescending. However, there are twofold benefits to this:

  1. I get to be clearer about my understanding of Dgraph things. Hopefully this does for you too.
  2. There’s always someone in the ten thousand.

Concepts

First, some concepts. This is useful because it frames the discussion. I will not use new terms without first introducing them first. I find this adds a lot of clarity to the situation.

Node

What is a node? A node in Dgraph is just a number - the UID.

But don’t nodes usually hold some data? We’ll get to that in a bit. For now, a node is just a number.

Edge

What is an edge? An edge is a connection between two nodes. In Dgraph, an edge is defined as

<uid> --- Predicate --- <uid>

Now, a Predicate is just the “type” of edge. Let’s continue with a more concrete example.

Let’s say there are two “types” of predicates: knows and reads.

A node (UID) may have multiple edges to multiple other nodes (UIDs). A imagery representation is presented below (ignore the red lines in all the images in this post [1]

image

This graph is represented by a list of edges, which we can put in a table below:

From To Predicate
0x1 0x2 knows
0x1 0x2 reads
0x1 0x3 reads
0x2 0x3 reads

One thing about this representation is that every edge is unique. You cannot repeat a row in the table above.

Where Is The Data Stored

So far we’ve talked about the abstract graph with some concrete examples. The graph in the example above is still just a bunch of numbers in a table. Not very useful (unless you’re a mathematician). No, we need to understand what each node represents.

So, let’s say each node represents a person. And all people have names[2].

So we want to add names to the nodes. How does this happen in Dgraph? Well, we use… edges!

We update the graph to be as follows:

There are some new things in here. Specifically there are edges that are dashed with the label “value”. These are not real edges in the graph. Rather, these are pointers to where the data is stored (i.e. in badger).

The table is now something like this:

From To Predicate Data Data Type
0x1 0x2 knows
0x1 0x2 reads
0x1 0x3 reads
0x2 0x3 reads
0x1 0x4 name “foo” string
0x2 0x5 name “bar” string
0x3 0x6 name "baz string

Types

So now our graph is a LOT more complicated. We can use the type system to define things better.

I’ll introduce a little bit of DQL syntax here because the syntax is quite clear in reflecting the semantics.

type Person {
    name
}

Here we are saying a Person is any node with a predicate name. The graph now looks like this

So what happens when we define two other types?

type Nerd {
    reads
}

type Fixer {
     knows
}

0x1 is now a Person, a Nerd and a Fixer! 0x2 is now a Person and a Nerd.

This is also commonly called “structural typing” in the programming language world. If you use Go, you may think of these “types” we have designed so far as interfaces.

Unfortunately Graphviz, the software I use to draw these graphs, are unable to handle these sorts of structural typing. You can imagine drawing overlapping boxes representing each connection…

Structural typing is really cool. But it’s probably not such a wise thing to do in a highly performant graph database either.

So instead, in Dgraph, types are nominal. They are represented by… edges!

Here, I coloured the types yellow and omitted the usual UID things. It’s also short one edge from 0x3 to nerd from summoning Beelzeebub from the fiery depths of Hell. You should be thankful I didn’t add that for this example.

Putting it all to practice

So, let’s put it all into practice. What are the implications of the paragraphs above?

Set up

To start off, let’s create a schema:

<knows>: [uid] .
<name>: string .
<reads>: [uid] .
<title>: string @index(hash) .
type <Fixer> {
	knows
}
type <Nerd> {
	reads
}
type <Person> {
	name
}

And now, let’s put in some data:

{

  set {
		_:x1 <name> "foo" .
    _:x1 <dgraph.type> "Person" .
    
    _:x2 <name> "bar" .
    _:x2 <dgraph.type> "Person" .
    
    _:x3 <name> "baz" .
    _:x3 <dgraph.type> "Person" .
    
    _:x1 <reads> _:x2 .
    _:x1 <knows> _:x2 .
    _:x1 <reads> _:x3 .
    
    _:x1 <dgraph.type> "Nerd" .
    _:x1 <dgraph.type> "Fixer" .
    
    _:x2 <reads> _:x3 .
    _:x2 <dgaph.type> "Nerd" .
		
// in the picture below, x4 is 0x7
    _:x4 <name> "quux" .
    _:x4 <reads> _:x3 .
  }

}

The graph you should have in your head should look something like this (apologies for the SPECTRE-ish layout. I’m not Blofeld. The circo layout engine would not draw clusters)

I added two things in this picture (when compared to the previous):

  1. I added 0x7 (“quux”) and 0x8, which I have labelled in a box called “untyped”
  2. I added the string type, of which are the type of names.

Now we are ready to see the implications of all that was written so far. We will do this in a narrative style (i.e. I will show you results that don’t conform to your expectation, and then show you how to modify the query to work towards what you want)

First Attempt

We may query for anything that has a type (something @NonLogicalDev had nearly correct):

Let’s start with this:

{
  q(func:has(<dgraph.type>)) @filter(not(type(<dgraph.graphql>))) @recurse  {
      expand(_all_)
  }
}

This results in the following JSON:

"data": {
    "q": [
      {
        "name": "bar"
      },
      {
        "name": "baz"
      },
      {
        "name": "foo"
      }
    ]
  },

All it returns are edges with the type "name"! Whatever happened to the other edges we defined?

Simply put, they were not expanded. This is because _all_ is a rather misleading term. It only expands all terminal edges (i.e. edges that lead to a value of a primitive type). It doesn’t expand predicates that are themselves graphs (i.e. subgraphs).

We can however, tell Dgraph to expand even the subgraphs. This is done by the @recurse directive.

Second Attempt: with @recurse

Adding @recurse, the query now looks like this:

{
  q(func:has(<dgraph.type>)) @filter(not(type(<dgraph.graphql>))) @recurse  {
      uid
      expand(_all_)
  }
}

On my machine, this returns:
image

The JSON result is as follows:

"data": {
    "q": [
      {
        "uid": "0x11",
        "reads": [
          {
            "uid": "0x12",
            "name": "baz"
          }
        ],
        "name": "bar"
      },
      {
        "uid": "0x12",
        "name": "baz"
      },
      {
        "uid": "0x14",
        "reads": [
          {
            "uid": "0x11",
            "name": "bar"
          },
          {
            "uid": "0x12",
            "name": "baz"
          }
        ],
        "name": "foo",
        "knows": [
          {
            "uid": "0x11",
            "name": "bar"
          }
        ]
      }
    ]
  }

Here you can see all the relevant non-meta edges have been returned correctly. You can simply parse the JSON for the predicates for your “inbound” or “outbound” edges

Job Done! Or Is It?

Querying for any nodes that has a <dgraph.type> and then adding @recurse seems to have done the job. But this doesn’t really fulfill what @labs20, @NonLogicalDev and @fosskers wants.

What they want is to input a UID, and then get all the predicates. Simple enough. Let’s try the following query:

{
  q(func:uid("0x14")) @recurse  {
    uid  
    expand(_all_)
  }
}

And the results are as follows:

"data": {
    "q": [
      {
        "uid": "0x14",
        "name": "foo",
        "knows": [
          {
            "uid": "0x11",
            "name": "bar"
          }
        ],
        "reads": [
          {
            "uid": "0x11",
            "name": "bar"
          },
          {
            "uid": "0x12",
            "name": "baz"
          }
        ]
      }
    ]
  }

Once again, you can just traverse the JSON to pick up all the keys as predicates that the node 0x14 (“foo”) has.

But Wait! I Don’t See Quux

You may have noticed that you do not see "quux" anywhere in any of the results. Why is that? That’s because _all_ only works on nodes with types.

So the following query

{
  q(func:uid("0x13")) @recurse  {
    uid  
    expand(_all_)
    }
}

Would not even return name, which is a terminal edge:

"data": {
    "q": [
      {
        "uid": "0x13"
      }
    ]
  }

If you have untyped data, your best bet would be to list all the known predicates and query for it:

{
  q(func:uid("0x13")) @recurse  {
    uid  
    knows
    reads
    name
  }
}

which will yield

  "data": {
    "q": [
      {
        "uid": "0x13",
        "reads": [
          {
            "uid": "0x12",
            "name": "baz"
          }
        ],
        "name": "quux"
      }
    ]
  }

Moral of the Story

I can think of three lessons I learned from writing this post:

  1. Dgraph’s concept and representation of an edge is quite different to other graph databases’ concept and representation. Understanding the underlying representations is vital in understanding what you can do, and why what you want to do may not be the thing you want to use.
  2. Use @recurse when you need to traverse the graph recursively.
  3. Types are friends, not food.

Footnotes


  1. I used my own version of Graphviz which I had modded to always output the first edge in red. ↩︎

  2. Technically not true - See also: Falsehood #40 in the List of Falsehoods Programmers Believe About Names ↩︎

6 Likes

This is still not clear.
How would you translate this query to GQL ?

in Gremlin → g.V(13).in()
in Cypher → MATCH (x)-[]->(y) WHERE id(y) = 13 RETURN x
in DQL → ???

GraphQL is strictly typed and strictly queried. You cannot ask for anything generic unless it is already defined as an interface or union.

Given the GraphQL schema:

type x {
  id: ID
  hasY: [y] @hasInverse(field: "hasX")
}
type y {
  id: String! @id
  hasX: [x]
}

You can get the incoming relationships to Y=13 by finding the outgoing relationships of that node if they are mapped with the @hasInverse directive.

query {
  getY(id: "13") {
    hasX {
      id
    }
  }
}

FYI, there is an idea on the development roadmap to support Gremlin. Maybe give your thoughts and use cases over there.

2 Likes