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:
default-prefix
- namespace or URI to prepend to GraphQL fields in the query.infer
- Specifies the kind of inference to use for this query. Valid values arefalse
,rdfs++
/true
, andhasvalue
.
The next three arguments have as values comma-separated with each item having two space separated parts.
namespaces
- comma-separated list of prefix/values separated by spaces. For example"foo <http://foo.com/>,bar <http://bar.com/>"
These namespaces shadow user/repo/default
namespaces (see Namespaces and query options, IRI aliases, and typevars). Space and other special characters must be uuencoded.
variables
- comma-separated list of name/values separated by spaces. For example"$size 10,$width 20"
aliases
- comma-separated list of alias/IRI separated by spaces (see Namespaces and query options, IRI aliases, and typevars).
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
:json
:list
- the list form of the JSON:listupi
- the list form of the JSON but instead of converting the upis to theirupi->value
so they are readable, we leave the upi's alone which makes them easier to use if you're doing subsequent work with them
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):
- default prefix
full iri: #@default <http://example.com/> if 'ex' is defined: #@default ex
- namespaces (same syntax as in SPARQL after the comment character '#')
#@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- aliases
#@alias type <http://www.w3.org/2000/01/rdf-schema#type>
GraphQL in AllegroGraph: General description
Summary of the AllegroGraph GraphQL implementation:
- Top Level Operators:
queries and fragment operators are supported.
type and mutation operators are not supported.
- Directives:
Standard directives:
@include(if: boolean)
and@skip(if: boolean)
Common directives:
@filter(if: "SPARQL expression")
AllegroGraph-specific directives:
@nullable(if: boolean)
which makes it ok for a field in a query to not have a value.boolean
can betrue
orfalse
.
- External specifications (details below):
#@default <http://some.uri/> #@prefix ns: <http://whatever.com/> #@alias myuri <http://some.long.uri/>
- When used as a boolean value the value
nil
or "false" represent a false value and everything else represents a true value.
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
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:
Hero
name
Luke
height
http://other.com/weight
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
- a name like
alpha
in which case the default prefix is prepended - fully specified like
<http://example.com/alpha>
- 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:
prefix
- namespace or URI to prepend to GraphQL fields in the query.namespaces
- space-separated list of namespace abbreviations, shadows user/repo/default namespaces. Space and other special characters must be uuencoded.infer
- Specifies the kind of inference to use for this query. Valid values arefalse
,rdfs++
/true
, andhasvalue
.var
- space-separated list of values of GraphQL variables.
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:
- default prefix:
full iri: #@default <http://example.com/> if 'ex' is defined: #@default ex
- namespaces (same syntax as in SPARQL after the comment character '#'):
#@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
The colon after the namespace abbreviation is optional as are the angle brackets around the resource. So an equivalent declaration is:
#@prefix rdfs http://www.w3.org/2000/01/rdf-schema#
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>"
]
}
]
}
]