GraphQL Mutation Tutorial¶
What’s in this document?
The following sections provide examples of Star Wars GraphQL mutations and responses. If you have started the services following the Getting Started guide, you can also try the queries out and modify them as you see fit.
All examples in this section are based on the Star Wars dataset.
Object Creation¶
There are a few types of object creations: creating a single or multiple objects of the same type, creating objects of different types, as well as creating multiple nested objects at once.
A create mutation is added for every concrete Semantic Object. It is not possible to create objects from an abstract object such as Character
,
but create mutations for all its concrete objects are available, e.g., create_Human
, create_Droid
, create_Aleena
, etc.
Note
As we do have automatic ID generation, it is not necessary to provide an ID for every object.
Other mandatory properties (such as name
in the Star Wars schema) must be provided on every object creation.
Each create mutation consists of two parts - mutation part and result part:
The mutation part is where we define what objects we are creating.
The result part is where we define the result we want to get from the mutation (created object with its properties).
The result part contains the following fields:
affected_count
with the fieldscount
andkind
– used to query the number of the objects modified by the current mutation, grouped by type.
affected_objects
with the fieldsids
andkind
– used to query the IRIs of the objects modified by the current mutation, grouped by type.a property that matches the mutation type and can be used to access the created objects. For example, the mutation
create_Human
will have a property namedhuman
that can be used to query the created humans from the current mutation. This selection supports result ordering and limiting via the argumentsorderBy
,limit
, andoffset
. Additional filtering can be applied via thewhere
argument, and default language for fetching theLiteral
properties can be applied via thelang
argument.For more information on the latter, check the Language Fetching section.
Note
Create, update, and delete mutations have identical response format. They only differ in the actual response that is returned.
For example:
create mutations return the newly created objects
update mutations return the newly created and updates objects
delete mutations return the removed objects before their deletion
If the two selections are included in the result part, the Platform will return the number of the affected objects and their IDs.
Create Semantic Objects of a Single Type (without Nesting)¶
Let’s start with simple, single type object creation without nesting.
Let’s say we want to create a human named Lando Calrissian
with a unique ID
property https://swapi.co/resource/human/25
.
The following create mutation will create an object of type Human
with name Lando Calrissian
and eyeColor
blue, and will return the created Human with its ID
and name
that were requested in the sub-selection.
mutation createHuman { create_Human(objects: { id: "https://swapi.co/resource/human/1025" rdfs_label: {value:"Lando"} eyeColor: "blue" }) { human { id name type eyeColor } affected_count { kind count } affected_objects { kind ids } } }
We can see the selected properties for the newly created Human. We also see that some of the properties like the type
are filled automatically according to the schema.
Now let’s create two Human
objects in a batch and also skip the ID
of the second Human.
mutation createHuman { create_Human(objects: [ { id: "https://swapi.co/resource/human/1026" rdfs_label: {value: "Anakin"} eyeColor: "blue" }, { rdfs_label: {value: "Palpatin"} eyeColor: "green" } ]) { human { id name type eyeColor } affected_count { kind count } affected_objects { kind ids } } }
Note how the two Human
objects are now wrapped inside []
, which indicates batch objects creation. Also, the second Human
has a generated ID
as we have skipped adding one during the creation.
Create Semantic Objects of Multiple Types (without Nesting)¶
Let’s say we want to create a Human
named Lor San Tekka
, another one named Major Bren Derlin
, and a Droid
named R2-D3
.
mutation createHumanAndDroid { create_Human(objects: [ { id: "https://swapi.co/resource/human/1027" rdfs_label: {value: "Lor San Tekka"} eyeColor: "blue" }, { rdfs_label: {value: "Major Bren Derlin"} eyeColor: "green" } ]) { human { id name type eyeColor } affected_objects { kind ids } }, create_Droid(objects: { rdfs_label: {value: "R2-D3"} mass: 222 }) { droid { id name type mass } affected_objects { kind ids } } }
A few things to note here:
Every type of object (
Human
andDroid
in the example) has its own mutation and results parts.
affected_count
andaffected_objects
are not mandatory.You can mix batch creations with single object creations, and also mix different types.
Nested Creation of Semantic Objects¶
We already saw how Semantic Objects are created, but there is an even more powerful option for creating them - the creation of nested Semantic Objects.
While creating an object, you can create nested objects which will be “linked” to the current object.
For example, let’s create a Human
who is from an unknown (new) Homeworld
.
mutation createHumanWithHomeworld { create_Human(objects: [ { id: "https://swapi.co/resource/human/1028" rdfs_label: {value: "Lando's brother"} eyeColor: [ "blue" ] homeworld: { planet: { id: "https://swapi.co/resource/planet/111" rdfs_label: {value: "Earth"} } } } ]) { human { id name type eyeColor homeworld { id type name } } affected_objects { kind ids } } }
Let’s see what’s new here:
We are creating a
Human
in the usual way it is done, but nowHomeworld
is included.The
Homeworld
is created during theHuman
creation, and it is of typePlanet
(as this is the only option in this case).The newly created
Homeworld
can be queried in the result.Now there are two objects created - one
Human
and onePlanet
. The Planet is linked to the Human as the Human’s Homeworld.
One more example of nested objects, this time with multiple levels of nesting:
mutation createHumans { create_Human(objects: [{ id: "https://swapi.co/resource/human/1011" rdfs_label: {value: "Palpatin"} starship: { starship: { id: "https://swapi.co/resource/starship/7451", rdfs_label: {value: "Deathstar X"} pilot: { yodasspecies: { id: "https://swapi.co/resource/yodasspecies/221" rdfs_label: {value: "Baby Yoda"} desc: {value: "Cute, but deadly", lang: "en"} film: { film: { id: "https://swapi.co/resource/film/4131", rdfs_label: {value: "One film to rule them all"}, vehicle: { vehicle: { id:"https://swapi.co/resource/vehicle/451", rdfs_label: {value: "Lightbuster"} crew:666 pilot: { human: { id:"https://swapi.co/resource/human/1001" rdfs_label: {value: "Admiral Conan Antonio Motti"} } } } } } } } } } } }]) { human { name id starship{ id name pilot{ id name desc{ value } film{ id name vehicle{ id name crew pilot{ id name } } } } } } affected_count { kind count } affected_objects{ ids kind } } }
A bit of a more complicated example, but let’s see what’s going on here:
A
Human
named “Palpatin” is created.His starship is created using a nested creation.
The starship’s pilot is created using a nested creation, the pilot is of type
yodasspecies
.The pilot will “act” in a film that is created using a nested creation.
Also, a vehicle is created using nested creation inside the film.
And finally. the vehicle has a pilot who is
Human
and is created using a nested creation.
Note that in the result, there are two humans on the same level as we are requesting all created humans with their properties.
Although the last human in the nested creation is very deep, it is still a Human
and is listed at level 1 as there is the Human
request.
Creation of Nested Semantic Objects Using ID Filter¶
Another useful feature is using an ID
filter during the creation of a nested object.
For example, you can create a Human
and “link” it to a Homeworld
by using the Homeworld’s ID
. This will save us one update mutation.
Normally, this would require the following mutations:
Create
Human
.Update the
Human
“linking” it to aHomeworld
.
Instead, if we know the Homeworld’s ID (for example let’s say the ID of the “Tatooine” is https://swapi.co/resource/planet/1
), we can do this:
mutation createHumanWithHomeworld { create_Human(objects: [ { id: "https://swapi.co/resource/human/1029" rdfs_label: {value: "Paige Tico"} eyeColor: "blue" homeworld: { ids: "https://swapi.co/resource/planet/1" } } ]) { human { id name type eyeColor homeworld { id type name } } affected_objects { kind ids } } }
Note that only the Human
is listed in the affected_objects
result as the planet “Tatooine” already exists.
We can also have multiple ID
values if the property type is multi value. For example a Human
can be “linked” to multiple Vehicle
objects.
The same mutation, but with multiple vehicle IDs (of vehicles that exist) would look like this:
mutation createHumanWithVehicle { create_Human(objects: [ { id: "https://swapi.co/resource/human/1030" rdfs_label: {value: "Dak Ralter"} eyeColor: "blue" vehicle: { ids: ["https://swapi.co/resource/vehicle/4", "https://swapi.co/resource/vehicle/7"] } } ]) { human { id name vehicle { id type name } } affected_objects { kind ids } } }
Creation of Nested Semantic Objects Using Full Filters¶
Another case that we might encounter is when we do not know the IDs of some objects, but we know some of their properties.
In this example we want to create a Human
and “link” it to all existing starships that have female pilots.
mutation create_human { create_Human (objects: [{ id: "https://swapi.co/resource/human/1031" rdfs_label: {value: "Admiral Piett"} starship: { where: {ALL_EXISTS: {pilot: {gender: {EQ: "female"}}}} } }]) { human { id name starship { id name pilot { id name gender } } } } }
All filters are available during nested creation. You can learn more about the filters here.
And as a final example, we can combine everything above in a single mutation like this:
mutation create_human { create_Human (objects: [{ id: "https://swapi.co/resource/human/1032" rdfs_label: {value: "Palpatin"} starship: { where: {ALL_EXISTS: {pilot: {gender: {EQ: "female"}}}} } homeworld: { ids: "https://swapi.co/resource/planet/8" } vehicle: { vehicle: { id: "https://swapi.co/resource/vehicle/111" rdfs_label: {value: "Rocket X"} } } }]) { human { id name starship { id name pilot { id name gender } } homeworld { id name } vehicle { id name } } } }
Object Updates¶
In the Object Creation section above, we learned how to create one or multiple Semantic Objects. Now, let’s see how we can modify and update them.
Using the update mutation requests, we can do the following:
modify single-valued scalar properties
modify multi-valued scalar properties
add or remove relations to other object
update specific objects by IRI
update multiple objects using filters
update objects via their interfaces
do all of the above for nested objects
In future releases, you will also be able to:
create relations between objects using select filters
create new a Semantic Object and link it to the updated parent object
remove relations and delete the other Semantic Objects
Before we take a look at each functionality, let’s see what the similarities and differences are compared to the Semantic Objects create.
Firstly, the mutation names are changed to update_Type
, where for Type
we can use any defined type: abstract and non-abstract such as update_Human
, update_Droid
and update_Film
. The main difference here is that we have update_Character
. This enables us to create requests that modify common properties for all sub-types.
Another major difference is that we can have only single root update request per mutation. In contrast, the creates can have multiple root level object creates of the same type.
There is a change in the multi-valued scalar properties update format as well. The format is as follows:
"Input for updating multi-value String fields"
input String_Multi_Value_Input {
"Values to add to the affected property"
value: [String!]
"Replace values matching this property. Cannot be combined with replace"
patch: String
"Overwrite all values for this property. Defaults to false."
replace: Boolean = false
}
Definitions as the one above one are defined for all literal types. This allows the creation of very powerful multi-valued update operations. To illustrate the possible combinations, we will use the Species
type and its property hairColor
:
Remove all
Species
hair colorshairColor: []
Add hair color
brown
to the updatedSpecies
hairColor: [{value: "brown"}]Add hair color
brown
andblond
hairColor: [{value: "brown"}, {value: "blond"}]hairColor: [{value: ["brown", "blond"]}]Update hair color
brown
with valuebrownish
hairColor: [{value: "brownish", patch: "brown"}]Update hair color
brown
set asbrownish
and add hair colorred
hairColor: [{value: "brownish", patch: "brown"}, {value: "red"}]Update hair color
brown
and replace withbrownish
; remove hair colorred
hairColor: [{value: "brownish", patch: "brown"}, {value: null, patch: "red"}]Update hair color
red
and replace it withpink
,peachy
, andvelvet
hairColor: [{value: ["pink", "peachy", "velvet"], patch: "red"}]Replace all hair colors with
pink
andvelvet
hairColor: [{value: ["pink", "velvet"], replace: true}]Patching and Replacement are invalid, and will throw a Validation exception
hairColor: [{value: ["bright red"], patch: "red", replace: true}]
The following sections demonstrate all features in detail, so let’s begin.
Update Semantic Objects by ID (without Nesting)¶
Let’s start with a simple update by ID for scalar properties.
Say we want to update a human named Lando Calrissian
to have different eye and hair colors, assuming we also know his ID to be https://swapi.co/resource/human/25
.
The following mutation will do an update by ID and type Human
, and will set new eyeColor
to dark blue and hairColor
to blond. It will return the updated Human with its ID
, name
, eyeColor
, and hairColor
that were requested in the sub-selection.
mutation updateHuman { update_Human(where: {ID: "https://swapi.co/resource/human/25"}, objects: { eyeColor: {value: "dark blue", replace: true} hairColor: {value: "blond", replace: true} }) { human { id name eyeColor hairColor } affected_count { kind count } affected_objects { kind ids } } }
We can see the selected properties for the updated Human. We also see that some of the properties like the name are fetched from the database and returned.
Now let’s change some multi-valued properties of Human
Species
and:
add
skinColor
albinoupdate the blue
eyeColor
with light blueremove the
hairColor
red
mutation updateHumanSpecies { update_Species(where: {ID: "https://swapi.co/vocabulary/Human"}, objects: { skinColor: [{value: "albino"}] eyeColor: [{value: "light blue", patch: "blue"}] hairColor: [{value: null, patch: "red"}] }) { species { id name skinColor eyeColor hairColor } affected_count { kind count } affected_objects { kind ids } } }
That was easy, right? Now let’s change some relations. We go to Lando Calrissian
again and change him again:
to appear in the new film
The Phantom Menace
identified by the IDhttps://swapi.co/resource/film/4
.remove the appearance from the film
The Empire Strikes Back
with IDhttps://swapi.co/resource/film/2
.change his starship allocation from
Millennium Falcon
with IDhttps://swapi.co/resource/starship/10
to theDeath Star
with IDhttps://swapi.co/resource/starship/9
.
mutation updateHuman { update_Human(where: {ID: "https://swapi.co/resource/human/25"}, objects: { film: [{ ids: "https://swapi.co/resource/film/4" },{ patch: "https://swapi.co/resource/film/2" }] starship: { ids: "https://swapi.co/resource/starship/9", patch: "https://swapi.co/resource/starship/10" } }) { human { name film { id name } starship { id name } } } }
Update Semantic Objects by ID with Nesting¶
Now let’s dive into the object structure and try to update other objects through an existing relation between them. If a nested update points to an object that does not have an existing relation, but the parent updates, then nothing will be changed.
Note
If no relation exists between the parent and the nested object, the nested update operation does nothing.
Let’s update Lando Calrissian
again by updating his homeworld
and set a new description, a planet diameter
, as well as add new resident
https://swapi.co/resource/human/28
.
mutation updateHuman { update_Human(where: {ID: "https://swapi.co/resource/human/25"}, objects: { homeworld: { planet: { desc: { value: {value: "The home planet of the famous Lando Calrissian", lang: "en"}} diameter: 14600 resident: { ids: "https://swapi.co/resource/human/28" } } patch: "https://swapi.co/resource/planet/30" } }) { human { name homeworld { id name desc{ value } diameter resident { id name } } } } }
We can see that the changes are applied to the linked homeworld
and the planet’s resident
list now include the Mon Mothma
.
The other important thing to note here is how the nested changes are wrapped in the property named planet
. This property matches the relation target type.
When dealing with concrete types, the relation range matches the type name. However, when the range of the relation is interface type, then we have more options.
We can choose to use one of the sub-types or the parent interface. Let’s look at an example.
We will update the planet Socorro
and its residents.
In the previous example, we added a second resident to the planet of Socorro
with name Mon Mothma
.
Now, let’s make Lando
and Mon
friends by creating a friend
relation between them. As the friend
relation is not part of the Character
interface, we cannot use it directly during an update operation. What we can do is use a concrete type name to access any specific property for that type. In this case, we can use the human
type selector to access the specific Human
properties as well as the inherited properties from the Character
interface. This is called a type selector as the Platform will automatically add a check whether the updated object matches the specified type, and will not modify the related entity if the type does not match.
Note
To access a specific type property, use the concrete type selector in relations with interface range.
mutation updatePlanet { update_Planet(where: {ID: "https://swapi.co/resource/planet/30"}, objects: { resident: [ { character:{ desc: {value: {value: "Lando Calrissian's friend", lang: "en"}} } human: { friend: { ids: "https://swapi.co/resource/human/25" } } where: {ID: "https://swapi.co/resource/human/28"} }, { character:{ desc: {value: {value: "Mon Mothma's friend", lang: "en"}} } human: { friend: { ids: "https://swapi.co/resource/human/28" } } where: {ID: "https://swapi.co/resource/human/25"} } ] }) { planet { name resident { id name desc{ value } ... on Human { friend { name desc { value } ... on Human { friend { name } } } } } } } }
Update Semantic Objects Using Full Filters¶
In this section, we will learn how to update objects using filters.
Let’s start with a simple update by name.
Suppose we want to update our friend Lando Calrissian
but we do not know his ID. This will not be a problem, as we can search him by his first name, and add a new friend to him. While we are at it, let’s also add Lando’s friend as a resident in his homeworld.
mutation updateHuman { update_Human(where: {name: {IRE: "Lando"}}, objects: { friend: [{ ids: "https://swapi.co/resource/human/39" }] homeworld:{ patch: "https://swapi.co/resource/planet/30" planet:{ resident: { ids: "https://swapi.co/resource/human/39" } } } }) { human { name friend { id name } } } }
Now let’s dive deeper. Suppose we have a task to do all of the following in a single request:
If there are
Human
residents on planetSocorro
, do the following tasks:Add new
resident
to the planet with IDhttps://swapi.co/resource/droid/3
(this would beR2-D2
);Assign a
primaryFunction
to that droid as well as change hishomeworld
;Assign
Lando
andMon
and the newly added droid to starshiphttps://swapi.co/resource/starship/10
(theMillennium Falcon
) if they are residents onSocorro
;Make
Lando
andMon
friends withR2-D2
.
Let’s try something different and build the query step by step.
In order to update any Planet
, we need to use the update_Planet
mutation. As we saw earlier, we can filter by any property. So write the following filter:
update_Planet(where: {name: {EQ: "Socorro"}, resident: {type: {IN: "https://swapi.co/vocabulary/Human"}}}
Or we can use the new type selector syntax and change it to
update_Planet(where: {name: {EQ: "Socorro"}, resident: {_ifHuman:{}}}
Next step will be to add a new resident. We already know how to it from the previous examples:
resident: {
ids: "https://swapi.co/resource/droid/3"
}
We have added the new resident, so let’s assign it a primaryFunction
. One of the features is to allow the modifying of an object right after adding it to the parent object so we can do the following:
resident: [
{
ids: "https://swapi.co/resource/droid/3"
},
{
patch: "https://swapi.co/resource/droid/3"
droid: {
primaryFunction: "repair droid"
}
}
So we set a primary function, but what about changing the homeworld
? As we need to replace the previous value of the droid’s homeworld with new value, we need to use the replace: true
argument. With that change, the droid update is complete:
resident: [
{
ids: "https://swapi.co/resource/droid/3"
},
{
patch: "https://swapi.co/resource/droid/3"
droid: {
primaryFunction: "repair droid"
homeworld: {
ids: "https://swapi.co/resource/planet/30"
replace: true
}
}
}
For the next step, we want to select Lando
and Mon
and the newly added droid, but we do not want to select Ric Olié
that we added in the previous example. This can be done with a filter like:
where: {_ifHuman: {name: {IRE: "lando|mon"}}, _ifDroid: {}}
We have selected everyone we need to update, so let’s add a change that is common for all of them, namely to assign them to the starship
with ID https://swapi.co/resource/starship/10
.
resident: {
where: {_ifHuman: {name: {IRE: "lando|mon"}}, _ifDroid: {}}
character: {
starship: {
ids: "https://swapi.co/resource/starship/10"
replace: true
}
}
}
What remains is to add the droid as a friend of Lando
and Mon
. From the previous examples, we saw that we can combine multiple type classifiers in a single request. They work as a filter as well, so we can safely add the last requirement to the same change.
resident: {
where: {_ifHuman: {name: {IRE: "lando|mon"}}, _ifDroid: {}}
character: {
starship: {
ids: "https://swapi.co/resource/starship/10"
replace: true
}
}
human: {
friend: {
ids: "https://swapi.co/resource/droid/3"
}
}
}
With this, the request is complete. These are the full request and the expected response:
mutation updateHuman { update_Planet(where: {name: {EQ: "Socorro"}, resident: {_ifHuman:{}}}, objects: { resident: [ { ids: "https://swapi.co/resource/droid/3" }, { patch: "https://swapi.co/resource/droid/3" droid: { primaryFunction: "repair droid" homeworld: { ids: "https://swapi.co/resource/planet/30" replace: true } } }, { where: {_ifHuman: {name: {IRE: "lando|mon"}}, _ifDroid: {}} character: { starship: { ids: "https://swapi.co/resource/starship/10" replace: true } } human: { friend: { ids: "https://swapi.co/resource/droid/3" } } } ] }) { planet { name resident(orderBy: {name:ASC}) { name ... on Human { friend { name } } ... on Droid { primaryFunction } starship { name } homeworld { name } } } } }
Object Deletion¶
Delete Semantic Objects of a Single Type¶
It is possible to delete a single or multiple objects of the same type.
A delete mutation is added for every concrete Semantic Object. It is not possible to delete objects from an abstract object such as Character
,
but delete mutations for all its concrete objects are available, e.g., delete_Human
, delete_Droid
, delete_Aleena
, etc.
Note
When deleting Semantic Objects, it is mandatory to provide a Semantic Object ID filter for one or many objects.
Filtered deletes, like for example “Delete all Humans with green eyes”, are currently not supported.
Delete mutations are kept simple and will operate using a mandatory where: {ID: ["X"]}
filter.
Batch delete using filters such as where: {ID: ["X", "Y"]
is supported as well.
The sub-selection in the mutation is mandatory. We can either retrieve the deleted properties or check the affected_count
and affected_objects
in it.
affected_count
returns the number of deleted objected sorted by kind.
affected_objects
returns theids
of the removed objects by kind.
Let’s say we want to delete a human named Lando Calrissian
who has a unique ID
property that equals https://swapi.co/resource/human/25
.
The following delete mutation will remove all properties of Lando Calrissian
from the database and return the deleted values that were requested in the sub-selection.
mutation deleteHumans { delete_Human( where: {ID: ["https://swapi.co/resource/human/25"]}) { human { id name (lang: "en") type desc (lang: "en") { value } eyeColor hairColor skinColor birthYear film { id } height mass homeworld { id } starship { id } vehicle { id } species { id } gender } affected_count { kind count } affected_objects { kind ids } } }
Here is another example with batch delete, where multiple objects of type Human
are being deleted.
mutation deleteHumans { delete_Human( where: {ID: ["https://swapi.co/resource/human/1", "https://swapi.co/resource/human/42", "https://swapi.co/resource/human/88"]}) { human { id name (lang: "en") type desc (lang: "en") { value } eyeColor hairColor skinColor birthYear film { id } height mass homeworld { id } starship { id } vehicle { id } species { id } gender } affected_count { kind count } } }
There are some basic validations performed upon mutation execution.
The mutation below, for example, tries to delete a Droid
instead of Human
:
mutation deleteHumans { delete_Human( where: {ID: ["https://swapi.co/resource/droid/8"]}) { human { id name (lang: "en") type desc (lang: "en") { value } eyeColor hairColor skinColor birthYear film { id } height mass homeworld { id } starship { id } vehicle { id } species { id } gender } affected_count { kind count } affected_objects { kind ids } } }
Not specifying any ID
will result in an empty response as nothing has been deleted.
mutation deleteWithoutID { delete_Human (where:{ID:[]}){ human { id name } affected_count { kind count } affected_objects { kind ids } } }
Mutations for Literal Properties¶
Literal properties have special handling as they are scalar properties but are represented as multi-property structures like objects.
Any scalar or object type can be represented with the Literal
type, with the difference being the type
property of the queried or mutated object. The Ontotext Platform supports the following scalar-based types:
langString
- represents literal string literals with non-empty language
stringOrLangString
- represents literal string with or without language tag
You can read more about the literal-based types in the Literals and Union Datatypes section.
Creating Literal Properties¶
When adding literal values, the value
property is required, but the rest of the properties are optional based on the concrete type of the property being saved.
When creating langString
, the language property is required as the type does not allow properties without language tag. This rule, however, is only valid if a schema or property default implicit
language spec is not set.
Note
For more information about the language configurations and defaults, see SOML property language configurations.
Let’s demonstrate a create mutation with the following example:
mutation createHumanWithDescription { create_Human (objects: [ { rdfs_label: {value: "Poe Dameron"} desc: [{ value: "Poe Dameron is a fictional character in the Star Wars franchise. Introduced in the 2015 film Star Wars: The Force Awakens" }, { value: "Poe Dameron ist eine fiktive Figur in der Star Wars-Reihe. Eingeführt im Film Star Wars 2015: The Force Awakens", lang: "de" }] } ]) { human { name desc { value lang } } } }
Here, we added two values for the desc
property. The first one did not have language tag specified, and in the response, it does.
This is the result of the implicit language configuration that in this case was set to implicit: en
.
All possible configurations for langString
typed properties are as follows:
value: “A title” |
No implicit in schema |
implicit: en |
---|---|---|
lang: “de” |
“A title”@de |
“A title”@de |
lang: “” |
error |
error |
lang: null |
error |
“A title”@en |
no lang value |
error |
“A title”@en |
When creating a stringOrLangString
value, the language is optional, and the end result depends on the value of the lang
property. This behavior looks the following way:
value: “A title” |
No implicit in schema |
implicit: en |
---|---|---|
lang: “de” |
“A title”@de |
“A title”@de |
lang: “” |
“A title” |
“A title” |
lang: null |
“A title” |
“A title”@en |
no lang value |
“A title” |
“A title”@en |
For any other type, the lang
argument is forbidden and an error will be emitted if a non-null value is passed.
For both langString
or stringOrLangString
typed properties, the type
property is forbidden. This is because it is implied based on the lang
argument, and will have either rdf:langString
or xsd:string
value.
Updating Literal Properties¶
The Platform provides powerful literal properties updates. In addition to the supported operations over scalar properties such as add, replace all, or update a specific value, it also supports arbitrary condition filtering of the value and/or the language by adding support for the where
argument.
mutation addHumanDescription { update_Human (where: {name: "Poe Dameron"}, objects: [ { desc: [{ value: { value: "Poe Dameron es un personaje ficticio de la franquicia de Star Wars. Introducido en la película de 2015 Star Wars: The Force Awakens", lang: "es" } }] } ]) { human { name desc { value lang } } } }
In this example, we added a new Spanish-language description to the previously added Human
named Poe Dameron
. In the next one, we will update the value with more details.
mutation updateHumanDescription { update_Human (where: {name: "Poe Dameron"}, objects: [ { desc: [{ value: { value: "Poe Dameron es un personaje ficticio de la franquicia de Star Wars. Introducido en la película de 2015 Star Wars: The Force Awakens, es interpretado por Oscar Isaac. Poe es un piloto de caza del ala X para la Resistencia que inadvertidamente trae al soldado de asalto renegado Finn (John Boyega) y al carroñero Jakku Rey (Daisy Ridley) en la lucha contra, y eventualmente una victoria sobre, la siniestra Primera Orden. Aparece en los medios y el merchandising de The Force Awakens, así como en una serie de cómics del mismo nombre, y aparecerá en la próxima secuela de películas, Star Wars: The Last Jedi. Isaac y el personaje han recibido críticas positivas, comparando a Poe con la caracterización de Han Solo (Harrison Ford) en la trilogía original de Star Wars.", lang: "es" }, patch: { value: "Poe Dameron es un personaje ficticio de la franquicia de Star Wars. Introducido en la película de 2015 Star Wars: The Force Awakens", lang: "es" } }] } ]) { human { name desc { value lang } } } }
Literal Properties Validation¶
When adding or updating langString
or stringOrLangString
types, the language parameter will be validated against an optional validation pattern.
Note
You can read more on how to configure the validation pattern in the SOML property language configurations section.
During the validation process, the following rules apply:
The mutation satisfies the property language validation pattern if all provided values match the positive list of allowed languages (if any), and no value matches the negative list of language exclusions (if any).
If
UNIQ
is specified in the validation pattern, then a property cannot have multiple values in the same language.
Note
Due to technical limitations, you can leverage the full support of the unique language validation feature only when SHACL validation is enabled. When SHACL validation is disabled, only create mutations will be validated for language uniqueness.
To enable SHACL support, see SHACL static validators.
Given the following SOML schema:
objects: Person: props: rdfs:label min: 1 lang: {validate: "UNIQ"}Either of these mutations will cause a validation error:
mutation { create_Human(objects: [ { id: "http://example.org/resource/Person1" rdfs_label: [ {value: "Alice"}, {value: "Bob"} ] }, { id: "http://example.org/resource/Person2" rdfs_label: [ {value: "Alice", lang: "en"}, {value: "Bob", lang: "en"} ] } ]) { human { rdfs_label { value lang } } } }This is intended to check the SKOS Integrity Condition S14, which states that a resource must have no more than one value of
skos:prefLabel
per language tag.
Mutations that specify an invalid language tag or violate a lang.validate
spec are rolled back, and a corresponding error is returned.
Note
Validation of constraints based on ALL
is currently not supported, because it relies on a specific SHACL shape that is currently not available in the RDF4J library. The progress of the required functionality in RDF4J can be tracked in the SHACL - Qualified shapes issue.
Multiple Mutations per Request¶
If multiple mutations are part of a mutation
request, they are executed strictly one after another, starting from the top of the request.
mutation insertHuman { create_Human(objects: [{id: "https://swapi.co/resource/human/1001", type: ["https://swapi.co/vocabulary/Human", "https://swapi.co/vocabulary/Character"], rdfs_label: {value: "New human", lang: "en"}}]) { human { name } affected_count { kind count } affected_objects { kind ids } } update_Human_1: update_Human(objects: {rdfs_label: {value: {value: "New human edit 1", lang: "en"}}}, where: {ID: "https://swapi.co/resource/human/1001"}) { human { name } affected_count { kind count } affected_objects { kind ids } } update_Human_2: update_Human(objects: {rdfs_label: {value: {value: "New human edit 2", lang: "en"}}}, where: {ID: "https://swapi.co/resource/human/1001"}) { human { name } affected_count { kind count } affected_objects { kind ids } } }
As you can see, first a new object of type Human
was created, then updated twice, and finally it was completely deleted in one mutation
request.
Note
All mutation requests are executed in a single transaction in order to ensure data consistency. If there is a problem with any of the mutations, the transaction will be rolled back and no changes will be committed to the store.
Furthermore, the result parts of the mutations are executed in a separate transaction after the data update is done. They only read the data from the store, which should not trigger the rollback of the request in case there is a problem during execution. This behavior is also necessary when the store is used in cluster mode.
Automatic Object Type Generation¶
As explained in the Object Typing section:
Each concrete Semantic Object type has a Type Descriptor.
The Type Descriptor is optional for the Abstract types.
Each object of a certain type is restricted to its own type descriptor as well as all its parents’ ones.
During the create and update phases, we need to make sure that all Type Descriptors are satisfied.
This is why for the create mutations we have enabled the option for automatic generation of the properties that describe the object. This functionality is triggered with the following property (enabled by default):
graphql.mutation.generation.options.TypeDataGenerator.enabled=true
So given the following schema:
objects:
ConceptScheme: {kind: abstract, type: [skos:ConceptScheme]}
Industry: {inherits: ConceptCommon, type: [industry/], typeProp: skos:inScheme}
If we try to create an Industry
object without specifying its type
and skos:inScheme
, they will be
automatically inferred to skos:ConceptScheme
and industry/
, and added to the mutation.
However, sometimes we cannot infer a value for the descriptors. This happens when we have a type with multi-valued
type
property. For example:
objects:
Geoname:
regex: '^http://sws\.geonames\.org/\d+/$'
type: [gn:Feature]
Country:
inherits: Geoname
typeProp: gn:featureCode
type: [gn:A.PCLI, gn:A.PCLD, gn:A.PCLIX, gn:A.PCLS, gn:A.PCL, gn:A.TERR, gn:A.PCLF]
If we want to create an object of type Country
, we would not be able to determine its gn:featureCode
. The reason for this is that, for the object to be valid, it is sufficient to have as gn:featureCode
at least one of the following:
[gn:A.PCLI, gn:A.PCLD, gn:A.PCLIX, gn:A.PCLS, gn:A.PCL, gn:A.TERR, gn:A.PCLF]
, but not all of them. So there is
no way to determine which gn:featureCode
needs to be picked from the list.
So for a create mutation to pass, each Country
object needs to have its gn:featureCode
explicitly set.
However, the Type Generation will still work for the Geoname
class, so the property type: gn:Feature
will be
added automatically.
Note that the Automatic Type Generation will only work if the property that needs to be
added is not added explicitly. This means that if we are creating a Country
with type: skos:Person
, we would not perform any of the following possible steps:
replace it with the proper
type
,append the proper
type
resulting intype: [skos:Person, gn:featureCode]
.
In this case, you will get notified that the object does not conform to its class and the mutation will not be executed.