GraphQL Mutations
This section describes the features supported by TopBraid to perform updates (aka mutations) on RDF databases. In a nutshell, the system uses SHACL shape definitions to automatically generate suitable GraphQL mutations to create, update or delete objects. When changes are submitted, they are validated against the constraints defined by the shape definitions and a report with violations and suggested fixes can be returned.
TopBraid takes SHACL shape definitions or GraphQL schemas as input and automatically generates an enhanced GraphQL schema that includes mutations that can be used to create, update or delete data from an underlying RDF graph database.
In the following example, we assume that the type Human
is used as input schema to the system:
1type Human {
2 id: ID!
3 name: String!
4 height: Float
5 friends: [Human]
6}
The system internally converts this schema to SHACL and produces an enhanced GraphQL schema with the following mutations:
1schema {
2 ...
3 mutation: RootRDFMutation
4}
5
6type RootRDFMutation {
7 createHuman (input: Human_Input!): Boolean
8 addToHuman (input: Human_Input!): Boolean # (addTo requires TopBraid 6.2 onwards)
9 updateHuman (input: Human_Input!): Boolean
10 delete (uri: ID, uris: [ID]): Boolean
11 report: _MutationReport
12 commit (ignoreViolations: Boolean, message: String): String
13}
14
15type _MutationReport {
16 addedCount: Int
17 deletedCount: Int
18 conforms: Boolean!
19 results: [_MutationResult]
20}
21
22type _MutationResult {
23 message: String
24 severity: String!
25 focusNode: _RDFNode!
26 path: String
27 value: _RDFNode
28 suggestions: [_MutationResultSuggestion]
29}
30
31type _RDFNode {
32 label: String!
33 uri: ID
34}
35
36type _MutationResultSuggestion {
37 message: String!
38 confidence: Float
39 applyData: String
40}
41
42input Human_Input {
43 uri: ID!
44 label: String
45 id: ID
46 name: String
47 height: Float
48 friends: [Human_Input]
49}
An example mutation request may look as follows:
1mutation {
2 createHuman (input: {
3 uri: "http://example.org/Humans/123",
4 id: "123",
5 name: "Darth Vader"
6 })
7 updateHuman (input: {
8 uri: "http://example.org/Humans/456",
9 father: {
10 uri: "http://example.org/Humans/123"
11 }
12 })
13 report {
14 addedCount
15 deletedCount
16 }
17 commit (message: "Added Luke's dad")
18}
The above request will produce the JSON result below and, as a side effect, create one instance of Human in the database, and make it the father of an existing instance. The number of RDF triples that were added or deleted are reported as part of the report object.
1{
2 "data": {
3 "createHuman": true,
4 "updateHuman": true,
5 "report": {
6 "addedCount": 4,
7 "deletedCount": 0
8 },
9 "commit": "Added Luke's dad"
10 }
11}
For those familiar with RDF, the new triples would be:
1<http://example.org/Humans/123>
2 a ex:Human ;
3 ex:id "123" ;
4 ex:name "Darth Vader" .
5
6<http://example.org/Humans/456>
7 ex:father <http://example.org/Humans/123> .
The mutations include the following fields:
createXY
operations to create a new instance, for each published data shapeaddToXY
operations that add values to an instance, for each published data shapeupdateXY
operations to modify an existing instance, for each published data shapedelete
operation to delete one or more instancesresults
to produce information about the operationscommit
to trigger the actual commit of the changes, instead of doing just a “dry run”
Create operations
For each published data shape in a schema, TopBraid generates a createXY
mutation.
These mutations take a mandatory input object that has one field for each property shape declared at the shape, and which must also have a uri
.
An optional parameter is graph
, which can be the URI of a named graph in which the new instance shall be created in.
The system will pick a suitable default if no such graph is specified.
When executed, the create mutation produces the RDF triples mentioned in the input object, including an rdf:type
triple if the shape is also a class.
The value of the uri
field is used to select the newly created instance.
Create operations only ever add new triples, while update operations may also remove triples (if fields are null).
Note that the uri
may refer to an object that is already in the database, for example because it does have other shapes (for example, an object may be both Writer and Human).
While SHACL property shapes may use path expressions of almost arbitrary complexity, the only property paths that can be used in create and update operations are one-step inverse paths. For other paths, it would be too difficult and ambiguous to fill in the intermediate triples.
AddTo operations
Similar to create operations, TopBraid generates a addToXY
mutation for each published shape in a schema.
This takes a single mandatory input
parameter, which must specify a uri
to identify the object that is being modified.
Only the values of the properties represented by the given fields are modified.
So for example when a multi-valued property friends
has 3 values then the object will have at least those three values after the modification.
Existing values for these properties will remain unchanged, in contrast to the update operation.
Update operations
TopBraid generates an updateXY
mutation for each published shape in a schema.
This takes a single mandatory input
parameter, which must specify a uri
to identify the object that is being modified.
Only the values of the properties represented by the given fields are modified.
So for example when a multi-valued property friends
has 3 values then the object will have exactly those three values after the modification.
If the value is given as null or the empty array [] then all values of the corresponding property will be deleted.
The main difference between addTo
and update
are that in order to add values using update you also need to include all old values, because otherwise these would be overwritten.
Delete operations
Use the delete
mutation, providing one or more URIs of the RDF resources that shall be deleted.
Clients must specify uri
(a single ID) or uris
(an array of IDs).
The following example deletes the resource with URI http://example.org/myObject
, assuming it was mentioned in three triples.
1mutation {
2 delete (uri: "http://example.org/myObject")
3 report {
4 deletedCount
5 }
6}
The output of the operation would be:
1{
2 "data": {
3 "delete": true,
4 "report": {
5 "deletedCount": 3
6 }
7 }
8}
Note that in contrast to the other operations, deleting a resource is independent of any give shape. All triples that have the resource as subject, predicate or object will be deleted.
If a deleted object is used in an RDF triple involving blank nodes (as RDF subject or object) then these blank nodes are considered to depend on the deleted object. Any depending triples, including their (possibly recursive) further dependents, will be deleted alongside of the originally deleted URI resources.
Result reports
The report field SHOULD be present in every mutation request, and must be used after any create, update or delete fields.
It produces a result
object with fields that contain details about the changes that were requested.
The fields addedCount
and deletedCount
return the number of triples that have been added and deleted, respectively.
The field conforms
returns true if no constraint violations have been found.
Any modified RDF node will be validated.
For example, if a resource is being deleted, this may cause violations in objects that are pointing at this resource (for example, violating a sh:minCount
constraint).
The operation will only be performed if no violations have been reported or ignoreViolations
has been set true in the commit
field, as mentioned later.
The field results returns an array of objects with details about any constraint violations produced by the SHACL engine. This array is empty if conforms is true. Further details will be provided later.
Commits and dry runs
If a mutation request does not have a commit
field, it will only be performed as a “dry run”, without causing the changes to be written to the actual database.
In that case, clients would merely request the report field to learn about whether the change would cause any constraint violations.
This can be used to validate an input form before it is being sent to the real database.
The commit
field can only be used as the last field of a mutation request.
If present, the system triggers a write of the actual changes to the database.
The client has the option to pass in a message
parameter, which may be used to record the change in a change history.
If no message
is provided, the system will auto-generate a suitable message by looking at the changes that were submitted.
That message is also the result value of the field.
The commit
field may have the value true for the parameter ignoreViolations
.
In this mode, the validation of the data against the shapes will not happen (unless explicitly requested in the report
object.
This allows updates that are known to violate constraints, for example because they are required as part of a larger set of changes, or because the user has confirmed that the updates should go ahead regardless.
This flag should be used sparingly because it risks follow-up problems.
For example, if a sh:maxCount 1
violation is ignored and an object has two values, the engine would deliver a random value in queries.
In some configurations, such modifications will still be rejected.
Read-only fields
Some fields are read-only and cannot be directly updated using GraphQL. If a read-only field is part of an input object, then the values of these fields are ignored.
The built-in field label is read-only because label is dynamically derived from other property values such as rdfs:label
and may produce different results per user language in each request.
To modify the values of label, mutate the underlying properties directly, e.g. using the field rdfs_label
or prefLabel
for instances of skos:Concept
.