Help building a selective recursive query

Hi,

I have the following graph with entities and roles in a tree. The roles attach to various entities in the tree and would provide access to all entities from the attached entity, down through the tree until it gets to another entity with a role attached.

eg

role 1 - Domain1
-- Sub1
role 2 -- Sub2
--Sub3

I want to query to start at domain1 returning role1 and recurse the tree returning all the sub domains that don't have a role attached to assoiate to the previous role.

So as an output I'm trying to effectively create an list of roles with their associated scope of domains they cover.

role1 {
scope: [domain1, sub1]
}
role2 {
scope: [sub2, sub3]
}

I've been trying out different quieries and have been able to return a set of results that does map the roles to the domains. The problem is I can't get the query to stop returning domains once it hits a lower role. This is my query so far.

select * where {
?user a :User;
?user :hasRole ?role.
?role :canAccess ?roleDomain;
?roleDomain a :Domain;
:name ?roleDomainName.

OPTIONAL {
    ?roleDomain (:hasDomain)+ ?subdomain.
    FILTER EXISTS {
        ?subdomain ^:canAccess ?subRole.
    }
    ?subdomain :name ?subName;
}    

}

This does return a row for role for each domain so 4 rows as follows

role 1 -> domain1 -> sub1
role 1 -> domain1 -> sub2
role 1 -> domain1 -> sub3
role 2 -> sub2 -> sub3

But I don't want the follwoing 2 lines returned.

role 1 -> domain1 -> sub2
role 1 -> domain1 -> sub3

Weirdly I was trying to say filter not exists, as in filter out subdomains that have a role, but that doesn't seem to work at all, so feels like FILTER EXISTS above is backwards, but it's the only way I can at least get the rows to return.

Any help would be greatly appreciated.

Hi Paul,

I had to guess at your data, but I was able to get the result you want by changing ?subRole to ?role in the FILTER EXISTS block. Does this modification work for you? I'm wondering if adding both subdomains and subroles is adding unnecessary complexity (and hence returning more results than you want).

For reference, here is the toy data I created to try to replicate your problem:

@prefix : <http://api.stardog.com/> .

:user1 a :User ;
    :hasRole :role1, :role2 .

:role1 a :Role ;
    :canAccess :domain1, :sub1 .

:domain1 a :Domain ;
    :name "domain1" ;
    :hasDomain :sub1, :sub2, :sub3 .

:sub1 a :Domain ;
    :name "sub1" .

:sub2 a :Domain ;
    :name "sub2" ;
    :hasDomain :sub3 .

:sub3 a :Domain ;
    :name "sub3" .

:role2 a :Role ;
    :canAccess :sub2, :sub3 .

Let me know if this is drastically different from what you're using.

Best,
Steve

Hi Steve,

Thanks so much for your time in replying. Apologies for not posting more info on the actual data, I guess will hold so much assumed knowledge when we read out own posts.

I think (I'm pretty new to sparql so I'm just assuming by looking at it) the only thing with the structure you've posted is the additional edges between each domain and role.

So would this:

:role1 a :Role ;
:canAccess :domain1, :sub1 .

result in the role having edges to both domain1 and sub1?

a:Role1 -:canAccess-> a:Domain1
a:Role1 -:canAccess-> a:Sub1

If so, that's not the case. The roles are attached to only one domain and I'm looking to traverse down from that domain through the :hasDomain edges until I find another domain that has it's own role. It's basically an inheritance tree. if my syntax is correct

a:role1 -:canAccess-> a:Domain1 -:hasDomain-> a:Sub1

This is the same with the domains. domain1 only has an edge to sub1, not to sub2 and sub3 as well. That relationship is through travsering the tree one domain to the next.

Cheers
Paul

*update - I did get a little further in that I didn't realise in order to do recursive filtering you put the recursion inside the filter.

FILTER NOT EXISTS {
?domain (:hasDomain)* ?y.
?y ^:canAccess ?subrole.
}
I'm wondering if this is more along the lines of what I need to be aiming for? Maybe I'm on the wrong path, but my thoughts are what's needed is someway to recurse the hasDomain until you find a domain that has a role. ie filter out subdomain with their own role?

OK slowly getting there, thought I'd just post up my findings in case useful to anyone later.

So I have this

select * where { 
    ?user :hasRole ?role.
    ?role :canAccess ?roleDomain;
    
    ?roleDomain (:hasDomain)* ?subdomain.
    MINUS {
       ?subdomain ^:canAccess/^:hasRole ?user.
    }

    OPTIONAL {
        ?subdomain :name ?subName.
    }
    
    ?roleDomain a :Domain;
            :name ?roleDomainName.
}    

which is now returning

a:Role1 -:hasRole-> a:Domain1 -:hasDomain-> a:Sub1
a:Role1 -:hasRole-> a:Domain1 -:hasDomain-> a:Sub3
a:Role1 -:hasRole-> a:Domain1 -:hasDomain-> a:Sub4
a:Role2 -:hasRole-> a:Sub2 -:hasDomain-> a:Sub3
a:Role2 -:hasRole-> a:Sub2 -:hasDomain-> a:Sub4

So It's removing the sub2 domain from role 1 becuase it has it's own role directly attached. But it's not stopping the recursion of role1 from carrying on down the tree.

Any thoughts on how to halt the recursion would be appreciated

Cheers

Hi Paul,

I'm struggling to reproduce your result. Can you post a data file, removing any sensitive information? Can you also send us the exact output you're getting and the exact output you want? That will enable us to tweak your query until we get your desired result.

Best,
Steve

Hi Steve,

Thanks for getting back to me. I'll try and find some time to export something today and post it up.

Cheers
Paul