You only have a single graph for multiple related and unrelated resources, which would be accessed over a web api (Hydra/LDP)
How do you handle updates and deletes to remove only the right triples.
Consider the resources, as can be found in tbbt-ld. Let's take the representation of Amy and Sheldon.
@base <http://tbbd-ld.tv/person/> .
@prefix s: <http://schema.org/> .
<amy-farrah-fowler> a s:Person;
s:additionalName "Farrah";
s:familyName "Fowler";
s:givenName "Amy";
s:jobTitle "neurobiologist";
s:knows
<bernadette-rostenkowski>,
<howard-wolowitz>,
<leonard-hofstadter>,
<penny>,
<rajesh-koothrappali>,
<sheldon-cooper>,
<stuart-bloom>
.
<sheldon-cooper> a s:Person;
s:additionalName "Lee";
s:address [
s:addressCountry "US";
s:addressLocality "Pasadena";
s:addressRegion "CA";
s:postalCode "91104";
s:streetAddress "2311 North Los Robles Avenue, Apartment 4A"
];
s:familyName "Cooper";
s:givenName "Sheldon";
s:jobTitle "theoretical physicist";
s:knows
<amy-farrah-fowler>,
<bernadette-rostenkowski>,
<howard-wolowitz>,
<leonard-hofstadter>,
<penny>,
<rajesh-koothrappali>,
<stuart-bloom>;
s:parent <mary-cooper>
.
We want the API to be able to manipulate these resources with HTTP interface
GET <sheldon-cooper>
PUT <mary-cooper>
DELETE <amy-farrah-fowler>
How does one ensure proper boundaries between resources, especially when doing updates?
While GET
could be naively implemented as a SPARQL DESCRIBE
, its implementation may vary between stores. Modifications are never so simple. For instance, given the connected subgraph
<amy-farrah-fowler> schema:knows <sheldon-cooper> .
<sheldon-cooper> schema:givenName "Sheldon"; schema:familyName "Cooper" .
Would the triples about Sheldon be removed or not?
Keep a SHACL Shape describing every managed resource, to track the exact subgraph which belongs to a given resource.
Excerpt from what that would look like for Sheldon:
[
sh:targetNode <sheldon-cooper> ;
sh:property [
# direct only listed as path
sh:path schema:name ;
] , [
# deep structures have their own Shape
sh:path schema:address ;
sh:node [
sh:property [
sh:path schema:addressCountry ;
]
] ;
]
] .
To load a resource, read the shape and retrieve only those properties and resources which it describes
base <http://tbbd-ld.tv/person/>
prefix sh: <http://www.w3.org/ns/shacl#>
construct {
<amy-farrah-fowler> ?rootProp ?rootObject .
?child ?childProp ?childObject .
}
from <http://tbbd-ld.tv/>
where {
# Find the Shape describing the retrieved resource
?rootShape sh:targetNode <amy-farrah-fowler> .
# But skip shapes nested in other Property Shapes
MINUS {
?propertyShape sh:node ?rootShape
}
# Load the direct properties
?rootShape sh:property/sh:path ?rootProp .
<amy-farrah-fowler> ?rootProp ?rootObject .
# Traverse all deep SHACL Property Shapes
# and retrieve the properties of reachable child resources
OPTIONAL {
?rootShape (sh:property/sh:node)+ ?childPropShape .
?childPropShape sh:targetNode ?child .
?childPropShape sh:property/sh:path ?childProp .
?child ?childProp ?childObject .
}
}
To remove a resource from the graph, delete the triples retrieved as above and the shape itself, which can be queries using a greedy property path:
base <http://tbbd-ld.tv/person/>
prefix sh: <http://www.w3.org/ns/shacl#>
with <http://tbbd-ld.tv/>
delete {
# Resource triples, as seen above
<amy-farrah-fowler> ?rootProp ?rootObject .
?child ?childProp ?childObject .
# Shape triples
<amy-farrah-fowler> sh:node ?shape .
?s ?p ?o .
}
where {
# Query resource data as above
# Find all deep shape resources but exclude sh:targetNode
?shape sh:targetNode <amy-farrah-fowler> .
?shape (!sh:targetNode)* ?s .
?s ?p ?o .
}
To insert or replace a resource is simply to DELETE
existing and follow that with an INSERT DATA
. This way is safer than DELETE/INSERT
because the blank nodes pattern solutions will easily lead to duplicate inserts. Also, it will not require an wrapping the entire query pattern in OPTIONAL
to accommodate for resources which have not existed.