nelson-ai / semantic-graphql

Create GraphQL schemas from RDF ontologies
MIT License
28 stars 4 forks source link

Add support for owl:unionOf when resolving rdfs:range on an owl:ObjectProperty #3

Open Astn opened 7 years ago

Astn commented 7 years ago

When using ontologies such as the OWL compliant version of schema.org created by topbraid (http://topbraid.org/schema) @ http://topbraid.org/schema/schema.ttl

It is common to encounter a union of resources as the object of rdfs:range. Example:

###  http://schema.org/acquiredFrom
<http://schema.org/acquiredFrom> rdf:type owl:ObjectProperty ;
                                 rdfs:domain <http://schema.org/OwnershipInfo> ;
                                 rdfs:range [ rdf:type owl:Class ;
                                              owl:unionOf ( <http://schema.org/Organization>
                                                            <http://schema.org/Person>
                                                          )
                                            ] ;
                                 rdfs:comment "The organization or person from which the product was acquired."^^xsd:string ;
                                 rdfs:label "acquired from"^^xsd:string .

I propose that we handle this case by adding all of the resources found in the owl:unionOf to a GraphQLUnionType and return that from the call to getGraphqlPolymorphicObjectType.

I have been working on a solution for this particular issue, and should be able to submit a pull request for review shortly.

dherault commented 7 years ago

re @Astn, there is a major difference between a GraphQLUnionType (badly named, it returns only one type, the resource must belong to either of the classes), and a owl:unionOf class (which is many classes at the same time, the resource must belong to all the classes).

Also, what about an interface ?

dherault commented 7 years ago

Mmm my bad it seems I'm wrong about owl:unionOf

dherault commented 7 years ago

Ok owl:unionOf is a logical disjunction so it could be either or both classes

dherault commented 7 years ago

A GraphQLUnion type would handle the "either" case, but not if it is "both". A GraphQLInterface type would handle fragments on "both", but we'd still have to resolve only one type (because GraphQL requires a single type) for a particular resource.

Let's say a field returns an owl:unionOf(Dog, Cat) and a user queries a resource that is both a Dog and a Cat (whatever) using a fragment on Dog and another one on Cat, for that resource we would have to resolve only one type (Dog or Cat) not both, and only one fragment would be collected.

This is a typical case where GraphQL and Semantic data do not mix well.

dherault commented 7 years ago

I've been thinking about a fix that would solve many GraphQL vs Semantic conflicts like this one : it would be to add some magic into the resolveType function, by introspecting the AST and knowing what the user wants. Does it need a Dog ? Then return 'Dog' as the type. A Cat ? 'Cat'. A Dog and a Cat ? Return a special DogCat GraphQL type.

Astn commented 7 years ago

From the brief testing I was doing earlier, this case is already handled O.K. I'm sure it could be better. Likely is the case that anything can be an owlThing. So is there a way for us to create a fragment on an objectProperty that has a rdfs:range of owlThing? My currently implementation does not allow such a thing.

But so far, I am able to formulate queries such as:

query {
  foo(bar:"bar"){
    __typename
    ... on I_sdo_Action {
      startTime
      agent {
        ... on sdo_Person {
          url
        }
      }
    }
    ... on sdo_FollowAction {
      followee {
        ... on I_sdo_Thing {
          url
        }
      }
    }
    ... on sdo_MarryAction {
      object {
        ... on I_sdo_Thing {
          url
        }
      }
    }
  }
}
Astn commented 7 years ago

So it appears that if the components of a Union implement a common interface, then you are able to pull the common fields from it right now. Not sure of what to do when some Thing is both a Cat and a Dog.

dherault commented 7 years ago

Yes the "both" case is still missing in the GraphQLUnion type solution. But the "both" case is the essence of owl:unionOf. Otherwise simply listing the classes instead of using an owl:unionOf would do.

Astn commented 7 years ago

Do you know any way that graphql supports some kind of a Both case? All I can think of at the moment, which I think you suggested earlier would be to generate another type that represented the logical disjunction. But that then still has the issue that derived types or more specific types are also allowed.

So in the case of

###  http://schema.org/acquiredFrom
<http://schema.org/acquiredFrom> rdf:type owl:ObjectProperty ;
                                 rdfs:domain <http://schema.org/OwnershipInfo> ;
                                 rdfs:range [ rdf:type owl:Class ;
                                              owl:unionOf ( <http://schema.org/Organization>
                                                            <http://schema.org/Person>
                                                          )
                                            ] ;
                                 rdfs:comment "The organization or person from which the product was acquired."^^xsd:string ;
                                 rdfs:label "acquired from"^^xsd:string .

The resource returned as the object of http://schema.org/aquiredFrom could be a schema:CafeOrCoffeeShop because

schema:CafeOrCoffeeShop subClassOf schema:LocalBusiness subClassOf schema:Organization


schema:CafeOrCoffeeShop
rdf:type owl:Class ;
rdfs:comment "A cafe or coffee shop." ;
rdfs:label "Cafe or coffee shop" ;
rdfs:subClassOf schema:FoodEstablishment ;

schema:FoodEstablishment rdf:type owl:Class ; rdfs:comment "A food-related business." ; rdfs:label "Food establishment" ; rdfs:subClassOf schema:LocalBusiness ;

schema:LocalBusiness rdf:type owl:Class ; rdfs:comment "A particular physical business or branch of an organization. Examples of LocalBusiness include a restaurant, a particular branch of a restaurant chain, a branch of a bank, a medical practice, a club, a bowling alley, etc." ; rdfs:label "Local business" ; rdfs:subClassOf schema:Organization ; rdfs:subClassOf schema:Place ;


So that makes me conclude that in the simple case of 

schema:error rdf:type owl:ObjectProperty ; rdfs:comment "For failed actions, more information on the cause of the failure." ; rdfs:domain schema:Action ; rdfs:label "error" ; rdfs:range owl:Thing ;

you *should* be able to formulate the following query because schema:APIReference is an owl:Thing

query { schemaActions { schemaerror { ... on schemaAPIReference { id } } } }



So where do we go from here?
dherault commented 7 years ago

Yes, you're right, that's why I wanted the "type" of fields to be GraphQLInterfaceType instead of GraphQLObjectType. This kind of fragments would then be available, without breaking changes. I never made the swich, it's on the TODO list thought.

Astn commented 7 years ago

So I think we have two separate issues here.

  1. This one #3 = Handle unions in rdfs:range position.
  2. Need to define = How to handle subtypes or implementers of some interface.

I would still like to try to solve this #3 without tackling the Interface / subtype issue yet. Currently there is no support for unions, so IMO some support is a step forward.