Skip to content

Instantly share code, notes, and snippets.

@tpluscode
Last active March 9, 2021 10:41
Show Gist options
  • Save tpluscode/a57f180c7b9dc717f81670c8279a656f to your computer and use it in GitHub Desktop.
Save tpluscode/a57f180c7b9dc717f81670c8279a656f to your computer and use it in GitHub Desktop.
Managing multiple resources in one graph

The problem

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.

Example

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?

Solution

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 ;
      ]
    ] ;
  ]
] .

GET

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 .
  }
}

DELETE

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 .
}

PUT

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment