ruby-rdf / sparql-client

SPARQL client for Ruby.
http://rubygems.org/gems/sparql-client
The Unlicense
112 stars 58 forks source link

FILTER inside OPTIONAL Clause #75

Closed mdorf closed 7 years ago

mdorf commented 7 years ago

We have a use case for writing a query where the FILTER statement resides inside an OPTIONAL clause. The query was given to us by our triple store support engineers (AllegroGraph). The query looks like this:

SELECT DISTINCT ?id ?prefLabel ?synonym 
FROM <http://data.bioontology.org/ontologies/CSV_TEST_BRO/submissions/1> 
WHERE { 
    ?id a <http://www.w3.org/2002/07/owl#Class> . 
    OPTIONAL { 
        ?id ?rewrite0 ?prefLabel . 
        FILTER(?rewrite0 = <http://data.bioontology.org/metadata/def/prefLabel> || 
            ?rewrite0 = <http://www.w3.org/2004/02/skos/core#prefLabel>)
    } 
    OPTIONAL { 
        ?id ?rewrite1 ?synonym . 
        FILTER(?rewrite1 = <http://www.geneontology.org/formats/oboInOwl#hasExactSynonym> || 
            ?rewrite1 = <http://purl.obolibrary.org/obo/synonym> || 
            ?rewrite1 = <http://www.geneontology.org/formats/oboInOwl#hasBroadSynonym>)     
    } 
    FILTER(?id = <http://bioontology.org/ontologies/Activity.owl#Activity> || 
        ?id = <http://bioontology.org/ontologies/Activity.owl#Biospecimen_Management> || 
        ?id = <http://bioontology.org/ontologies/Activity.owl#Community_Engagement> || 
        ?id = <http://bioontology.org/ontologies/Activity.owl#Deprecated_Activity>)
}

I've reviewed the sparql-client API and didn't find an obvious way to embed a FILTER statement inside an OPTIONAL block. I wanted to check with you to make sure I'm not missing anything obvious. Is it possible to construct such a query without customizing the sparql-client?

Thank you!

gkellogg commented 7 years ago

This will require that optional take a block; there may be other operators we want to do this for too (arguably, most things that return a result set could take a block). A query construction might look like the following:

subject.select(:id, :prefLabel, :synonym).
  from("http://data.bioontology.org/ontologies/CSV_TEST_BRO/submissions/1").
  where([:id, RDF.type, RDF::OWL.Class]).optional {|q|
    q << [:id, :rewrite0, :prefLabel]
    q.filter(%(?rewrite0 = <http://data.bioontology.org/metadata/def/prefLabel> || 
            ?rewrite0 = <http://www.w3.org/2004/02/skos/core#prefLabel>))
  }.optional {
    self << [:id, :rewrite1, :synonym]
    filter(%(?rewrite1 = <http://www.geneontology.org/formats/oboInOwl#hasExactSynonym> || 
            ?rewrite1 = <http://purl.obolibrary.org/obo/synonym> || 
            ?rewrite1 = <http://www.geneontology.org/formats/oboInOwl#hasBroadSynonym>))
  }.filter(%(?id = <http://bioontology.org/ontologies/Activity.owl#Activity> || 
        ?id = <http://bioontology.org/ontologies/Activity.owl#Biospecimen_Management> || 
        ?id = <http://bioontology.org/ontologies/Activity.owl#Community_Engagement> || 
        ?id = <http://bioontology.org/ontologies/Activity.owl#Deprecated_Activity>))

Filter could probably use more DSL elements so that it simply didn't take a string, but that's a separate matter.

This pattern is similar to that used in RDF::Query.

gkellogg commented 7 years ago

Revised syntax:

SPARQL::Client::Query.select(:id, :prefLabel, :synonym).
  from(RDF::URI("http://data.bioontology.org/ontologies/CSV_TEST_BRO/submissions/1")).
  where([:id, RDF.type, RDF::OWL.Class]).optional([:id, :rewrite0, :prefLabel]) {|q|
    q.filter(%(?rewrite0 = <http://data.bioontology.org/metadata/def/prefLabel> || 
            ?rewrite0 = <http://www.w3.org/2004/02/skos/core#prefLabel>))
  }.optional([:id, :rewrite1, :synonym]) {
    filter(%(?rewrite1 = <http://www.geneontology.org/formats/oboInOwl#hasExactSynonym> || 
            ?rewrite1 = <http://purl.obolibrary.org/obo/synonym> || 
            ?rewrite1 = <http://www.geneontology.org/formats/oboInOwl#hasBroadSynonym>))
  }.filter(%(?id = <http://bioontology.org/ontologies/Activity.owl#Activity> || 
        ?id = <http://bioontology.org/ontologies/Activity.owl#Biospecimen_Management> || 
        ?id = <http://bioontology.org/ontologies/Activity.owl#Community_Engagement> || 
        ?id = <http://bioontology.org/ontologies/Activity.owl#Deprecated_Activity>)).to_s