浏览代码

Rework patch method to use deltas.

Stefano Cossu 7 年之前
父节点
当前提交
0fcac91a4a

+ 33 - 49
lakesuperior/model/ldp_rs.py

@@ -71,7 +71,7 @@ class LdpRs(Ldpr):
         '''
         g = Graph().parse(data=data, format=format, publicID=self.urn)
 
-        self._check_mgd_terms_rdf(g)
+        self._check_mgd_terms(g)
         self._ensure_single_subject_rdf(g)
 
         for t in self.base_types:
@@ -89,7 +89,7 @@ class LdpRs(Ldpr):
         '''
         g = Graph().parse(data=data, format=format, publicID=self.urn)
 
-        self._check_mgd_terms_rdf(g)
+        self._check_mgd_terms(g)
         self._ensure_single_subject_rdf(g)
 
         for t in self.base_types:
@@ -106,15 +106,14 @@ class LdpRs(Ldpr):
         '''
         https://www.w3.org/TR/ldp/#ldpr-HTTP_PATCH
         '''
-        self._check_mgd_terms_sparql(data)
-        self._ensure_single_subject_sparql_update(data)
+        remove, add = self._sparql_delta(data)
 
-        self.rdfly.patch_rsrc(data)
+        self.rdfly.modify_rsrc(remove, add)
 
 
     ## PROTECTED METHODS ##
 
-    def _check_mgd_terms_rdf(self, g):
+    def _check_mgd_terms(self, g):
         '''
         Check whether server-managed terms are in a RDF payload.
         '''
@@ -131,68 +130,53 @@ class LdpRs(Ldpr):
             raise ServerManagedTermError(offending_types, 't')
 
 
-    def _check_mgd_terms_sparql(self, q):
+    def _sparql_delta(self, q):
         '''
-        Parse tokens in update query and verify that none of the terms being
-        modified is server-managed.
+        Calculate the delta obtained by a SPARQL Update operation.
 
-        The only reasonable way to do this is to perform the query on a copy
-        and verify if any of the server managed terms is in the delta. If it
-        is, it means that some server-managed term is being modified and
-        an error should be raised.
+        This does a couple of extra things:
+
+        1. It ensures that no resources outside of the subject of the request
+        are modified (e.g. by variable subjects)
+        2. It verifies that none of the terms being modified is server-managed.
+
+        This method extracts an in-memory copy of the resource and performs the
+        query on that once it has checked if any of the server managed terms is
+        in the delta. If it is, it raises an exception.
 
         NOTE: This only checks if a server-managed term is effectively being
         modified. If a server-managed term is present in the query but does not
         cause any change in the updated resource, no error is raised.
-        '''
-
-        before_test = self.rdfly.extract_imr().graph
-        after_test = deepcopy(before_test)
 
-        after_test.update(q)
-
-        delta = before_test ^ after_test
-        self._logger.info('Delta: {}'.format(delta.serialize(format='turtle')
-                .decode('utf8')))
+        @return tuple Remove and add triples. These can be used with
+        `BaseStoreLayout.update_resource`.
+        '''
 
-        for s,p,o in delta:
-            if s in srv_mgd_subjects:
-                raise ServerManagedTermError(s, 's')
-            if p in srv_mgd_predicates:
-                raise ServerManagedTermError(p, 'p')
-            if p == RDF.type and o in srv_mgd_types:
-                raise ServerManagedTermError(o, 't')
+        pre_g = self.rdfly.extract_imr().graph
 
+        post_g = deepcopy(pre_g)
+        post_g.update(q)
 
-    def _ensure_single_subject_sparql_update(self, qs):
-        '''
-        Ensure that a SPARQL update query only affects the current resource.
+        remove = pre_g - post_g
+        add = post_g - pre_g
 
-        This prevents a query such as
+        self._logger.info('Removing: {}'.format(
+            remove.serialize(format='turtle').decode('utf8')))
+        self._logger.info('Adding: {}'.format(
+            add.serialize(format='turtle').decode('utf8')))
 
-        DELETE {
-          ?s a ns:Class .
-        }
-        INSERT {
-          ?s a ns:OtherClass .
-        }
-        WHERE {
-          ?s a ns:Class .
-        }
+        self._check_mgd_terms(remove + add)
 
-        from affecting multiple resources.
-        '''
-        # @TODO This requires some quirky algebra parsing and manipulation.
-        # Will need to investigate.
-        pass
+        return remove, add
 
 
     def _ensure_single_subject_rdf(self, g):
         '''
         Ensure that a RDF payload for a POST or PUT has a single resource.
         '''
-        if not all(s == self.uri for s in set(g.subjects())):
-            return SingleSubjectError(self.uri)
+        for s in set(g.subjects()):
+            if not s == self.uri:
+                return SingleSubjectError(s, self.uri)
 
 
 class Ldpc(LdpRs):

+ 13 - 1
lakesuperior/store_layouts/rdf/base_rdf_layout.py

@@ -18,7 +18,7 @@ def needs_rsrc(fn):
     Decorator for methods that cannot be called without `self.rsrc` set.
     '''
     def wrapper(self, *args, **kwargs):
-        if not isset(self, '_rsrc') or self._rsrc is None:
+        if not hasattr(self, 'rsrc') or self.rsrc is None:
             raise TypeError(
                 'This method must be called by an instance with `rsrc` set.')
 
@@ -254,6 +254,18 @@ class BaseRdfLayout(metaclass=ABCMeta):
         pass
 
 
+    @abstractmethod
+    @needs_rsrc
+    def modify_rsrc(self, remove, add):
+        '''
+        Adds and/or removes triples from a graph.
+
+        @param remove (rdflib.Graph) Triples to be removed.
+        @param add (rdflib.Graph) Triples to be added.
+        '''
+        pass
+
+
     @abstractmethod
     @needs_rsrc
     def delete_rsrc(self, urn, commit=True):

+ 20 - 1
lakesuperior/store_layouts/rdf/simple_layout.py

@@ -10,7 +10,8 @@ from rdflib.term import Literal, URIRef, Variable
 
 from lakesuperior.dictionaries.namespaces import ns_collection as nsc
 from lakesuperior.dictionaries.namespaces import ns_mgr as nsm
-from lakesuperior.store_layouts.rdf.base_rdf_layout import BaseRdfLayout
+from lakesuperior.store_layouts.rdf.base_rdf_layout import BaseRdfLayout, \
+        needs_rsrc
 from lakesuperior.util.digest import Digest
 from lakesuperior.util.translator import Translator
 
@@ -80,6 +81,7 @@ class SimpleLayout(BaseRdfLayout):
         return Resource(g, uri)
 
 
+    @needs_rsrc
     def out_rsrc(self, srv_mgd=True, inbound=False, embed_children=False):
         '''
         See base_rdf_layout.out_rsrc.
@@ -105,6 +107,7 @@ class SimpleLayout(BaseRdfLayout):
         return (uri, Variable('p'), Variable('o')) in self.ds
 
 
+    @needs_rsrc
     def create_or_replace_rsrc(self, g):
         '''
         See base_rdf_layout.create_or_replace_rsrc.
@@ -136,6 +139,7 @@ class SimpleLayout(BaseRdfLayout):
             self.ds.add((s, p, o))
 
 
+    @needs_rsrc
     def create_rsrc(self, g):
         '''
         See base_rdf_layout.create_rsrc.
@@ -154,9 +158,12 @@ class SimpleLayout(BaseRdfLayout):
             self.ds.add((s, p, o))
 
 
+    @needs_rsrc
     def patch_rsrc(self, data):
         '''
         Perform a SPARQL UPDATE on a resource.
+
+        @TODO deprecate.
         '''
         # @TODO Use gunicorn to get request timestamp.
         ts = Literal(arrow.utcnow(), datatype=XSD.dateTime)
@@ -170,6 +177,18 @@ class SimpleLayout(BaseRdfLayout):
         self.ds.update(q)
 
 
+    @needs_rsrc
+    def modify_rsrc(self, remove, add):
+        '''
+        See base_rdf_layout.update_rsrc.
+        '''
+        for t in remove.predicate_objects():
+            self.rsrc.remove(t[0], t[1])
+
+        for t in add.predicate_objects():
+            self.rsrc.add(t[0], t[1])
+
+
     def delete_rsrc(self, inbound=False):
         '''
         Delete a resource. If `inbound` is specified, delete all inbound

+ 14 - 0
tests/data/update_algebra_tests/delete+insert+where.sparql

@@ -0,0 +1,14 @@
+PREFIX dc: <http://purl.org/dc/elements/1.1/>
+
+DELETE {
+  <> dc:title "Hello" .
+}
+INSERT {
+  <> dc:title "Ciao" .
+}
+WHERE {
+  <> dc:title ?t .
+}
+
+
+

+ 12 - 0
tests/data/update_algebra_tests/delete_variable_where.sparql

@@ -0,0 +1,12 @@
+PREFIX dc: <http://purl.org/dc/elements/1.1/>
+
+DELETE {
+  <> dc:title "Hello" .
+}
+INSERT {
+}
+WHERE {
+  <> dc:title ?t .
+}
+
+

+ 11 - 0
tests/data/update_algebra_tests/delete_variable_where_variable.sparql

@@ -0,0 +1,11 @@
+PREFIX dc: <http://purl.org/dc/elements/1.1/>
+
+DELETE {
+  <> dc:title ?t .
+}
+INSERT {
+}
+WHERE {
+  <> dc:title ?t .
+}
+

+ 16 - 0
tests/data/update_algebra_tests/multiline_clause.sparql

@@ -0,0 +1,16 @@
+PREFIX dc: <http://purl.org/dc/elements/1.1/>
+
+DELETE {
+  <> dc:title "Hello" .
+}
+INSERT {
+  <> dc:title "Ciao" .
+  <> dc:title "Hola" .
+}
+WHERE {
+  <> dc:title "Hello" .
+  <> dc:description "Some text" .
+}
+
+
+

+ 10 - 0
tests/data/update_algebra_tests/simple_delete.sparql

@@ -0,0 +1,10 @@
+PREFIX dc: <http://purl.org/dc/elements/1.1/>
+
+DELETE {
+  <> dc:title "Hello" .
+}
+INSERT {
+}
+WHERE {
+  <> dc:title "Hello" .
+}

+ 7 - 0
tests/data/update_algebra_tests/simple_insert.sparql

@@ -0,0 +1,7 @@
+PREFIX dc: <http://purl.org/dc/elements/1.1/>
+
+INSERT {
+  <> dc:title "Hello" .
+}
+WHERE {
+}

+ 16 - 0
tests/data/update_algebra_tests/union_where.sparql

@@ -0,0 +1,16 @@
+PREFIX dc: <http://purl.org/dc/elements/1.1/>
+
+DELETE {
+  ?s dc:title "Hello" .
+}
+INSERT {
+  ?s dc:title "Ciao" .
+}
+WHERE {
+  {
+    ?s dc:title "Hello" .
+  } UNION {
+    ?s dc:title "Hola" .
+  }
+}
+

+ 11 - 0
tests/data/update_algebra_tests/variable_subjects.sparql

@@ -0,0 +1,11 @@
+PREFIX dc: <http://purl.org/dc/elements/1.1/>
+
+DELETE {
+  ?s dc:title "Hello" .
+}
+INSERT {
+  ?s dc:title "Ciao" .
+}
+WHERE {
+  ?s dc:title ?t .
+}