megatrond
megatrond

Reputation: 123

Cypher return multiple hops through pattern of relationships and nodes

I'm making a proof of concept access control system with neo4j at work, and I need some help with Cypher.

The data model is as follows:

(:User|Business)-[:can]->(:Permission)<-[:allows]-(:Business)

Now I want to get a path from a User or a Business to all the Business-nodes that you can reach trough the

 -[:can]->(:Permission)<-[:allows]- 

pattern. I have managed to write a MATCH that gets me halfway there:

MATCH
  path = 
    (:User {userId: 'e96cca53-475c-4534-9fe1-06671909fa93'})-[:can|allows*]-(b:Business)

but this doesn't have any directions, and I can't figure out how to include the directions without reducing the returned matches to only the direct matches (i.e it doesn't continue after the first hit on a :Business node)

So what I'm wondering is:

  1. Is there a way to match multiple of these hops in one query?
  2. Should I model this entirely different?
  3. Am I on the wrong path completely and the query should be completely rewritten

Upvotes: 2

Views: 1542

Answers (3)

mojo2go
mojo2go

Reputation: 11

I see a good solution has been provided via path expanding APOC procedures.
But I'll focus on your point #2: "Should I model this entirely differently?" Well, not entirely but I think yes.

The really liberating part of working with Neo4j is that you can change the road you are driving over as easily as you can change your driving strategy: model vs query. And since you are at an early stage in your project, you can experiment with different models. There's a good opportunity to make just a semantic change to make an 'end run' around the problem.

The semantics of a relationship in Neo4j are expressed through

  1. the mandatory TYPE you assign to the relationship, combined with
  2. the direction you choose to point the mandatory arrow

The trick you solved with APOC was how to traverse a path of relationships that alternate between pointing forward and backward along the query's path. But before reaching for a power tool, why not just reverse the direction of either of your relationship types. You can change the model for allows from

<-[:allows]-

to

-[:is_allowed_by]->

and that buys you a lot. Now the directions of both relationships are the same and you can combine both relationships into a single relationship in the match pattern. And the path traversal can be expressed like this, short & sweet:

(u:User)-[:can|is_allowed_by*]->(c:Company)

That will literally go to all lengths to find every user-to-company path, branching included.

Upvotes: 1

InverseFalcon
InverseFalcon

Reputation: 30407

Currently the syntax of variable-length expansions doesn't allow fine control for separate directions for certain types. There are improvements in the pipeline around this, but for the moment Cypher alone won't get you what you want.

We can use APOC Procedures for this, as fine control of the direction of types in the expansion, and sequences of relationships, are supported in the path expander procs.

First, though, you'll need to figure out how to address your user-or-business match, either by adding a common label to these nodes by which you can MATCH either type by property, or you can use a subquery with two UNIONed queries, one for :Business nodes, the other for :User nodes, that way you can still take advantage of an index on either, and get possible results in a single variable.

Once you've got that, you can use apoc.path.expandConfig(), passing some options to get what you want:

// assume you've matched to your `start` node already
CALL apoc.path.expandConfig(start, {relationshipFilter:'can>|<allows', labelFilter:'>Business'}) YIELD path
RETURN path

This one doesn't use sequences, but it does restrict the direction of expansion per relationship type. We are also setting the labelFilter such that :Business nodes are the end node of the path and not nodes of any other label.

Upvotes: 2

Dom Davis
Dom Davis

Reputation: 331

You can specify the path as follows:

MATCH path = (:User {userId: $id})-[:can]->(:Permission)
    <-[:allows]-(:Business))
RETURN path

This should return the results you're after.

Upvotes: 1

Related Questions