Introduction

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools. (Description from the GraphQL webpage.)

Usage

AllegroGraph can run GraphQL queries at repositories/REPOSITORY/graphql HTTP endpoint as well as from Lisp using the agq.graphql:run-graphql function.

The HTTP POST arguments:

The next three arguments have as values comma-separated with each item having two space separated parts.

These namespaces shadow user/repo/default namespaces (see Namespaces and query options, IRI aliases, and typevars). Space and other special characters must be uuencoded.

Here is an example HTTP POST:

POST /repositories/REPONAME/graphql?default-prefix=http://example.com/&namespaces=rdfs%20http://www.w3.org/2000/01/rdf-schema# 

Example curl request to repository 'test' at port 10050 from user test with password xtzzy:

curl 'test:xyzzy@localhost:10050/repositories/test/graphql?default-prefix=http://example.com/&namespaces=purl%20http://purl.org/vocab/relationship/' --data-binary @query.gql  
 
Request body: GraphQL query 

Lisp equivalent:

(with-namespace-abbreviations  
    '(("rdfs" . "http://www.w3.org/2000/01/rdf-schema#"))  
  (agq.graphql::run-graphql  
   my-graphql-query  
   :default-prefix "http://example.com/"  
   :results-format :list)) 

Possible values for `:results-format are

When run in AGWebView, GraphQL query results are displayed as pretty-printed JSON. In AGWebView, a user can access it by choosing GraphQL as the language in the Query Editor.

Predefined namespaces can be used in the query (see Namespaces and query options)

Extensions to GraphQL syntax

Additionally, the GraphQL syntax is extended with magic comments (see below for more information):

GraphQL in AllegroGraph: General description

Summary of the AllegroGraph GraphQL implementation:

GraphQL and RDF triple stores

In RDF the two main objects are resources and literals. Resources are usually URI's and are in some places written as with the angle brackets not being part of the resource name but simply a means of specifying that the character string inside the brackets denotes a resource.

Literals can be any string and are written surrounded by double quotes.

In GraphQL we have names which consist of letters and digits which cannot be considered a number. There are numbers which are integers or floats. And finally there are strings which is any text surrounded by double quotes.

In the AllegroGraph GraphQL query engine names are considered to be RDF resources. Strings and numbers are considered to be RDF literals.

GraphQL's syntax for names cannot express all RDF resource names so we've extended the GraphQL syntax to include the angle brackets.

You can't write in GraphQL the resource name http://example.com/Hero since the colon is not a permitted character in a GraphQL name. So we've extended the syntax of GraphQL and you can write <http://example.com/Hero> to name this RDF resource in GraphQL.

Also you may have defined "ex" as a namespace abbreviation for "http://example.com/". You can't write ex:Hero in GraphQL since the colon cannot appear in a name. You can write <ex:Hero> and the AllegroGraph GraphQL parser will expand the namespace abbreviation to yield <http://example.com/Hero>.

Consider

#@default http://example.com/  
{  
    Hero (name: Luke)  
     {  
        height  
        <http://other.com/weight>  
     }  
} 

Here we see five GraphQL names:

All five of these will turn into resources and the default prefix will be prepended to the first four.

http://example.com/Hero  
http://example.com/name  
http://example.com/Luke  
http://example.com/height  
http://other.com/weight 

If we had written it as

#@default http://example.com/  
{  
    Hero (name: "Luke Skywalker")  
     {  
        height  
        <http://other.com/weight>  
     }  
} 

Then we have four GraphQL names and one GraphQL string so when this is applied to an RDF store we have four resources:

http://example.com/Hero  
http://example.com/name  
http://example.com/height  
http://other.com/weight 

and one literal

"Luke Skywalker" 

Note that in the directives that follow #@ you can write a resource with or without the angle brackets.

#@default http://example.com/ 

is the same as

#@default <http://example.com/> 

Details

A query can be explicit

query myquery($var1: type = value)  
{  
    alpha  
       { beta  
         gamma  
       }  
}  

or implied

{  
    alpha  
       { beta  
         gamma  
       }  
}  

The explicit syntax allows you to specify variables which can be used inside the query.

There is no standard mapping of a GraphQL query to an RDF store like AllegroGraph so we have chosen a mapping that makes the most sense and is similar to the mapping used by other RDF stores.

In this query

{  
    alpha  
       { beta  
           { delta }  
         gamma  
       }  
} 

alpha is the name of a class, and beta, gamma, and delta are predicates.

That is we find all objects ?0 such that:

?0 rdf:type alpha 

and for each we retrieve the objects of the triples

?0 beta ?1 

and

?0 gamma ?2 

and further we look for the delta predicate value for those objects pointed to by the beta predicate

?1 delta ?3 

An RDF store consists of triples or quads of resources and literals and blank nodes.

Resources are usually named something like http://example.com/alpha and this is written <http://example.com/alpha> to denote that this is a resource with that name and not simply that literal string.

In order to make GraphQL easy to write and read we allow one to specify part of the resource name outside the query itself

#@default <http://example.com/>  
{  
    alpha  
       { beta  
           { delta }  
         gamma  
       }  
} 

says that the default prefix for resources is http://example.com/ Thus the above is the same as:

{  
    <http://example.com/alpha>  
       { <http://example.com/beta>  
           { <http://example.com/delta> }  
         <http://example.com/gamma>  
       }  
} 

But the former form is much to read.

You can also declare namespaces as done in SPARQL if a single prefix does not sufice for all resources mentioned, for example:

#@prefix ex: <http://example.com/>  
{  
    <ex:alpha>  
       { <ex:beta>  
           { <ex:delta> }  
         <ex:gamma>  
       }  
} 

To summarize, a resource can be written in a GraphQL query as

  1. a name like alpha in which case the default prefix is prepended
  2. fully specified like <http://example.com/alpha>
  3. use a defined namespace to specify the prefix <ex:alpha>

AllegroGraph has a set of the common namespaces predefined. See the Namespaces and query options document.

Star Wars Example

In the following, we'll make use of our version of the Star Wars database commonly used in GraphQL documentation:

@prefix ex: <http://example.com/> .  
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .  
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .  
 
ex:star-wars rdf:type ex:Movie .  
ex:star-wars ex:episode ex:newhope .  
ex:star-wars ex:episode ex:empire .  
ex:star-wars ex:episode ex:jedi .  
 
ex:newhope rdf:type ex:Episode .  
ex:empire  rdf:type ex:Episode .  
ex:jedi    rdf:type ex:Episode .  
 
ex:star-wars ex:hero ex:r2-d2 .  
ex:star-wars ex:hero ex:Luke .  
ex:star-wars ex:hero ex:Lando .  
 
ex:r2-d2 rdf:type ex:Hero .  
ex:Luke  rdf:type ex:Hero .  
ex:Lando rdf:type ex:Hero .  
 
ex:newhope ex:name "Star Wars" .  
ex:newhope ex:date "May 25, 1977"^^<rdfs:date> .  
ex:newhope ex:number 4 .  
 
ex:empire ex:name "The Empire Strikes Back" .  
ex:empire ex:date "May 17, 1980"^^<rdfs:date> .  
ex:empire ex:number 5 .  
 
ex:jedi ex:name "Return of the Jedi" .  
ex:jedi ex:date "May 25, 1983"^^<rdfs:date> .  
ex:jedi ex:number 6 .  
 
ex:r2-d2 ex:name "R2-D2" .  
ex:r2-d2 rdf:type ex:Robot .  
ex:r2-d2 ex:appearsIn ex:newhope .  
ex:r2-d2 ex:appearsIn ex:empire .  
ex:r2-d2 ex:appearsIn ex:jedi .  
ex:r2-d2 <http://purl.org/vocab/relationship/friendOf> ex:Luke .  
ex:r2-d2 ex:owner ex:Luke .  
ex:r2-d2 ex:primaryFunction "Astromech" .  
 
ex:Luke ex:name "Luke Skywalker" .  
ex:Luke rdf:type ex:Human .  
ex:Luke ex:appearsIn ex:newhope .  
ex:Luke ex:appearsIn ex:empire .  
ex:Luke ex:appearsIn ex:jedi .  
ex:Luke <http://purl.org/vocab/relationship/friendOf> ex:Lando .  
ex:Luke <http://purl.org/vocab/relationship/friendOf> ex:r2-d2 .  
ex:Luke ex:height "1.60"^^xsd:double .  
 
ex:Lando ex:name "Lando Calrissian" .  
ex:Lando rdf:type ex:Human .  
ex:Lando ex:appearsIn ex:empire .  
ex:Lando ex:appearsIn ex:jedi .  
ex:Lando <http://purl.org/vocab/relationship/friendOf> ex:Luke .  
ex:Lando <http://purl.org/vocab/relationship/friendOf> ex:r2-d2 .  
ex:Lando ex:height "1.65"^^xsd:double . 

Here is very simple query

{  
   <http://example.com/Episode> { <http://example.com/name> }  
} 

and the result is JSON which we show here this way to make it more readable

{  
   "<http://example.com/Episode>":   
        [  
          {  
             "<http://example.com/name>":  "Return of the Jedi",  
           },  
          {  
             "<http://example.com/name>":  "The Empire Strikes Back",  
           },  
          {  
             "<http://example.com/name>":  "Star Wars",  
           },  
          ],  
  } 

There are multiple Episodes so we have an array of results. Each result shows the name of the Episode.

If we wrote the query this way:

#@default <http://example.com/>  
{  
   Episode { name }  
} 

the we get an easier to read result:

{  
   "Episode":  [  
                 {  
                   "name":  "Return of the Jedi",  
                 },  
                 {  
                   "name":  "The Empire Strikes Back",  
                 },  
                 {  
                   "name":  "Star Wars",  
                 },  
               ],  
}  

Directives are used to alter how predicates are processed.

Each GraphQL implementation supports the @include and @skip directives. We also support the @nullable directive which is important since we don't process GraphQL type definitions so normally a type definition specifies which fields are nullable

A field marked as @nullable need be present and if it's not present the query will continue and that single field will not be present in the result.

For example

#@default <http://example.com/>  
{  
    Hero  
       { name  
         height  
       }  
} 

returns:

[  
   {  
     "Hero": [  
       {  
         "name": "Lando Calrissian",  
         "height": "\"1.65E0\"^^<http://www.w3.org/2001/XMLSchema#double>"  
       },  
       {  
         "name": "Luke Skywalker",  
         "height": "\"1.6E0\"^^<http://www.w3.org/2001/XMLSchema#double>"  
       }  
     ]  
   }  
] 

You'll note that rd-d2 isn't returned since it doesn't have a height property. That doesn't seem reasonable since we want a list of heroes and whether or not they have a given height should not be important.

So we declare the height property to be nullable. Directives must take an argument to determine if they are active which is the reason for the (if: true).

Given this:

 #@default <http://example.com/>  
 {  
     Hero  
        { name  
          height @nullable(if: true)  
        }  
 }  

we get back a complete list of the heroes but r2-d2 doesn't have a height value returned.

[  
   {  
     "Hero": [  
       {  
         "name": "Lando Calrissian",  
         "height": "\"1.65E0\"^^<http://www.w3.org/2001/XMLSchema#double>"  
       },  
       {  
         "name": "Luke Skywalker",  
         "height": "\"1.6E0\"^^<http://www.w3.org/2001/XMLSchema#double>"  
       },  
       {  
         "name": "R2-D2"  
       }  
     ]  
   }  
] 

If you want be explicit about the types for which you want to retrieve a slot you can do the same as the above using the fragment syntax. Here the height property will only be checked if the Hero is also of type Human

#@default <http://example.com/>  
{ Hero  
    {name  
     ... on Human  
        { height }  
    }  
} 

with the result

[  
   {  
     "Hero": [  
       {  
         "name": "Lando Calrissian",  
         "height": "\"1.65E0\"^^<http://www.w3.org/2001/XMLSchema#double>"  
       },  
       {  
         "name": "Luke Skywalker",  
         "height": "\"1.6E0\"^^<http://www.w3.org/2001/XMLSchema#double>"  
       },  
       {  
         "name": "R2-D2"  
       }  
     ]  
   }  
] 

That was an anonymous fragment. You can also define a fragment and use it by name.

This returns exactly what the previous query returns.

#@default <http://example.com/>  
{ Hero  
    {name  
     ... humancheck  
    }  
}  
fragment humancheck on Human  
{  
   height  
} 

It's possible to do more than one query in a single invocation of GraphQL as in the following examples.

This:

#@default <http://example.com/>  
{ Hero  
    { name }  
}  
{  
   Human  
    { name }  
}  

or this:

#@default <http://example.com/>  
{ Hero  
    { name }  
   Human  
    { name }  
} 

will return an array of the results:

[  
   {  
     "Human":  [  
                 {  
                   "name":  "Lando Calrissian",  
                 },  
                 {  
                   "name":  "Luke Skywalker",  
                 },  
               ],  
   },  
   {  
     "Hero":  [  
                {  
                  "name":  "Lando Calrissian",  
                },  
                {  
                  "name":  "Luke Skywalker",  
                },  
                {  
                  "name":  "R2-D2",  
                },  
              ],  
   },  
]  

The @filter directive is given after a name but it actually applies to the whole enclosing object

For example:

#@default <http://example.com/>  
{  
    Hero  
     {  
        name  
        height  
        appearsIn @filter(if: "$height > 1.62")  
      }  
} 

the filter applies to all of

 {  
     name  
     height  
     appearsIn  
 } 

it could have been placed after any of the name, e.g. this is equivalent:

#@default <http://example.com/>  
{  
    Hero  
     {  
        name    @filter(if: "$height > 1.62")  
        height  
        appearsIn  
      }  
} 

The filter expression is any valid SPARQL expression that might be found in a SPARQL filter clause.

In the filter expression $xxxx ($height in the example) refers to the value of field named xxxx. While $xxxx looks like a GraphQL variable, it is not. You can use $xxxx to refer to any field name given in the GraphQL expression.

AllegroGraph can run GraphQL queries at repositories/REPOSITORY/graphql HTTP endpoint as well as from Lisp using the agq.graphql:run-graphql function.

The HTTP request arguments:

Here is an example HTTP request:

POST /reporitories/REPONAME/graphql?default-prefix=http://example.com/&namespaces=rdfs http://www.w3.org/2000/01/rdf-schema# 

Example curl request to repository 'test' at port 10050

curl 'test:xyzzy@localhost:10050/repositories/test/graphql?default-prefix=http://example.com/&namespaces=purl%20http://purl.org/vocab/relationship/' --data-binary @query.gql  
 
Request body: GraphQL query 

Lisp equivalent:

(with-namespace-abbreviations  
    '(("rdfs" . "http://www.w3.org/2000/01/rdf-schema#"))  
  (agq.graphql::run-graphql  
   my-graphql-query  
   :default-prefix "http://example.com/"  
   :results-format :alist)) 

When run in AGWebView, GraphQL query results are displayed as pretty-printed JSON. In AGWebView, a user can access it by choosing "GraphQL" as the language in the Query Editor.

Predefined namespaces can be used in the query.

Extensions to GraphQL syntax

Additionally, the GraphQL syntax is extended with magic comments:

aliases: The alias magic comment is a bit more complex.

Unlike the other magic comments the angle brackets are important in an alias since it's basically a string substitution

#@alias type http://www.w3.org/2000/01/rdf-schema#type 

is not correct since that's not a good name while

#@alias type <http://www.w3.org/2000/01/rdf-schema#type> 

is correct since you want it to be interpreted a resource.

Some examples using alias:

#@default <http://example.com/>  
#@alias ain appearsIn  
{  
     Hero  
      {  
         name  
         ain  
       }  
} 

yields:

[  
   {  
     "Hero": [  
       {  
         "name": "Lando Calrissian",  
         "appearsIn": [  
           "<http://example.com/jedi>",  
           "<http://example.com/empire>"  
         ]  
       },  
       {  
         "name": "Luke Skywalker",  
         "appearsIn": [  
           "<http://example.com/jedi>",  
           "<http://example.com/empire>",  
           "<http://example.com/newhope>"  
         ]  
       },  
       {  
         "name": "R2-D2",  
         "appearsIn": [  
           "<http://example.com/jedi>",  
           "<http://example.com/empire>",  
           "<http://example.com/newhope>"  
         ]  
       }  
     ]  
   }  
] 

and if we instead alias to the full resource name:

#@default <http://example.com/>  
#@alias ain <http://example.com/appearsIn>  
{  
     Hero  
      {  
         name  
         ain  
       }  
} 

yields:

[  
   {  
     "Hero": [  
       {  
         "name": "Lando Calrissian",  
         "<http://example.com/appearsIn>": [  
           "<http://example.com/jedi>",  
           "<http://example.com/empire>"  
         ]  
       },  
       {  
         "name": "Luke Skywalker",  
         "<http://example.com/appearsIn>": [  
           "<http://example.com/jedi>",  
           "<http://example.com/empire>",  
           "<http://example.com/newhope>"  
         ]  
       },  
       {  
         "name": "R2-D2",  
         "<http://example.com/appearsIn>": [  
           "<http://example.com/jedi>",  
           "<http://example.com/empire>",  
           "<http://example.com/newhope>"  
         ]  
       }  
     ]  
   }  
]