Additional Virtuoso GraphQL-SPARQL Bridge Examples: Querying Sample Movies Dataset

The examples in this post can be replicated using the following methods:

  1. query HitchhikerGuideFilm {
     title(id: "tt0371724") {
     	title
     	startYear
     	numVotes
     	averageRating
     	directors {
     		name
     	}
     	 writers {
     				name
     			}
     	principals {
     		id
     		name
     		birthYear
     	}
     }
    }
    
  2. query GetName {
    name(id:"nm0002490") {
        name
        birthYear
        deathYear
        profesion {
          label
        }
        knownForTitle {
         title
         startYear
         averageRating
        }
      }
    }
    
  3. query TenTitlesIn2022 {
      titles(first: 10, startYear: 2022) {
        id
        title
        averageRating
        numVotes
        startYear
      }
    }
    

Executing example 1 with cURL:

curl --request POST \
  --url https://graphql.demo.openlinksw.com/graphql \
  --header 'Content-Type: application/json' \
  --data '{"query":"query HitchhikerGuide {\n\ttitle(id: \"tt0371724\") {\n\t\ttitle\n\t\tstartYear\n\t\tnumVotes\n\t\taverageRating\n\t\tdirectors {\n\t\t\tname\n\t\t}\n\t\t writers {\n\t\t\t\t\tname\n\t\t\t\t}\n\t\tprincipals {\n\t\t\tid\n\t\t\tname\n\t\t\tbirthYear\n\t\t}\n\t}\n}\n","operationName":"HitchhikerGuide"}'

IRIs in results

When a field is defined as an ObjectProperty, i.e., when it is a reference to an RDF Class, the result would contains the IRI string reference to that object.

In the following example, countries is defined as:

r:countries a                owl:ObjectProperty ;
            rdfs:isDefinedBy s:schema ;
            rdfs:domain      r:Region ;
            rdfs:range       c:Country ;
            gql:type         gql:Array ;
            gql:field        gql:countries .

Thus, if it is asked via —

query {
  regions (code:["AN"]) {
    name
    countries
  }
}

— it would produce —

{
  "data": {
    "regions": [
      {
        "name": "Antarctica",
        "countries": [
          "http://example.org/country/AQ"
        ]
      }
    ]
  }
}

Variables

The GraphQL protocol may have as an input parameter variables. This parameter is used to bind variables and contains key/value pairs in a JSON object. Here is an example:

Query text

query CountriesForRegion($ccode: String, $ccodes: [String]) {
  region(code: $ccode) {
    name
    code
    countries(code: $ccodes) {
      name
      code3
      country_code
    }
  }
}

Variables document

{
	"ccode": "AM",
	"ccodes": [
		"US",
		"CL",
		"BR"
	]
}

NOTE: The variables JSON document is not a part of a GraphQL document. That is, it is a separate input parameter for the endpoint.

Here is a cURL example for the above request:

curl --request POST \
  --url https://graphql.demo.openlinksw.com/graphql \
  --header 'Content-Type: application/json' \
  --data '{"query":"query CountriesForRegion($ccode: String, $ccodes: [String]) {\n  region(code: $ccode) {\n    name\n    code\n    countries(code: $ccodes) {\n      name\n      code3\n      country_code\n    }\n  }\n}\n","variables":{"ccode":"AM","ccodes":["US","CL","BR"]},"operationName":"CountriesForRegion"}'

Here is an example involving IRIs.

Query text

query FiveEmployeesFiltered($iri: String!) {
  Employees(iri: $iri) {
    employeeid
    iri
    firstname
    lastname
    homephone
    hiredate
    notes
  }
}

Variables document

{
 "iri" : "http://[host:port]/Demo/employees/EmployeeID/4#this"
}

Default Variables

In certain cases, we might want to have a default behavior for a parameterized query; to achieve this, we can use default values for variables.

query MyQuery($code: String! = "GB") {
  country(code: $code) {
    name
    code3
    region {
      name
    }
  }
}

Conditional directives

A query can have conditional blocks to include or exclude certain blocks of fields or fragments in the selection set. The supported built-in directives are @include and @skip which should be self explanatory. These are used together with an if argument which is boolean. These are particularly useful when developing interfaces, and certain blocks may be used for diagnostics, etc.

Here is an example.

Note the directive use in inlined fragment — ... @include(if: $expand) — and in detached fragment — ...regionFields @include(if: $extended).

Query text

query CountryByCode($expand: Boolean! = true, $extended: Boolean!, $skip: Boolean!) {
  country(code: "AQ") {
    code
    ... @include(if: $expand) {
      name
      code3
    }
    country_code @skip(if: $skip)
    region @include(if: $expand) {
      code
      ...regionFields @include(if: $extended)
    }
  }
}

fragment regionFields on region {
  name
  ccode
  population
}

Variables document

{
	"expand": true,
	"skip": false,
	"extended": false
}

Searching and filtering

The common use of field:value gives an equality; therefore, the following extensions are added:

  • free text search on fields in the form of special argument, contains:
  • filters for less-than, greater-than, etc., as a special value object, {op:value}, where currently op can be eq, neq, lt ,gt, lte, gte. More can be added on demand.

Supported filter expressions

  • equ
  • neq
  • lt
  • lte
  • gt
  • gte
  • like
  • in
  • regex
  • strstr
  • contains
  • not_like
  • not_in
  • not_contains

Example

Query text

query {
  titles(
    contains: "Blade runner"
    startYear: { lt: 1990 }
    numVotes: { gt: 100 }
  ) {
    id
    title
    genre {
      label
    }
    startYear
    runtimeMinutes
    averageRating
    numVotes
  }
}

Custom built-in Directives

  • directive @sqlOption(option:TableOption, index:IndexOption) on FIELD
  • directive @inferenceOption(sameAs:SameAsOption, ifp:IfpOption) on QUERY
  • directive @notNull on FIELD
  • directive @filter(expression: String!) on FIELD
  • directive @dataGraph (uri:IRI!) repeatable on FIELD|QUERY|MUTATION|SUBSCRIPTION|FRAGMENT_SPREAD|INLINE_FRAGMENT

— where the following ENUM types are used —

enum TableOption {
  INDEX
  LOOP
}

enum IndexOption {
  RDF_QUAD
  RDF_QUAD_POGS
  S
  O
  G
}

enum SameAsOption {
  SAME_AS_OFF
  SAME_AS_S
  SAME_AS_O
  SAME_AS_S_O
  SAME_AS
  SAME_AS_P
}

enum IfpOption {
  IFP_OFF
  IFP_S
  IFP_O
  IFP
}

Examples

Query text

getAuthor  author(code: "ca6a26894a") @sqlOption(option: LOOP, index: S) {
    name
    age @notNull
    birthYear
  }
}

query countryQr @inferenceOption(sameAs:SAME_AS ifp:IFP_OFF) {
  country(code: "EN"){
    name
    region {
      name
      population
    }
  }
}

@dataGraph usage examples

query text

query qrGraph @dataGraph (uri:"urn:cciso:data") {
   regions {
    code
  }  
}

query fldGraph {
   regions @dataGraph (uri:"urn:cciso:data") {
    code
  }  
}

Note: Examples are figurative. Live examples depend on a specific dataset.

Mutations

Mutation support is implemented in two ways; we call these basic and templated.

  • basic mutation definition is based on an RDF/OWL class and its explicit properties.
  • templated mutation definition is a SPASQL query with parameters defined in such a way to behave as a mutation operation; therefore, it can implement more sophisticated changes.

Note: since a GraphQL field is meant to be a function which returns results, the template approach can be used for calling PL stored procedures or any arbitrary code supported by the Virtuoso engine.

Basic mutation definition

The relevant ontology definition to update fields on a geographical region can be defined as:

gql:updateRegion gql:type         gql:Object ;
                 rdfs:label       "Region update mutaion mapping" ;
                 gql:mutationType "UPDATE";
                 gql:rdfClass     r:Region .

Then request as —

mutation updateRegion($code: String!, $population: Float) {
  updateRegion(code: $code, population: $population) {
    name
    population
  }
}

— with variables document —

{
	"code": "AN",
	"population": 4490
}

— produces the following SPASQL code —

SPARQL WITH <urn:cciso:data> DELETE { 
  <http://example.org/region/AN> <http://example.org/region/code>       ?updateRegion·code . 
  <http://example.org/region/AN> <http://example.org/region/population> ?updateRegion·population . 
  } 
WHERE { 
  <http://example.org/region/AN> <http://example.org/region/code>       ?updateRegion·code . 
  <http://example.org/region/AN> <http://example.org/region/population> ?updateRegion·population . 
  };

SPARQL WITH  <urn:cciso:data> INSERT { 
  <http://example.org/region/AN> <http://example.org/region/code>       'AN' . 
  <http://example.org/region/AN> <http://example.org/region/population> 4490 . 
};

Template mutation

Ontology annotation:

gql:insertMovie 
    gql:type         gql:Function ;
    gql:mutationType "SPARQL";
    gql:sparqlQuery  """ 
        prefix  :  <http://example.org/schema/>
        WITH <urn:object:data> 
        INSERT { 
            `iri(?::ID)` a      :Movie ; 
                         :code  ?::code ; 
                         :title ?::name ; 
    }
    """;

In this case, there is no generated code; the statement is executed, and if possible, cached, with named parameters. The values are passed via variables or as an inline arguments to the request.

Example request

mutation {
  insertMovie(
    code: "KZZ"
    title: "Kin-dza-dza"
  ) {
    title
    code
  }
}

Related

Hello, Hugh
Thank you for such expressive examples.
Given that example I have the following question: as gql:Function and gql:sparlQuery are applied only for mutations, is there a way to use sparql SELECT query for ordinary GraphQL queries?

I have a case where results of an analytical SPARQL query should be available via GraphQL.

Hi @Eduard_BABKIN,

To clarify, which of the following are you seeking:

  1. Passing SPARQL through GraphQL?
  2. Using GraphQL as an alternative interface to a SPARQL Query Solution?

Hello,
The case #2 is more close to my expectations.
perhaps the best description of my case was proposed by Copilot:

Here’s a conceptual example of how gql:sparqlQuery might be used in a GraphQL mapping schema:

type Query {
  getPerson(id: ID!): Person @gql:sparqlQuery(
    query: """
      SELECT ?name ?age WHERE {
        ?person a <http://schema.org/Person>;
                <http://schema.org/name> ?name;
                <http://schema.org/age> ?age.
        FILTER (?person = :id)
      }
    """,
    variables: {
      id: "IRI"
    }
  )
}

So, if it was true, I was able to send an ordinary GraphQL query, but behind the scenes, SPARQL query was executed to provide data.

Unfortunately that was a case of ChatGPT hallucination, and actually Virtuoso does not accept such query.
Are there some plans for implementation of that feature ?
May be there is another workaround solution to allow on-demand processing of SPARQL queries in GraphQL queries.