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]

The system internally converts this schema to SHACL and produces an enhanced GraphQL schema with the following mutations:

 1schema {
 2	...
 3	mutation: RootRDFMutation
 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
15type _MutationReport {
16	addedCount: Int
17	deletedCount: Int
18	conforms: Boolean!
19	results: [_MutationResult]
22type _MutationResult {
23	message: String
24	severity: String!
25	focusNode: _RDFNode!
26	path: String
27	value: _RDFNode
28	suggestions: [_MutationResultSuggestion]
31type _RDFNode {
32	label: String!
33	uri: ID
36type _MutationResultSuggestion {
37	message: String!
38	confidence: Float
39	applyData: String
42input Human_Input {
43	uri: ID!
44	label: String
45	id: ID
46	name: String
47	height: Float
48	friends: [Human_Input]

An example mutation request may look as follows:

 1mutation {
 2	createHuman (input: {
 3		uri: "",
 4		id: "123",
 5		name: "Darth Vader"
 6	})
 7	updateHuman (input: {
 8		uri: "",
 9		father: {
10			uri: ""
11		}
12	})
13	report {
14		addedCount
15		deletedCount
16	}
17	commit (message: "Added Luke's dad")

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.

 2    "data": {
 3        "createHuman": true,
 4        "updateHuman": true,
 5        "report": {
 6            "addedCount": 4,
 7            "deletedCount": 0
 8        },
 9        "commit": "Added Luke's dad"
10    }

For those familiar with RDF, the new triples would be:

2	a ex:Human ;
3	ex:id "123" ;
4	ex:name "Darth Vader" .
7	ex:father <> .

The mutations include the following fields:

  • createXY operations to create a new instance, for each published data shape

  • addToXY operations that add values to an instance, for each published data shape

  • updateXY operations to modify an existing instance, for each published data shape

  • delete operation to delete one or more instances

  • results to produce information about the operations

  • commit 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, assuming it was mentioned in three triples.

1mutation {
2	delete (uri: "")
3	report {
4		deletedCount
5	}

The output of the operation would be:

2    "data": {
3        "delete": true,
4        "report": {
5            "deletedCount": 3
6        }
7    }

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.