Formal definition of GraphQL mutations

24 views
Skip to first unread message

Davide Sottara

unread,
Jul 17, 2024, 3:49:52 PMJul 17
to TopBraid Suite Users
Hi!
I have been using the out of the box GraphQL mutations, "createX", "addToX", "updateX" where X is one of my class/shapes.
I have been observing some behaviors that are not immediately evident from the 
documentation here:
https://www.topquadrant.com/doc/7.8/graphql/mutations.html

For example, "createX(uri)" does not violate any constraint if "uri" already
exists as an individual, but reports a violation if uri is an instance of X.
That is, it does not check that "uri ?p ?o", but rather "uri a _ subClassOf* X".
This would be consistent with the documentation. 

However, updateX(uri) does not seem to check "uri a X" as a precondition.
In fact, if the mutation is able to assert "uri a Y" through a property shape
bound to rdf:type, the mutation succeeds, effectively creating a new
individual with a type that may be unrelated to X.
Besides being a way to implement "duck typing", this operation behaves
more like "upsert" than "update" - very useful!, but not exactly what I would
expect from the name and the docs.

In general, is there a more precise definition of the mutations - for example 
by means of an equivalent SPARQL query ?

Thank you in advance
Davide


Holger Knublauch

unread,
Jul 18, 2024, 3:30:08 AMJul 18
to 'Luis Enrique Ramos García' via TopBraid Suite Users
Hi Davide,

On 17 Jul 2024, at 9:49 PM, Davide Sottara <dso...@gmail.com> wrote:

Hi!
I have been using the out of the box GraphQL mutations, "createX", "addToX", "updateX" where X is one of my class/shapes.
I have been observing some behaviors that are not immediately evident from the 
documentation here:
https://www.topquadrant.com/doc/7.8/graphql/mutations.html

For example, "createX(uri)" does not violate any constraint if "uri" already
exists as an individual, but reports a violation if uri is an instance of X.
That is, it does not check that "uri ?p ?o", but rather "uri a _ subClassOf* X".
This would be consistent with the documentation. 

As some rather geeky insight, I'll copy the source code of the createXY operations below:

@SuppressWarnings("rawtypes")

private static boolean create(DataFetchingEnvironment environment, ObjectType objectType) {

Map obj = environment.getArgument(INPUT);

String uri = (String) obj.get(Schema.URI_FIELD_NAME);

if(uri != null) {

QueryContext context = environment.getContext();

Model dataModel = ModelFactory.createModelForGraph(context.getDiffGraph());

Resource r = GraphQLUtil.getResource(dataModel, uri);

Resource type = r.getPropertyResourceValue(RDF.type);

if(type != null) {

Report report = context.getDiffReport();

ValidationResult result = report.createValidationResult(null, r.asNode());

result.addMessage("URI " + uri + " already used as instance of " + RDFLabels.get().getCustomizedLabel(type));

result.constraintComponent(NodeFactory.createURI(DASH.NS + "URIAlreadyUsedConstraintComponent"));

}

}

return mutate(environment, objectType, false, false);

}



From that it seems that the "already used" error is reported if the resource already has any type (due to type != null).

I don't see handling of subClassOf that you mention.


However, updateX(uri) does not seem to check "uri a X" as a precondition.
In fact, if the mutation is able to assert "uri a Y" through a property shape
bound to rdf:type, the mutation succeeds, effectively creating a new
individual with a type that may be unrelated to X.
Besides being a way to implement "duck typing", this operation behaves
more like "upsert" than "update" - very useful!, but not exactly what I would
expect from the name and the docs.

Here is what the updateXY operations do:

private static boolean update(DataFetchingEnvironment environment, ObjectType objectType) {

return mutate(environment, objectType, true, false);

}

@SuppressWarnings("rawtypes")

private static boolean mutate(DataFetchingEnvironment environment, ObjectType objectType, boolean replace, boolean addTo) {

QueryContext context = environment.getContext();

Model dataModel = ModelFactory.createModelForGraph(context.getDiffGraph());

Map obj = environment.getArgument(INPUT);

addFieldValues(dataModel, obj, objectType, replace, addTo, context);

return true;

}


@SuppressWarnings("rawtypes")

private static Resource addFieldValues(Model dataModel, Map obj, ObjectType objectType, boolean replace, boolean addTo, QueryContext context) {

// replace is true when called from an 'update', false for 'create'

String uri = (String) obj.get(Schema.URI_FIELD_NAME);

Resource resource = GraphQLUtil.getResource(dataModel, uri);

context.addDiffReportFocusNode(resource.asNode());


// Update rdf:type/dash:shape based on the object type and the GraphQL field "type"

Resource nodeShape = objectType.getNodeShape();

Set<Resource> newTypes = new HashSet<>();

Object types = obj.get(Schema.TYPE_FIELD_NAME);

Property typePredicate = JenaUtil.hasIndirectType(nodeShape, RDFS.Class) ? RDF.type : DASH.shape;

if(types instanceof List) {

// If types have been explicitly passed in, just use those

typePredicate = RDF.type;

for(Object member : ((List)types)) {

String typeURI = (String) ((Map)member).get(Schema.URI_FIELD_NAME);

newTypes.add(GraphQLUtil.getResource(dataModel, typeURI));

}

for(RDFNode node : newTypes) {

dataModel.add(resource, typePredicate, node);

context.addDiffReportFocusNode(resource.asNode());

}

if(replace) {

for(RDFNode old : JenaUtil.getResourceProperties(resource, typePredicate)) {

if(!newTypes.contains(old)) {

removeIfFromBaseModel(dataModel, resource, typePredicate, old);

}

}

}

}

else if(!replace && !addTo) {

// For newly created instances, add type triple, unless this is called from an addTo mutation

dataModel.add(resource, typePredicate, nodeShape);

}

...


I am afraid I don't see a problem with the current behaviour, and would shy away from making changes to the GraphQL semantics as this would impact users that rely on the current implementation.

Do you have a specific suggestion on how I should update the documentation to better reflect the current situation?


In general, is there a more precise definition of the mutations - for example 
by means of an equivalent SPARQL query ?

See source code above :)

HTH
Holger




Thank you in advance
Davide



--
The topics of this mailing list include TopBraid EDG and related technologies such as SHACL.
To post to this group, send email to topbrai...@googlegroups.com
---
You received this message because you are subscribed to the Google Groups "TopBraid Suite Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to topbraid-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/topbraid-users/3cf38e5a-e1b2-4ed6-afbf-c5665f7e6fe9n%40googlegroups.com.

Reply all
Reply to author
Forward
0 new messages